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
+ }
+}