mod auth; mod pages; use crate::auth::ProtectedRoute; use crate::pages::*; use gloo_net::http::Request; use wasm_bindgen_futures::spawn_local; use yew::prelude::*; use yew_router::prelude::*; /// Defines the application's various routes and their corresponding paths. /// /// This enum is used by `yew-router` to map URLs to specific components, /// enabling navigation within the single-page application. #[derive(Clone, PartialEq, Routable)] enum Route { /// The application's home page. #[at("/")] Home, /// Route for submitting a new ticket. #[at("/ticket")] Ticket, /// Route for viewing a specific ticket by its ID. #[at("/tickets/:id")] TicketById { id: i32 }, /// Route for viewing all tickets. #[at("/tickets")] AllTickets, /// Route for viewing archived tickets. #[at("/tickets/archive")] ArchivedTickets, /// Route for user registration. #[at("/register")] Register, /// Route for user login. #[at("/login")] Login, /// Route for the initial administrator setup. #[at("/setup")] Setup, /// Route for viewing all users. #[at("/users")] AllUsers, /// Route for viewing a specific user by their ID. #[at("/users/:id")] UserByID { id: i16 }, /// Route for displaying diagnostics information (admin-only). #[at("/diagnostics")] Diagnostics, /// Route displayed when a user attempts to access a page without sufficient permissions. #[at("/denied")] PermissionDenied, /// Catch-all route for unmatched paths, leading to a 404 Not Found page. #[not_found] #[at("/404")] NotFound, } /// Properties for the [`SidebarShell`] component. #[derive(Properties, PartialEq)] pub struct SidebarShellProps { /// The child components to be rendered within the main content area of the shell. pub children: Children, } /// A shell component that provides a consistent layout with a sidebar and a main content area. /// /// This component is designed to wrap page-specific content, ensuring that the sidebar /// is always present for navigation. /// /// # Components /// - [`sidebar::Sidebar`]: The navigation sidebar component. /// - Main content area: Renders the `children` passed to this component. /// /// # Example /// ```rust /// html! { /// ///

{"Your page content goes here."}

///
/// } /// ``` #[component(SidebarShell)] fn sidebar_shell(props: &SidebarShellProps) -> Html { html! {
{ for props.children.iter() }
} } /// Props for the AdminCheckWrapper component. #[derive(Properties, PartialEq)] pub struct AdminCheckWrapperProps { pub children: Children, } /// Wrapper component that checks if an admin exists before rendering children. /// /// This component is used to gate access to pages that should only be accessible before /// system initialization (e.g., login page). It performs an asynchronous check to the /// `/api/check-admin` endpoint to determine system state. /// /// # Behavior /// - **Loading**: Displays "Loading..." while checking admin status /// - **No Admin**: Automatically redirects to `/setup` page to initialize /// - **Admin Exists**: Renders the wrapped children (e.g., login page) /// /// # Example Usage /// ```ignore /// /// /// /// ``` /// /// # Implementation Detail /// The check happens in `use_effect_with` on component mount: /// ```ignore /// spawn_local(async move { /// match Request::get("/api/check-admin").send().await { /// Ok(resp) if resp.status() == 200 => { /// let has_admin = data["has_admin"].as_bool().unwrap_or(false); /// admin_exists.set(Some(has_admin)); /// } /// _ => admin_exists.set(Some(false)) /// } /// }); /// ``` #[component(AdminCheckWrapper)] fn admin_check_wrapper(props: &AdminCheckWrapperProps) -> Html { let admin_exists = use_state(|| None::); let navigator = use_navigator().unwrap(); { let admin_exists = admin_exists.clone(); use_effect_with((), move |_| { let admin_exists = admin_exists.clone(); spawn_local(async move { match Request::get("/api/check-admin").send().await { Ok(resp) if resp.status() == 200 => { if let Ok(data) = resp.json::().await { let has_admin = data["has_admin"].as_bool().unwrap_or(false); admin_exists.set(Some(has_admin)); } } _ => { admin_exists.set(Some(false)); } } }); || () }); } match *admin_exists { None => html! {
{ "Loading..." }
}, Some(false) => { navigator.push(&Route::Setup); html! {
{ "Redirecting to setup..." }
} } Some(true) => props.children.clone().into(), } } /// The main routing logic for the application. /// /// This function takes a `Route` enum variant and returns the corresponding HTML /// content to be rendered. It acts as a central dispatcher for the application's /// navigation. /// /// Many routes are wrapped in a [`ProtectedRoute`] to enforce authentication /// and authorization, and in a [`SidebarShell`] to maintain consistent layout. /// /// # Arguments /// - `route`: The [`Route`] enum variant representing the current URL path. /// /// # Returns /// An `Html` component that should be rendered for the given route. fn switch(route: Route) -> Html { match route { Route::Home => html! { }, Route::NotFound => html! { }, Route::Ticket => html! { }, Route::TicketById { id } => html! { }, Route::AllTickets => html! { }, Route::ArchivedTickets => html! { }, Route::Register => html! { }, Route::Login => html! { }, Route::Setup => html! { }, Route::AllUsers => html! { }, Route::UserByID { id } => html! { }, Route::PermissionDenied => html! { }, Route::Diagnostics => html! { }, } } /// The root component of the Yew application. /// /// This component sets up the application's routing using `yew-router`'s /// [`BrowserRouter`] and [`Switch`] components. All other application content /// is rendered based on the current route. /// /// # Structure /// - [`BrowserRouter`]: Enables client-side routing. /// - [`Switch`]: Renders components based on the matched [`Route`]. #[component(App)] pub fn app() -> Html { html! { render={switch} /> } }