Files
certifai/src/components/sidebar.rs
2026-02-18 10:45:53 +01:00

154 lines
4.7 KiB
Rust

use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::{
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid, BsHouseDoor, BsRobot,
};
use dioxus_free_icons::icons::fa_solid_icons::FaCubes;
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<NavItem> = vec![
NavItem {
label: "Overview",
route: Route::OverviewPage {},
icon: rsx! { Icon { icon: BsHouseDoor, width: 18, height: 18 } },
},
NavItem {
label: "Documentation",
route: Route::OverviewPage {},
icon: rsx! { Icon { icon: BsFileEarmarkText, width: 18, height: 18 } },
},
NavItem {
label: "Agents",
route: Route::OverviewPage {},
icon: rsx! { Icon { icon: BsRobot, width: 18, height: 18 } },
},
NavItem {
label: "Models",
route: Route::OverviewPage {},
icon: rsx! { Icon { icon: FaCubes, width: 18, height: 18 } },
},
NavItem {
label: "Settings",
route: Route::OverviewPage {},
icon: rsx! { Icon { icon: BsGear, width: 18, height: 18 } },
},
];
// Determine current path to highlight the active nav link.
let current_route = use_route::<Route>();
rsx! {
aside { class: "sidebar",
// -- Header: avatar circle + email --
SidebarHeader { email: email.clone(), avatar_url }
// -- Navigation links --
nav { class: "sidebar-nav",
for item in nav_items {
{
// Simple active check: highlight Overview only when on `/`.
let is_active = 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}" }
}
}
}
}
}
// -- Logout button --
div { class: "sidebar-logout",
Link {
to: NavigationTarget::<Route>::External("/auth/logout".into()),
class: "sidebar-link logout-btn",
Icon { icon: BsBoxArrowRight, width: 18, height: 18 }
span { "Logout" }
}
}
// -- Footer: version + social links --
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::<String>()
.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}" }
}
}
}