import { useState, useCallback } from "react"; import { X, Loader2, Sparkles, CheckCircle } from "lucide-react"; import type { ProjectInfo, ProjectCreateInput } from "../api"; import { registerProject } from "../api"; import { DirectoryPicker } from "./DirectoryPicker"; import { suggestProjectName } from "../utils/projectDetection"; import { useNodes } from "../hooks/useNodes"; export interface SetupWizardModalProps { /** Called when a single project is registered */ onProjectRegistered: (project: ProjectInfo) => void; /** Called when wizard is closed (completed or cancelled) */ onClose?: () => void; } type WizardStep = "manual" | "complete"; interface WizardState { step: WizardStep; manualPath: string; manualName: string; manualIsolationMode: "in-process" | "child-process"; manualNodeId: string; isRegistering: boolean; error: string | null; } /** * Setup wizard for first-run project registration. * * Provides a polished onboarding experience with a directory picker * for selecting the project directory and auto-name suggestion. */ export function SetupWizardModal({ onProjectRegistered, onClose, }: SetupWizardModalProps) { const [isOpen, setIsOpen] = useState(true); const [state, setState] = useState({ step: "manual", manualPath: "", manualName: "", manualIsolationMode: "in-process", manualNodeId: "", isRegistering: false, error: null, }); const { nodes, loading: nodesLoading } = useNodes(); const localNodeId = nodes.find((n) => n.type === "local")?.id; const handleClose = useCallback(() => { setIsOpen(false); onClose?.(); }, [onClose]); const handlePathChange = useCallback((path: string) => { setState((prev) => { const updates: Partial = { manualPath: path }; // Auto-suggest name when path changes and name is empty or was previously auto-suggested if (path && (!prev.manualName || prev.manualName === suggestProjectName(prev.manualPath))) { updates.manualName = suggestProjectName(path); } return { ...prev, ...updates }; }); }, []); const handleManualRegister = useCallback(async () => { if (!state.manualPath || !state.manualName) return; setState((prev) => ({ ...prev, isRegistering: true, error: null })); try { const input: ProjectCreateInput = { name: state.manualName, path: state.manualPath, isolationMode: state.manualIsolationMode, nodeId: state.manualNodeId || undefined, }; const result = await registerProject(input); onProjectRegistered(result); setState((prev) => ({ ...prev, step: "complete", isRegistering: false, })); } catch (err) { setState((prev) => ({ ...prev, isRegistering: false, error: err instanceof Error ? err.message : "Failed to register project", })); } }, [state.manualPath, state.manualName, state.manualIsolationMode, state.manualNodeId, onProjectRegistered]); if (!isOpen) return null; return (
{/* Header */}

{state.step === "manual" && "Welcome to Fusion"} {state.step === "complete" && "Setup Complete!"}

{state.step !== "complete" && ( )}
{/* Content */}
{/* Manual Step */} {state.step === "manual" && (

Let's set up your first project. Browse to your project directory or type the path manually.

{/* Node selector */}
Runtime Node

Select or type the absolute path to your project

setState((prev) => ({ ...prev, manualName: e.target.value })) } placeholder="my-project" />
{state.error && (
{state.error}
)}
)} {/* Complete Step */} {state.step === "complete" && (

All Set!

Your project has been registered successfully.

You can add more projects anytime from the project overview.

)}
{/* Footer */}
{state.step === "manual" && ( )} {state.step === "complete" && ( )}
); }