use dioxus::prelude::*; use dioxus_free_icons::icons::bs_icons::{ BsBoxArrowRight, BsBuilding, BsChatDots, BsCloudArrowUp, BsCodeSlash, BsCollection, BsGithub, BsGrid, BsHouseDoor, BsPuzzle, }; use dioxus_free_icons::Icon; use crate::Route; /// Navigation entry for the sidebar. struct NavItem { label: &'static str, route: Route, /// Bootstrap icon element rendered beside the label. icon: Element, } /// Fixed left sidebar containing header, navigation, logout, and footer. /// /// # Arguments /// /// * `email` - Email address displayed beneath the avatar placeholder. /// * `avatar_url` - URL for the avatar image (unused placeholder for now). #[component] pub fn Sidebar(email: String, avatar_url: String) -> Element { let nav_items: Vec = vec![ NavItem { label: "Dashboard", route: Route::DashboardPage {}, icon: rsx! { Icon { icon: BsHouseDoor, width: 18, height: 18 } }, }, NavItem { label: "Providers", route: Route::ProvidersPage {}, icon: rsx! { Icon { icon: BsCloudArrowUp, width: 18, height: 18 } }, }, NavItem { label: "Chat", route: Route::ChatPage {}, icon: rsx! { Icon { icon: BsChatDots, width: 18, height: 18 } }, }, NavItem { label: "Tools", route: Route::ToolsPage {}, icon: rsx! { Icon { icon: BsPuzzle, width: 18, height: 18 } }, }, NavItem { label: "Knowledge Base", route: Route::KnowledgePage {}, icon: rsx! { Icon { icon: BsCollection, width: 18, height: 18 } }, }, NavItem { label: "Developer", route: Route::AgentsPage {}, icon: rsx! { Icon { icon: BsCodeSlash, width: 18, height: 18 } }, }, NavItem { label: "Organization", route: Route::OrgPricingPage {}, icon: rsx! { Icon { icon: BsBuilding, width: 18, height: 18 } }, }, ]; // Determine current path to highlight the active nav link. let current_route = use_route::(); rsx! { aside { class: "sidebar", SidebarHeader { email: email.clone(), avatar_url } nav { class: "sidebar-nav", for item in nav_items { { // Active detection for nested routes: highlight the parent nav // item when any child route within the nested shell is active. let is_active = match ¤t_route { Route::AgentsPage {} | Route::FlowPage {} | Route::AnalyticsPage {} => { item.label == "Developer" } Route::OrgPricingPage {} | Route::OrgDashboardPage {} => { item.label == "Organization" } _ => item.route == current_route, }; let cls = if is_active { "sidebar-link active" } else { "sidebar-link" }; rsx! { Link { to: item.route, class: cls, {item.icon} span { "{item.label}" } } } } } } div { class: "sidebar-logout", Link { to: NavigationTarget::::External("/auth/logout".into()), class: "sidebar-link logout-btn", Icon { icon: BsBoxArrowRight, width: 18, height: 18 } span { "Logout" } } } SidebarFooter {} } } } /// Avatar circle and email display at the top of the sidebar. /// /// # Arguments /// /// * `email` - User email to display. /// * `avatar_url` - Placeholder for future avatar image URL. #[component] fn SidebarHeader(email: String, avatar_url: String) -> Element { // Extract initials from email (first two chars before @). let initials: String = email .split('@') .next() .unwrap_or("U") .chars() .take(2) .collect::() .to_uppercase(); rsx! { div { class: "sidebar-header", div { class: "avatar-circle", span { class: "avatar-initials", "{initials}" } } p { class: "sidebar-email", "{email}" } } } } /// Footer section with version string and placeholder social links. #[component] fn SidebarFooter() -> Element { let version = env!("CARGO_PKG_VERSION"); rsx! { footer { class: "sidebar-footer", div { class: "sidebar-social", a { href: "#", class: "social-link", title: "GitHub", Icon { icon: BsGithub, width: 16, height: 16 } } a { href: "#", class: "social-link", title: "Impressum", Icon { icon: BsGrid, width: 16, height: 16 } } } p { class: "sidebar-version", "v{version}" } } } }