//! Application shell: skip link, header, router, footer. use leptos::prelude::*; use leptos_router::components::{A, Redirect, Route, Router, Routes}; use leptos_router::path; use web::model::SessionProbe; use web::viewmodel::{NAV_ITEMS, PRODUCT_NAME, dashboard_nav_visible}; use super::components::dashboard::Dashboard; use super::components::deployment::ConfigResource; use super::components::feedback::SessionSkeleton; use super::components::icons::{Icon, IconKind}; use super::components::landing::Landing; use super::components::theme::ThemeToggle; /// Root component, mounted to `` by `main.rs`. #[component] pub fn App() -> impl IntoView { // One app-level session probe, shared with the header and the `/dashboard` // guard so navigation gating and route guarding read a single source of // truth. `LocalResource` is `Copy`, so context provision is cheap. let session = LocalResource::new(|| async { crate::api::probe_session().await }); provide_context(session); // One app-level config resource, shared with the landing CTA and the // dashboard deployment card so both read a single `/api/config` fetch. let config: ConfigResource = LocalResource::new(|| async { crate::api::fetch_config().await }); provide_context(config); view! {
"Skip to content"
}>
} } /// Header navigation: `Landing` is always rendered; `Dashboard` appears only /// once the shared probe reports [`SessionProbe::Active`]. #[component] fn PrimaryNav() -> impl IntoView { let session = use_context::>(); let dashboard_visible = Signal::derive(move || { session .and_then(|resource| resource.get()) .is_some_and(|probe| dashboard_nav_visible(&probe)) }); view! { } } /// `/dashboard` guard: an active session renders the dashboard, a definitive /// [`SessionProbe::Unauthenticated`] redirects to the landing page, and /// loading/error states keep rendering the dashboard's own skeleton and typed /// error cards so a transient BFF failure is never misread as logged-out. #[component] fn DashboardRoute() -> impl IntoView { let session = use_context::>(); // Coarse memo: `Active(_)` -> `Active(_)` refetches must not remount the // dashboard, or its refresh/log-out state would reset underneath the user. let redirect_home = Memo::new(move |_| { session.is_some_and(|resource| matches!(resource.get(), Some(SessionProbe::Unauthenticated))) }); view! { {move || { let Some(resource) = session else { return view! { }.into_any(); }; if redirect_home.get() { view! { }.into_any() } else { view! { }.into_any() } }} } } /// Fallback for unmatched routes. #[component] fn NotFound() -> impl IntoView { view! {

"404"

"Page not found"

"That route does not exist in this client."

"Back to landing"
} }