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>
72 lines
2.0 KiB
TypeScript
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>
|
|
);
|
|
}
|