mod auth; mod pages; mod dark_mode; 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. Each route is protected /// by [`ProtectedRoute`] middleware where appropriate to enforce authentication and authorization. /// See [`switch`] for the routing logic. #[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. Integrates with [`crate::pages::sidebar::Sidebar`] for navigation. /// /// # Components /// - [`crate::pages::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 /// backend's `/api/check-admin` endpoint (via `crate::handlers::auth::check_admin_exists`) to determine system state. /// /// # Behavior /// - **Loading**: Displays "Loading..." while checking admin status from the backend /// - **No Admin**: Automatically redirects to [`Route::Setup`] page for initialization /// - **Admin Exists**: Renders the wrapped children (e.g., login page) /// /// # Example Usage /// ```ignore /// /// /// /// ``` /// /// # Backend Integration /// The check queries the backend's `/api/check-admin` endpoint which returns `{"has_admin": bool}`. /// This allows the frontend to determine if initial admin setup is required. #[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! {
{ "Lade..." }
}, Some(false) => { navigator.push(&Route::Setup); html! {
{ "Leite weiter zur Einrichtung..." }
} } 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 and authentication flow. /// /// Most routes are wrapped in [`ProtectedRoute`] to enforce authentication /// and authorization based on the `admin_page` flag, and in [`SidebarShell`] to maintain consistent layout. /// Login and Setup routes use [`AdminCheckWrapper`] instead to handle pre-authentication states. /// /// # Arguments /// - `route`: The [`Route`] enum variant representing the current URL path. /// /// # Returns /// An `Html` component that should be rendered for the given route. /// /// # Route Protection /// - **Admin-required routes** (`admin_page={true}`): Require both authentication and admin privileges /// - **Public routes** (`admin_page={false}`): Require only authentication /// - **Pre-auth routes** (`AdminCheckWrapper`): Used before admin creation or login 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`] matched by the `switch` function. /// /// Uses [`switch`] as the routing dispatcher to handle all route-specific rendering, /// which applies appropriate middleware like [`ProtectedRoute`] and [`AdminCheckWrapper`]. /// /// # Structure /// - `BrowserRouter`: Enables client-side routing. /// - `Switch`: Renders components based on the matched [`Route`]. /// - `switch` function: Determines which component to render for each route. #[component(App)] pub fn app() -> Html { html! { render={switch} /> } }