From bf36bf55fec5f5575dff0e9a27ebc36ce3fa1773 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 3 Jul 2026 18:30:41 +0700 Subject: [PATCH] feat(leptos): integrate auth with API + WS auth gating --- services/frontend-leptos/frontend/src/app.rs | 14 +++++ services/frontend-leptos/frontend/src/auth.rs | 60 +++++++++++++++---- .../frontend/src/ws/context.rs | 7 +++ .../frontend-leptos/frontend/src/ws/socket.rs | 7 +++ 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/services/frontend-leptos/frontend/src/app.rs b/services/frontend-leptos/frontend/src/app.rs index 69472ec..d60f57f 100644 --- a/services/frontend-leptos/frontend/src/app.rs +++ b/services/frontend-leptos/frontend/src/app.rs @@ -1,6 +1,7 @@ use leptos::prelude::*; use shared_types::ui_state::Tab; use crate::auth::AuthOverlay; +use crate::ws::context::WsContext; // ── Contexts ──────────────────────────────────────────── @@ -33,6 +34,9 @@ pub fn App() -> impl IntoView { provide_context(auth.clone()); provide_context(ui.clone()); + let ws = WsContext::new("ws://localhost:3001"); + provide_context(ws.clone()); + // Auth check: redirect "live" tab to "messages" if not authenticated create_effect(move |_| { if !auth.authenticated.get() && ui.active_tab.get() == Tab::Live { @@ -40,6 +44,16 @@ pub fn App() -> impl IntoView { } }); + { + let ws = ws.clone(); + let auth = auth.clone(); + create_effect(move |_| { + if auth.authenticated.get() { + ws.connect(); + } + }); + } + view! {
// Auth overlay diff --git a/services/frontend-leptos/frontend/src/auth.rs b/services/frontend-leptos/frontend/src/auth.rs index a3c97b0..c718809 100644 --- a/services/frontend-leptos/frontend/src/auth.rs +++ b/services/frontend-leptos/frontend/src/auth.rs @@ -1,24 +1,53 @@ +// services/frontend-leptos/frontend/src/auth.rs use leptos::prelude::*; -use leptos::ev::SubmitEvent; +use wasm_bindgen_futures::spawn_local; +use crate::app::AuthContext; +use crate::api::auth as auth_api; #[component] -pub fn AuthOverlay( - /// Called when password is submitted — parent handles the actual API call - #[prop(default = ())] - on_submit: (), -) -> impl IntoView { +pub fn AuthOverlay() -> impl IntoView { + let auth = use_context::().expect("AuthContext not provided"); let (password, set_password) = create_signal(String::new()); let (error, set_error) = create_signal(Option::::None); let (loading, set_loading) = create_signal(false); - let handle_submit = move |ev: SubmitEvent| { + let handle_submit = move |ev: leptos::ev::SubmitEvent| { ev.prevent_default(); - if password.get().is_empty() { + let pwd = password.get(); + if pwd.is_empty() { set_error.set(Some("Password diperlukan".to_string())); return; } - // TODO: actual login call (wired in Task 7) set_loading.set(true); + set_error.set(None); + + let auth_clone = auth.clone(); + let pwd_clone = pwd.clone(); + let set_loading_clone = set_loading.clone(); + let set_error_clone = set_error.clone(); + + spawn_local(async move { + match auth_api::login(&pwd_clone).await { + Ok(true) => { + // Store password in sessionStorage + if let Some(storage) = web_sys::window() + .and_then(|w| w.local_storage().ok()) + .flatten() + { + let _ = storage.set_item("admin-password", &pwd_clone); + } + auth_clone.authenticated.set(true); + auth_clone.password.set(pwd_clone); + } + Ok(false) => { + set_error_clone.set(Some("Login gagal — password salah".to_string())); + } + Err(e) => { + set_error_clone.set(Some(format!("Error: {}", e.message))); + } + } + set_loading_clone.set(false); + }); }; view! { @@ -26,9 +55,14 @@ pub fn AuthOverlay(