import { type ReactNode } from "react"; import { CopyIcon, ExternalLinkIcon, LaptopIcon, TerminalIcon } from "lucide-react"; import { cn } from "../lib/cn"; import type { MachineState } from "../types/machines"; import { ConnectionStatusPill } from "./machine-status-pill"; import { useOpenGeniText } from "../messages/runtime"; /** The poll state of an in-flight device-flow enrollment. */ export type DeviceFlowPhase = "pending" | "authorized" | "denied" | "expired" | "disabled"; export type EnrollmentDeviceFlowProps = { /** The short user code the agent printed (shown big for the human to confirm). */ userCode: string; /** Where the user approves — the same-origin device page. */ verificationUri: string; /** The complete URI (code pre-filled) for a one-click / QR open. */ verificationUriComplete?: string | undefined; /** The install one-liner the user ran (echoed for context). Optional. */ installCommand?: string | undefined; phase?: DeviceFlowPhase | undefined; /** Seconds remaining before the code expires (drives a quiet countdown). */ expiresInSeconds?: number | undefined; onCopyCode?: (() => void) | undefined; onCopyInstall?: (() => void) | undefined; onOpenVerification?: (() => void) | undefined; className?: string | undefined; }; const PHASE_TO_STATE: Record = { pending: "enrolling", authorized: "online", denied: "offline", expired: "offline", disabled: "offline", }; function PhaseLabel({ phase }: { phase: DeviceFlowPhase }): ReactNode { const t = useOpenGeniText(); const text: Record = { pending: t("enrollmentDeviceFlow.waitingForApproval"), authorized: t("enrollmentDeviceFlow.connected"), denied: t("enrollmentDeviceFlow.denied"), expired: t("enrollmentDeviceFlow.codeExpired"), disabled: t("enrollmentDeviceFlow.enrollmentDisabled"), }; return {text[phase]}; } /** * The IN-SESSION device-flow panel: the agent (after the install one-liner) prints * a short `userCode` + a `verificationUri`; this surfaces them so the user can open * the approve page and confirm the code. Polls in the caller; this renders the * pending → authorized/denied/expired progression with a connection pill. This is * the FIRST step of the IA flow (device-flow → Machines list → attach/swap). */ export function EnrollmentDeviceFlow({ userCode, verificationUri, verificationUriComplete, installCommand, phase = "pending", expiresInSeconds, onCopyCode, onCopyInstall, onOpenVerification, className, }: EnrollmentDeviceFlowProps) { const t = useOpenGeniText(); const href = verificationUriComplete ?? verificationUri; // Before the one-liner runs the caller passes a placeholder (dashes) for the // code — surface that as an explicit waiting state rather than dashes that look // like a real code to type. const codeIsPlaceholder = !/[A-Za-z0-9]/.test(userCode); const awaitingCode = phase === "pending" && codeIsPlaceholder; return (

{t("enrollmentDeviceFlow.connectAMachine")}

) as unknown as string} size="sm" />
{installCommand ? (
{t("enrollmentDeviceFlow.n1RunOnTheMachine")}
{installCommand}
) : null}
{installCommand ? t("enrollmentDeviceFlow.n2ApproveTheMachine") : t("enrollmentDeviceFlow.approveTheMachine")}
{awaitingCode ? ( {t("enrollmentDeviceFlow.runTheCommandAboveYour")} ) : ( {userCode} )}
{t("enrollmentDeviceFlow.openApprovalPage")}

{awaitingCode ? ( <> {t("enrollmentDeviceFlow.theOneLinerPrintsA")}{" "} {verificationUri}{" "} {t("enrollmentDeviceFlow.andConfirmTheCodeShown")} ) : ( <> {t("enrollmentDeviceFlow.confirmCode")}{" "} {userCode}{" "} {t("enrollmentDeviceFlow.theOnePrintedByThe")}{" "} {verificationUri}. {typeof expiresInSeconds === "number" ? ( <> {" "} {t("enrollmentDeviceFlow.expiresIn")}{" "} {Math.max(0, Math.round(expiresInSeconds / 60))} {t("enrollmentDeviceFlow.min")} ) : null} )}

); }