// src/state/pages/auth.rs use canvas::canvas::{CanvasState, ActionContext, CanvasAction, AppMode}; use canvas::autocomplete::{AutocompleteCanvasState, AutocompleteState, SuggestionItem}; use lazy_static::lazy_static; lazy_static! { pub static ref AVAILABLE_ROLES: Vec = vec![ "admin".to_string(), "moderator".to_string(), "accountant".to_string(), "viewer".to_string(), ]; } /// Represents the authenticated session state #[derive(Default)] pub struct AuthState { pub auth_token: Option, pub user_id: Option, pub role: Option, pub decoded_username: Option, } /// Represents the state of the Login form UI pub struct LoginState { pub username: String, pub password: String, pub error_message: Option, pub current_field: usize, pub current_cursor_pos: usize, pub has_unsaved_changes: bool, pub login_request_pending: bool, pub app_mode: AppMode, } impl Default for LoginState { fn default() -> Self { Self { username: String::new(), password: String::new(), error_message: None, current_field: 0, current_cursor_pos: 0, has_unsaved_changes: false, login_request_pending: false, app_mode: AppMode::Edit, } } } /// Represents the state of the Registration form UI #[derive(Clone)] pub struct RegisterState { pub username: String, pub email: String, pub password: String, pub password_confirmation: String, pub role: String, pub error_message: Option, pub current_field: usize, pub current_cursor_pos: usize, pub has_unsaved_changes: bool, pub autocomplete: AutocompleteState, pub app_mode: AppMode, } impl Default for RegisterState { fn default() -> Self { Self { username: String::new(), email: String::new(), password: String::new(), password_confirmation: String::new(), role: String::new(), error_message: None, current_field: 0, current_cursor_pos: 0, has_unsaved_changes: false, autocomplete: AutocompleteState::new(), app_mode: AppMode::Edit, } } } impl AuthState { pub fn new() -> Self { Self::default() } } impl LoginState { pub fn new() -> Self { Self { app_mode: AppMode::Edit, ..Default::default() } } } impl RegisterState { pub fn new() -> Self { let mut state = Self { autocomplete: AutocompleteState::new(), app_mode: AppMode::Edit, ..Default::default() }; // Initialize autocomplete with role suggestions let suggestions: Vec> = AVAILABLE_ROLES .iter() .map(|role| SuggestionItem::simple(role.clone(), role.clone())) .collect(); // Set suggestions but keep inactive initially state.autocomplete.set_suggestions(suggestions); state.autocomplete.is_active = false; // Not active by default state } } // Implement external library's CanvasState for LoginState impl CanvasState for LoginState { fn current_field(&self) -> usize { self.current_field } fn current_cursor_pos(&self) -> usize { self.current_cursor_pos } fn set_current_field(&mut self, index: usize) { if index < 2 { self.current_field = index; } } fn set_current_cursor_pos(&mut self, pos: usize) { self.current_cursor_pos = pos; } fn get_current_input(&self) -> &str { match self.current_field { 0 => &self.username, 1 => &self.password, _ => "", } } fn get_current_input_mut(&mut self) -> &mut String { match self.current_field { 0 => &mut self.username, 1 => &mut self.password, _ => panic!("Invalid current_field index in LoginState"), } } fn inputs(&self) -> Vec<&String> { vec![&self.username, &self.password] } fn fields(&self) -> Vec<&str> { vec!["Username/Email", "Password"] } fn has_unsaved_changes(&self) -> bool { self.has_unsaved_changes } fn set_has_unsaved_changes(&mut self, changed: bool) { self.has_unsaved_changes = changed; } fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option { match action { CanvasAction::Custom(action_str) if action_str == "submit" => { if !self.username.is_empty() && !self.password.is_empty() { Some(format!("Submitting login for: {}", self.username)) } else { Some("Please fill in all required fields".to_string()) } } _ => None, } } fn current_mode(&self) -> AppMode { self.app_mode } } // Implement external library's CanvasState for RegisterState impl CanvasState for RegisterState { fn current_field(&self) -> usize { self.current_field } fn current_cursor_pos(&self) -> usize { self.current_cursor_pos } fn set_current_field(&mut self, index: usize) { if index < 5 { self.current_field = index; // Auto-activate autocomplete when moving to role field (index 4) if index == 4 && !self.autocomplete.is_active { self.activate_autocomplete(); } else if index != 4 && self.autocomplete.is_active { self.deactivate_autocomplete(); } } } fn set_current_cursor_pos(&mut self, pos: usize) { self.current_cursor_pos = pos; } fn get_current_input(&self) -> &str { match self.current_field { 0 => &self.username, 1 => &self.email, 2 => &self.password, 3 => &self.password_confirmation, 4 => &self.role, _ => "", } } fn get_current_input_mut(&mut self) -> &mut String { match self.current_field { 0 => &mut self.username, 1 => &mut self.email, 2 => &mut self.password, 3 => &mut self.password_confirmation, 4 => &mut self.role, _ => panic!("Invalid current_field index in RegisterState"), } } fn inputs(&self) -> Vec<&String> { vec![ &self.username, &self.email, &self.password, &self.password_confirmation, &self.role, ] } fn fields(&self) -> Vec<&str> { vec![ "Username", "Email (Optional)", "Password (Optional)", "Confirm Password", "Role (Optional)" ] } fn has_unsaved_changes(&self) -> bool { self.has_unsaved_changes } fn set_has_unsaved_changes(&mut self, changed: bool) { self.has_unsaved_changes = changed; } fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option { match action { CanvasAction::Custom(action_str) if action_str == "submit" => { if !self.username.is_empty() { Some(format!("Submitting registration for: {}", self.username)) } else { Some("Username is required".to_string()) } } _ => None, } } fn current_mode(&self) -> AppMode { self.app_mode } } // Add autocomplete support for RegisterState impl AutocompleteCanvasState for RegisterState { type SuggestionData = String; fn supports_autocomplete(&self, field_index: usize) -> bool { field_index == 4 // Only role field supports autocomplete } fn autocomplete_state(&self) -> Option<&AutocompleteState> { Some(&self.autocomplete) } fn autocomplete_state_mut(&mut self) -> Option<&mut AutocompleteState> { Some(&mut self.autocomplete) } fn activate_autocomplete(&mut self) { let current_field = self.current_field(); if self.supports_autocomplete(current_field) { self.autocomplete.activate(current_field); // Re-filter suggestions based on current input let current_input = self.role.to_lowercase(); let filtered_suggestions: Vec> = AVAILABLE_ROLES .iter() .filter(|role| role.to_lowercase().contains(¤t_input)) .map(|role| SuggestionItem::simple(role.clone(), role.clone())) .collect(); self.autocomplete.set_suggestions(filtered_suggestions); } } fn deactivate_autocomplete(&mut self) { self.autocomplete.deactivate(); } fn is_autocomplete_active(&self) -> bool { self.autocomplete.is_active } fn is_autocomplete_ready(&self) -> bool { self.autocomplete.is_ready() } fn apply_autocomplete_selection(&mut self) -> Option { // First, get the data we need and clone it to avoid borrowing conflicts let selection_info = self.autocomplete.get_selected().map(|selected| { (selected.value_to_store.clone(), selected.display_text.clone()) }); // Now do the mutable operations if let Some((value, display_text)) = selection_info { self.role = value; self.set_has_unsaved_changes(true); self.deactivate_autocomplete(); Some(format!("Selected role: {}", display_text)) } else { None } } fn set_autocomplete_suggestions(&mut self, suggestions: Vec>) { if let Some(state) = self.autocomplete_state_mut() { state.set_suggestions(suggestions); } } fn set_autocomplete_loading(&mut self, loading: bool) { if let Some(state) = self.autocomplete_state_mut() { state.is_loading = loading; } } }