import { ExternalLink, Loader2, Play, ScrollText, Wrench } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { cn } from "../lib/utils"; import type { WorkspaceAccessState } from "../lib/workspace-access-state"; /** * Workspace access surface (PAP-17572). * * One card per workspace that names the state, the cause, and the single safe * next action. It replaces the previous behavior where a cloned workspace whose * database was missing still rendered a plain "Open" link and answered with * "invalid email or password". */ /** * Badge tones come from the semantic status token layer, so a theme change moves * these states with every other status surface. `ready` reuses the "done" tone * and `degraded` the "todo" (amber) tone; the icon variants are the contrast- * corrected pair the token layer already tunes per mode. */ type ActiveWorkspaceAccessState = Exclude; const STATE_BADGE_CLASSES: Record = { provisioning: "border-border text-muted-foreground", validating: "border-border text-muted-foreground", ready: "border-(--status-task-done) text-(--status-task-icon-done)", degraded: "border-(--status-task-todo) text-(--status-task-icon-todo)", repairing: "border-border text-muted-foreground", failed: "border-destructive/50 text-destructive", }; const STATE_LABELS: Record = { provisioning: "Provisioning", validating: "Validating clone", ready: "Ready", degraded: "Degraded", repairing: "Repairing", failed: "Failed", }; const ACTION_ICONS = { open: ExternalLink, start: Play, repair: Wrench, view_logs: ScrollText, wait: Loader2, } as const; export function WorkspaceAccessCard({ access, isBusy, onOpen, onStart, onRepair, onViewLogs, errorMessage, }: { access: WorkspaceAccessState; isBusy?: boolean; onOpen: () => void; onStart: () => void; onRepair: () => void; onViewLogs: () => void; errorMessage?: string | null; }) { const Icon = ACTION_ICONS[access.action.kind]; const isWaiting = access.action.kind === "wait"; const handlers: Record void> = { open: onOpen, start: onStart, repair: onRepair, view_logs: onViewLogs, wait: () => undefined, }; const SecondaryNoticeIcon = access.secondaryNotice ? ACTION_ICONS[access.secondaryNotice.action.kind] : null; return (
{access.title} {access.state !== "stopped" ? ( {(access.state === "repairing" || access.state === "provisioning") && ( )} {STATE_LABELS[access.state]} ) : null}
{access.description}
{access.state === "ready" && !access.handoffAvailable ? ( Signs in with the snapshot-local credentials captured when this clone was made. ) : null} {access.state === "ready" && access.handoffAvailable ? ( Uses a single-use login handoff — no password needed. ) : null}
{errorMessage ? (

{errorMessage}

) : null} {access.secondaryNotice && SecondaryNoticeIcon ? (

{access.secondaryNotice.title}

{access.secondaryNotice.description}

) : null}
); }