use leptos::prelude::*; use wasm_bindgen_futures::spawn_local; use crate::api::{self, Me}; use crate::i18n::{self, Locale}; use crate::router::{Location, parse_location}; #[derive(Clone, Copy, PartialEq, Debug)] pub enum AuthPhase { Loading, Setup, Login, Authed, } #[component] pub fn App() -> impl IntoView { let (phase, set_phase) = signal(AuthPhase::Loading); let (me, set_me) = signal(Option::::None); let (loc, set_loc) = signal(Location::root()); // Tri-state theme (auto/light/dark): restore the persisted choice and // apply it before first paint of the app UI. let initial_theme = crate::theme::Theme::load(); initial_theme.apply(); let (theme, set_theme) = signal(initial_theme); // UI language: start from the browser's language; the signed-in user's // profile setting (fetched below) overrides it via the effect. i18n::init(Locale::from_browser().unwrap_or(Locale::En)); Effect::new(move |_| { let lang = me.get().and_then(|m| m.user).and_then(|u| u.language); let locale = match lang.as_deref().and_then(Locale::from_code) { Some(l) => l, // No stored preference (or an unsupported one): follow the browser. None => Locale::from_browser().unwrap_or(Locale::En), }; // Untracked read: comparing via `current()` would register a // dependency on the signal we write below, retriggering the effect. if i18n::current_untracked() != locale { i18n::set_locale(locale); } }); // The URL hash is the source of truth for the location. { set_loc.set(parse_location()); crate::util::owned_window_listener(leptos::ev::hashchange, move |_| { set_loc.set(parse_location()); }); } // Initial /me probe: first boot, signed in, or needs login. spawn_local(async move { match api::me().await { Ok(m) => { let first_boot = m.first_boot; set_me.set(Some(m)); set_phase.set(if first_boot { AuthPhase::Setup } else { AuthPhase::Authed }); } Err(_) => set_phase.set(AuthPhase::Login), } }); view! { {move || { // Public share links (`#/share/{token}/...`) bypass the auth flow. let loc_now = loc.get(); if let Some(token) = loc_now.share_token.clone() { return view! { } .into_view() .into_any(); } match phase.get() { AuthPhase::Loading => view! {

{i18n::tr(i18n::k::LOADING)}

} .into_view() .into_any(), AuthPhase::Setup => view! { } .into_view() .into_any(), AuthPhase::Login => view! { } .into_view() .into_any(), AuthPhase::Authed => view! { } .into_view() .into_any(), } }} } }