use gloo_storage::{LocalStorage, Storage}; use serde::{Deserialize, Serialize}; use serde_json; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct UserPreferences { pub calendar_selected_date: Option, pub calendar_time_increment: Option, pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_colors: Option, } #[derive(Debug, Serialize)] pub struct UpdatePreferencesRequest { pub calendar_selected_date: Option, pub calendar_time_increment: Option, pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_colors: Option, } pub struct PreferencesService { base_url: String, } impl PreferencesService { pub fn new() -> Self { let base_url = option_env!("BACKEND_API_URL") .unwrap_or("http://localhost:3000/api") .to_string(); Self { base_url } } /// Load preferences from LocalStorage (cached from login) pub fn load_cached() -> Option { if let Ok(prefs_json) = LocalStorage::get::("user_preferences") { serde_json::from_str(&prefs_json).ok() } else { None } } /// Update a single preference field and sync with backend pub async fn update_preference(&self, field: &str, value: serde_json::Value) -> Result<(), String> { // Get session token let session_token = LocalStorage::get::("session_token") .map_err(|_| "No session token found".to_string())?; // Load current preferences let mut preferences = Self::load_cached().unwrap_or(UserPreferences { calendar_selected_date: None, calendar_time_increment: None, calendar_view_mode: None, calendar_theme: None, calendar_colors: None, }); // Update the specific field match field { "calendar_selected_date" => { preferences.calendar_selected_date = value.as_str().map(|s| s.to_string()); } "calendar_time_increment" => { preferences.calendar_time_increment = value.as_i64().map(|i| i as i32); } "calendar_view_mode" => { preferences.calendar_view_mode = value.as_str().map(|s| s.to_string()); } "calendar_theme" => { preferences.calendar_theme = value.as_str().map(|s| s.to_string()); } "calendar_colors" => { preferences.calendar_colors = value.as_str().map(|s| s.to_string()); } _ => return Err(format!("Unknown preference field: {}", field)), } // Save to LocalStorage cache if let Ok(prefs_json) = serde_json::to_string(&preferences) { let _ = LocalStorage::set("user_preferences", &prefs_json); } // Sync with backend let request = UpdatePreferencesRequest { calendar_selected_date: preferences.calendar_selected_date.clone(), calendar_time_increment: preferences.calendar_time_increment, calendar_view_mode: preferences.calendar_view_mode.clone(), calendar_theme: preferences.calendar_theme.clone(), calendar_colors: preferences.calendar_colors.clone(), }; self.sync_preferences(&session_token, &request).await } /// Sync all preferences with backend async fn sync_preferences( &self, session_token: &str, request: &UpdatePreferencesRequest, ) -> Result<(), String> { let window = web_sys::window().ok_or("No global window exists")?; let json_body = serde_json::to_string(request) .map_err(|e| format!("JSON serialization failed: {}", e))?; let opts = RequestInit::new(); opts.set_method("POST"); opts.set_mode(RequestMode::Cors); opts.set_body(&wasm_bindgen::JsValue::from_str(&json_body)); let url = format!("{}/preferences", self.base_url); let request = Request::new_with_str_and_init(&url, &opts) .map_err(|e| format!("Request creation failed: {:?}", e))?; request .headers() .set("Content-Type", "application/json") .map_err(|e| format!("Header setting failed: {:?}", e))?; request .headers() .set("X-Session-Token", session_token) .map_err(|e| format!("Header setting failed: {:?}", e))?; let resp_value = JsFuture::from(window.fetch_with_request(&request)) .await .map_err(|e| format!("Network request failed: {:?}", e))?; let resp: Response = resp_value .dyn_into() .map_err(|e| format!("Response cast failed: {:?}", e))?; if resp.ok() { Ok(()) } else { Err(format!("Failed to update preferences: {}", resp.status())) } } /// Migrate preferences from LocalStorage to backend (on first login after update) pub async fn migrate_from_local_storage(&self) -> Result<(), String> { let session_token = LocalStorage::get::("session_token") .map_err(|_| "No session token found".to_string())?; let mut request = UpdatePreferencesRequest { calendar_selected_date: LocalStorage::get::("calendar_selected_date").ok(), calendar_time_increment: LocalStorage::get::("calendar_time_increment").ok().map(|i| i as i32), calendar_view_mode: LocalStorage::get::("calendar_view_mode").ok(), calendar_theme: LocalStorage::get::("calendar_theme").ok(), calendar_colors: LocalStorage::get::("calendar_colors").ok(), }; // Only migrate if we have some preferences to migrate if request.calendar_selected_date.is_some() || request.calendar_time_increment.is_some() || request.calendar_view_mode.is_some() || request.calendar_theme.is_some() || request.calendar_colors.is_some() { self.sync_preferences(&session_token, &request).await?; // Clear old LocalStorage entries after successful migration let _ = LocalStorage::delete("calendar_selected_date"); let _ = LocalStorage::delete("calendar_time_increment"); let _ = LocalStorage::delete("calendar_view_mode"); let _ = LocalStorage::delete("calendar_theme"); let _ = LocalStorage::delete("calendar_colors"); } Ok(()) } }