diff --git a/client/src/components/auth/login.rs b/client/src/components/auth/login.rs index b70b23b..54c61f9 100644 --- a/client/src/components/auth/login.rs +++ b/client/src/components/auth/login.rs @@ -1,14 +1,17 @@ // src/components/auth/login.rs + use crate::{ config::colors::themes::Theme, state::pages::auth::AuthState, + components::common::dialog, + state::state::AppState, // Add this import }; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect, Margin}, - style::{Color, Style, Modifier}, + style::{Style, Modifier, Color}, // Removed unused Color import widgets::{Block, BorderType, Borders, Paragraph}, Frame, - text::{Line, Span}, + text::Line, // Removed unused Span import }; pub fn render_login( @@ -16,6 +19,7 @@ pub fn render_login( area: Rect, theme: &Theme, state: &AuthState, + app_state: &AppState, // Add AppState parameter is_edit_mode: bool, ) { // Main container @@ -129,4 +133,15 @@ pub fn render_login( chunks[1], ); } + + if app_state.ui.dialog.show_dialog { + dialog::render_dialog( + f, + f.area(), // Use area() instead of deprecated size() + theme, + &app_state.ui.dialog.dialog_title, + &app_state.ui.dialog.dialog_message, + app_state.ui.dialog.dialog_button_active, + ); + } } diff --git a/client/src/components/common.rs b/client/src/components/common.rs index be11d6a..b87d858 100644 --- a/client/src/components/common.rs +++ b/client/src/components/common.rs @@ -2,7 +2,9 @@ pub mod command_line; pub mod status_line; pub mod background; +pub mod dialog; pub use command_line::*; pub use status_line::*; pub use background::*; +pub use dialog::*; diff --git a/client/src/components/common/dialog.rs b/client/src/components/common/dialog.rs new file mode 100644 index 0000000..754157c --- /dev/null +++ b/client/src/components/common/dialog.rs @@ -0,0 +1,101 @@ +// src/components/common/dialog.rs +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect, Margin}, + style::{Color, Modifier, Style}, + widgets::{Block, BorderType, Borders, Paragraph}, + Frame, + text::{Text, Line, Span} +}; +use ratatui::prelude::Alignment; +use crate::config::colors::themes::Theme; + +pub fn render_dialog( + f: &mut Frame, + area: Rect, + theme: &Theme, + title: &str, + message: &str, + is_active: bool, +) { + // Create a centered rect for the dialog + let dialog_area = centered_rect(60, 25, area); + + // Main dialog container + let block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(theme.accent)) + .title(title) + .style(Style::default().bg(theme.bg)); + + f.render_widget(&block, dialog_area); + + // Inner content area + let inner_area = block.inner(dialog_area).inner(Margin { + horizontal: 2, + vertical: 1, + }); + + // Split into message and button areas + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Min(3), // Message content + Constraint::Length(3), // Button + ]) + .split(inner_area); + + // Message text + let message_text = Text::from(message.lines().map(|l| Line::from(Span::styled( + l, + Style::default().fg(theme.fg) + ))).collect::>()); + + let message_paragraph = Paragraph::new(message_text) + .alignment(Alignment::Center); + f.render_widget(message_paragraph, chunks[0]); + + // OK Button + let button_style = if is_active { + Style::default() + .fg(theme.highlight) + .add_modifier(Modifier::BOLD) + } else { + Style::default().fg(theme.fg) + }; + + let button_block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Plain) + .border_style(Style::default().fg(theme.accent)) + .style(Style::default().bg(theme.bg)); + + f.render_widget( + Paragraph::new("OK") + .block(button_block) + .style(button_style) + .alignment(Alignment::Center), + chunks[1], + ); +} + +/// Helper function to center a rect with given percentage values +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .split(r); + + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .split(popup_layout[1])[1] +} diff --git a/client/src/state/state.rs b/client/src/state/state.rs index 8b8f101..ff52d02 100644 --- a/client/src/state/state.rs +++ b/client/src/state/state.rs @@ -5,6 +5,14 @@ use common::proto::multieko2::table_definition::ProfileTreeResponse; use crate::components::IntroState; use crate::modes::handlers::mode_manager::AppMode; +#[derive(Default)] +pub struct DialogState { + pub show_dialog: bool, + pub dialog_title: String, + pub dialog_message: String, + pub dialog_button_active: bool, +} + pub struct UiState { pub show_sidebar: bool, pub is_saved: bool, @@ -13,6 +21,7 @@ pub struct UiState { pub show_form: bool, pub show_login: bool, pub intro_state: IntroState, + pub dialog: DialogState, // Add dialog state here } pub struct GeneralState { @@ -66,6 +75,24 @@ impl AppState { pub fn update_mode(&mut self, mode: AppMode) { self.current_mode = mode; } + + // Add dialog helper methods + pub fn show_dialog(&mut self, title: &str, message: &str) { + self.ui.dialog.show_dialog = true; + self.ui.dialog.dialog_title = title.to_string(); + self.ui.dialog.dialog_message = message.to_string(); + self.ui.dialog.dialog_button_active = true; + } + + pub fn hide_dialog(&mut self) { + self.ui.dialog.show_dialog = false; + self.ui.dialog.dialog_title.clear(); + self.ui.dialog.dialog_message.clear(); + } + + pub fn set_dialog_button_active(&mut self, active: bool) { + self.ui.dialog.dialog_button_active = active; + } } impl Default for UiState { @@ -78,6 +105,7 @@ impl Default for UiState { show_form: false, show_login: false, intro_state: IntroState::new(), + dialog: DialogState::default(), } } } diff --git a/client/src/tui/functions/login.rs b/client/src/tui/functions/login.rs index f447a96..bf8ff8b 100644 --- a/client/src/tui/functions/login.rs +++ b/client/src/tui/functions/login.rs @@ -19,10 +19,13 @@ pub async fn handle_action( } else if auth_state.current_field == 0 { // Username -> Password (wrap around fields only) auth_state.current_field = 1; + } else if auth_state.current_field == 2 { + // From Login button to Password field + auth_state.current_field = 1; } // Update cursor position only when in a field - if !auth_state.return_selected { + if auth_state.current_field < 2 { let current_input = auth_state.get_current_input(); let max_cursor_pos = current_input.len(); auth_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); @@ -40,12 +43,15 @@ pub async fn handle_action( auth_state.current_field = 1; } else if auth_state.current_field == 1 { // Password -> Login button + auth_state.current_field = 2; auth_state.return_selected = false; - // No cursor update needed when on buttons + } else if auth_state.current_field == 2 { + // Login button -> Return button + auth_state.return_selected = true; } // Update cursor position only when in a field - if !auth_state.return_selected { + if auth_state.current_field < 2 { let current_input = auth_state.get_current_input(); let max_cursor_pos = current_input.len(); auth_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 16a17db..7198759 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -47,7 +47,14 @@ pub fn render_ui( // Use app_state's intro_state directly app_state.ui.intro_state.render(f, main_content_area, theme); }else if app_state.ui.show_login { - render_login(f, main_content_area, theme, auth_state, !auth_state.return_selected); + render_login( + f, + main_content_area, + theme, + auth_state, + app_state, // Add AppState reference here + auth_state.current_field < 2 + ); } else if app_state.ui.show_admin { // Create temporary AdminPanelState for rendering let mut admin_state = AdminPanelState::new(