Rustelo/client/src/app.rs

189 lines
9.3 KiB
Rust
Raw Normal View History

2025-07-07 23:05:46 +01:00
//#![allow(unused_imports)]
//#![allow(dead_code)]
//#![allow(unused_variables)]
// Suppress leptos_router warnings about reactive signal access outside tracking context
#![allow(clippy::redundant_closure)]
//#![allow(unused_assignments)]
//use crate::defs::{NAV_LINK_CLASS, ROUTES};
use crate::auth::AuthProvider;
use crate::components::NavMenu;
use crate::i18n::{I18nProvider, ThemeProvider};
use crate::pages::{AboutPage, DaisyUIPage, FeaturesDemoPage, HomePage, UserPage};
2025-07-07 23:05:46 +01:00
use crate::state::*;
2025-07-11 21:20:26 +01:00
use crate::utils::make_popstate_effect;
2025-07-07 23:05:46 +01:00
use leptos::children::Children;
use leptos::prelude::*;
use leptos_meta::{MetaTags, Title, provide_meta_context};
// use regex::Regex;
use shared::{get_bundle, t};
2025-07-07 23:05:46 +01:00
use std::collections::HashMap;
//// Wrapper component for consistent layout.
#[component]
fn Wrapper(children: Children) -> impl IntoView {
view! { <>{children()}</> }
}
/// NotFoundPage component for 404s.
#[component]
fn NotFoundPage() -> impl IntoView {
view! { <div class="text-center">"Page not found."</div> }
}
/// Main app component with SSR path awareness and SPA routing.
#[component]
2025-07-11 21:20:26 +01:00
pub fn App(#[prop(default = String::new())] _initial_path: String) -> impl IntoView {
2025-07-07 23:05:46 +01:00
provide_meta_context();
// Always start with HOME during SSR, then route to correct page on client
let (path, set_path) = signal("/".to_string());
2025-07-07 23:05:46 +01:00
make_popstate_effect(set_path);
// Update path from URL after hydration (client-side redirect)
#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen_futures::spawn_local;
spawn_local(async move {
if let Some(win) = web_sys::window() {
let current_path = win
.location()
.pathname()
.unwrap_or_else(|_| "/".to_string());
// If URL path is different from home, redirect to it
if current_path != "/" {
web_sys::console::log_1(
&format!("Client-side redirect to: {}", current_path).into(),
);
set_path.set(current_path);
}
}
});
}
2025-07-07 23:05:46 +01:00
let (lang, _set_lang) = signal("en".to_string());
// --- Unit test placeholder for route matching ---
// #[cfg(test)]
// mod tests {
// use super::*;
// #[test]
// fn test_user_route() {
// let re = Regex::new(r"^/user/(\\d+)$").expect("Valid regex");
// assert!(re.is_match("/user/42"));
// }
// }
view! {
<GlobalStateProvider>
<ThemeProvider>
<I18nProvider>
<ToastProvider>
<AuthProvider>
<UserProvider>
<AppStateProvider>
<Title text="Welcome to Leptos"/>
<header class="absolute inset-x-0 top-2 z-90 mx-2">
<Wrapper><NavMenu set_path=set_path /></Wrapper>
2025-07-07 23:05:46 +01:00
</header>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
2025-07-07 23:05:46 +01:00
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
{ let lang = lang.clone(); let path = path.clone();
move || {
let p = path.get();
let lang_val = lang.get();
let bundle = get_bundle(&lang_val).unwrap_or_else(|_| {
// Fallback to a simple bundle if loading fails
use fluent::FluentBundle;
use unic_langid::LanguageIdentifier;
let langid: LanguageIdentifier = "en".parse().unwrap_or_else(|e| {
web_sys::console::error_1(&format!("Failed to parse default language 'en': {:?}", e).into());
// This should never happen, but create a minimal fallback
LanguageIdentifier::from_parts(
unic_langid::subtags::Language::from_bytes(b"en").unwrap_or_else(|e| {
web_sys::console::error_1(&format!("Critical error: failed to create 'en' language: {:?}", e).into());
// Fallback to creating a new language identifier from scratch
match "en".parse::<unic_langid::subtags::Language>() {
Ok(lang) => lang,
Err(_) => {
// If even this fails, we'll use the default language
web_sys::console::error_1(&"Using default language as final fallback".into());
unic_langid::subtags::Language::default()
}
}
}),
None,
None,
&[],
)
});
FluentBundle::new(vec![langid])
});
let content = match p.as_str() {
"/" => t(&bundle, "main-desc", None),
"/about" => t(&bundle, "about-desc", None),
"/user" => "User Dashboard".to_string(),
"/daisyui" => "DaisyUI Components Demo".to_string(),
"/features-demo" => "New Features Demo".to_string(),
2025-07-07 23:05:46 +01:00
_ if p.starts_with("/user/") => {
if let Some(id) = p.strip_prefix("/user/") {
let mut args = HashMap::new();
args.insert("id", id);
t(&bundle, "user-page", Some(&args))
} else {
t(&bundle, "not-found", None)
}
},
_ => t(&bundle, "not-found", None),
};
view! {
<Wrapper>
<div>{content}</div>
{match p.as_str() {
"/" => view! { <div><HomePage /></div> }.into_any(),
"/about" => view! { <div><AboutPage /></div> }.into_any(),
"/user" => view! { <div><UserPage /></div> }.into_any(),
"/daisyui" => view! { <div><DaisyUIPage /></div> }.into_any(),
"/features-demo" => view! { <div><FeaturesDemoPage /></div> }.into_any(),
2025-07-07 23:05:46 +01:00
_ => view! { <div>Not found</div> }.into_any(),
}}
</Wrapper>
}
}}
2025-07-07 23:05:46 +01:00
</main>
</div>
</AppStateProvider>
</UserProvider>
</AuthProvider>
</ToastProvider>
</I18nProvider>
</ThemeProvider>
</GlobalStateProvider>
}
}
/// The SSR shell for Leptos/Axum integration.
pub fn shell(options: LeptosOptions) -> impl IntoView {
shell_with_path(options, None)
}
/// The SSR shell for Leptos/Axum integration with path support.
pub fn shell_with_path(options: LeptosOptions, path: Option<String>) -> impl IntoView {
2025-07-07 23:05:46 +01:00
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<link rel="stylesheet" id="leptos" href="/public/website.css"/>
2025-07-07 23:05:46 +01:00
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
<MetaTags/>
</head>
<body>
2025-07-11 21:20:26 +01:00
<App _initial_path=path.unwrap_or_default() />
2025-07-07 23:05:46 +01:00
</body>
</html>
}
}