gRPC implementation of a registration working

This commit is contained in:
filipriec
2025-04-10 21:14:32 +02:00
parent dfb6f5b375
commit d36348b84f
6 changed files with 193 additions and 6 deletions

View File

@@ -2,12 +2,13 @@
use crossterm::event::{Event, KeyCode}; use crossterm::event::{Event, KeyCode};
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::state::AppState;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use crate::state::state::AppState;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::RegisterState;
use crate::services::auth::AuthClient; use crate::services::auth::AuthClient;
use crate::modes::handlers::event::EventOutcome; use crate::modes::handlers::event::EventOutcome;
use crate::tui::functions::common::login; use crate::tui::functions::common::{login, register};
/// Handles key events specifically when a dialog is active. /// Handles key events specifically when a dialog is active.
/// Returns Some(Result<EventOutcome, Error>) if the event was handled (consumed), /// Returns Some(Result<EventOutcome, Error>) if the event was handled (consumed),
@@ -17,6 +18,7 @@ pub async fn handle_dialog_event(
config: &Config, config: &Config,
app_state: &mut AppState, app_state: &mut AppState,
auth_state: &mut AuthState, auth_state: &mut AuthState,
register_state: &mut RegisterState,
auth_client: &mut AuthClient, auth_client: &mut AuthClient,
) -> Option<Result<EventOutcome, Box<dyn std::error::Error>>> { ) -> Option<Result<EventOutcome, Box<dyn std::error::Error>>> {
if let Event::Key(key) = event { if let Event::Key(key) = event {
@@ -85,7 +87,32 @@ pub async fn handle_dialog_event(
} }
} }
} }
// Add cases for other DialogPurpose variants here if needed DialogPurpose::RegisterSuccess => { // Add this arm
match selected_index {
0 => { // "OK" button for RegisterSuccess
app_state.hide_dialog();
// Go back to intro after successful registration dialog
let message = register::back_to_main(register_state, app_state).await;
return Some(Ok(EventOutcome::Ok(message)));
}
_ => { // Default for RegisterSuccess
app_state.hide_dialog();
return Some(Ok(EventOutcome::Ok("Unknown dialog button selected".to_string())));
}
}
}
DialogPurpose::RegisterFailed => { // Add this arm
match selected_index {
0 => { // "OK" button for RegisterFailed
app_state.hide_dialog(); // Just dismiss
return Some(Ok(EventOutcome::Ok("Register failed dialog dismissed".to_string())));
}
_ => { // Default for RegisterFailed
app_state.hide_dialog();
return Some(Ok(EventOutcome::Ok("Unknown dialog button selected".to_string())));
}
}
}
} }
} }
_ => {} // Ignore other general actions when dialog is shown _ => {} // Ignore other general actions when dialog is shown

View File

