- Read-only summary card with hardware, mode, provider, root dir rows - SummaryRow helper component with optional mono styling - Start chatting CTA with spinner and disabled state - 6 unit tests covering rendering, empty root dir, error, click, loading
129 lines
3.6 KiB
TypeScript
129 lines
3.6 KiB
TypeScript
// [nexus] Summary screen for onboarding wizard step 5 — read-only review before starting
|
|
import type { HardwareInfo, HardwareTier, NexusMode } from "@/api/hardware";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
interface OnboardingSummaryStepProps {
|
|
hardwareInfo: HardwareInfo | undefined;
|
|
selectedMode: NexusMode;
|
|
providerLabel: string;
|
|
rootDir: string;
|
|
loading: boolean;
|
|
error: string | null;
|
|
onStartChat: () => void;
|
|
onBack: () => void;
|
|
}
|
|
|
|
interface SummaryRowProps {
|
|
label: string;
|
|
value: string;
|
|
mono?: boolean;
|
|
}
|
|
|
|
function SummaryRow({ label, value, mono }: SummaryRowProps) {
|
|
return (
|
|
<div className="flex items-start justify-between gap-4">
|
|
<span className="text-sm text-muted-foreground shrink-0">{label}</span>
|
|
<span className={mono ? "font-mono text-sm text-right" : "text-sm text-right"}>{value}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const HARDWARE_TIER_LABELS: Record<HardwareTier, string> = {
|
|
gpu: "GPU",
|
|
apple_silicon: "Apple Silicon",
|
|
cpu_only: "CPU Only",
|
|
};
|
|
|
|
const MODE_LABELS: Record<NexusMode, string> = {
|
|
personal_ai: "Personal AI Assistant",
|
|
project_builder: "Project Builder",
|
|
both: "Both (recommended)",
|
|
};
|
|
|
|
export function OnboardingSummaryStep({
|
|
hardwareInfo,
|
|
selectedMode,
|
|
providerLabel,
|
|
rootDir,
|
|
loading,
|
|
error,
|
|
onStartChat,
|
|
onBack,
|
|
}: OnboardingSummaryStepProps) {
|
|
const hardwareLabel = hardwareInfo
|
|
? (HARDWARE_TIER_LABELS[hardwareInfo.hardwareTier] ?? "Unknown")
|
|
: "Unknown";
|
|
|
|
const modeLabel = MODE_LABELS[selectedMode];
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
<div className="flex flex-col gap-2 text-center">
|
|
<h1 className="text-2xl font-semibold tracking-tight">Ready to go</h1>
|
|
<p className="text-sm text-muted-foreground">Review your setup before starting.</p>
|
|
</div>
|
|
|
|
{/* Summary card */}
|
|
<div className="rounded-lg border border-border p-4 flex flex-col gap-3">
|
|
<SummaryRow label="Hardware" value={hardwareLabel} />
|
|
<SummaryRow label="Mode" value={modeLabel} />
|
|
<SummaryRow label="Provider" value={providerLabel} />
|
|
{rootDir && <SummaryRow label="Root directory" value={rootDir} mono />}
|
|
</div>
|
|
|
|
{/* Error message */}
|
|
{error && (
|
|
<p className="text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2">
|
|
{error}
|
|
</p>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
<div className="flex flex-col gap-2">
|
|
<Button
|
|
type="button"
|
|
onClick={onStartChat}
|
|
disabled={loading}
|
|
className="w-full"
|
|
>
|
|
{loading ? (
|
|
<span className="flex items-center gap-2">
|
|
<svg
|
|
className="h-4 w-4 animate-spin"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
aria-hidden="true"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
/>
|
|
</svg>
|
|
Setting up...
|
|
</span>
|
|
) : (
|
|
"Start chatting"
|
|
)}
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
onClick={onBack}
|
|
disabled={loading}
|
|
className="w-full"
|
|
>
|
|
Back
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|