import { useState, useEffect, useMemo, type ReactNode } from 'react' import { Outlet, useLocation, useNavigate } from 'react-router-dom' import { Box, Cpu, GitBranch, Settings, FileText, ChevronRight, X } from 'lucide-react' import { AppShell } from '../design-system/layout/AppShell/AppShell' import { Sidebar } from '../design-system/layout/Sidebar/Sidebar' import { SidebarTree } from '../design-system/layout/Sidebar/SidebarTree' import type { SidebarTreeNode } from '../design-system/layout/Sidebar/SidebarTree' import { useStarred } from '../design-system/layout/Sidebar/useStarred' import { StatusDot } from '../design-system/primitives/StatusDot/StatusDot' import { SIDEBAR_APPS } from '../mocks/sidebar' import type { SidebarApp } from '../mocks/sidebar' import camelLogoUrl from '../assets/camel-logo.svg' // ── Helpers ───────────────────────────────────────────────────────────────── function formatCount(n: number): string { if (n >= 1000) return `${(n / 1000).toFixed(1)}k` return String(n) } // ── Tree node builders ────────────────────────────────────────────────────── function buildAppTreeNodes(apps: SidebarApp[]): SidebarTreeNode[] { return apps.map((app) => ({ id: app.id, label: app.name, icon: , badge: formatCount(app.exchangeCount), path: `/apps/${app.id}`, starrable: true, starKey: `app:${app.id}`, children: app.routes.map((route) => ({ id: `${app.id}/${route.id}`, label: route.name, icon: , badge: formatCount(route.exchangeCount), path: `/apps/${app.id}/${route.id}`, })), })) } function buildRouteTreeNodes(apps: SidebarApp[]): SidebarTreeNode[] { return apps .filter((app) => app.routes.length > 0) .map((app) => ({ id: `routes:${app.id}`, label: app.name, icon: , badge: `${app.routes.length} route${app.routes.length !== 1 ? 's' : ''}`, path: `/routes/${app.id}`, starrable: true, starKey: `routestat:${app.id}`, children: app.routes.map((route) => ({ id: `routes:${app.id}/${route.id}`, label: route.name, icon: , badge: formatCount(route.exchangeCount), path: `/routes/${app.id}/${route.id}`, })), })) } function buildAgentTreeNodes(apps: SidebarApp[]): SidebarTreeNode[] { return apps .filter((app) => app.agents.length > 0) .map((app) => { const liveCount = app.agents.filter((a) => a.status === 'live').length return { id: `agents:${app.id}`, label: app.name, icon: , badge: `${liveCount}/${app.agents.length} live`, path: `/agents/${app.id}`, starrable: true, starKey: `agent:${app.id}`, children: app.agents.map((agent) => ({ id: `agents:${app.id}/${agent.id}`, label: agent.name, icon: , badge: `${agent.tps} tps`, path: `/agents/${app.id}/${agent.id}`, })), } }) } // ── Starred items ─────────────────────────────────────────────────────────── interface StarredItem { starKey: string label: string icon?: ReactNode path: string type: 'application' | 'route' | 'agent' | 'routestat' parentApp?: string } function collectStarredItems( apps: SidebarApp[], starredIds: Set, ): StarredItem[] { const items: StarredItem[] = [] for (const app of apps) { if (starredIds.has(`app:${app.id}`)) { items.push({ starKey: `app:${app.id}`, label: app.name, icon: , path: `/apps/${app.id}`, type: 'application', }) } for (const route of app.routes) { if (starredIds.has(`route:${app.id}/${route.id}`)) { items.push({ starKey: `route:${app.id}/${route.id}`, label: route.name, icon: , path: `/apps/${app.id}/${route.id}`, type: 'route', parentApp: app.name, }) } } if (starredIds.has(`routestat:${app.id}`)) { items.push({ starKey: `routestat:${app.id}`, label: app.name, icon: , path: `/routes/${app.id}`, type: 'routestat', }) } if (starredIds.has(`agent:${app.id}`)) { items.push({ starKey: `agent:${app.id}`, label: app.name, icon: , path: `/agents/${app.id}`, type: 'agent', }) } } return items } // ── Starred group component ───────────────────────────────────────────────── interface StarredGroupProps { label: string items: StarredItem[] onRemove: (starKey: string) => void onNavigate: (path: string) => void } function StarredGroup({ label, items, onRemove, onNavigate }: StarredGroupProps) { if (items.length === 0) return null return (
{label}
{items.map((item) => (
onNavigate(item.path)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onNavigate(item.path) }} > {item.icon && ( {item.icon} )} {item.label} {item.parentApp && ( {item.parentApp} )}
))}
) } // ── localStorage-backed section collapse ──────────────────────────────────── function usePersistedCollapse(key: string, defaultValue: boolean): [boolean, () => void] { const [value, setValue] = useState(() => { try { const raw = localStorage.getItem(key) if (raw !== null) return raw === 'true' } catch { /* ignore */ } return defaultValue }) const toggle = () => { setValue((prev) => { const next = !prev try { localStorage.setItem(key, String(next)) } catch { /* ignore */ } return next }) } return [value, toggle] } // ── LayoutShell ───────────────────────────────────────────────────────────── export function LayoutShell() { const navigate = useNavigate() const location = useLocation() const { starredIds, isStarred, toggleStar } = useStarred() const [sidebarCollapsed, setSidebarCollapsed] = useState(false) const [filterQuery, setFilterQuery] = useState('') // Section collapse state — persisted to localStorage const [appsCollapsed, toggleAppsCollapsed] = usePersistedCollapse('cameleer:sidebar:apps-collapsed', false) const [agentsCollapsed, toggleAgentsCollapsed] = usePersistedCollapse('cameleer:sidebar:agents-collapsed', false) const [routesCollapsed, toggleRoutesCollapsed] = usePersistedCollapse('cameleer:sidebar:routes-collapsed', false) // Tree data — static, so empty deps const appNodes = useMemo(() => buildAppTreeNodes(SIDEBAR_APPS), []) const agentNodes = useMemo(() => buildAgentTreeNodes(SIDEBAR_APPS), []) const routeNodes = useMemo(() => buildRouteTreeNodes(SIDEBAR_APPS), []) // Sidebar reveal from Cmd-K navigation const sidebarRevealPath = (location.state as { sidebarReveal?: string } | null)?.sidebarReveal ?? null // Auto-uncollapse matching sections when sidebarRevealPath changes useEffect(() => { if (!sidebarRevealPath) return if (sidebarRevealPath.startsWith('/apps') && appsCollapsed) { toggleAppsCollapsed() } if (sidebarRevealPath.startsWith('/agents') && agentsCollapsed) { toggleAgentsCollapsed() } if (sidebarRevealPath.startsWith('/routes') && routesCollapsed) { toggleRoutesCollapsed() } }, [sidebarRevealPath]) // eslint-disable-line react-hooks/exhaustive-deps const effectiveSelectedPath = sidebarRevealPath ?? location.pathname // Starred items — collected and grouped const allStarred = useMemo( () => collectStarredItems(SIDEBAR_APPS, starredIds), [starredIds], ) const starredApps = allStarred.filter((s) => s.type === 'application') const starredRoutes = allStarred.filter((s) => s.type === 'route') const starredAgents = allStarred.filter((s) => s.type === 'agent') const starredRouteStats = allStarred.filter((s) => s.type === 'routestat') const hasStarred = allStarred.length > 0 const camelLogo = ( ) return ( setSidebarCollapsed((c) => !c)} searchValue={filterQuery} onSearchChange={setFilterQuery} > navigate('/apps')} /> } open={!appsCollapsed} onToggle={toggleAppsCollapsed} active={location.pathname.startsWith('/apps')} > } open={!agentsCollapsed} onToggle={toggleAgentsCollapsed} active={location.pathname.startsWith('/agents')} > } open={!routesCollapsed} onToggle={toggleRoutesCollapsed} active={location.pathname.startsWith('/routes')} > {hasStarred && ( } open={true} onToggle={() => {}} active={false} > )} } label="Admin" onClick={() => navigate('/admin')} active={location.pathname.startsWith('/admin')} /> } label="API Docs" onClick={() => navigate('/api-docs')} active={location.pathname === '/api-docs'} /> } > ) }