registration now has working form
This commit is contained in:
@@ -100,7 +100,7 @@ pub fn up(app_state: &mut AppState, router: &mut Router) {
|
||||
Page::Register(state) if app_state.ui.focus_outside_canvas => {
|
||||
if app_state.focused_button_index == 0 {
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
let last_field_index = state.field_count().saturating_sub(1);
|
||||
let last_field_index = state.state.field_count().saturating_sub(1);
|
||||
state.set_current_field(last_field_index);
|
||||
} else {
|
||||
app_state.focused_button_index =
|
||||
|
||||
@@ -330,6 +330,44 @@ impl EventHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Page::Register(register_page) = &mut router.current {
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
|
||||
// Inside canvas: at the last field, 'j' or Down moves focus to buttons
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
let last_idx = register_page.editor.data_provider().field_count().saturating_sub(1);
|
||||
let at_last = register_page.editor.current_field() >= last_idx;
|
||||
if at_last
|
||||
&& matches!(
|
||||
(key_code, modifiers),
|
||||
(KeyCode::Char('j'), KeyModifiers::NONE) | (KeyCode::Down, _)
|
||||
)
|
||||
{
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
app_state.focused_button_index = 0; // focus "Register" button
|
||||
register_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok("Focus moved to buttons".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Only forward to the canvas while focus is inside it
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
match register_page.handle_key_event(key_event) {
|
||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||
self.command_message = msg;
|
||||
return Ok(EventOutcome::Ok("Register input updated".to_string()));
|
||||
}
|
||||
KeyEventOutcome::Consumed(None) => {
|
||||
return Ok(EventOutcome::Ok("Register input updated".to_string()));
|
||||
}
|
||||
KeyEventOutcome::Pending => {
|
||||
return Ok(EventOutcome::Ok("Waiting for next key...".to_string()));
|
||||
}
|
||||
KeyEventOutcome::NotMatched => {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if toggle_sidebar(
|
||||
|
||||
@@ -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,24 +62,33 @@ 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);
|
||||
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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 doesn’t 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = ®ister_page.state;
|
||||
let editor = ®ister_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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::modes::common::commands::CommandHandler;
|
||||
use crate::modes::handlers::event::{EventHandler, EventOutcome};
|
||||
use crate::modes::handlers::mode_manager::{AppMode, ModeManager};
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::pages::register::RegisterState;
|
||||
use crate::pages::login::LoginFormState;
|
||||
use crate::pages::register::RegisterFormState;
|
||||
use crate::pages::admin::AdminState;
|
||||
use crate::pages::admin::AdminFocus;
|
||||
use crate::pages::intro::IntroState;
|
||||
@@ -70,7 +70,8 @@ pub async fn run_ui() -> Result<()> {
|
||||
let mut auth_state = AuthState::default();
|
||||
let mut login_state = LoginFormState::new();
|
||||
login_state.editor.set_keymap(config.build_canvas_keymap());
|
||||
let mut register_state = RegisterState::default();
|
||||
let mut register_state = RegisterFormState::default();
|
||||
register_state.editor.set_keymap(config.build_canvas_keymap());
|
||||
let mut intro_state = IntroState::default();
|
||||
let mut admin_state = AdminState::default();
|
||||
let mut router = Router::new();
|
||||
@@ -387,7 +388,15 @@ pub async fn run_ui() -> Result<()> {
|
||||
router.navigate(Page::Login(page));
|
||||
}
|
||||
}
|
||||
AppView::Register => router.navigate(Page::Register(register_state.clone())),
|
||||
AppView::Register => {
|
||||
if let Page::Register(_) = &router.current {
|
||||
// already on register page
|
||||
} else {
|
||||
let mut page = RegisterFormState::new();
|
||||
page.editor.set_keymap(config.build_canvas_keymap());
|
||||
router.navigate(Page::Register(page));
|
||||
}
|
||||
}
|
||||
AppView::Admin => {
|
||||
if let Page::Admin(current) = &router.current {
|
||||
admin_state = current.clone();
|
||||
@@ -647,8 +656,7 @@ pub async fn run_ui() -> Result<()> {
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos =
|
||||
if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
|
||||
state.current_cursor_pos =
|
||||
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
state.set_current_cursor_pos(event_handler.ideal_cursor_column.min(max_cursor_pos));
|
||||
}
|
||||
} else if let Page::Login(state) = &mut router.current {
|
||||
if !app_state.is_canvas_edit_mode() {
|
||||
|
||||
Reference in New Issue
Block a user