Files
guru-connect/dashboard/src/components/layout/Topbar.tsx
Mike Swanson 43a9432b81
All checks were successful
Build and Test / Build Agent (Windows) (push) Successful in 6m56s
Build and Test / Build Server (Linux) (push) Successful in 10m15s
Build and Test / Security Audit (push) Successful in 4m12s
Build and Test / Build Summary (push) Successful in 10s
feat(dashboard): GuruConnect v2 operator console (pass 1)
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>
2026-05-30 12:51:11 -07:00

52 lines
1.5 KiB
TypeScript

import { useAuth } from "../../auth/AuthContext";
import { useRelayStatus } from "../../lib/useRelayStatus";
import { Badge } from "../ui/Badge";
import { Button } from "../ui/Button";
import { LogoutIcon } from "./icons";
function roleTone(role: string | undefined): "accent" | "ok" | "neutral" {
if (role === "admin") return "accent";
if (role === "operator") return "ok";
return "neutral";
}
export function Topbar() {
const { user, logout } = useAuth();
const { live, checking } = useRelayStatus();
const relayClass = live ? "relay relay--live" : "relay relay--down";
const relayLabel = checking ? "probing" : live ? "live" : "offline";
return (
<header className="topbar">
<div
className={relayClass}
title="GuruConnect relay connection"
aria-label={`Relay ${relayLabel}`}
>
<span className="relay__pip" aria-hidden="true" />
<span>Relay</span>
<span className="relay__label mono">{relayLabel}</span>
</div>
<div className="topbar__spacer" />
<div className="topbar__user">
<div className="topbar__id">
<span className="topbar__username">{user?.username}</span>
</div>
<Badge tone={roleTone(user?.role)}>{user?.role ?? "—"}</Badge>
<Button
variant="ghost"
size="sm"
onClick={() => void logout()}
aria-label="Log out"
>
<LogoutIcon width={15} height={15} />
Logout
</Button>
</div>
</header>
);
}