@@ -60,6 +60,8 @@ pub async fn handle_navigation_event(
(UiContext::Intro, app_state.ui.intro_state.selected_option) (UiContext::Intro, app_state.ui.intro_state.selected_option)
} else if app_state.ui.show_login && app_state.ui.focus_outside_canvas { } else if app_state.ui.show_login && app_state.ui.focus_outside_canvas {
(UiContext::Login, app_state.general.selected_item) (UiContext::Login, app_state.general.selected_item)
} else if app_state.ui.show_register && app_state.ui.focus_outside_canvas {
(UiContext::Register, app_state.general.selected_item)
} else if app_state.ui.show_admin { } else if app_state.ui.show_admin {
(UiContext::Admin, app_state.general.selected_item) (UiContext::Admin, app_state.general.selected_item)
} else if app_state.ui.dialog.dialog_show { } else if app_state.ui.dialog.dialog_show {

View File

@@ -13,7 +13,7 @@ use crate::state::canvas_state::CanvasState;
use crate::ui::handlers::rat_state::UiStateHandler; use crate::ui::handlers::rat_state::UiStateHandler;
use crate::ui::handlers::context::UiContext; use crate::ui::handlers::context::UiContext;
use crate::tui::functions::{intro, admin}; use crate::tui::functions::{intro, admin};
use crate::tui::functions::common::login; use crate::tui::functions::common::{login, register};
use crate::modes::{ use crate::modes::{
common::command_mode, common::command_mode,
canvas::{edit, read_only, common_mode}, canvas::{edit, read_only, common_mode},
@@ -76,7 +76,7 @@ impl EventHandler {
// --- DIALOG MODALITY --- // --- DIALOG MODALITY ---
if app_state.ui.dialog.dialog_show { if app_state.ui.dialog.dialog_show {
if let Some(dialog_result) = dialog::handle_dialog_event( if let Some(dialog_result) = dialog::handle_dialog_event(
&event, config, app_state, auth_state, &mut self.auth_client &event, config, app_state, auth_state, register_state, &mut self.auth_client
).await { ).await {
return dialog_result; return dialog_result;
} }
@@ -122,6 +122,13 @@ impl EventHandler {
_ => "Invalid Login Option".to_string(), _ => "Invalid Login Option".to_string(),
}; };
} }
UiContext::Register => {
message = match index {
0 => register::save(register_state, &mut self.auth_client, app_state).await?,
1 => register::back_to_main(register_state, app_state).await,
_ => "Invalid Login Option".to_string(),
};
}
UiContext::Admin => { UiContext::Admin => {
// Assuming handle_admin_selection uses app_state.general.selected_item // Assuming handle_admin_selection uses app_state.general.selected_item
admin::handle_admin_selection(app_state); admin::handle_admin_selection(app_state);

View File

@@ -2,4 +2,4 @@
pub mod form; pub mod form;
pub mod login; pub mod login;
pub mod register;

View File

@@ -0,0 +1,148 @@
// src/tui/functions/common/register.rs
use crate::{
services::auth::AuthClient,
state::{
pages::auth::RegisterState,
state::AppState,
canvas_state::CanvasState,
},
ui::handlers::context::DialogPurpose,
};
/// Attempts to register the user using the provided details via gRPC.
/// Updates RegisterState and AppState on success or failure.
pub async fn save(
register_state: &mut RegisterState,
auth_client: &mut AuthClient,
app_state: &mut AppState,
) -> Result<String, Box<dyn std::error::Error>> {
let username = register_state.username.clone();
let email = register_state.email.clone();
// Handle optional passwords: send None if empty, Some(value) otherwise
let password = if register_state.password.is_empty() {
None
} else {
Some(register_state.password.clone())
};
let password_confirmation = if register_state.password_confirmation.is_empty() {
None
} else {
Some(register_state.password_confirmation.clone())
};
// Basic client-side validation (example)
if username.is_empty() {
app_state.show_dialog(
"Registration Failed",
"Username cannot be empty.",
vec!["OK".to_string()],
DialogPurpose::RegisterFailed,
);
register_state.error_message = Some("Username cannot be empty.".to_string());
return Ok("Registration failed: Username cannot be empty.".to_string());
}
if password.is_some() && password != password_confirmation {
app_state.show_dialog(
"Registration Failed",
"Passwords do not match.",
vec!["OK".to_string()],
DialogPurpose::RegisterFailed,
);
register_state.error_message = Some("Passwords do not match.".to_string());
return Ok("Registration failed: Passwords do not match.".to_string());
}
// Clear previous error/dialog state before attempting
register_state.error_message = None;
app_state.hide_dialog();
// Call the gRPC register method
match auth_client.register(username, email, password, password_confirmation).await {
Ok(response) => {
// Clear fields on success? Optional, maybe wait for dialog confirmation.
// register_state.username.clear();
// register_state.email.clear();
// register_state.password.clear();
// register_state.password_confirmation.clear();
register_state.set_has_unsaved_changes(false);
let success_message = format!(
"Registration Successful!\n\n\
User ID: {}\n\
Username: {}\n\
Email: {}\n\
Role: {}",
response.id,
response.username,
response.email,
response.role
);
// Show success dialog
app_state.show_dialog(
"Registration Success",
&success_message,
vec!["OK".to_string()], // Simple OK for now
DialogPurpose::RegisterSuccess,
);
Ok("Registration successful, details shown in dialog.".to_string())
}
Err(e) => {
let error_message = format!("{}", e);
register_state.error_message = Some(error_message.clone());
register_state.set_has_unsaved_changes(true); // Keep changes on error
// Show error dialog
app_state.show_dialog(
"Registration Failed",
&error_message,
vec!["OK".to_string()],
DialogPurpose::RegisterFailed,
);
Ok(format!("Registration failed: {}", error_message))
}
}
}
/// Clears the registration form fields.
pub async fn revert(
register_state: &mut RegisterState,
_app_state: &mut AppState, // Keep signature consistent if needed elsewhere
) -> String {
register_state.username.clear();
register_state.email.clear();
register_state.password.clear();
register_state.password_confirmation.clear();
register_state.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;
"Registration form cleared".to_string()
}
/// Clears the form and returns to the intro screen.
pub async fn back_to_main(
register_state: &mut RegisterState,
app_state: &mut AppState,
) -> String {
// Clear fields first
let _ = revert(register_state, app_state).await;
// Ensure dialog is hidden
app_state.hide_dialog();
// Navigation logic
app_state.ui.show_register = false;
app_state.ui.show_intro = true;
// Reset focus state
app_state.ui.focus_outside_canvas = false;
app_state.general.selected_item = 0; // Reset intro selection
"Returned to main menu".to_string()
}

View File

@@ -4,6 +4,7 @@
pub enum UiContext { pub enum UiContext {
Intro, Intro,
Login, Login,
Register,
Admin, Admin,
Dialog, Dialog,
} }
@@ -12,6 +13,8 @@ pub enum UiContext {
pub enum DialogPurpose { pub enum DialogPurpose {
LoginSuccess, LoginSuccess,
LoginFailed, LoginFailed,
RegisterSuccess,
RegisterFailed,
// TODO in the future: // TODO in the future:
// ConfirmQuit, // ConfirmQuit,
} }