//! Combined application for lazy-loading admin interface. //! //! This module provides a unified app that serves both user and admin interfaces, //! with the admin interface lazy-loaded to reduce initial WASM bundle size. use leptos::prelude::*; use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context}; use leptos_router::{ ParamSegment, StaticSegment, components::{Route, Router, Routes}, }; // Re-export user pages for inline route definitions use chattyness_user_ui::pages::{HomePage, LoginPage, PasswordResetPage, ProfilePage, RealmPage, SignupPage, UserProfilePage}; // Lazy-load admin pages to split WASM bundle // Each lazy function includes the admin CSS stylesheet for on-demand loading #[lazy] fn lazy_dashboard() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_login() -> AnyView { view! { } .into_any() } #[lazy] fn lazy_config() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_users() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_user_new() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_user_detail() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_staff() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_realms() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_realm_new() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_realm_detail() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_scenes() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_scene_new() -> AnyView { view! { }.into_any() } #[lazy] fn lazy_scene_detail() -> AnyView { view! { }.into_any() } /// Admin loading fallback - shown on both server (SSR) and client until lazy content loads. #[component] fn AdminLoading() -> impl IntoView { view! {

"Loading admin panel..."

} } /// Macro to generate lazy admin route view functions. /// Both SSR and client render the same Suspense structure initially. /// On SSR: Suspense child is empty, so fallback always shows. /// On client: Suspense child is the lazy content, which loads after hydration. macro_rules! lazy_admin_view { ($name:ident, $lazy_fn:ident) => { fn $name() -> impl IntoView { view! { { // On server: empty content, Suspense shows fallback // On client: lazy content loads after hydration #[cfg(feature = "ssr")] { () } #[cfg(feature = "hydrate")] { Suspend::new(async { $lazy_fn().await }) } } } } }; } // Generate view functions for each admin route lazy_admin_view!(admin_login_view, lazy_login); lazy_admin_view!(admin_dashboard_view, lazy_dashboard); lazy_admin_view!(admin_config_view, lazy_config); lazy_admin_view!(admin_users_view, lazy_users); lazy_admin_view!(admin_user_new_view, lazy_user_new); lazy_admin_view!(admin_user_detail_view, lazy_user_detail); lazy_admin_view!(admin_staff_view, lazy_staff); lazy_admin_view!(admin_realms_view, lazy_realms); lazy_admin_view!(admin_realm_new_view, lazy_realm_new); lazy_admin_view!(admin_realm_detail_view, lazy_realm_detail); lazy_admin_view!(admin_scenes_view, lazy_scenes); lazy_admin_view!(admin_scene_new_view, lazy_scene_new); lazy_admin_view!(admin_scene_detail_view, lazy_scene_detail); /// Combined app state for unified SSR. #[cfg(feature = "ssr")] #[derive(Clone)] pub struct CombinedAppState { pub pool: sqlx::PgPool, pub leptos_options: LeptosOptions, } #[cfg(feature = "ssr")] impl axum::extract::FromRef for LeptosOptions { fn from_ref(state: &CombinedAppState) -> Self { state.leptos_options.clone() } } #[cfg(feature = "ssr")] impl axum::extract::FromRef for sqlx::PgPool { fn from_ref(state: &CombinedAppState) -> Self { state.pool.clone() } } /// Combined shell for SSR. pub fn combined_shell(options: LeptosOptions) -> impl IntoView { view! { } } /// Combined application component with lazy-loaded admin routes. /// /// User routes are eagerly loaded, admin routes are lazy-loaded via LocalResource /// to ensure consistent SSR/hydration (server renders fallback, client loads lazy content). #[component] pub fn CombinedApp() -> impl IntoView { provide_meta_context(); view! { <Router> <main> <Routes fallback=|| "Page not found.".into_view()> // ========================================== // User routes (eager loading) // ========================================== <Route path=StaticSegment("") view=LoginPage /> <Route path=StaticSegment("signup") view=SignupPage /> <Route path=StaticSegment("home") view=HomePage /> <Route path=StaticSegment("profile") view=ProfilePage /> <Route path=StaticSegment("password-reset") view=PasswordResetPage /> <Route path=(StaticSegment("users"), ParamSegment("username")) view=UserProfilePage /> <Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage /> <Route path=( StaticSegment("realms"), ParamSegment("slug"), StaticSegment("scenes"), ParamSegment("scene_slug"), ) view=RealmPage /> // ========================================== // Admin routes (lazy loading) // Server renders fallback, client loads lazy WASM after hydration. // ========================================== <Route path=(StaticSegment("admin"), StaticSegment("login")) view=admin_login_view /> <Route path=StaticSegment("admin") view=admin_dashboard_view /> <Route path=(StaticSegment("admin"), StaticSegment("config")) view=admin_config_view /> <Route path=(StaticSegment("admin"), StaticSegment("users")) view=admin_users_view /> <Route path=(StaticSegment("admin"), StaticSegment("users"), StaticSegment("new")) view=admin_user_new_view /> <Route path=(StaticSegment("admin"), StaticSegment("users"), ParamSegment("user_id")) view=admin_user_detail_view /> <Route path=(StaticSegment("admin"), StaticSegment("staff")) view=admin_staff_view /> <Route path=(StaticSegment("admin"), StaticSegment("realms")) view=admin_realms_view /> <Route path=(StaticSegment("admin"), StaticSegment("realms"), StaticSegment("new")) view=admin_realm_new_view /> // Scene routes (must come before realm detail to match first) <Route path=(StaticSegment("admin"), StaticSegment("realms"), ParamSegment("slug"), StaticSegment("scenes")) view=admin_scenes_view /> <Route path=(StaticSegment("admin"), StaticSegment("realms"), ParamSegment("slug"), StaticSegment("scenes"), StaticSegment("new")) view=admin_scene_new_view /> <Route path=(StaticSegment("admin"), StaticSegment("realms"), ParamSegment("slug"), StaticSegment("scenes"), ParamSegment("scene_id")) view=admin_scene_detail_view /> // Realm detail (must come after more specific routes) <Route path=(StaticSegment("admin"), StaticSegment("realms"), ParamSegment("slug")) view=admin_realm_detail_view /> </Routes> </main> </Router> } }