use gloo_net::http::Request; use gloo_storage::{LocalStorage, Storage}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::rc::Rc; use wasm_bindgen_futures::spawn_local; use yew::prelude::*; use yew_router::prelude::*; const STORAGE_KEY: &str = "sidebar_state"; /// Represents the expansion state of collapsible menus within the sidebar. /// /// This struct is used to persist the open/closed state of sidebar submenus, /// improving user experience by remembering their last interaction. /// The state is stored in and retrieved from `LocalStorage`. /// /// # Fields /// - `ticket_open`: A boolean indicating if the "Tickets" submenu is expanded (`true`) or collapsed (`false`). /// - `users_open`: A boolean indicating if the "Users" submenu is expanded (`true`) or collapsed (`false`). #[derive(Clone, PartialEq, Serialize, Deserialize)] pub struct SidebarExpandState { pub ticket_open: bool, pub users_open: bool, } impl Default for SidebarExpandState { /// Provides the default expansion state, where all submenus are collapsed. fn default() -> Self { Self { ticket_open: false, users_open: false, } } } /// Represents the shared state for the sidebar's expandable menus. /// /// This context struct is provided via Yew's `ContextProvider` and allows child components /// within the sidebar to read and modify the expansion state of the "Tickets" and "Users" menus. /// /// # Fields /// - `expand`: The current [`SidebarExpandState`] holding whether each menu is open or closed. /// - `set_tickets_open`: A `Callback` to explicitly set the open state of the "Tickets" menu. /// - `toggle_tickets`: A `Callback<()>` to toggle the open state of the "Tickets" menu. /// - `set_users_open`: A `Callback` to explicitly set the open state of the "Users" menu. /// - `toggle_users`: A `Callback<()>` to toggle the open state of the "Users" menu. #[derive(Clone, PartialEq)] pub struct SidebarState { pub expand: SidebarExpandState, pub set_tickets_open: Callback, pub toggle_tickets: Callback<()>, pub set_users_open: Callback, pub toggle_users: Callback<()>, } impl SidebarState { /// Creates a new `SidebarState` instance. /// /// This constructor is typically used within the [`SidebarStateProvider`] to /// bundle the current expansion state and its associated callbacks for context sharing. fn new( expand: SidebarExpandState, set_tickets_open: Callback, toggle_tickets: Callback<()>, set_users_open: Callback, toggle_users: Callback<()>, ) -> Self { Self { expand, set_tickets_open, toggle_tickets, set_users_open, toggle_users, } } } /// Properties for components that provide sidebar state. /// /// This struct is typically used by context providers that wrap child components /// and supply them with shared sidebar-related state or functionality. /// /// # Fields /// - `children`: The child components that will have access to the provided sidebar state. #[derive(Properties, PartialEq)] pub struct SidebarProps { pub children: Children, } /// A Yew context provider component that manages and supplies the sidebar's expansion state. /// /// This component is responsible for: /// 1. Loading the initial `SidebarExpandState` from browser `LocalStorage` (or using defaults). /// 2. Providing a `SidebarState` context (`Rc`) to its children, which includes /// the current expansion state and callbacks to modify it. /// 3. Persisting any changes to the `SidebarExpandState` back to `LocalStorage`. /// /// Child components (like [`TicketMenu`] and [`UsersMenu`]) can consume this context /// to react to and control the sidebar's collapsible sections. /// /// # LocalStorage Key /// The state is stored under the key `STORAGE_KEY` ("sidebar_state"). /// /// # Example Usage /// ```rust /// html! { /// /// // Sidebar and its sub-components will have access to the state /// /// } /// ``` #[component(SidebarStateProvider)] pub fn sidebar_state_provider(props: &SidebarProps) -> Html { let default = LocalStorage::get(STORAGE_KEY).unwrap_or_else(|_| SidebarExpandState::default()); let state = use_state(|| default); { let state = state.clone(); use_effect_with(state, move |s| { LocalStorage::set(STORAGE_KEY, &**s).ok(); || () }); } let set_tickets_open = { let state = state.clone(); Callback::from(move |v: bool| { state.set(SidebarExpandState { ticket_open: v, users_open: (*state).users_open, }) }) }; let toggle_tickets = { let state = state.clone(); Callback::from(move |_| { let current = (*state).ticket_open; state.set(SidebarExpandState { ticket_open: !current, users_open: (*state).users_open, }); }) }; let set_users_open = { let state = state.clone(); Callback::from(move |v: bool| { state.set(SidebarExpandState { ticket_open: (*state).ticket_open, users_open: v, }) }) }; let toggle_users = { let state = state.clone(); Callback::from(move |_| { let current = (*state).users_open; state.set(SidebarExpandState { ticket_open: (*state).ticket_open, users_open: !current, }); }) }; let ctx = SidebarState::new( (*state).clone(), set_tickets_open, toggle_tickets, set_users_open, toggle_users, ); html! { > context={Rc::new(ctx)}> { for props.children.iter() } >> } } /// A collapsible menu component for "Tickets" within the sidebar. /// /// This component consumes the [`SidebarState`] context to manage its expanded/collapsed state. /// It displays a button to toggle its visibility and, when expanded, reveals links /// for "Submit Ticket" and "View Tickets". /// /// # Context /// Requires [`SidebarStateProvider`] as an ancestor to provide the necessary context. /// /// # Functionality /// - **Toggle Button**: Clicking the button toggles the `ticket_open` state in the `SidebarState`. /// - **Submenu Links**: /// - [`crate::Route::Ticket`]: Link to submit a new ticket. /// - [`crate::Route::AllTickets`]: Link to view all tickets. /// /// # Example /// ```rust /// html! { /// /// } /// ``` #[component(TicketMenu)] pub fn ticket_menu() -> Html { let ctx = use_context::>().expect("TicketsMenu must be inside SidebarStateProvider"); let on_toggle = { let cb = ctx.toggle_tickets.clone(); Callback::from(move |_| cb.emit(())) }; let open = ctx.expand.ticket_open; html! {
  • { if open { html! { } } else { html!{} } }
  • } } /// A collapsible menu component for "Users" within the sidebar. /// /// This component consumes the [`SidebarState`] context to manage its expanded/collapsed state. /// It displays a button to toggle its visibility and, when expanded, reveals links /// for "Create User" and "View Users". This menu is typically only visible to administrators. /// /// # Context /// Requires [`SidebarStateProvider`] as an ancestor to provide the necessary context. /// /// # Functionality /// - **Toggle Button**: Clicking the button toggles the `users_open` state in the `SidebarState`. /// - **Submenu Links**: /// - [`crate::Route::Register`]: Link to create a new user account. /// - [`crate::Route::AllUsers`]: Link to view all registered users. /// /// # Example /// ```rust /// html! { /// /// } /// ``` #[component(UsersMenu)] pub fn users_menu() -> Html { let ctx = use_context::>().expect("UsersMenu must be inside SidebarStateProvider"); let on_toggle = { let cb = ctx.toggle_users.clone(); Callback::from(move |_| cb.emit(())) }; let open = ctx.expand.users_open; html! {
  • { if open { html! { } } else { html!{} } }
  • } } /// The main sidebar component of the application. /// /// This component dynamically renders its content based on the user's authentication /// and administrative status. It fetches the current user's details via `/api/users/current` /// to determine what menu items to display. /// /// # Structure /// - Wraps its content in a [`SidebarStateProvider`] to allow nested menus to manage their state. /// - Contains a navigation (`