registration now has working form

This commit is contained in:
filipriec
2025-08-29 12:22:25 +02:00
parent cf79bc7bd5
commit 72c2691a17
7 changed files with 241 additions and 65 deletions

View File

@@ -1,13 +1,11 @@
// src/pages/register/logic.rs
use crate::services::auth::AuthClient;
use crate::state::{
app::state::AppState,
};
use crate::state::app::state::AppState;
use crate::ui::handlers::context::DialogPurpose;
use crate::buffer::state::{AppView, BufferState};
use common::proto::komp_ac::auth::AuthResponse;
use crate::pages::register::RegisterState;
use crate::pages::register::RegisterFormState;
use anyhow::Context;
use tokio::spawn;
use tokio::sync::mpsc;
@@ -22,24 +20,26 @@ pub enum RegisterResult {
/// Clears the registration form fields.
pub async fn revert(
register_state: &mut RegisterState,
_app_state: &mut AppState, // Keep signature consistent if needed elsewhere
register_state: &mut RegisterFormState,
app_state: &mut AppState,
) -> String {
register_state.username.clear();
register_state.email.clear();
register_state.password.clear();
register_state.password_confirmation.clear();
register_state.role.clear();
register_state.error_message = None;
register_state.username_mut().clear();
register_state.email_mut().clear();
register_state.password_mut().clear();
register_state.password_confirmation_mut().clear();
register_state.role_mut().clear();
register_state.set_error_message(None);
register_state.set_has_unsaved_changes(false);
register_state.current_field = 0; // Reset focus to first field
register_state.current_cursor_pos = 0;
register_state.set_current_field(0); // Reset focus to first field
register_state.set_current_cursor_pos(0);
app_state.hide_dialog();
"Registration form cleared".to_string()
}
/// Clears the form and returns to the intro screen.
pub async fn back_to_login(
register_state: &mut RegisterState,
register_state: &mut RegisterFormState,
app_state: &mut AppState,
buffer_state: &mut BufferState,
) -> String {
@@ -62,25 +62,34 @@ pub async fn back_to_login(
/// Validates input, shows loading, and spawns the registration task.
pub fn initiate_registration(
register_state: &RegisterState,
register_state: &RegisterFormState,
app_state: &mut AppState,
mut auth_client: AuthClient,
sender: mpsc::Sender<RegisterResult>,
) -> String {
// Clone necessary data
let username = register_state.username.clone();
let email = register_state.email.clone();
let password = register_state.password.clone();
let password_confirmation = register_state.password_confirmation.clone();
let role = register_state.role.clone();
let username = register_state.username().to_string();
let email = register_state.email().to_string();
let password = register_state.password().to_string();
let password_confirmation = register_state.password_confirmation().to_string();
let role = register_state.role().to_string();
// 1. Client-side validation
if username.trim().is_empty() {
app_state.show_dialog("Registration Failed", "Username cannot be empty.", vec!["OK".to_string()], DialogPurpose::RegisterFailed);
app_state.show_dialog(
"Registration Failed",
"Username cannot be empty.",
vec!["OK".to_string()],
DialogPurpose::RegisterFailed,
);
"Username cannot be empty.".to_string()
} else if !password.is_empty() && password != password_confirmation {
app_state.show_dialog("Registration Failed", "Passwords do not match.", vec!["OK".to_string()], DialogPurpose::RegisterFailed);
"Passwords do not match.".to_string()
app_state.show_dialog(
"Registration Failed",
"Passwords do not match.",
vec!["OK".to_string()],
DialogPurpose::RegisterFailed,
);
"Passwords do not match.".to_string()
} else {
// 2. Show Loading Dialog
app_state.show_loading_dialog("Registering", "Please wait...");
@@ -88,14 +97,19 @@ pub fn initiate_registration(
// 3. Spawn the registration task
spawn(async move {
let password_opt = if password.is_empty() { None } else { Some(password) };
let password_conf_opt = if password_confirmation.is_empty() { None } else { Some(password_confirmation) };
let password_conf_opt =
if password_confirmation.is_empty() { None } else { Some(password_confirmation) };
let role_opt = if role.is_empty() { None } else { Some(role) };
let register_outcome = match auth_client.register(username.clone(), email, password_opt, password_conf_opt, role_opt).await
let register_outcome = match auth_client
.register(username.clone(), email, password_opt, password_conf_opt, role_opt)
.await
.with_context(|| format!("Spawned register task failed for username: {}", username))
{
Ok(response) => RegisterResult::Success(response),
Err(e) => RegisterResult::Failure(format!("{}", e)),
};
// Send result back to the main UI thread
if let Err(e) = sender.send(register_outcome).await {
error!("Failed to send registration result: {}", e);
@@ -112,7 +126,7 @@ pub fn initiate_registration(
pub fn handle_registration_result(
result: RegisterResult,
app_state: &mut AppState,
register_state: &mut RegisterState,
register_state: &mut RegisterFormState,
) -> bool {
match result {
RegisterResult::Success(response) => {
@@ -133,7 +147,7 @@ pub fn handle_registration_result(
vec!["OK".to_string()],
DialogPurpose::RegisterFailed,
);
register_state.error_message = Some(err_msg.clone());
register_state.set_error_message(Some(err_msg.clone()));
error!(error = %err_msg, "Registration failed/connection error");
}
}

View File

@@ -1,6 +1,7 @@
// src/pages/register/state.rs
use canvas::{DataProvider, AppMode};
use canvas::{DataProvider, AppMode, FormEditor};
use std::fmt;
use lazy_static::lazy_static;
lazy_static! {
@@ -182,3 +183,128 @@ impl DataProvider for RegisterState {
field_index == 4 // only Role field supports suggestions
}
}
/// Wrapper that owns both the raw register state and its editor
pub struct RegisterFormState {
pub state: RegisterState,
pub editor: FormEditor<RegisterState>,
}
impl Default for RegisterFormState {
fn default() -> Self {
Self::new()
}
}
// manual Debug because FormEditor doesnt implement Debug
impl fmt::Debug for RegisterFormState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RegisterFormState")
.field("state", &self.state)
.finish()
}
}
impl RegisterFormState {
pub fn new() -> Self {
let state = RegisterState::default();
let editor = FormEditor::new(state.clone());
Self { state, editor }
}
// === Delegates to RegisterState ===
pub fn username(&self) -> &str {
&self.state.username
}
pub fn username_mut(&mut self) -> &mut String {
&mut self.state.username
}
pub fn email(&self) -> &str {
&self.state.email
}
pub fn email_mut(&mut self) -> &mut String {
&mut self.state.email
}
pub fn password(&self) -> &str {
&self.state.password
}
pub fn password_mut(&mut self) -> &mut String {
&mut self.state.password
}
pub fn password_confirmation(&self) -> &str {
&self.state.password_confirmation
}
pub fn password_confirmation_mut(&mut self) -> &mut String {
&mut self.state.password_confirmation
}
pub fn role(&self) -> &str {
&self.state.role
}
pub fn role_mut(&mut self) -> &mut String {
&mut self.state.role
}
pub fn error_message(&self) -> Option<&String> {
self.state.error_message.as_ref()
}
pub fn set_error_message(&mut self, msg: Option<String>) {
self.state.error_message = msg;
}
pub fn has_unsaved_changes(&self) -> bool {
self.state.has_unsaved_changes
}
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
self.state.has_unsaved_changes = changed;
}
pub fn clear(&mut self) {
self.state.username.clear();
self.state.email.clear();
self.state.password.clear();
self.state.password_confirmation.clear();
self.state.role.clear();
self.state.error_message = None;
self.state.has_unsaved_changes = false;
self.state.current_field = 0;
self.state.current_cursor_pos = 0;
}
// === Delegates to cursor/input ===
pub fn current_field(&self) -> usize {
self.state.current_field()
}
pub fn set_current_field(&mut self, index: usize) {
self.state.set_current_field(index);
}
pub fn current_cursor_pos(&self) -> usize {
self.state.current_cursor_pos()
}
pub fn set_current_cursor_pos(&mut self, pos: usize) {
self.state.set_current_cursor_pos(pos);
}
pub fn get_current_input(&self) -> &str {
self.state.get_current_input()
}
pub fn get_current_input_mut(&mut self) -> &mut String {
self.state.get_current_input_mut(self.state.current_field)
}
// === Delegates to FormEditor ===
pub fn mode(&self) -> canvas::AppMode {
self.editor.mode()
}
pub fn cursor_position(&self) -> usize {
self.editor.cursor_position()
}
pub fn handle_key_event(
&mut self,
key_event: crossterm::event::KeyEvent,
) -> canvas::keymap::KeyEventOutcome {
self.editor.handle_key_event(key_event)
}
}

View File

@@ -3,7 +3,6 @@
use crate::{
config::colors::themes::Theme,
state::app::state::AppState,
modes::handlers::mode_manager::AppMode,
};
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
@@ -12,16 +11,20 @@ use ratatui::{
Frame,
};
use crate::dialog;
use crate::pages::register::RegisterState;
use canvas::{FormEditor, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme};
use crate::pages::register::RegisterFormState;
use canvas::{render_canvas, render_suggestions_dropdown, DefaultCanvasTheme};
pub fn render_register(
f: &mut Frame,
area: Rect,
theme: &Theme,
state: &RegisterState,
register_page: &RegisterFormState,
app_state: &AppState,
) {
let state = &register_page.state;
let editor = &register_page.editor;
// Outer block
let block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Plain)
@@ -46,15 +49,8 @@ pub fn render_register(
])
.split(inner_area);
// Wrap RegisterState in FormEditor
let editor = FormEditor::new(state.clone());
let input_rect = render_canvas(
f,
chunks[0],
&editor,
theme,
);
// ✅ Render the form canvas
let input_rect = render_canvas(f, chunks[0], editor, theme);
// --- HELP TEXT ---
let help_text = Paragraph::new("* are optional fields")
@@ -80,11 +76,8 @@ pub fn render_register(
// Register Button
let register_button_index = 0;
let register_active = if app_state.ui.focus_outside_canvas {
app_state.focused_button_index == register_button_index
} else {
false
};
let register_active = app_state.ui.focus_outside_canvas
&& app_state.focused_button_index == register_button_index;
let mut register_style = Style::default().fg(theme.fg);
let mut register_border = Style::default().fg(theme.border);
if register_active {
@@ -107,11 +100,8 @@ pub fn render_register(
// Return Button
let return_button_index = 1;
let return_active = if app_state.ui.focus_outside_canvas {
app_state.focused_button_index == return_button_index
} else {
false
};
let return_active = app_state.ui.focus_outside_canvas
&& app_state.focused_button_index == return_button_index;
let mut return_style = Style::default().fg(theme.fg);
let mut return_border = Style::default().fg(theme.border);
if return_active {
@@ -132,15 +122,15 @@ pub fn render_register(
button_chunks[1],
);
// --- AUTOCOMPLETE DROPDOWN (Using new canvas suggestions) ---
// --- AUTOCOMPLETE DROPDOWN ---
if editor.mode() == canvas::AppMode::Edit {
if let Some(input_rect) = input_rect {
render_suggestions_dropdown(
f,
f.area(), // Frame area
input_rect, // Current input field rect
f.area(),
input_rect,
&DefaultCanvasTheme,
&editor,
editor,
);
}
}

View File

@@ -7,14 +7,14 @@ use crate::state::pages::{
use crate::pages::admin::AdminState;
use crate::pages::forms::FormState;
use crate::pages::login::LoginFormState;
use crate::pages::register::RegisterState;
use crate::pages::register::RegisterFormState;
use crate::pages::intro::IntroState;
#[derive(Debug)]
pub enum Page {
Intro(IntroState),
Login(LoginFormState),
Register(RegisterState),
Register(RegisterFormState),
Admin(AdminState),
AddLogic(AddLogicState),
AddTable(AddTableState),