use crate::i18n::Locale; use crate::{components::*, pages::*}; use dioxus::prelude::*; /// Application routes. /// /// Public pages (`LandingPage`, `ImpressumPage`, `PrivacyPage`) live /// outside the `AppShell` layout. Authenticated pages are wrapped in /// `AppShell` which renders the sidebar. `DeveloperShell` and `OrgShell` /// provide nested tab navigation within the app shell. #[derive(Debug, Clone, Routable, PartialEq)] #[rustfmt::skip] pub enum Route { #[route("/")] LandingPage {}, #[route("/impressum")] ImpressumPage {}, #[route("/privacy")] PrivacyPage {}, #[layout(AppShell)] #[route("/dashboard")] DashboardPage {}, #[route("/providers")] ProvidersPage {}, #[route("/chat")] ChatPage {}, #[route("/tools")] ToolsPage {}, #[route("/knowledge")] KnowledgePage {}, #[layout(DeveloperShell)] #[route("/developer/agents")] AgentsPage {}, #[route("/developer/flow")] FlowPage {}, #[route("/developer/analytics")] AnalyticsPage {}, #[end_layout] #[layout(OrgShell)] #[route("/organization/pricing")] OrgPricingPage {}, #[route("/organization/dashboard")] OrgDashboardPage {}, #[end_layout] #[end_layout] #[route("/login?:redirect_url")] Login { redirect_url: String }, } const FAVICON: Asset = asset!("/assets/favicon.svg"); const MAIN_CSS: Asset = asset!("/assets/main.css"); const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css"); const MANIFEST: Asset = asset!("/assets/manifest.json"); /// Google Fonts URL for Inter (body) and Space Grotesk (headings). const GOOGLE_FONTS: &str = "https://fonts.googleapis.com/css2?\ family=Inter:wght@400;500;600&\ family=Space+Grotesk:wght@500;600;700&\ display=swap"; /// Root application component. Loads global assets and mounts the router. /// /// Provides a `Signal` context that all child components can read /// via `use_context::>()` to access the current locale. /// The locale is persisted in `localStorage` under `"certifai_locale"`. #[component] pub fn App() -> Element { // Read persisted locale from localStorage on first render. let initial_locale = { #[cfg(feature = "web")] { web_sys::window() .and_then(|w| w.local_storage().ok().flatten()) .and_then(|s| s.get_item("certifai_locale").ok().flatten()) .map(|code| Locale::from_code(&code)) .unwrap_or_default() } #[cfg(not(feature = "web"))] { Locale::default() } }; use_context_provider(|| Signal::new(initial_locale)); rsx! { // Seggwat feedback widget document::Script { src: "https://seggwat.com/static/widgets/v1/seggwat-feedback.js", r#defer: true, "data-project-key": "a04b8cf1-9177-42ce-8a7b-084f38b99799", "data-button-color": "#6d85c6", "data-button-position": "right-side", "data-enable-screenshots": "true", } document::Link { rel: "icon", href: FAVICON } document::Link { rel: "manifest", href: MANIFEST } document::Meta { name: "theme-color", content: "#4B3FE0" } document::Meta { name: "apple-mobile-web-app-capable", content: "yes" } document::Meta { name: "apple-mobile-web-app-status-bar-style", content: "black-translucent", } document::Link { rel: "apple-touch-icon", href: FAVICON } document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" } document::Link { rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "anonymous", } document::Link { rel: "stylesheet", href: GOOGLE_FONTS } document::Link { rel: "stylesheet", href: TAILWIND_CSS } document::Link { rel: "stylesheet", href: MAIN_CSS } // Register PWA service worker document::Script { r#" if ('serviceWorker' in navigator) {{ navigator.serviceWorker.register('/assets/sw.js') .catch(function(e) {{ console.warn('SW registration failed:', e); }}); }} "# } // Apply persisted theme to before first paint to avoid flash. // Default to certifai-dark when no preference is stored. document::Script { r#" (function() {{ var t = localStorage.getItem('theme') || 'certifai-dark'; document.documentElement.setAttribute('data-theme', t); }})(); "# } Router:: {} } }