361 lines
10 KiB
Rust
361 lines
10 KiB
Rust
// 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<String> = 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<String>,
|
|
pub user_id: Option<String>,
|
|
pub role: Option<String>,
|
|
pub decoded_username: Option<String>,
|
|
}
|
|
|
|
/// Represents the state of the Login form UI
|
|
pub struct LoginState {
|
|
pub username: String,
|
|
pub password: String,
|
|
pub error_message: Option<String>,
|
|
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<String>,
|
|
pub current_field: usize,
|
|
pub current_cursor_pos: usize,
|
|
pub has_unsaved_changes: bool,
|
|
pub autocomplete: AutocompleteState<String>,
|
|
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<SuggestionItem<String>> = 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<String> {
|
|
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<String> {
|
|
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<Self::SuggestionData>> {
|
|
Some(&self.autocomplete)
|
|
}
|
|
|
|
fn autocomplete_state_mut(&mut self) -> Option<&mut AutocompleteState<Self::SuggestionData>> {
|
|
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<SuggestionItem<String>> = 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<String> {
|
|
// 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<SuggestionItem<Self::SuggestionData>>) {
|
|
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;
|
|
}
|
|
}
|
|
}
|