148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
import { motion } from "framer-motion";
|
|
import {
|
|
CheckCircle,
|
|
XCircle,
|
|
ArrowRight,
|
|
ArrowCounterClockwise,
|
|
Terminal,
|
|
} from "@phosphor-icons/react";
|
|
import { ActionButton } from "../ui";
|
|
import type { TerminalCommandKey } from "@shared/contracts";
|
|
|
|
interface NodeCheckStepProps {
|
|
nodeStatus: { installed: boolean; version: string | null } | null;
|
|
checking: boolean;
|
|
onCheck: () => void;
|
|
onInstall: (commandKey: TerminalCommandKey) => void;
|
|
onContinue: () => void;
|
|
onBack: () => void;
|
|
t: (key: string) => string;
|
|
}
|
|
|
|
const INSTALL_COMMANDS: Record<string, { label: string; code: string; commandKey: TerminalCommandKey }> = {
|
|
darwin: {
|
|
label: "macOS",
|
|
code: "brew install node@22",
|
|
commandKey: "install_node_darwin",
|
|
},
|
|
win32: {
|
|
label: "Windows",
|
|
code: "winget install -e --id OpenJS.NodeJS.LTS",
|
|
commandKey: "install_node_win32",
|
|
},
|
|
linux: {
|
|
label: "Linux",
|
|
code: `# Download and install nvm:
|
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
|
|
|
|
# in lieu of restarting the shell
|
|
\\. "$HOME/.nvm/nvm.sh"
|
|
|
|
# Download and install Node.js:
|
|
nvm install 22
|
|
|
|
# Verify the Node.js version:
|
|
node -v`,
|
|
commandKey: "install_node_linux",
|
|
},
|
|
};
|
|
|
|
export function NodeCheckStep({
|
|
nodeStatus,
|
|
checking,
|
|
onCheck,
|
|
onInstall,
|
|
onContinue,
|
|
onBack,
|
|
t,
|
|
}: NodeCheckStepProps) {
|
|
const platform = window.agentCompany.platform;
|
|
const platformInfo = INSTALL_COMMANDS[platform] ?? INSTALL_COMMANDS.linux;
|
|
|
|
return (
|
|
<motion.div
|
|
key="nodecheck"
|
|
initial={{ opacity: 0, y: 18 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -18 }}
|
|
transition={{ duration: 0.32, ease: [0.16, 1, 0.3, 1] }}
|
|
className="bg-card border border-border shadow-sm w-full rounded-lg p-7"
|
|
>
|
|
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
|
{t("onboarding.stepLabel1")} 1 / 5 — {t("onboarding.nodeCheckStep")}
|
|
</div>
|
|
<div className="mb-1 text-[18px] font-semibold tracking-[-0.01em] text-foreground">
|
|
{t("onboarding.nodeCheckTitle")}
|
|
</div>
|
|
<div className="mb-6 h-px bg-[color:var(--border)]" />
|
|
|
|
{/* Status */}
|
|
<div className="mb-5 flex items-center gap-2.5 text-[13px]">
|
|
{checking ? (
|
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-muted-foreground border-t-primary" />
|
|
) : nodeStatus?.installed ? (
|
|
<CheckCircle size={18} weight="fill" className="shrink-0 text-success" />
|
|
) : nodeStatus ? (
|
|
<XCircle size={18} weight="fill" className="shrink-0 text-destructive" />
|
|
) : null}
|
|
|
|
{checking ? (
|
|
<span className="text-muted-foreground">{t("onboarding.providerChecking")}...</span>
|
|
) : nodeStatus?.installed ? (
|
|
<span className="font-medium text-success">
|
|
{t("onboarding.nodeInstalled").replace("{version}", nodeStatus.version ?? "")}
|
|
</span>
|
|
) : nodeStatus ? (
|
|
<span className="font-medium text-destructive">
|
|
{t("onboarding.nodeNotInstalled")}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
|
|
{/* Platform-specific install instructions (shown when not installed) */}
|
|
{nodeStatus && !nodeStatus.installed && (
|
|
<div className="mb-5 space-y-3">
|
|
<div className="text-[12px] font-medium text-muted-foreground">
|
|
{platformInfo.label}
|
|
</div>
|
|
<pre className="bg-muted rounded-lg border border-border px-4 py-3 font-mono text-[12px] leading-relaxed text-foreground whitespace-pre-wrap">
|
|
{platformInfo.code}
|
|
</pre>
|
|
<ActionButton
|
|
label={t("onboarding.installNodeTerminal")}
|
|
onClick={() => onInstall(platformInfo.commandKey)}
|
|
icon={Terminal}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Check Again */}
|
|
<div className="mb-6">
|
|
<ActionButton
|
|
label={t("onboarding.checkAgain")}
|
|
onClick={onCheck}
|
|
icon={ArrowCounterClockwise}
|
|
/>
|
|
</div>
|
|
|
|
{/* Bottom bar */}
|
|
<div className="flex items-center justify-between">
|
|
<button
|
|
type="button"
|
|
onClick={onBack}
|
|
className="text-[12px] font-medium text-muted-foreground transition hover:text-foreground"
|
|
>
|
|
{t("onboarding.back")}
|
|
</button>
|
|
<ActionButton
|
|
label={t("onboarding.continue")}
|
|
onClick={onContinue}
|
|
icon={ArrowRight}
|
|
tone="accent"
|
|
disabled={!nodeStatus?.installed}
|
|
/>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|