Add user information and calendar list to sidebar
Backend changes: - Add /api/user/info endpoint to fetch user details and calendar list - Create UserInfo and CalendarInfo models for API responses - Filter out generic calendar collections from sidebar display - Extract readable calendar names with proper title case formatting Frontend changes: - Fetch and display user info (username, server URL) in sidebar header - Show list of user's calendars with hover effects and styling - Add loading states and error handling for user info - Reorganize sidebar layout: header, navigation, calendar list, logout Styling: - Enhanced sidebar with user info section and calendar list - Responsive design hides user info and calendar list on mobile - Improved logout button positioning in sidebar footer - Professional styling with proper spacing and visual hierarchy 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
91
src/app.rs
91
src/app.rs
@@ -2,7 +2,7 @@ use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use crate::components::{Login, Calendar};
|
||||
use crate::services::{CalendarService, CalendarEvent};
|
||||
use crate::services::{CalendarService, CalendarEvent, UserInfo, CalendarInfo};
|
||||
use std::collections::HashMap;
|
||||
use chrono::{Local, NaiveDate, Datelike};
|
||||
|
||||
@@ -21,6 +21,8 @@ pub fn App() -> Html {
|
||||
let auth_token = use_state(|| -> Option<String> {
|
||||
LocalStorage::get("auth_token").ok()
|
||||
});
|
||||
|
||||
let user_info = use_state(|| -> Option<UserInfo> { None });
|
||||
|
||||
let on_login = {
|
||||
let auth_token = auth_token.clone();
|
||||
@@ -31,12 +33,57 @@ pub fn App() -> Html {
|
||||
|
||||
let on_logout = {
|
||||
let auth_token = auth_token.clone();
|
||||
let user_info = user_info.clone();
|
||||
Callback::from(move |_| {
|
||||
let _ = LocalStorage::delete("auth_token");
|
||||
auth_token.set(None);
|
||||
user_info.set(None);
|
||||
})
|
||||
};
|
||||
|
||||
// Fetch user info when token is available
|
||||
{
|
||||
let user_info = user_info.clone();
|
||||
let auth_token = auth_token.clone();
|
||||
|
||||
use_effect_with((*auth_token).clone(), move |token| {
|
||||
if let Some(token) = token {
|
||||
let user_info = user_info.clone();
|
||||
let token = token.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let calendar_service = CalendarService::new();
|
||||
|
||||
// Get password from stored credentials
|
||||
let password = if let Ok(credentials_str) = LocalStorage::get::<String>("caldav_credentials") {
|
||||
if let Ok(credentials) = serde_json::from_str::<serde_json::Value>(&credentials_str) {
|
||||
credentials["password"].as_str().unwrap_or("").to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
if !password.is_empty() {
|
||||
match calendar_service.fetch_user_info(&token, &password).await {
|
||||
Ok(info) => {
|
||||
user_info.set(Some(info));
|
||||
}
|
||||
Err(err) => {
|
||||
web_sys::console::log_1(&format!("Failed to fetch user info: {}", err).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
user_info.set(None);
|
||||
}
|
||||
|
||||
|| ()
|
||||
});
|
||||
}
|
||||
|
||||
html! {
|
||||
<BrowserRouter>
|
||||
<div class="app">
|
||||
@@ -47,11 +94,51 @@ pub fn App() -> Html {
|
||||
<aside class="app-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h1>{"Calendar App"}</h1>
|
||||
{
|
||||
if let Some(ref info) = *user_info {
|
||||
html! {
|
||||
<div class="user-info">
|
||||
<div class="username">{&info.username}</div>
|
||||
<div class="server-url">{&info.server_url}</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! { <div class="user-info loading">{"Loading..."}</div> }
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<Link<Route> to={Route::Calendar} classes="nav-link">{"Calendar"}</Link<Route>>
|
||||
<button onclick={on_logout} class="logout-button">{"Logout"}</button>
|
||||
</nav>
|
||||
{
|
||||
if let Some(ref info) = *user_info {
|
||||
if !info.calendars.is_empty() {
|
||||
html! {
|
||||
<div class="calendar-list">
|
||||
<h3>{"My Calendars"}</h3>
|
||||
<ul>
|
||||
{
|
||||
info.calendars.iter().map(|cal| {
|
||||
html! {
|
||||
<li key={cal.path.clone()}>
|
||||
<span class="calendar-name">{&cal.display_name}</span>
|
||||
</li>
|
||||
}
|
||||
}).collect::<Html>()
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! { <div class="no-calendars">{"No calendars found"}</div> }
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
<div class="sidebar-footer">
|
||||
<button onclick={on_logout} class="logout-button">{"Logout"}</button>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="app-main">
|
||||
<Switch<Route> render={move |route| {
|
||||
|
||||
Reference in New Issue
Block a user