import { useQueryClient } from "@tanstack/react-query"; import { Link, useMatchRoute } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; import { Boxes, Braces, Check, ChevronRight, ChevronsUpDown, Eye, EyeOff, Layers, LayoutDashboard, Lightbulb, MessageSquare, Moon, MoonStar, Settings, Sun, Users, Webhook, } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { HealthDot } from "@/components/shared/HealthDot"; import { useDemo } from "@/hooks/useDemo"; import { useHealthStatus } from "@/hooks/useHealthStatus"; import { useInstances } from "@/hooks/useInstances"; import { useMetadata } from "@/hooks/useMetadata"; import { useTheme } from "@/hooks/useTheme"; import { COLOR } from "@/lib/constants"; const TOP_NAV = [ { to: "/" as const, label: "Dashboard", icon: LayoutDashboard, exact: true }, { to: "/workspaces" as const, label: "Workspaces", icon: Boxes, exact: false }, { to: "/seed-kits" as const, label: "Seed Kits", icon: Layers, exact: false }, { to: "/settings" as const, label: "Settings", icon: Settings, exact: false }, ]; const WORKSPACE_SECTIONS = [ { label: "Peers", icon: Users, section: "peers" }, { label: "Sessions", icon: MessageSquare, section: "sessions" }, { label: "Conclusions", icon: Lightbulb, section: "conclusions" }, { label: "Dreams", icon: MoonStar, section: "dreams" }, { label: "Webhooks", icon: Webhook, section: "webhooks" }, ] as const; function formatLastUpdated(value: number | null, now = Date.now()): string { if (!value) return "Not updated yet"; const elapsed = Math.max(0, now - value); if (elapsed < 10_000) return "Updated just now"; if (elapsed < 60_000) return `Updated ${Math.floor(elapsed / 1000)}s ago`; if (elapsed < 3_600_000) return `Updated ${Math.floor(elapsed / 60_000)}m ago`; return `Updated ${Math.floor(elapsed / 3_600_000)}h ago`; } function useLastDataUpdate(): string { const queryClient = useQueryClient(); const [updatedAt, setUpdatedAt] = useState(null); const [now, setNow] = useState(() => Date.now()); useEffect(() => { function refresh() { // No setNow here — calling setNow on every cache event causes a render loop on // CI (each Date.now() call crosses a ms boundary → new value → React re-renders // Sidebar → cache events fire again → loop). setNow belongs only in the interval. const latest = queryClient .getQueryCache() .getAll() .reduce((max, query) => Math.max(max, query.state.dataUpdatedAt || 0), 0); setUpdatedAt(latest || null); } refresh(); const unsubscribe = queryClient.getQueryCache().subscribe(refresh); const interval = window.setInterval(() => { setNow(Date.now()); // refresh relative-time display ("X ago") every 30s refresh(); }, 30_000); return () => { unsubscribe(); window.clearInterval(interval); }; }, [queryClient]); return formatLastUpdated(updatedAt, now); } export function Sidebar() { const matchRoute = useMatchRoute(); const { instances, active, activate } = useInstances(); const { theme, toggle } = useTheme(); const { demo, toggle: toggleDemo, mask } = useDemo(); const { showMetadata, toggle: toggleMeta } = useMetadata(); const { data: health } = useHealthStatus(); const lastUpdated = useLastDataUpdate(); const [switcherOpen, setSwitcherOpen] = useState(false); const switcherRef = useRef(null); useEffect(() => { if (!switcherOpen) return; function onClick(e: MouseEvent) { if (!switcherRef.current?.contains(e.target as Node)) { setSwitcherOpen(false); } } window.addEventListener("mousedown", onClick); return () => window.removeEventListener("mousedown", onClick); }, [switcherOpen]); // Detect workspace context — matchRoute returns params or false const wsMatch = matchRoute({ to: "/workspaces/$workspaceId" as never, fuzzy: true, }) as { workspaceId: string } | false; const activeWorkspaceId = wsMatch ? wsMatch.workspaceId : null; return ( {/* Logo */}
OpenConcho
OpenConcho
{active && (
{switcherOpen && instances.length > 1 && (
{instances.map((inst) => ( ))}
)}
)}
{/* Nav */} {/* Footer — version, demo, metadata, theme */}

v{__APP_VERSION__}

); }