diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index e4021b8..92c7d95 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,7 +1,7 @@ "use client"; import Image from "next/image"; -import { useEffect, useState, type MouseEvent } from "react"; +import { useEffect, useRef, useState, type MouseEvent } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { auth, clearToken, redirectToLogin } from "@/lib/api"; @@ -10,7 +10,7 @@ import OrganizationSwitcher from "@/components/OrganizationSwitcher"; import { navigation } from "@/lib/navigation"; import { addPreferencesListener, getStoredPanelName } from "@/lib/preferences"; import { formatRoleLabel, hasMinimumRole } from "@/lib/permissions"; -import { LogOut, ChevronLeft, ChevronRight, X } from "lucide-react"; +import { LogOut, ChevronsUpDown, ChevronLeft, ChevronRight, X, Crown } from "lucide-react"; interface SidebarProps { /** Desktop: whether sidebar is in collapsed (icon-only) state */ @@ -35,6 +35,8 @@ export default function Sidebar({ const pathname = usePathname(); const currentUser = useCurrentUser(); const [loggingOut, setLoggingOut] = useState(false); + const [accountOpen, setAccountOpen] = useState(false); + const accountMenuRef = useRef(null); const [pendingHref, setPendingHref] = useState(null); const [panelName, setPanelName] = useState("DOKTAINER"); const visibleNavigation = navigation @@ -45,6 +47,12 @@ export default function Sidebar({ ), })) .filter((section) => section.items.length > 0); + const accountMenuSections = visibleNavigation.filter( + (section) => section.label === "INTEGRATIONS" || section.label === "MANAGEMENT", + ); + const sidebarNavigation = visibleNavigation.filter( + (section) => section.label !== "INTEGRATIONS" && section.label !== "MANAGEMENT", + ); const handleLogout = async () => { if (loggingOut) return; @@ -79,6 +87,14 @@ export default function Sidebar({ return () => window.clearTimeout(timeoutId); }, [pathname]); + useEffect(() => { + const handlePointerDown = (event: PointerEvent) => { + if (!accountMenuRef.current?.contains(event.target as Node)) setAccountOpen(false); + }; + document.addEventListener("pointerdown", handlePointerDown); + return () => document.removeEventListener("pointerdown", handlePointerDown); + }, []); + const handleNavigate = ( event: MouseEvent, href: string, @@ -249,7 +265,7 @@ export default function Sidebar({ gap: 2, }} > - {visibleNavigation.map((section) => ( + {sidebarNavigation.map((section) => (
{!isCollapsed && (
setAccountOpen((value) => !value)} style={{ padding: isCollapsed ? "12px 8px" : "12px 16px", borderTop: "1px solid var(--border)", display: "flex", alignItems: "center", gap: 10, + position: "relative", + cursor: "pointer", }} >
)} + {!isCollapsed && } + {accountOpen && ( +
event.stopPropagation()} style={{ position: "absolute", bottom: "calc(100% + 8px)", left: isCollapsed ? "calc(100% + 14px)" : 8, width: 300, maxHeight: "min(520px, calc(100vh - 32px))", overflowY: "auto", padding: 12, background: "var(--bg-secondary)", border: "1px solid var(--border)", borderRadius: 8, boxShadow: "0 16px 40px rgba(0,0,0,0.22)", cursor: "default", zIndex: 60 }}> +
{currentUser?.name || currentUser?.email || "Authenticated User"} · {formatRoleLabel(currentUser?.role)}{currentUser?.role === "SUPER_ADMIN" && }
{currentUser?.email}
+ {accountMenuSections.map((section) =>
{section.label}
{section.items.map((item) => { const Icon = item.icon; const active = pathname === item.href; return { setAccountOpen(false); onMobileClose(); }} style={{ alignItems: "center", background: active ? "rgba(59, 130, 246, 0.12)" : "transparent", borderRadius: 7, color: active ? "#3b82f6" : "var(--text-secondary)", display: "flex", fontSize: 13, fontWeight: 500, gap: 9, padding: "8px 6px", textDecoration: "none" }}>{item.label}; })}
)} +
+ +
+ )}