Files
guru-connect/dashboard/src/components/layout/Sidebar.tsx
Mike Swanson 664f33d5ab
Some checks failed
Build and Test / Build Server (Linux) (push) Failing after 3m27s
Build and Test / Build Agent (Windows) (push) Successful in 7m11s
Build and Test / Security Audit (push) Successful in 4m32s
Build and Test / Build Summary (push) Has been skipped
feat(dashboard): GuruConnect v2 Support Codes view
Generate, list, and cancel attended-support codes (XXX-XXX-XXX), built
on the v2 codes API and existing UI primitives.

- Codes table: code in mono, status badge (pending+pulse/connected/
  completed/cancelled), bound client/machine, created-by, created
  (relative + absolute tooltip). Sticky header, skeleton load,
  actionable empty/error states.
- Generate opens a focused reveal modal showing the code large in
  JetBrains Mono with copy and a read-aloud instruction; the code is
  announced character-by-character for screen readers. Mint is ref-
  guarded so it creates exactly one code per open (no StrictMode dupe).
- Cancel via confirm dialog (POST /api/codes/:code/cancel), disabled for
  non-cancellable statuses; invalidates the codes query. List polls 7s.
- Shared API client now tolerates non-JSON 200 bodies, so the cancel
  endpoint's plain-text "Code cancelled" success no longer surfaces as a
  failure. Error-envelope handling unchanged.

Passed Code Review (no blockers after fixes) and local gates
(tsc/lint/build green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 13:59:18 -07:00

72 lines
2.0 KiB
TypeScript

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: true },
{ to: "/codes", label: "Codes", Icon: CodesIcon, enabled: true },
{ 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>
);
}