From 2520faad47cb580f4b54f1cfd7f6c18ef2a275cb Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 3 Jul 2026 18:22:28 +0700 Subject: [PATCH] feat(leptos): app shell with auth gate and tab routing --- services/frontend-leptos/frontend/src/app.rs | 102 ++++++++++++++++-- services/frontend-leptos/frontend/src/auth.rs | 56 ++++++++++ services/frontend-leptos/frontend/src/lib.rs | 1 + 3 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 services/frontend-leptos/frontend/src/auth.rs diff --git a/services/frontend-leptos/frontend/src/app.rs b/services/frontend-leptos/frontend/src/app.rs index cbfa661..69472ec 100644 --- a/services/frontend-leptos/frontend/src/app.rs +++ b/services/frontend-leptos/frontend/src/app.rs @@ -1,19 +1,109 @@ use leptos::prelude::*; +use shared_types::ui_state::Tab; +use crate::auth::AuthOverlay; + +// ── Contexts ──────────────────────────────────────────── + +#[derive(Clone)] +pub struct AuthContext { + pub authenticated: RwSignal, + pub password: RwSignal, +} -// Temporary stub until Task 4 creates the real UiContext #[derive(Clone)] pub struct UiContext { - pub active_tab: RwSignal, + pub active_tab: RwSignal, + pub selected_guild: RwSignal>, } +// ── App ───────────────────────────────────────────────── + #[component] pub fn App() -> impl IntoView { + // Initialize contexts + let auth = AuthContext { + authenticated: create_rw_signal(false), + password: create_rw_signal(String::new()), + }; + let ui = UiContext { + active_tab: create_rw_signal(Tab::Messages), + selected_guild: create_rw_signal(None), + }; + + provide_context(auth.clone()); + provide_context(ui.clone()); + + // Auth check: redirect "live" tab to "messages" if not authenticated + create_effect(move |_| { + if !auth.authenticated.get() && ui.active_tab.get() == Tab::Live { + ui.active_tab.set(Tab::Messages); + } + }); + view! { -
- -
- "Hello from Leptos" +
+ // Auth overlay + {move || (!auth.authenticated.get()).then(|| { + view! { } + })} + + // Main content (minimal for now — filled in later tasks) +
+
+ "IMPHNEN" + "Discord Moderation" +
+ +
+ // Sidebar placeholder + + + // Content area +
+ {move || match ui.active_tab.get() { + Tab::Messages => view! {
"Messages Panel"
}.into_view(), + Tab::Live => view! {
"Live Panel"
}.into_view(), + Tab::Dashboard => view! {
"Dashboard Panel"
}.into_view(), + }} +
+
} } + +// ── Tab Button Helper ─────────────────────────────────── + +#[component] +fn TabButton( + tab: Tab, + ui: UiContext, + label: &'static str, +) -> impl IntoView { + let active_tab = ui.active_tab.clone(); + let tab1 = tab.clone(); + let tab2 = tab.clone(); + let tab3 = tab.clone(); + let tab4 = tab; + + view! { + + } +} diff --git a/services/frontend-leptos/frontend/src/auth.rs b/services/frontend-leptos/frontend/src/auth.rs new file mode 100644 index 0000000..a3c97b0 --- /dev/null +++ b/services/frontend-leptos/frontend/src/auth.rs @@ -0,0 +1,56 @@ +use leptos::prelude::*; +use leptos::ev::SubmitEvent; + +#[component] +pub fn AuthOverlay( + /// Called when password is submitted — parent handles the actual API call + #[prop(default = ())] + on_submit: (), +) -> impl IntoView { + 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| { + ev.prevent_default(); + if password.get().is_empty() { + set_error.set(Some("Password diperlukan".to_string())); + return; + } + // TODO: actual login call (wired in Task 7) + set_loading.set(true); + }; + + view! { + + } +} diff --git a/services/frontend-leptos/frontend/src/lib.rs b/services/frontend-leptos/frontend/src/lib.rs index e2ac5be..74a9c4d 100644 --- a/services/frontend-leptos/frontend/src/lib.rs +++ b/services/frontend-leptos/frontend/src/lib.rs @@ -1,5 +1,6 @@ pub mod api; pub mod app; +pub mod auth; pub mod layout; pub mod ui; pub mod ws;