"use client"; import { useEffect, useState } from "react"; import { IconDashboard, IconFileDescription, IconDatabase, IconTable, IconClipboardList, IconUsers, IconLogout, IconKey, IconVariable, IconTag, IconPuzzle, IconSettings, IconInfoCircle, IconApps, IconArrowUpCircle, IconArrowsShuffle, IconSkull, IconFlask, } from "@tabler/icons-react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useAuth } from "@/lib/auth-context"; import { useConfig } from "@/lib/config-context"; import { useCurrentUser } from "@/hooks/use-current-user"; import { usePluginUpdates } from "@/components/plugin-update-provider"; import { Scroller } from "@/components/ui/scroller"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarSeparator, } from "@/components/ui/sidebar"; type NavItem = { title: string; url: string; icon: React.ComponentType; requiredPermissions?: string[]; }; const dataItems: NavItem[] = [ { title: "Lexicons", url: "/dashboard/lexicons", icon: IconFileDescription }, { title: "Records", url: "/dashboard/records", icon: IconTable }, { title: "Backfill", url: "/dashboard/backfill", icon: IconDatabase }, { title: "Dead Letters", url: "/dashboard/dead-letters", icon: IconSkull, requiredPermissions: ["dead-letters:read"], }, ]; const accessItems: NavItem[] = [ { title: "Users", url: "/dashboard/settings/users", icon: IconUsers, requiredPermissions: ["users:read"], }, { title: "API Keys", url: "/dashboard/settings/api-keys", icon: IconKey, requiredPermissions: ["api-keys:read"], }, { title: "API Clients", url: "/dashboard/settings/api-clients", icon: IconApps, requiredPermissions: ["api-clients:view"], }, ]; const integrationItems: NavItem[] = [ { title: "Plugins", url: "/dashboard/settings/plugins", icon: IconPuzzle, requiredPermissions: ["plugins:read"], }, { title: "Labelers", url: "/dashboard/settings/labelers", icon: IconTag, requiredPermissions: ["labelers:read"], }, ]; const systemItems: NavItem[] = [ { title: "General", url: "/dashboard/settings/general", icon: IconSettings, requiredPermissions: ["settings:manage"], }, { title: "XRPC Proxy", url: "/dashboard/settings/xrpc-proxy", icon: IconArrowsShuffle, requiredPermissions: ["settings:manage"], }, { title: "ENV Variables", url: "/dashboard/settings/env-variables", icon: IconVariable, requiredPermissions: ["script-variables:read"], }, { title: "Event Logs", url: "/dashboard/events", icon: IconClipboardList, requiredPermissions: ["events:read"], }, { title: "Experiments", url: "/dashboard/settings/experiments", icon: IconFlask, requiredPermissions: ["settings:manage"], }, ]; export function AppSidebar({ ...props }: React.ComponentProps) { const pathname = usePathname(); const { logout } = useAuth(); const { app_name, logo_url } = useConfig(); const { hasPermission } = useCurrentUser(); const { hasUpdates } = usePluginUpdates(); const [deadLetterCount, setDeadLetterCount] = useState(0); useEffect(() => { let cancelled = false; async function fetchCount() { try { const { getDeadLetterCount } = await import("@/lib/api"); const data = await getDeadLetterCount("false"); if (!cancelled) setDeadLetterCount(data.count); } catch { // sidebar badge is best-effort } } fetchCount(); const interval = setInterval(fetchCount, 30000); return () => { cancelled = true; clearInterval(interval); }; }, []); function filterByPermission(items: NavItem[]) { return items.filter( (item) => !item.requiredPermissions || item.requiredPermissions.some((perm) => hasPermission(perm)), ); } function isActive(url: string) { return url === "/dashboard" ? pathname === "/dashboard" : pathname.startsWith(url); } const visibleData = filterByPermission(dataItems); const visibleAccess = filterByPermission(accessItems); const visibleIntegrations = filterByPermission(integrationItems); const visibleSystem = filterByPermission(systemItems); return ( {logo_url ? ( {app_name ) : ( <> HappyView HappyView )} Dashboard {visibleData.length > 0 && ( Data {visibleData.map((item) => { const showDeadLetterBadge = item.title === "Dead Letters" && deadLetterCount > 0; return ( {item.title} {showDeadLetterBadge && ( {deadLetterCount} )} ); })} )} {visibleAccess.length > 0 && ( Access {visibleAccess.map((item) => ( {item.title} ))} )} {visibleIntegrations.length > 0 && ( Integrations {visibleIntegrations.map((item) => { const showUpdateBadge = item.title === "Plugins" && hasUpdates; return ( {item.title} {showUpdateBadge && ( )} ); })} )} {visibleSystem.length > 0 && ( System {visibleSystem.map((item) => ( {item.title} ))} )} About Log out ); }