diff --git a/index.html b/index.html index 707d284..ba44f03 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,6 @@ /> - Waffle - Template @@ -26,6 +25,43 @@ diff --git a/src/app.rs b/src/app.rs index d84b367..9351a42 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,6 +6,7 @@ use crate::db::idb::LANGUAGES; use crate::erust::uiux::search::SearchWidget; use crate::erust::state::{AppState, WaffleState}; use crate::erust::uiux::auth::AuthWidget; +use crate::erust::uiux::user::User; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq, Eq)] pub enum LoadingState { @@ -63,6 +64,12 @@ pub struct TemplateApp { search_widget: Option, #[serde(skip)] auth_widget: AuthWidget, + #[serde(skip)] + user: User, + #[serde(skip)] + show_auth_window: bool, // Track if auth window should be shown + #[serde(skip)] + show_welcome: bool, // Track if welcome window should be shown } impl Default for TemplateApp { @@ -84,6 +91,9 @@ impl Default for TemplateApp { filter_loading: false, search_widget: Some(SearchWidget::new()), auth_widget: AuthWidget::new(false), + user: User::default(), + show_auth_window: false, + show_welcome: true, // Show welcome window by default } } } @@ -306,6 +316,17 @@ impl TemplateApp { ui.label("App Log:"); ui.label(&self.waffle_state.log); } + // Add Login/Register buttons if not authenticated + if !self.user.is_logged_in() { + if ui.button("Login").clicked() { + self.auth_widget.view = crate::erust::uiux::auth::AuthView::Login; + self.show_auth_window = true; + } + if ui.button("Register").clicked() { + self.auth_widget.view = crate::erust::uiux::auth::AuthView::Register; + self.show_auth_window = true; + } + } }); egui::CentralPanel::default().show(ctx, |ui| { // --- Logo image loading and display using egui_extras loader system --- @@ -343,15 +364,6 @@ impl TemplateApp { } }); - // --- Authentication Widget (Login/Register) --- - egui::Window::new("Authentication") - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_TOP, egui::Vec2::new(0.0, 40.0)) - .show(ctx, |ui| { - self.auth_widget.show(ctx, ui); - }); - // Update filtered_repos from egui context temp data if available if let Some(widget) = &mut self.search_widget { widget.update_results_from_ctx(ctx); @@ -366,16 +378,46 @@ impl TemplateApp { } // Show welcome dialog if DB is empty - if self.app_state == AppState::Empty { + if self.app_state == AppState::Empty && self.show_welcome { egui::Window::new("Welcome to the Waffle!") .collapsible(false) .resizable(false) .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .open(&mut self.show_welcome) .show(ctx, |ui| { ui.heading("Welcome to the Waffle!"); ui.label("Please sync the languages you would like to see."); }); } + + // --- Always-visible bottom panel with Logout button --- + egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { + ui.horizontal(|ui| { + if ui.button("Logout").clicked() { + crate::erust::uiux::javascript_interop::send_action_message( + "logout", + "", + "", + "", + ); + self.toast_message = Some("Sent logout request to JS".to_string()); + } + ui.label("Waffle v0.1.0"); + }); + }); + + // --- Authentication Widget (Login/Register) --- + // Only show the authentication window if user is not logged in and show_auth_window is true + if !self.user.is_logged_in() && self.show_auth_window { + egui::Window::new("Authentication") + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_TOP, egui::Vec2::new(0.0, 40.0)) + .open(&mut self.show_auth_window) // Adds the X close button + .show(ctx, |ui| { + self.auth_widget.show(ctx, ui); + }); + } } } diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/uiux/mod.rs b/src/core/uiux/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/uiux/search.rs b/src/core/uiux/search.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/erust/uiux/javascript_interop.rs b/src/erust/uiux/javascript_interop.rs index b4c69c4..66d591a 100644 --- a/src/erust/uiux/javascript_interop.rs +++ b/src/erust/uiux/javascript_interop.rs @@ -29,15 +29,31 @@ pub fn send_action_message(action: &str, email: &str, password: &str, captcha_to /// Register a JS callback handler for responses from JS to Rust /// The callback will be called with a JsValue (the response object) use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use js_sys::JSON; -#[wasm_bindgen] -pub fn set_jsrust_response_handler(cb: &js_sys::Function) { - // Store the callback globally in JS (window.JSRustResponseHandler) - let _ = js_sys::Reflect::set( - &js_sys::global(), - &JsValue::from_str("JSRustResponseHandler"), - cb, - ); +/// Call this during app initialization to handle JS->Rust responses +pub fn setup_jsrust_response_handler(mut callback: F) +where + F: 'static + FnMut(serde_json::Value), +{ + let handler = Closure::wrap(Box::new(move |resp: JsValue| { + // Convert JsValue to serde_json::Value using JSON::stringify and serde_json + let resp_json = if let Ok(js_str) = JSON::stringify(&resp) { + if let Some(s) = js_str.as_string() { + serde_json::from_str(&s).unwrap_or_default() + } else { + serde_json::Value::Null + } + } else { + serde_json::Value::Null + }; + callback(resp_json); + }) as Box); + + // Call the local set_jsrust_response_handler directly + set_jsrust_response_handler(handler.as_ref().unchecked_ref()); + handler.forget(); // Prevent the closure from being dropped } /// Call this from JS to send a response back to Rust @@ -51,3 +67,30 @@ pub fn handle_jsrust_response(response: &JsValue) { } } } + +// Add back the set_jsrust_response_handler as a public function so it is available in scope +#[wasm_bindgen] +pub fn set_jsrust_response_handler(cb: &js_sys::Function) { + // Store the callback globally in JS (window.JSRustResponseHandler) + let _ = js_sys::Reflect::set( + &js_sys::global(), + &JsValue::from_str("JSRustResponseHandler"), + cb, + ); +} + +// Expand AppState for interop waiting +// In state.rs (or wherever your AppState is defined): +// +// pub enum AppState { +// Init, +// Normal, +// Empty, +// Error(String), +// InteropPending(String), // Add this variant for JS interop waiting +// } +// +// Usage example: +// self.app_state = AppState::InteropPending("Waiting for JS response...".to_string()); +// +// In your interop callback, set it back to Normal or Error as appropriate. diff --git a/src/erust/uiux/mod.rs b/src/erust/uiux/mod.rs index 59fe10b..80df5a6 100644 --- a/src/erust/uiux/mod.rs +++ b/src/erust/uiux/mod.rs @@ -2,4 +2,5 @@ pub mod search; pub mod hcaptcha; pub mod auth; pub mod supabase; -pub mod javascript_interop; \ No newline at end of file +pub mod javascript_interop; +pub mod user; \ No newline at end of file diff --git a/src/erust/uiux/user.rs b/src/erust/uiux/user.rs new file mode 100644 index 0000000..179df7d --- /dev/null +++ b/src/erust/uiux/user.rs @@ -0,0 +1,40 @@ +#[derive(Debug, Clone, Default)] +pub struct User { + pub id: Option, + pub email: Option, + pub username: Option, + pub is_authenticated: bool, +} + +impl User { + pub fn new() -> Self { + Self::default() + } + + pub fn set_id(&mut self, id: String) { + self.id = Some(id); + } + + pub fn set_email(&mut self, email: String) { + self.email = Some(email); + } + + pub fn set_username(&mut self, username: String) { + self.username = Some(username); + } + + pub fn authenticate(&mut self) { + self.is_authenticated = true; + } + + pub fn logout(&mut self) { + self.id = None; + self.email = None; + self.username = None; + self.is_authenticated = false; + } + + pub fn is_logged_in(&self) -> bool { + self.is_authenticated + } +}