feat(dashboard): GuruConnect v2 operator console (pass 1)
All checks were successful
All checks were successful
React + Vite + TypeScript SPA: scaffold, operations-terminal design system, Bearer-token auth, and the Machines view. - Design system: OKLCH-tinted dark theme (ink-slate + signal-cyan), Hanken Grotesk + JetBrains Mono, status-color language (online/offline/granted/pending/denied/not_required), motion with prefers-reduced-motion honored. - Auth: token in sessionStorage via ref (never React state), protected routes, 401 session teardown, admin-gated per-agent-key UI. - Machines view: data table (sticky header, keyboard-activated rows, skeleton loading, actionable empty/error states), non-blocking detail drawer, delete confirm, admin key management with copy-once reveal. - UI primitives: Modal (focus trap + inert + portal + dialogStack), Drawer, Table, Badge/StatusDot, toast, states. - Typed API client normalizing the two error-envelope shapes. Passed Code Review (no blockers), impeccable critique-and-polish, and local gates (tsc/lint/build green). Dev-only Vite proxy to :3002. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
71
dashboard/src/components/layout/Sidebar.tsx
Normal file
71
dashboard/src/components/layout/Sidebar.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import type { ComponentType, SVGProps } from "react";
|
||||
import {
|
||||
CodesIcon,
|
||||
MachinesIcon,
|
||||
SessionsIcon,
|
||||
UsersIcon,
|
||||
} from "./icons";
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
label: string;
|
||||
Icon: ComponentType<SVGProps<SVGSVGElement>>;
|
||||
/** Pass-1 stubs are disabled until their views land in later passes. */
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const NAV: NavItem[] = [
|
||||
{ to: "/machines", label: "Machines", Icon: MachinesIcon, enabled: true },
|
||||
{ to: "/sessions", label: "Sessions", Icon: SessionsIcon, enabled: false },
|
||||
{ to: "/codes", label: "Codes", Icon: CodesIcon, enabled: false },
|
||||
{ to: "/users", label: "Users", Icon: UsersIcon, enabled: false },
|
||||
];
|
||||
|
||||
export function Sidebar() {
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<div className="sidebar__brand">
|
||||
<span className="sidebar__logo" aria-hidden="true">
|
||||
GC
|
||||
</span>
|
||||
<span className="sidebar__name">
|
||||
GuruConnect
|
||||
<small>Operator Console</small>
|
||||
</span>
|
||||
</div>
|
||||
<nav className="sidebar__nav" aria-label="Primary">
|
||||
<span className="sidebar__section">Operations</span>
|
||||
{NAV.map(({ to, label, Icon, enabled }) =>
|
||||
enabled ? (
|
||||
<NavLink
|
||||
key={to}
|
||||
to={to}
|
||||
className={({ isActive }) =>
|
||||
`navlink${isActive ? " navlink--active" : ""}`
|
||||
}
|
||||
>
|
||||
<span className="navlink__icon">
|
||||
<Icon />
|
||||
</span>
|
||||
{label}
|
||||
</NavLink>
|
||||
) : (
|
||||
<span
|
||||
key={to}
|
||||
className="navlink navlink--disabled"
|
||||
aria-disabled="true"
|
||||
title={`${label} — coming in a later pass`}
|
||||
>
|
||||
<span className="navlink__icon">
|
||||
<Icon />
|
||||
</span>
|
||||
{label}
|
||||
<span className="navlink__soon">Soon</span>
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user