Refactor app.rs by extracting components for better organization
Split monolithic app.rs into focused, reusable components: - Sidebar component for user info, navigation and calendar management - CalendarListItem component for individual calendar items with color picker - RouteHandler component to eliminate duplicated routing logic - Reduced app.rs from 645 to 338 lines (47% reduction) - Improved separation of concerns and maintainability - Clean props-based component communication 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										226
									
								
								src/components/route_handler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/components/route_handler.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | ||||
| use yew::prelude::*; | ||||
| use yew_router::prelude::*; | ||||
| use crate::components::Login; | ||||
| use crate::services::calendar_service::UserInfo; | ||||
|  | ||||
| #[derive(Clone, Routable, PartialEq)] | ||||
| pub enum Route { | ||||
|     #[at("/")] | ||||
|     Home, | ||||
|     #[at("/login")] | ||||
|     Login, | ||||
|     #[at("/calendar")] | ||||
|     Calendar, | ||||
| } | ||||
|  | ||||
| #[derive(Properties, PartialEq)] | ||||
| pub struct RouteHandlerProps { | ||||
|     pub auth_token: Option<String>, | ||||
|     pub user_info: Option<UserInfo>, | ||||
|     pub on_login: Callback<String>, | ||||
| } | ||||
|  | ||||
| #[function_component(RouteHandler)] | ||||
| pub fn route_handler(props: &RouteHandlerProps) -> Html { | ||||
|     let auth_token = props.auth_token.clone(); | ||||
|     let user_info = props.user_info.clone(); | ||||
|     let on_login = props.on_login.clone(); | ||||
|      | ||||
|     html! { | ||||
|         <Switch<Route> render={move |route| { | ||||
|             let auth_token = auth_token.clone(); | ||||
|             let user_info = user_info.clone(); | ||||
|             let on_login = on_login.clone(); | ||||
|              | ||||
|             match route { | ||||
|                 Route::Home => { | ||||
|                     if auth_token.is_some() { | ||||
|                         html! { <Redirect<Route> to={Route::Calendar}/> } | ||||
|                     } else { | ||||
|                         html! { <Redirect<Route> to={Route::Login}/> } | ||||
|                     } | ||||
|                 } | ||||
|                 Route::Login => { | ||||
|                     if auth_token.is_some() { | ||||
|                         html! { <Redirect<Route> to={Route::Calendar}/> } | ||||
|                     } else { | ||||
|                         html! { <Login {on_login} /> } | ||||
|                     } | ||||
|                 } | ||||
|                 Route::Calendar => { | ||||
|                     if auth_token.is_some() { | ||||
|                         html! { <CalendarView user_info={user_info} /> } | ||||
|                     } else { | ||||
|                         html! { <Redirect<Route> to={Route::Login}/> } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }} /> | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Properties, PartialEq)] | ||||
| pub struct CalendarViewProps { | ||||
|     pub user_info: Option<UserInfo>, | ||||
| } | ||||
|  | ||||
| use gloo_storage::{LocalStorage, Storage}; | ||||
| use crate::services::{CalendarService, CalendarEvent}; | ||||
| use crate::components::Calendar; | ||||
| use std::collections::HashMap; | ||||
| use chrono::{Local, NaiveDate, Datelike}; | ||||
|  | ||||
| #[function_component(CalendarView)] | ||||
| pub fn calendar_view(props: &CalendarViewProps) -> Html { | ||||
|     let events = use_state(|| HashMap::<NaiveDate, Vec<CalendarEvent>>::new()); | ||||
|     let loading = use_state(|| true); | ||||
|     let error = use_state(|| None::<String>); | ||||
|     let refreshing_event = use_state(|| None::<String>); | ||||
|      | ||||
|     let auth_token: Option<String> = LocalStorage::get("auth_token").ok(); | ||||
|      | ||||
|     let today = Local::now().date_naive(); | ||||
|     let current_year = today.year(); | ||||
|     let current_month = today.month(); | ||||
|      | ||||
|     let on_event_click = { | ||||
|         let events = events.clone(); | ||||
|         let refreshing_event = refreshing_event.clone(); | ||||
|         let auth_token = auth_token.clone(); | ||||
|          | ||||
|         Callback::from(move |event: CalendarEvent| { | ||||
|             if let Some(token) = auth_token.clone() { | ||||
|                 let events = events.clone(); | ||||
|                 let refreshing_event = refreshing_event.clone(); | ||||
|                 let uid = event.uid.clone(); | ||||
|                  | ||||
|                 refreshing_event.set(Some(uid.clone())); | ||||
|                  | ||||
|                 wasm_bindgen_futures::spawn_local(async move { | ||||
|                     let calendar_service = CalendarService::new(); | ||||
|                      | ||||
|                     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() | ||||
|                     }; | ||||
|                      | ||||
|                     match calendar_service.refresh_event(&token, &password, &uid).await { | ||||
|                         Ok(Some(refreshed_event)) => { | ||||
|                             let mut updated_events = (*events).clone(); | ||||
|                              | ||||
|                             for (_, day_events) in updated_events.iter_mut() { | ||||
|                                 day_events.retain(|e| e.uid != uid); | ||||
|                             } | ||||
|                              | ||||
|                             if refreshed_event.recurrence_rule.is_some() { | ||||
|                                 let new_occurrences = CalendarService::expand_recurring_events(vec![refreshed_event.clone()]); | ||||
|                                  | ||||
|                                 for occurrence in new_occurrences { | ||||
|                                     let date = occurrence.get_date(); | ||||
|                                     updated_events.entry(date) | ||||
|                                         .or_insert_with(Vec::new) | ||||
|                                         .push(occurrence); | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 let date = refreshed_event.get_date(); | ||||
|                                 updated_events.entry(date) | ||||
|                                     .or_insert_with(Vec::new) | ||||
|                                     .push(refreshed_event); | ||||
|                             } | ||||
|                              | ||||
|                             events.set(updated_events); | ||||
|                         } | ||||
|                         Ok(None) => { | ||||
|                             let mut updated_events = (*events).clone(); | ||||
|                             for (_, day_events) in updated_events.iter_mut() { | ||||
|                                 day_events.retain(|e| e.uid != uid); | ||||
|                             } | ||||
|                             events.set(updated_events); | ||||
|                         } | ||||
|                         Err(_err) => { | ||||
|                         } | ||||
|                     } | ||||
|                      | ||||
|                     refreshing_event.set(None); | ||||
|                 }); | ||||
|             } | ||||
|         }) | ||||
|     }; | ||||
|      | ||||
|     { | ||||
|         let events = events.clone(); | ||||
|         let loading = loading.clone(); | ||||
|         let error = error.clone(); | ||||
|         let auth_token = auth_token.clone(); | ||||
|          | ||||
|         use_effect_with((), move |_| { | ||||
|             if let Some(token) = auth_token { | ||||
|                 let events = events.clone(); | ||||
|                 let loading = loading.clone(); | ||||
|                 let error = error.clone(); | ||||
|                  | ||||
|                 wasm_bindgen_futures::spawn_local(async move { | ||||
|                     let calendar_service = CalendarService::new(); | ||||
|                      | ||||
|                     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() | ||||
|                     }; | ||||
|                      | ||||
|                     match calendar_service.fetch_events_for_month(&token, &password, current_year, current_month).await { | ||||
|                         Ok(calendar_events) => { | ||||
|                             let grouped_events = CalendarService::group_events_by_date(calendar_events); | ||||
|                             events.set(grouped_events); | ||||
|                             loading.set(false); | ||||
|                         } | ||||
|                         Err(err) => { | ||||
|                             error.set(Some(format!("Failed to load events: {}", err))); | ||||
|                             loading.set(false); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } else { | ||||
|                 loading.set(false); | ||||
|                 error.set(Some("No authentication token found".to_string())); | ||||
|             } | ||||
|              | ||||
|             || () | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     html! { | ||||
|         <div class="calendar-view"> | ||||
|             { | ||||
|                 if *loading { | ||||
|                     html! { | ||||
|                         <div class="calendar-loading"> | ||||
|                             <p>{"Loading calendar events..."}</p> | ||||
|                         </div> | ||||
|                     } | ||||
|                 } else if let Some(err) = (*error).clone() { | ||||
|                     let dummy_callback = Callback::from(|_: CalendarEvent| {}); | ||||
|                     html! { | ||||
|                         <div class="calendar-error"> | ||||
|                             <p>{format!("Error: {}", err)}</p> | ||||
|                             <Calendar events={HashMap::new()} on_event_click={dummy_callback} refreshing_event_uid={(*refreshing_event).clone()} user_info={props.user_info.clone()} /> | ||||
|                         </div> | ||||
|                     } | ||||
|                 } else { | ||||
|                     html! { | ||||
|                         <Calendar events={(*events).clone()} on_event_click={on_event_click} refreshing_event_uid={(*refreshing_event).clone()} user_info={props.user_info.clone()} /> | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         </div> | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Connor Johnstone
					Connor Johnstone