Compare commits
	
		
			5 Commits
		
	
	
		
			bugfix/sql
			...
			970b0a07da
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 970b0a07da | ||
|   | e2e5813b54 | ||
|   | 73567c185c | ||
| 0587762bbb | |||
| 4d2aad404b | 
							
								
								
									
										2
									
								
								backend/migrations/004_add_style_preference.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								backend/migrations/004_add_style_preference.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | -- Add calendar style preference to user preferences | ||||||
|  | ALTER TABLE user_preferences ADD COLUMN calendar_style TEXT DEFAULT 'default'; | ||||||
| @@ -91,6 +91,7 @@ impl AuthService { | |||||||
|                         calendar_time_increment: preferences.calendar_time_increment, |                         calendar_time_increment: preferences.calendar_time_increment, | ||||||
|                         calendar_view_mode: preferences.calendar_view_mode, |                         calendar_view_mode: preferences.calendar_view_mode, | ||||||
|                         calendar_theme: preferences.calendar_theme, |                         calendar_theme: preferences.calendar_theme, | ||||||
|  |                         calendar_style: preferences.calendar_style, | ||||||
|                         calendar_colors: preferences.calendar_colors, |                         calendar_colors: preferences.calendar_colors, | ||||||
|                     }, |                     }, | ||||||
|                 }) |                 }) | ||||||
|   | |||||||
| @@ -93,6 +93,7 @@ pub struct UserPreferences { | |||||||
|     pub calendar_time_increment: Option<i32>, |     pub calendar_time_increment: Option<i32>, | ||||||
|     pub calendar_view_mode: Option<String>, |     pub calendar_view_mode: Option<String>, | ||||||
|     pub calendar_theme: Option<String>, |     pub calendar_theme: Option<String>, | ||||||
|  |     pub calendar_style: Option<String>, | ||||||
|     pub calendar_colors: Option<String>, // JSON string |     pub calendar_colors: Option<String>, // JSON string | ||||||
|     pub updated_at: DateTime<Utc>, |     pub updated_at: DateTime<Utc>, | ||||||
| } | } | ||||||
| @@ -106,6 +107,7 @@ impl UserPreferences { | |||||||
|             calendar_time_increment: Some(15), |             calendar_time_increment: Some(15), | ||||||
|             calendar_view_mode: Some("month".to_string()), |             calendar_view_mode: Some("month".to_string()), | ||||||
|             calendar_theme: Some("light".to_string()), |             calendar_theme: Some("light".to_string()), | ||||||
|  |             calendar_style: Some("default".to_string()), | ||||||
|             calendar_colors: None, |             calendar_colors: None, | ||||||
|             updated_at: Utc::now(), |             updated_at: Utc::now(), | ||||||
|         } |         } | ||||||
| @@ -264,14 +266,15 @@ impl<'a> PreferencesRepository<'a> { | |||||||
|             sqlx::query( |             sqlx::query( | ||||||
|                 "INSERT INTO user_preferences  |                 "INSERT INTO user_preferences  | ||||||
|                  (user_id, calendar_selected_date, calendar_time_increment,  |                  (user_id, calendar_selected_date, calendar_time_increment,  | ||||||
|                   calendar_view_mode, calendar_theme, calendar_colors, updated_at)  |                   calendar_view_mode, calendar_theme, calendar_style, calendar_colors, updated_at)  | ||||||
|                  VALUES (?, ?, ?, ?, ?, ?, ?)", |                  VALUES (?, ?, ?, ?, ?, ?, ?, ?)", | ||||||
|             ) |             ) | ||||||
|             .bind(&prefs.user_id) |             .bind(&prefs.user_id) | ||||||
|             .bind(&prefs.calendar_selected_date) |             .bind(&prefs.calendar_selected_date) | ||||||
|             .bind(&prefs.calendar_time_increment) |             .bind(&prefs.calendar_time_increment) | ||||||
|             .bind(&prefs.calendar_view_mode) |             .bind(&prefs.calendar_view_mode) | ||||||
|             .bind(&prefs.calendar_theme) |             .bind(&prefs.calendar_theme) | ||||||
|  |             .bind(&prefs.calendar_style) | ||||||
|             .bind(&prefs.calendar_colors) |             .bind(&prefs.calendar_colors) | ||||||
|             .bind(&prefs.updated_at) |             .bind(&prefs.updated_at) | ||||||
|             .execute(self.db.pool()) |             .execute(self.db.pool()) | ||||||
| @@ -286,7 +289,7 @@ impl<'a> PreferencesRepository<'a> { | |||||||
|         sqlx::query( |         sqlx::query( | ||||||
|             "UPDATE user_preferences  |             "UPDATE user_preferences  | ||||||
|              SET calendar_selected_date = ?, calendar_time_increment = ?,  |              SET calendar_selected_date = ?, calendar_time_increment = ?,  | ||||||
|                  calendar_view_mode = ?, calendar_theme = ?,  |                  calendar_view_mode = ?, calendar_theme = ?, calendar_style = ?, | ||||||
|                  calendar_colors = ?, updated_at = ? |                  calendar_colors = ?, updated_at = ? | ||||||
|              WHERE user_id = ?", |              WHERE user_id = ?", | ||||||
|         ) |         ) | ||||||
| @@ -294,6 +297,7 @@ impl<'a> PreferencesRepository<'a> { | |||||||
|         .bind(&prefs.calendar_time_increment) |         .bind(&prefs.calendar_time_increment) | ||||||
|         .bind(&prefs.calendar_view_mode) |         .bind(&prefs.calendar_view_mode) | ||||||
|         .bind(&prefs.calendar_theme) |         .bind(&prefs.calendar_theme) | ||||||
|  |         .bind(&prefs.calendar_style) | ||||||
|         .bind(&prefs.calendar_colors) |         .bind(&prefs.calendar_colors) | ||||||
|         .bind(Utc::now()) |         .bind(Utc::now()) | ||||||
|         .bind(&prefs.user_id) |         .bind(&prefs.user_id) | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ use axum::{extract::State, http::HeaderMap, response::Json}; | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
| use crate::calendar::CalDAVClient; | use crate::calendar::CalDAVClient; | ||||||
| use crate::config::CalDAVConfig; |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     models::{ApiError, AuthResponse, CalDAVLoginRequest, CalendarInfo, UserInfo}, |     models::{ApiError, AuthResponse, CalDAVLoginRequest, CalendarInfo, UserInfo}, | ||||||
|     AppState, |     AppState, | ||||||
|   | |||||||
| @@ -38,6 +38,7 @@ pub async fn get_preferences( | |||||||
|         calendar_time_increment: preferences.calendar_time_increment, |         calendar_time_increment: preferences.calendar_time_increment, | ||||||
|         calendar_view_mode: preferences.calendar_view_mode, |         calendar_view_mode: preferences.calendar_view_mode, | ||||||
|         calendar_theme: preferences.calendar_theme, |         calendar_theme: preferences.calendar_theme, | ||||||
|  |         calendar_style: preferences.calendar_style, | ||||||
|         calendar_colors: preferences.calendar_colors, |         calendar_colors: preferences.calendar_colors, | ||||||
|     })) |     })) | ||||||
| } | } | ||||||
| @@ -78,6 +79,9 @@ pub async fn update_preferences( | |||||||
|     if request.calendar_theme.is_some() { |     if request.calendar_theme.is_some() { | ||||||
|         preferences.calendar_theme = request.calendar_theme; |         preferences.calendar_theme = request.calendar_theme; | ||||||
|     } |     } | ||||||
|  |     if request.calendar_style.is_some() { | ||||||
|  |         preferences.calendar_style = request.calendar_style; | ||||||
|  |     } | ||||||
|     if request.calendar_colors.is_some() { |     if request.calendar_colors.is_some() { | ||||||
|         preferences.calendar_colors = request.calendar_colors; |         preferences.calendar_colors = request.calendar_colors; | ||||||
|     } |     } | ||||||
| @@ -94,6 +98,7 @@ pub async fn update_preferences( | |||||||
|             calendar_time_increment: preferences.calendar_time_increment, |             calendar_time_increment: preferences.calendar_time_increment, | ||||||
|             calendar_view_mode: preferences.calendar_view_mode, |             calendar_view_mode: preferences.calendar_view_mode, | ||||||
|             calendar_theme: preferences.calendar_theme, |             calendar_theme: preferences.calendar_theme, | ||||||
|  |             calendar_style: preferences.calendar_style, | ||||||
|             calendar_colors: preferences.calendar_colors, |             calendar_colors: preferences.calendar_colors, | ||||||
|         }), |         }), | ||||||
|     )) |     )) | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ pub struct UserPreferencesResponse { | |||||||
|     pub calendar_time_increment: Option<i32>, |     pub calendar_time_increment: Option<i32>, | ||||||
|     pub calendar_view_mode: Option<String>, |     pub calendar_view_mode: Option<String>, | ||||||
|     pub calendar_theme: Option<String>, |     pub calendar_theme: Option<String>, | ||||||
|  |     pub calendar_style: Option<String>, | ||||||
|     pub calendar_colors: Option<String>, |     pub calendar_colors: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -37,6 +38,7 @@ pub struct UpdatePreferencesRequest { | |||||||
|     pub calendar_time_increment: Option<i32>, |     pub calendar_time_increment: Option<i32>, | ||||||
|     pub calendar_view_mode: Option<String>, |     pub calendar_view_mode: Option<String>, | ||||||
|     pub calendar_theme: Option<String>, |     pub calendar_theme: Option<String>, | ||||||
|  |     pub calendar_style: Option<String>, | ||||||
|     pub calendar_colors: Option<String>, |     pub calendar_colors: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								calendar.db
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								calendar.db
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1,17 +1,16 @@ | |||||||
| services: | services: | ||||||
|   calendar-backend: |   calendar-backend: | ||||||
|     build: . |     build: . | ||||||
|     env_file: |  | ||||||
|       - .env |  | ||||||
|     ports: |     ports: | ||||||
|       - "3000:3000" |       - "3000:3000" | ||||||
|     volumes: |     volumes: | ||||||
|       - ./data/site_dist:/srv/www |       - ./data/site_dist:/srv/www | ||||||
|  |       - ./data/db:/db | ||||||
|  |  | ||||||
|   calendar-frontend: |   calendar-frontend: | ||||||
|     image: caddy |     image: caddy | ||||||
|     env_file: |     environment: | ||||||
|       - .env |       - BACKEND_API_URL=http://localhost:3000/api | ||||||
|     ports: |     ports: | ||||||
|       - "80:80" |       - "80:80" | ||||||
|       - "443:443" |       - "443:443" | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ web-sys = { version = "0.3", features = [ | |||||||
|     "HtmlSelectElement",  |     "HtmlSelectElement",  | ||||||
|     "HtmlInputElement", |     "HtmlInputElement", | ||||||
|     "HtmlTextAreaElement", |     "HtmlTextAreaElement", | ||||||
|  |     "HtmlLinkElement", | ||||||
|  |     "HtmlHeadElement", | ||||||
|     "Event", |     "Event", | ||||||
|     "MouseEvent", |     "MouseEvent", | ||||||
|     "InputEvent", |     "InputEvent", | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <base data-trunk-public-url /> |     <base data-trunk-public-url /> | ||||||
|     <link data-trunk rel="css" href="styles.css"> |     <link data-trunk rel="css" href="styles.css"> | ||||||
|  |     <link data-trunk rel="copy-file" href="styles/google.css"> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
|     <script> |     <script> | ||||||
|   | |||||||
| @@ -3,10 +3,12 @@ use crate::components::{ | |||||||
|     EditAction, EventClass, EventContextMenu, EventCreationData, EventStatus, RecurrenceType, |     EditAction, EventClass, EventContextMenu, EventCreationData, EventStatus, RecurrenceType, | ||||||
|     ReminderType, RouteHandler, Sidebar, Theme, ViewMode, |     ReminderType, RouteHandler, Sidebar, Theme, ViewMode, | ||||||
| }; | }; | ||||||
|  | use crate::components::sidebar::{Style}; | ||||||
| use crate::models::ical::VEvent; | use crate::models::ical::VEvent; | ||||||
| use crate::services::{calendar_service::UserInfo, CalendarService}; | use crate::services::{calendar_service::UserInfo, CalendarService}; | ||||||
| use chrono::NaiveDate; | use chrono::NaiveDate; | ||||||
| use gloo_storage::{LocalStorage, Storage}; | use gloo_storage::{LocalStorage, Storage}; | ||||||
|  | use wasm_bindgen::JsCast; | ||||||
| use web_sys::MouseEvent; | use web_sys::MouseEvent; | ||||||
| use yew::prelude::*; | use yew::prelude::*; | ||||||
| use yew_router::prelude::*; | use yew_router::prelude::*; | ||||||
| @@ -96,6 +98,16 @@ pub fn App() -> Html { | |||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // Style state - load from localStorage if available | ||||||
|  |     let current_style = use_state(|| { | ||||||
|  |         // Try to load saved style from localStorage | ||||||
|  |         if let Ok(saved_style) = LocalStorage::get::<String>("calendar_style") { | ||||||
|  |             Style::from_value(&saved_style) | ||||||
|  |         } else { | ||||||
|  |             Style::Default // Default style | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     let available_colors = use_state(|| get_theme_event_colors()); |     let available_colors = use_state(|| get_theme_event_colors()); | ||||||
|  |  | ||||||
|     let on_login = { |     let on_login = { | ||||||
| @@ -152,6 +164,42 @@ pub fn App() -> Html { | |||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     let on_style_change = { | ||||||
|  |         let current_style = current_style.clone(); | ||||||
|  |         Callback::from(move |new_style: Style| { | ||||||
|  |             // Save style to localStorage | ||||||
|  |             let _ = LocalStorage::set("calendar_style", new_style.value()); | ||||||
|  |  | ||||||
|  |             // Hot-swap stylesheet | ||||||
|  |             if let Some(window) = web_sys::window() { | ||||||
|  |                 if let Some(document) = window.document() { | ||||||
|  |                     // Remove existing style link if it exists | ||||||
|  |                     if let Some(existing_link) = document.get_element_by_id("dynamic-style") { | ||||||
|  |                         existing_link.remove(); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     // Create and append new stylesheet link only if style has a path | ||||||
|  |                     if let Some(stylesheet_path) = new_style.stylesheet_path() { | ||||||
|  |                         if let Ok(link) = document.create_element("link") { | ||||||
|  |                             let link = link.dyn_into::<web_sys::HtmlLinkElement>().unwrap(); | ||||||
|  |                             link.set_id("dynamic-style"); | ||||||
|  |                             link.set_rel("stylesheet"); | ||||||
|  |                             link.set_href(stylesheet_path); | ||||||
|  |                              | ||||||
|  |                             if let Some(head) = document.head() { | ||||||
|  |                                 let _ = head.append_child(&link); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     // If stylesheet_path is None (Default style), just removing the dynamic link is enough | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Update state | ||||||
|  |             current_style.set(new_style); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     // Apply initial theme on mount |     // Apply initial theme on mount | ||||||
|     { |     { | ||||||
|         let current_theme = current_theme.clone(); |         let current_theme = current_theme.clone(); | ||||||
| @@ -165,6 +213,32 @@ pub fn App() -> Html { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Apply initial style on mount | ||||||
|  |     { | ||||||
|  |         let current_style = current_style.clone(); | ||||||
|  |         use_effect_with((), move |_| { | ||||||
|  |             let style = (*current_style).clone(); | ||||||
|  |             if let Some(window) = web_sys::window() { | ||||||
|  |                 if let Some(document) = window.document() { | ||||||
|  |                     // Create and append stylesheet link for initial style only if it has a path | ||||||
|  |                     if let Some(stylesheet_path) = style.stylesheet_path() { | ||||||
|  |                         if let Ok(link) = document.create_element("link") { | ||||||
|  |                             let link = link.dyn_into::<web_sys::HtmlLinkElement>().unwrap(); | ||||||
|  |                             link.set_id("dynamic-style"); | ||||||
|  |                             link.set_rel("stylesheet"); | ||||||
|  |                             link.set_href(stylesheet_path); | ||||||
|  |                              | ||||||
|  |                             if let Some(head) = document.head() { | ||||||
|  |                                 let _ = head.append_child(&link); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     // If initial style is Default (None), no additional stylesheet needed | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Fetch user info when token is available |     // Fetch user info when token is available | ||||||
|     { |     { | ||||||
|         let user_info = user_info.clone(); |         let user_info = user_info.clone(); | ||||||
| @@ -718,6 +792,8 @@ pub fn App() -> Html { | |||||||
|                                     on_view_change={on_view_change} |                                     on_view_change={on_view_change} | ||||||
|                                     current_theme={(*current_theme).clone()} |                                     current_theme={(*current_theme).clone()} | ||||||
|                                     on_theme_change={on_theme_change} |                                     on_theme_change={on_theme_change} | ||||||
|  |                                     current_style={(*current_style).clone()} | ||||||
|  |                                     on_style_change={on_style_change} | ||||||
|                                 /> |                                 /> | ||||||
|                                 <main class="app-main"> |                                 <main class="app-main"> | ||||||
|                                     <RouteHandler |                                     <RouteHandler | ||||||
|   | |||||||
| @@ -32,6 +32,12 @@ pub enum Theme { | |||||||
|     Mint, |     Mint, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, PartialEq)] | ||||||
|  | pub enum Style { | ||||||
|  |     Default, | ||||||
|  |     Google, | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Theme { | impl Theme { | ||||||
|     pub fn value(&self) -> &'static str { |     pub fn value(&self) -> &'static str { | ||||||
|         match self { |         match self { | ||||||
| @@ -60,6 +66,30 @@ impl Theme { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Style { | ||||||
|  |     pub fn value(&self) -> &'static str { | ||||||
|  |         match self { | ||||||
|  |             Style::Default => "default", | ||||||
|  |             Style::Google => "google", | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn from_value(value: &str) -> Self { | ||||||
|  |         match value { | ||||||
|  |             "google" => Style::Google, | ||||||
|  |             _ => Style::Default, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     pub fn stylesheet_path(&self) -> Option<&'static str> { | ||||||
|  |         match self { | ||||||
|  |             Style::Default => None, // No additional stylesheet needed - uses base styles.css | ||||||
|  |             Style::Google => Some("google.css"), // Trunk copies to root level | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Default for ViewMode { | impl Default for ViewMode { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         ViewMode::Month |         ViewMode::Month | ||||||
| @@ -80,6 +110,8 @@ pub struct SidebarProps { | |||||||
|     pub on_view_change: Callback<ViewMode>, |     pub on_view_change: Callback<ViewMode>, | ||||||
|     pub current_theme: Theme, |     pub current_theme: Theme, | ||||||
|     pub on_theme_change: Callback<Theme>, |     pub on_theme_change: Callback<Theme>, | ||||||
|  |     pub current_style: Style, | ||||||
|  |     pub on_style_change: Callback<Style>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[function_component(Sidebar)] | #[function_component(Sidebar)] | ||||||
| @@ -111,6 +143,18 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     let on_style_change = { | ||||||
|  |         let on_style_change = props.on_style_change.clone(); | ||||||
|  |         Callback::from(move |e: Event| { | ||||||
|  |             let target = e.target_dyn_into::<HtmlSelectElement>(); | ||||||
|  |             if let Some(select) = target { | ||||||
|  |                 let value = select.value(); | ||||||
|  |                 let new_style = Style::from_value(&value); | ||||||
|  |                 on_style_change.emit(new_style); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     html! { |     html! { | ||||||
|         <aside class="app-sidebar"> |         <aside class="app-sidebar"> | ||||||
|             <div class="sidebar-header"> |             <div class="sidebar-header"> | ||||||
| @@ -175,6 +219,7 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|                 <div class="theme-selector"> |                 <div class="theme-selector"> | ||||||
|  |                     <label>{"Theme:"}</label> | ||||||
|                     <select class="theme-selector-dropdown" onchange={on_theme_change}> |                     <select class="theme-selector-dropdown" onchange={on_theme_change}> | ||||||
|                         <option value="default" selected={matches!(props.current_theme, Theme::Default)}>{"Default"}</option> |                         <option value="default" selected={matches!(props.current_theme, Theme::Default)}>{"Default"}</option> | ||||||
|                         <option value="ocean" selected={matches!(props.current_theme, Theme::Ocean)}>{"Ocean"}</option> |                         <option value="ocean" selected={matches!(props.current_theme, Theme::Ocean)}>{"Ocean"}</option> | ||||||
| @@ -187,6 +232,14 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|                     </select> |                     </select> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="style-selector"> | ||||||
|  |                     <label>{"Style:"}</label> | ||||||
|  |                     <select class="style-selector-dropdown" onchange={on_style_change}> | ||||||
|  |                         <option value="default" selected={matches!(props.current_style, Style::Default)}>{"Default"}</option> | ||||||
|  |                         <option value="google" selected={matches!(props.current_style, Style::Google)}>{"Google Calendar"}</option> | ||||||
|  |                     </select> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|                 <button onclick={props.on_logout.reform(|_| ())} class="logout-button">{"Logout"}</button> |                 <button onclick={props.on_logout.reform(|_| ())} class="logout-button">{"Logout"}</button> | ||||||
|             </div> |             </div> | ||||||
|         </aside> |         </aside> | ||||||
|   | |||||||
| @@ -585,11 +585,6 @@ pub fn week_view(props: &WeekViewProps) -> Html { | |||||||
|                                                 } |                                                 } | ||||||
|                                             }).collect::<Html>() |                                             }).collect::<Html>() | ||||||
|                                         } |                                         } | ||||||
|                                         // Final boundary slot to complete the 24-hour visual grid - make it interactive like other slots |  | ||||||
|                                         <div class="time-slot boundary-slot"> |  | ||||||
|                                             <div class="time-slot-half"></div> |  | ||||||
|                                             <div class="time-slot-half"></div> |  | ||||||
|                                         </div> |  | ||||||
|  |  | ||||||
|                                         // Events positioned absolutely based on their actual times |                                         // Events positioned absolutely based on their actual times | ||||||
|                                         <div class="events-container"> |                                         <div class="events-container"> | ||||||
| @@ -1029,8 +1024,13 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool) | |||||||
|     let local_start = event.dtstart.with_timezone(&Local); |     let local_start = event.dtstart.with_timezone(&Local); | ||||||
|     let event_date = local_start.date_naive(); |     let event_date = local_start.date_naive(); | ||||||
|  |  | ||||||
|     // Only position events that are on this specific date |     // Position events based on when they appear in local time, not their original date | ||||||
|     if event_date != date { |     // For timezone issues: an event created at 10 PM Sunday might be stored as Monday UTC | ||||||
|  |     // but should still display on Sunday's column since that's when the user sees it | ||||||
|  |     let should_display_here = event_date == date ||  | ||||||
|  |         (event_date == date - chrono::Duration::days(1) && local_start.hour() >= 20); | ||||||
|  |      | ||||||
|  |     if !should_display_here { | ||||||
|         return (0.0, 0.0, false); // Event not on this date |         return (0.0, 0.0, false); // Event not on this date | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,4 +2,3 @@ pub mod calendar_service; | |||||||
| pub mod preferences; | pub mod preferences; | ||||||
|  |  | ||||||
| pub use calendar_service::CalendarService; | pub use calendar_service::CalendarService; | ||||||
| pub use preferences::PreferencesService; |  | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ pub struct UserPreferences { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize)] | #[derive(Debug, Serialize)] | ||||||
|  | #[allow(dead_code)] | ||||||
| pub struct UpdatePreferencesRequest { | pub struct UpdatePreferencesRequest { | ||||||
|     pub calendar_selected_date: Option<String>, |     pub calendar_selected_date: Option<String>, | ||||||
|     pub calendar_time_increment: Option<i32>, |     pub calendar_time_increment: Option<i32>, | ||||||
| @@ -23,10 +24,12 @@ pub struct UpdatePreferencesRequest { | |||||||
|     pub calendar_colors: Option<String>, |     pub calendar_colors: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[allow(dead_code)] | ||||||
| pub struct PreferencesService { | pub struct PreferencesService { | ||||||
|     base_url: String, |     base_url: String, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[allow(dead_code)] | ||||||
| impl PreferencesService { | impl PreferencesService { | ||||||
|     pub fn new() -> Self { |     pub fn new() -> Self { | ||||||
|         let base_url = option_env!("BACKEND_API_URL") |         let base_url = option_env!("BACKEND_API_URL") | ||||||
| @@ -147,7 +150,7 @@ impl PreferencesService { | |||||||
|         let session_token = LocalStorage::get::<String>("session_token") |         let session_token = LocalStorage::get::<String>("session_token") | ||||||
|             .map_err(|_| "No session token found".to_string())?; |             .map_err(|_| "No session token found".to_string())?; | ||||||
|          |          | ||||||
|         let mut request = UpdatePreferencesRequest { |         let request = UpdatePreferencesRequest { | ||||||
|             calendar_selected_date: LocalStorage::get::<String>("calendar_selected_date").ok(), |             calendar_selected_date: LocalStorage::get::<String>("calendar_selected_date").ok(), | ||||||
|             calendar_time_increment: LocalStorage::get::<u32>("calendar_time_increment").ok().map(|i| i as i32), |             calendar_time_increment: LocalStorage::get::<u32>("calendar_time_increment").ok().map(|i| i as i32), | ||||||
|             calendar_view_mode: LocalStorage::get::<String>("calendar_view_mode").ok(), |             calendar_view_mode: LocalStorage::get::<String>("calendar_view_mode").ok(), | ||||||
|   | |||||||
| @@ -1,9 +1,120 @@ | |||||||
|  | /* Base Styles - Always Loaded */ | ||||||
| * { | * { | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     padding: 0; |     padding: 0; | ||||||
|     box-sizing: border-box; |     box-sizing: border-box; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | :root { | ||||||
|  |     /* CSS Variables for Style System */ | ||||||
|  |     --border-radius-small: 4px; | ||||||
|  |     --border-radius-medium: 8px; | ||||||
|  |     --border-radius-large: 12px; | ||||||
|  |     --spacing-xs: 4px; | ||||||
|  |     --spacing-sm: 8px; | ||||||
|  |     --spacing-md: 16px; | ||||||
|  |     --spacing-lg: 24px; | ||||||
|  |     --spacing-xl: 32px; | ||||||
|  |     --shadow-sm: 0 1px 3px rgba(0,0,0,0.1); | ||||||
|  |     --shadow-md: 0 4px 6px rgba(0,0,0,0.1); | ||||||
|  |     --shadow-lg: 0 8px 25px rgba(0,0,0,0.15); | ||||||
|  |     --border-light: 1px solid #e9ecef; | ||||||
|  |     --border-medium: 1px solid #dee2e6; | ||||||
|  |     --transition-fast: 0.15s ease; | ||||||
|  |     --transition-normal: 0.2s ease; | ||||||
|  |     --transition-slow: 0.3s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body { | ||||||
|  |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|  |     background-color: #f8f9fa; | ||||||
|  |     color: #333; | ||||||
|  |     line-height: 1.6; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .app { | ||||||
|  |     min-height: 100vh; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .login-layout { | ||||||
|  |     min-height: 100vh; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Base Layout */ | ||||||
|  | .main-content { | ||||||
|  |     flex: 1; | ||||||
|  |     margin-left: 280px; | ||||||
|  |     overflow-x: hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Basic Form Elements */ | ||||||
|  | input, select, textarea, button { | ||||||
|  |     font-family: inherit; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Utility Classes */ | ||||||
|  | .loading { | ||||||
|  |     opacity: 0.7; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .error { | ||||||
|  |     color: #dc3545; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .success { | ||||||
|  |     color: #28a745; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Theme Data Attributes for Color Schemes */ | ||||||
|  | [data-theme="default"] { | ||||||
|  |     --primary-color: #667eea; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="ocean"] { | ||||||
|  |     --primary-color: #006994; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #006994 0%, #0891b2 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="forest"] { | ||||||
|  |     --primary-color: #065f46; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #065f46 0%, #047857 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="sunset"] { | ||||||
|  |     --primary-color: #ea580c; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #ea580c 0%, #dc2626 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="purple"] { | ||||||
|  |     --primary-color: #7c3aed; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="dark"] { | ||||||
|  |     --primary-color: #374151; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #374151 0%, #1f2937 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="rose"] { | ||||||
|  |     --primary-color: #e11d48; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #e11d48 0%, #f43f5e 100%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | [data-theme="mint"] { | ||||||
|  |     --primary-color: #10b981; | ||||||
|  |     --primary-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%); | ||||||
|  | }* { | ||||||
|  |     margin: 0; | ||||||
|  |     padding: 0; | ||||||
|  |     box-sizing: border-box; | ||||||
|  | } | ||||||
|  |  | ||||||
| body { | body { | ||||||
|     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|     background-color: #f8f9fa; |     background-color: #f8f9fa; | ||||||
| @@ -574,12 +685,13 @@ body { | |||||||
|     flex: 1; |     flex: 1; | ||||||
|     overflow-y: auto; |     overflow-y: auto; | ||||||
|     overflow-x: hidden; |     overflow-x: hidden; | ||||||
|  |     min-height: 0; /* Allow flex item to shrink below content size */ | ||||||
| } | } | ||||||
|  |  | ||||||
| .time-grid { | .time-grid { | ||||||
|     display: grid; |     display: grid; | ||||||
|     grid-template-columns: 80px 1fr; |     grid-template-columns: 80px 1fr; | ||||||
|     min-height: 100%; |     min-height: 1530px; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Time Labels */ | /* Time Labels */ | ||||||
| @@ -589,6 +701,7 @@ body { | |||||||
|     position: sticky; |     position: sticky; | ||||||
|     left: 0; |     left: 0; | ||||||
|     z-index: 5; |     z-index: 5; | ||||||
|  |     min-height: 1440px; /* Match the time slots height */ | ||||||
| } | } | ||||||
|  |  | ||||||
| .time-label { | .time-label { | ||||||
| @@ -614,12 +727,13 @@ body { | |||||||
| .week-days-grid { | .week-days-grid { | ||||||
|     display: grid; |     display: grid; | ||||||
|     grid-template-columns: repeat(7, 1fr); |     grid-template-columns: repeat(7, 1fr); | ||||||
|  |     min-height: 1440px; /* Ensure grid is tall enough for 24 time slots */ | ||||||
| } | } | ||||||
|  |  | ||||||
| .week-day-column { | .week-day-column { | ||||||
|     position: relative; |     position: relative; | ||||||
|     border-right: 1px solid var(--time-label-border, #e9ecef); |     border-right: 1px solid var(--time-label-border, #e9ecef); | ||||||
|     min-height: 1500px; /* 25 time labels × 60px = 1500px total */ |     min-height: 1440px; /* 24 time slots × 60px = 1440px total */ | ||||||
| } | } | ||||||
|  |  | ||||||
| .week-day-column:last-child { | .week-day-column:last-child { | ||||||
| @@ -3014,6 +3128,50 @@ body { | |||||||
|     padding: 0.5rem; |     padding: 0.5rem; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Style Selector Styles */ | ||||||
|  | .style-selector { | ||||||
|  |     margin-bottom: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .style-selector label { | ||||||
|  |     display: block; | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  |     font-size: 0.8rem; | ||||||
|  |     font-weight: 600; | ||||||
|  |     text-transform: uppercase; | ||||||
|  |     letter-spacing: 0.5px; | ||||||
|  |     color: rgba(255, 255, 255, 0.8); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .style-selector-dropdown { | ||||||
|  |     width: 100%; | ||||||
|  |     padding: 0.5rem; | ||||||
|  |     background: rgba(255, 255, 255, 0.1); | ||||||
|  |     border: 1px solid rgba(255, 255, 255, 0.2); | ||||||
|  |     border-radius: 6px; | ||||||
|  |     color: white; | ||||||
|  |     font-size: 0.85rem; | ||||||
|  |     cursor: pointer; | ||||||
|  |     transition: all 0.2s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .style-selector-dropdown:hover { | ||||||
|  |     background: rgba(255, 255, 255, 0.15); | ||||||
|  |     border-color: rgba(255, 255, 255, 0.3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .style-selector-dropdown:focus { | ||||||
|  |     outline: none; | ||||||
|  |     background: rgba(255, 255, 255, 0.2); | ||||||
|  |     border-color: rgba(255, 255, 255, 0.4); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .style-selector-dropdown option { | ||||||
|  |     background: #333; | ||||||
|  |     color: white; | ||||||
|  |     padding: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Theme Definitions */ | /* Theme Definitions */ | ||||||
| :root { | :root { | ||||||
|     /* Default Theme */ |     /* Default Theme */ | ||||||
|   | |||||||
							
								
								
									
										3501
									
								
								frontend/styles.css.backup
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3501
									
								
								frontend/styles.css.backup
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										51
									
								
								frontend/styles/base.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/styles/base.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | /* Base Styles - Always Loaded */ | ||||||
|  | * { | ||||||
|  |     margin: 0; | ||||||
|  |     padding: 0; | ||||||
|  |     box-sizing: border-box; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body { | ||||||
|  |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|  |     background-color: #f8f9fa; | ||||||
|  |     color: #333; | ||||||
|  |     line-height: 1.6; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .app { | ||||||
|  |     min-height: 100vh; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .login-layout { | ||||||
|  |     min-height: 100vh; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Base Layout */ | ||||||
|  | .main-content { | ||||||
|  |     flex: 1; | ||||||
|  |     margin-left: 280px; | ||||||
|  |     overflow-x: hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Basic Form Elements */ | ||||||
|  | input, select, textarea, button { | ||||||
|  |     font-family: inherit; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Utility Classes */ | ||||||
|  | .loading { | ||||||
|  |     opacity: 0.7; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .error { | ||||||
|  |     color: #dc3545; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .success { | ||||||
|  |     color: #28a745; | ||||||
|  | } | ||||||
							
								
								
									
										3501
									
								
								frontend/styles/default.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3501
									
								
								frontend/styles/default.css
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										645
									
								
								frontend/styles/google.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										645
									
								
								frontend/styles/google.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,645 @@ | |||||||
|  | /* Google Calendar-inspired styles */ | ||||||
|  |  | ||||||
|  | /* Override CSS Variables for Google Calendar Style */ | ||||||
|  | :root { | ||||||
|  |     /* Google-style spacing */ | ||||||
|  |     --spacing-xs: 2px; | ||||||
|  |     --spacing-sm: 4px; | ||||||
|  |     --spacing-md: 8px; | ||||||
|  |     --spacing-lg: 12px; | ||||||
|  |     --spacing-xl: 16px; | ||||||
|  |      | ||||||
|  |     /* Google-style borders and radius */ | ||||||
|  |     --border-radius-small: 2px; | ||||||
|  |     --border-radius-medium: 4px; | ||||||
|  |     --border-radius-large: 8px; | ||||||
|  |     --border-light: 1px solid #dadce0; | ||||||
|  |     --border-medium: 1px solid #dadce0; | ||||||
|  |      | ||||||
|  |     /* Google-style shadows */ | ||||||
|  |     --shadow-sm: 0 1px 2px 0 rgba(60,64,67,.3), 0 1px 3px 1px rgba(60,64,67,.15); | ||||||
|  |     --shadow-md: 0 1px 3px 0 rgba(60,64,67,.3), 0 4px 8px 3px rgba(60,64,67,.15); | ||||||
|  |     --shadow-lg: 0 4px 6px 0 rgba(60,64,67,.3), 0 8px 25px 5px rgba(60,64,67,.15); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style sidebar - override all theme variants */ | ||||||
|  | body .app-sidebar, | ||||||
|  | [data-theme] .app-sidebar, | ||||||
|  | [data-theme="default"] .app-sidebar, | ||||||
|  | [data-theme="ocean"] .app-sidebar, | ||||||
|  | [data-theme="forest"] .app-sidebar, | ||||||
|  | [data-theme="sunset"] .app-sidebar, | ||||||
|  | [data-theme="purple"] .app-sidebar, | ||||||
|  | [data-theme="dark"] .app-sidebar, | ||||||
|  | [data-theme="rose"] .app-sidebar, | ||||||
|  | [data-theme="mint"] .app-sidebar { | ||||||
|  |     background: #ffffff !important; | ||||||
|  |     border-right: 1px solid #dadce0 !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  |     box-shadow: 2px 0 8px rgba(60,64,67,.1) !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .sidebar-header, | ||||||
|  | [data-theme] .sidebar-header { | ||||||
|  |     background: transparent !important; | ||||||
|  |     border-bottom: 1px solid #dadce0 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .sidebar-header h1, | ||||||
|  | [data-theme] .sidebar-header h1 { | ||||||
|  |     font-size: 20px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .user-info, | ||||||
|  | [data-theme] .user-info { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .user-info .username, | ||||||
|  | [data-theme] .user-info .username { | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .user-info .server-url, | ||||||
|  | [data-theme] .user-info .server-url { | ||||||
|  |     color: #5f6368 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style buttons */ | ||||||
|  | .create-calendar-button { | ||||||
|  |     background: #1a73e8 !important; | ||||||
|  |     color: white !important; | ||||||
|  |     border: none !important; | ||||||
|  |     border-radius: 4px !important; | ||||||
|  |     padding: 8px 16px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     font-size: 14px !important; | ||||||
|  |     cursor: pointer !important; | ||||||
|  |     box-shadow: 0 1px 2px 0 rgba(60,64,67,.3), 0 1px 3px 1px rgba(60,64,67,.15) !important; | ||||||
|  |     transition: box-shadow 0.2s ease !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .create-calendar-button:hover { | ||||||
|  |     box-shadow: 0 1px 3px 0 rgba(60,64,67,.3), 0 4px 8px 3px rgba(60,64,67,.15) !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .logout-button { | ||||||
|  |     background: transparent !important; | ||||||
|  |     color: #1a73e8 !important; | ||||||
|  |     border: 1px solid #dadce0 !important; | ||||||
|  |     border-radius: 4px !important; | ||||||
|  |     padding: 8px 16px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     font-size: 14px !important; | ||||||
|  |     cursor: pointer !important; | ||||||
|  |     transition: background-color 0.2s ease !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .logout-button:hover { | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style navigation and sidebar text */ | ||||||
|  | body .sidebar-nav .nav-link, | ||||||
|  | [data-theme] .sidebar-nav .nav-link { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     text-decoration: none !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .sidebar-nav .nav-link:hover, | ||||||
|  | [data-theme] .sidebar-nav .nav-link:hover { | ||||||
|  |     color: #1a73e8 !important; | ||||||
|  |     background: #f1f3f4 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Calendar list styling */ | ||||||
|  | body .calendar-list h3, | ||||||
|  | [data-theme] .calendar-list h3 { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .calendar-list .calendar-name, | ||||||
|  | [data-theme] .calendar-list .calendar-name { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .no-calendars, | ||||||
|  | [data-theme] .no-calendars { | ||||||
|  |     color: #5f6368 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Form labels and text */ | ||||||
|  | body .sidebar-footer label, | ||||||
|  | [data-theme] .sidebar-footer label, | ||||||
|  | body .view-selector label, | ||||||
|  | [data-theme] .view-selector label, | ||||||
|  | body .theme-selector label, | ||||||
|  | [data-theme] .theme-selector label, | ||||||
|  | body .style-selector label, | ||||||
|  | [data-theme] .style-selector label { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style selectors */ | ||||||
|  | body .view-selector-dropdown, | ||||||
|  | body .theme-selector-dropdown, | ||||||
|  | body .style-selector-dropdown, | ||||||
|  | [data-theme] .view-selector-dropdown, | ||||||
|  | [data-theme] .theme-selector-dropdown, | ||||||
|  | [data-theme] .style-selector-dropdown { | ||||||
|  |     border: 1px solid #dadce0 !important; | ||||||
|  |     border-radius: 4px !important; | ||||||
|  |     padding: 8px !important; | ||||||
|  |     font-size: 14px !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     background: white !important; | ||||||
|  |     font-family: inherit !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .view-selector-dropdown:focus, | ||||||
|  | .theme-selector-dropdown:focus, | ||||||
|  | .style-selector-dropdown:focus { | ||||||
|  |     outline: none; | ||||||
|  |     border-color: #1a73e8; | ||||||
|  |     box-shadow: 0 0 0 2px rgba(26,115,232,.2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style calendar list items */ | ||||||
|  | .calendar-list h3 { | ||||||
|  |     font-size: 14px; | ||||||
|  |     font-weight: 500; | ||||||
|  |     color: #3c4043; | ||||||
|  |     margin-bottom: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .calendar-list ul { | ||||||
|  |     list-style: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .calendar-list .calendar-item { | ||||||
|  |     padding: 4px 0; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     transition: background-color 0.15s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .calendar-list .calendar-item:hover { | ||||||
|  |     background-color: #f1f3f4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .calendar-list .calendar-name { | ||||||
|  |     color: #3c4043; | ||||||
|  |     font-size: 14px; | ||||||
|  |     font-weight: 400; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style main content area */ | ||||||
|  | body .app-main, | ||||||
|  | [data-theme] .app-main { | ||||||
|  |     background: #ffffff !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Calendar header elements */ | ||||||
|  | body .calendar-header, | ||||||
|  | [data-theme] .calendar-header { | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body .calendar-header h2, | ||||||
|  | body .calendar-header h3, | ||||||
|  | body .month-header, | ||||||
|  | body .week-header, | ||||||
|  | [data-theme] .calendar-header h2, | ||||||
|  | [data-theme] .calendar-header h3, | ||||||
|  | [data-theme] .month-header, | ||||||
|  | [data-theme] .week-header { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Month name and title - aggressive override */ | ||||||
|  | body h1, | ||||||
|  | body h2,  | ||||||
|  | body h3, | ||||||
|  | body .month-title, | ||||||
|  | body .calendar-title, | ||||||
|  | body .current-month, | ||||||
|  | body .month-year, | ||||||
|  | body .header-title, | ||||||
|  | [data-theme] h1, | ||||||
|  | [data-theme] h2, | ||||||
|  | [data-theme] h3, | ||||||
|  | [data-theme] .month-title, | ||||||
|  | [data-theme] .calendar-title, | ||||||
|  | [data-theme] .current-month, | ||||||
|  | [data-theme] .month-year, | ||||||
|  | [data-theme] .header-title { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Navigation arrows and buttons - aggressive override */ | ||||||
|  | body button, | ||||||
|  | body .nav-button, | ||||||
|  | body .calendar-nav-button, | ||||||
|  | body .prev-button, | ||||||
|  | body .next-button, | ||||||
|  | body .arrow-button, | ||||||
|  | body .navigation-arrow, | ||||||
|  | body [class*="arrow"], | ||||||
|  | body [class*="nav"], | ||||||
|  | body [class*="button"], | ||||||
|  | [data-theme] button, | ||||||
|  | [data-theme] .nav-button, | ||||||
|  | [data-theme] .calendar-nav-button, | ||||||
|  | [data-theme] .prev-button, | ||||||
|  | [data-theme] .next-button, | ||||||
|  | [data-theme] .arrow-button, | ||||||
|  | [data-theme] .navigation-arrow, | ||||||
|  | [data-theme] [class*="arrow"], | ||||||
|  | [data-theme] [class*="nav"], | ||||||
|  | [data-theme] [class*="button"] { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     border: 1px solid #dadce0 !important; | ||||||
|  |     border-radius: 4px !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body button:hover, | ||||||
|  | body .nav-button:hover, | ||||||
|  | body .calendar-nav-button:hover, | ||||||
|  | body .prev-button:hover, | ||||||
|  | body .next-button:hover, | ||||||
|  | body .arrow-button:hover, | ||||||
|  | [data-theme] button:hover, | ||||||
|  | [data-theme] .nav-button:hover, | ||||||
|  | [data-theme] .calendar-nav-button:hover, | ||||||
|  | [data-theme] .prev-button:hover, | ||||||
|  | [data-theme] .next-button:hover, | ||||||
|  | [data-theme] .arrow-button:hover { | ||||||
|  |     background: #e8f0fe !important; | ||||||
|  |     color: #1a73e8 !important; | ||||||
|  |     border-color: #1a73e8 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Calendar controls and date display */ | ||||||
|  | body .calendar-controls, | ||||||
|  | body .current-date, | ||||||
|  | body .date-display, | ||||||
|  | [data-theme] .calendar-controls, | ||||||
|  | [data-theme] .current-date, | ||||||
|  | [data-theme] .date-display { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Ultimate nuclear approach - override EVERYTHING */ | ||||||
|  | html body .app-main, | ||||||
|  | html body .app-main *, | ||||||
|  | html body .main-content, | ||||||
|  | html body .main-content *, | ||||||
|  | html body .calendar-container, | ||||||
|  | html body .calendar-container *, | ||||||
|  | html [data-theme] .app-main, | ||||||
|  | html [data-theme] .app-main *, | ||||||
|  | html [data-theme] .main-content, | ||||||
|  | html [data-theme] .main-content *, | ||||||
|  | html [data-theme] .calendar-container, | ||||||
|  | html [data-theme] .calendar-container *, | ||||||
|  | html [data-theme="default"] .app-main *, | ||||||
|  | html [data-theme="ocean"] .app-main *, | ||||||
|  | html [data-theme="forest"] .app-main *, | ||||||
|  | html [data-theme="sunset"] .app-main *, | ||||||
|  | html [data-theme="purple"] .app-main *, | ||||||
|  | html [data-theme="dark"] .app-main *, | ||||||
|  | html [data-theme="rose"] .app-main *, | ||||||
|  | html [data-theme="mint"] .app-main * { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     text-shadow: none !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Force all text elements */ | ||||||
|  | html body .app-main h1, | ||||||
|  | html body .app-main h2, | ||||||
|  | html body .app-main h3, | ||||||
|  | html body .app-main h4, | ||||||
|  | html body .app-main h5, | ||||||
|  | html body .app-main h6, | ||||||
|  | html body .app-main p, | ||||||
|  | html body .app-main span, | ||||||
|  | html body .app-main div, | ||||||
|  | html body .app-main button, | ||||||
|  | html [data-theme] .app-main h1, | ||||||
|  | html [data-theme] .app-main h2, | ||||||
|  | html [data-theme] .app-main h3, | ||||||
|  | html [data-theme] .app-main h4, | ||||||
|  | html [data-theme] .app-main h5, | ||||||
|  | html [data-theme] .app-main h6, | ||||||
|  | html [data-theme] .app-main p, | ||||||
|  | html [data-theme] .app-main span, | ||||||
|  | html [data-theme] .app-main div, | ||||||
|  | html [data-theme] .app-main button { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Exception for buttons - make them stand out */ | ||||||
|  | body .app-main button, | ||||||
|  | body .main-content button, | ||||||
|  | [data-theme] .app-main button, | ||||||
|  | [data-theme] .main-content button { | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     border: 1px solid #dadce0 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style calendar grid - more aggressive styling */ | ||||||
|  | html body .calendar-grid, | ||||||
|  | html [data-theme] .calendar-grid, | ||||||
|  | body .calendar-container, | ||||||
|  | [data-theme] .calendar-container { | ||||||
|  |     border: 1px solid #dadce0 !important; | ||||||
|  |     border-radius: 8px !important; | ||||||
|  |     overflow: hidden !important; | ||||||
|  |     background: white !important; | ||||||
|  |     box-shadow: 0 1px 3px 0 rgba(60,64,67,.3), 0 4px 8px 3px rgba(60,64,67,.15) !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .calendar-header, | ||||||
|  | html [data-theme] .calendar-header { | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     border-bottom: 1px solid #dadce0 !important; | ||||||
|  |     padding: 16px !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .month-header, | ||||||
|  | html body .week-header, | ||||||
|  | html [data-theme] .month-header, | ||||||
|  | html [data-theme] .week-header { | ||||||
|  |     font-size: 22px !important; | ||||||
|  |     font-weight: 400 !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style calendar cells - complete overhaul */ | ||||||
|  | html body .calendar-day, | ||||||
|  | html [data-theme] .calendar-day, | ||||||
|  | body .day-cell, | ||||||
|  | [data-theme] .day-cell { | ||||||
|  |     border: 1px solid #e8eaed !important; | ||||||
|  |     background: white !important; | ||||||
|  |     transition: background-color 0.15s ease !important; | ||||||
|  |     padding: 8px !important; | ||||||
|  |     min-height: 120px !important; | ||||||
|  |     position: relative !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .calendar-day:hover, | ||||||
|  | html [data-theme] .calendar-day:hover, | ||||||
|  | body .day-cell:hover, | ||||||
|  | [data-theme] .day-cell:hover { | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     box-shadow: inset 0 0 0 1px #dadce0 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .calendar-day.today, | ||||||
|  | html [data-theme] .calendar-day.today, | ||||||
|  | body .day-cell.today, | ||||||
|  | [data-theme] .day-cell.today { | ||||||
|  |     background: #e8f0fe !important; | ||||||
|  |     border-color: #1a73e8 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .calendar-day.other-month, | ||||||
|  | html [data-theme] .calendar-day.other-month, | ||||||
|  | body .day-cell.other-month, | ||||||
|  | [data-theme] .day-cell.other-month { | ||||||
|  |     background: #fafafa !important; | ||||||
|  |     color: #9aa0a6 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .day-number, | ||||||
|  | html [data-theme] .day-number, | ||||||
|  | body .date-number, | ||||||
|  | [data-theme] .date-number { | ||||||
|  |     font-size: 14px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     color: #3c4043 !important; | ||||||
|  |     margin-bottom: 4px !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Day headers (Mon, Tue, Wed, etc.) */ | ||||||
|  | html body .day-header, | ||||||
|  | html [data-theme] .day-header, | ||||||
|  | body .weekday-header, | ||||||
|  | [data-theme] .weekday-header { | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     color: #5f6368 !important; | ||||||
|  |     font-size: 12px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     text-transform: uppercase !important; | ||||||
|  |     letter-spacing: 0.8px !important; | ||||||
|  |     padding: 8px !important; | ||||||
|  |     border-bottom: 1px solid #dadce0 !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google Calendar-style events - complete redesign */ | ||||||
|  | html body .app-main .event, | ||||||
|  | html [data-theme] .app-main .event, | ||||||
|  | html body .calendar-container .event, | ||||||
|  | html [data-theme] .calendar-container .event, | ||||||
|  | body .event, | ||||||
|  | [data-theme] .event { | ||||||
|  |     border-radius: 4px !important; | ||||||
|  |     padding: 2px 8px !important; | ||||||
|  |     font-size: 11px !important; | ||||||
|  |     font-weight: 400 !important; | ||||||
|  |     margin: 1px 0 2px 0 !important; | ||||||
|  |     cursor: pointer !important; | ||||||
|  |     border: none !important; | ||||||
|  |     color: white !important; | ||||||
|  |     font-family: 'Google Sans', 'Roboto', sans-serif !important; | ||||||
|  |     box-shadow: 0 1px 3px rgba(60,64,67,.3) !important; | ||||||
|  |     transition: transform 0.1s ease, box-shadow 0.1s ease !important; | ||||||
|  |     display: block !important; | ||||||
|  |     text-overflow: ellipsis !important; | ||||||
|  |     overflow: hidden !important; | ||||||
|  |     white-space: nowrap !important; | ||||||
|  |     line-height: 1.3 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .app-main .event *, | ||||||
|  | html [data-theme] .app-main .event *, | ||||||
|  | html body .calendar-container .event *, | ||||||
|  | html [data-theme] .calendar-container .event *, | ||||||
|  | body .event *, | ||||||
|  | [data-theme] .event * { | ||||||
|  |     color: white !important; | ||||||
|  |     font-family: inherit !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .app-main .event:hover, | ||||||
|  | html [data-theme] .app-main .event:hover, | ||||||
|  | body .event:hover, | ||||||
|  | [data-theme] .event:hover { | ||||||
|  |     transform: translateY(-1px) !important; | ||||||
|  |     box-shadow: 0 2px 8px rgba(60,64,67,.4) !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* All-day events styling */ | ||||||
|  | html body .event.all-day, | ||||||
|  | html [data-theme] .event.all-day { | ||||||
|  |     border-radius: 12px !important; | ||||||
|  |     padding: 4px 12px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     margin: 2px 0 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Event time display */ | ||||||
|  | html body .event-time, | ||||||
|  | html [data-theme] .event-time { | ||||||
|  |     opacity: 0.9 !important; | ||||||
|  |     font-size: 10px !important; | ||||||
|  |     margin-right: 4px !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Week view events */ | ||||||
|  | html body .week-view .event, | ||||||
|  | html [data-theme] .week-view .event { | ||||||
|  |     border-left: 3px solid rgba(255,255,255,0.8) !important; | ||||||
|  |     border-radius: 0 4px 4px 0 !important; | ||||||
|  |     padding-left: 6px !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Calendar table structure */ | ||||||
|  | html body .calendar-table, | ||||||
|  | html [data-theme] .calendar-table, | ||||||
|  | body table, | ||||||
|  | [data-theme] table { | ||||||
|  |     border-collapse: separate !important; | ||||||
|  |     border-spacing: 0 !important; | ||||||
|  |     width: 100% !important; | ||||||
|  |     background: white !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .calendar-table td, | ||||||
|  | html [data-theme] .calendar-table td, | ||||||
|  | body table td, | ||||||
|  | [data-theme] table td { | ||||||
|  |     vertical-align: top !important; | ||||||
|  |     border: 1px solid #e8eaed !important; | ||||||
|  |     background: white !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Month/Week view toggle */ | ||||||
|  | html body .view-toggle, | ||||||
|  | html [data-theme] .view-toggle { | ||||||
|  |     display: flex !important; | ||||||
|  |     gap: 4px !important; | ||||||
|  |     background: #f1f3f4 !important; | ||||||
|  |     border-radius: 6px !important; | ||||||
|  |     padding: 2px !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .view-toggle button, | ||||||
|  | html [data-theme] .view-toggle button { | ||||||
|  |     padding: 6px 12px !important; | ||||||
|  |     border: none !important; | ||||||
|  |     background: transparent !important; | ||||||
|  |     color: #5f6368 !important; | ||||||
|  |     border-radius: 4px !important; | ||||||
|  |     font-size: 13px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     cursor: pointer !important; | ||||||
|  |     transition: all 0.15s ease !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .view-toggle button.active, | ||||||
|  | html [data-theme] .view-toggle button.active { | ||||||
|  |     background: white !important; | ||||||
|  |     color: #1a73e8 !important; | ||||||
|  |     box-shadow: 0 1px 3px rgba(60,64,67,.3) !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Today button */ | ||||||
|  | html body .today-button, | ||||||
|  | html [data-theme] .today-button { | ||||||
|  |     background: white !important; | ||||||
|  |     border: 1px solid #dadce0 !important; | ||||||
|  |     color: #1a73e8 !important; | ||||||
|  |     padding: 8px 16px !important; | ||||||
|  |     border-radius: 4px !important; | ||||||
|  |     font-weight: 500 !important; | ||||||
|  |     font-size: 14px !important; | ||||||
|  |     cursor: pointer !important; | ||||||
|  |     transition: all 0.15s ease !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html body .today-button:hover, | ||||||
|  | html [data-theme] .today-button:hover { | ||||||
|  |     background: #f8f9fa !important; | ||||||
|  |     border-color: #1a73e8 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style modals */ | ||||||
|  | .modal-overlay { | ||||||
|  |     background: rgba(0, 0, 0, 0.5); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .modal-content { | ||||||
|  |     background: white; | ||||||
|  |     border-radius: 8px; | ||||||
|  |     box-shadow: var(--shadow-lg); | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .modal h2 { | ||||||
|  |     font-size: 20px; | ||||||
|  |     font-weight: 500; | ||||||
|  |     color: #3c4043; | ||||||
|  |     font-family: 'Google Sans', sans-serif; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style form inputs */ | ||||||
|  | input[type="text"], | ||||||
|  | input[type="email"], | ||||||
|  | input[type="password"], | ||||||
|  | input[type="url"], | ||||||
|  | input[type="date"], | ||||||
|  | input[type="time"], | ||||||
|  | textarea, | ||||||
|  | select { | ||||||
|  |     border: 1px solid #dadce0; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     padding: 8px 12px; | ||||||
|  |     font-size: 14px; | ||||||
|  |     color: #3c4043; | ||||||
|  |     background: white; | ||||||
|  |     font-family: inherit; | ||||||
|  |     transition: border-color 0.15s ease, box-shadow 0.15s ease; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input:focus, | ||||||
|  | textarea:focus, | ||||||
|  | select:focus { | ||||||
|  |     outline: none; | ||||||
|  |     border-color: #1a73e8; | ||||||
|  |     box-shadow: 0 0 0 2px rgba(26,115,232,.2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Google-style labels */ | ||||||
|  | label { | ||||||
|  |     font-size: 14px; | ||||||
|  |     font-weight: 500; | ||||||
|  |     color: #3c4043; | ||||||
|  |     margin-bottom: 4px; | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user