use dioxus::prelude::*; use crate::components::sidebar::Sidebar; use crate::i18n::{t, tw, Locale}; use crate::infrastructure::auth_check::check_auth; use crate::models::AuthInfo; use crate::Route; /// Application shell layout that wraps all authenticated pages. /// /// Calls [`check_auth`] on mount to fetch the current user's session. /// If unauthenticated, redirects to `/auth`. Otherwise renders the /// sidebar with real user data and the active child route. #[component] pub fn AppShell() -> Element { let locale = use_context::>(); // use_resource memoises the async call and avoids infinite re-render // loops that use_effect + spawn + signal writes can cause. #[allow(clippy::redundant_closure)] let auth = use_resource(move || check_auth()); // Clone the inner value out of the Signal to avoid holding the // borrow across the rsx! return (Dioxus lifetime constraint). let auth_snapshot: Option> = auth.read().clone(); match auth_snapshot { Some(Ok(info)) if info.authenticated => { rsx! { div { class: "app-shell", Sidebar { email: info.email, name: info.name, avatar_url: info.avatar_url, } main { class: "main-content", Outlet:: {} } } } } Some(Ok(_)) => { // Not authenticated -- redirect to login. let nav = navigator(); nav.push(NavigationTarget::::External("/auth".into())); rsx! { div { class: "app-shell loading", p { {t(*locale.read(), "auth.redirecting_login")} } } } } Some(Err(e)) => { let msg = e.to_string(); let error_text = tw(*locale.read(), "auth.auth_error", &[("msg", &msg)]); rsx! { div { class: "auth-error", p { {error_text} } a { href: "/auth", {t(*locale.read(), "common.login")} } } } } None => { // Still loading. rsx! { div { class: "app-shell loading", p { {t(*locale.read(), "common.loading")} } } } } } }