use compliance_core::models::auth::AuthInfo; use dioxus::prelude::*; use dioxus_free_icons::icons::bs_icons::*; use dioxus_free_icons::Icon; use crate::app::Route; struct NavItem { label: &'static str, route: Route, icon: Element, } #[component] pub fn Sidebar() -> Element { let current_route = use_route::(); let mut collapsed = use_signal(|| true); let nav_items = [ NavItem { label: "Overview", route: Route::OverviewPage {}, icon: rsx! { Icon { icon: BsSpeedometer2, width: 18, height: 18 } }, }, NavItem { label: "Repositories", route: Route::RepositoriesPage {}, icon: rsx! { Icon { icon: BsFolder2Open, width: 18, height: 18 } }, }, NavItem { label: "Findings", route: Route::FindingsPage {}, icon: rsx! { Icon { icon: BsShieldExclamation, width: 18, height: 18 } }, }, NavItem { label: "SBOM", route: Route::SbomPage {}, icon: rsx! { Icon { icon: BsBoxSeam, width: 18, height: 18 } }, }, NavItem { label: "Issues", route: Route::IssuesPage {}, icon: rsx! { Icon { icon: BsListTask, width: 18, height: 18 } }, }, NavItem { label: "Code Graph", route: Route::GraphIndexPage {}, icon: rsx! { Icon { icon: BsDiagram3, width: 18, height: 18 } }, }, NavItem { label: "AI Chat", route: Route::ChatIndexPage {}, icon: rsx! { Icon { icon: BsChatDots, width: 18, height: 18 } }, }, NavItem { label: "DAST", route: Route::DastOverviewPage {}, icon: rsx! { Icon { icon: BsBug, width: 18, height: 18 } }, }, NavItem { label: "MCP Servers", route: Route::McpServersPage {}, icon: rsx! { Icon { icon: BsPlug, width: 18, height: 18 } }, }, NavItem { label: "Settings", route: Route::SettingsPage {}, icon: rsx! { Icon { icon: BsGear, width: 18, height: 18 } }, }, ]; let docs_url = option_env!("DOCS_URL").unwrap_or("/docs"); let sidebar_class = if collapsed() { "sidebar collapsed" } else { "sidebar" }; rsx! { nav { class: "{sidebar_class}", div { class: "sidebar-header", Icon { icon: BsShieldCheck, width: 24, height: 24 } if !collapsed() { h1 { "Compliance Scanner" } } } div { class: "sidebar-nav", for item in nav_items { { let is_active = match (¤t_route, &item.route) { (Route::FindingDetailPage { .. }, Route::FindingsPage {}) => true, (Route::GraphIndexPage {}, Route::GraphIndexPage {}) => true, (Route::GraphExplorerPage { .. }, Route::GraphIndexPage {}) => true, (Route::ImpactAnalysisPage { .. }, Route::GraphIndexPage {}) => true, (Route::ChatPage { .. }, Route::ChatIndexPage {}) => true, (Route::DastTargetsPage {}, Route::DastOverviewPage {}) => true, (Route::DastFindingsPage {}, Route::DastOverviewPage {}) => true, (Route::DastFindingDetailPage { .. }, Route::DastOverviewPage {}) => true, (a, b) => a == b, }; let class = if is_active { "nav-item active" } else { "nav-item" }; rsx! { Link { to: item.route.clone(), class: class, {item.icon} if !collapsed() { span { "{item.label}" } } } } } } } a { href: "{docs_url}", target: "_blank", class: "nav-item", Icon { icon: BsBook, width: 18, height: 18 } if !collapsed() { span { "Docs" } } } button { class: "sidebar-toggle", onclick: move |_| collapsed.set(!collapsed()), if collapsed() { Icon { icon: BsChevronRight, width: 14, height: 14 } } else { Icon { icon: BsChevronLeft, width: 14, height: 14 } } } { let auth_info = use_context::>(); let info = auth_info(); let initials = info.name.chars().next().unwrap_or('U').to_uppercase().to_string(); let user_class = if collapsed() { "sidebar-user sidebar-user-collapsed" } else { "sidebar-user" }; rsx! { div { class: "{user_class}", div { class: "user-avatar", if info.avatar_url.is_empty() { span { class: "avatar-initials", "{initials}" } } else { img { src: "{info.avatar_url}", alt: "avatar", class: "avatar-img" } } } if !collapsed() { span { class: "user-name", "{info.name}" } } a { href: "/logout", class: if collapsed() { "logout-btn logout-btn-collapsed" } else { "logout-btn" }, title: "Sign out", Icon { icon: BsBoxArrowRight, width: 16, height: 16 } } } } } } } }