import { useState, useCallback, useEffect } from "react"; import { X, ChevronRight, ChevronLeft, Folder, Check, Loader2, AlertCircle } from "lucide-react"; import type { ProjectInfo, ProjectCreateInput } from "../api"; export interface SetupWizardProps { isOpen: boolean; onClose: () => void; onProjectCreated: (project: ProjectInfo) => void; onRegisterProject?: (input: ProjectCreateInput) => Promise; } type WizardStep = "directory" | "name" | "isolation" | "validation" | "summary"; interface WizardState { step: WizardStep; directory: string; name: string; isolationMode: "in-process" | "child-process"; isValidating: boolean; validationError: string | null; hasFusionDir: boolean | null; isCreating: boolean; createError: string | null; } const STEP_ORDER: WizardStep[] = ["directory", "name", "isolation", "validation", "summary"]; function getStepIndex(step: WizardStep): number { return STEP_ORDER.indexOf(step); } function isLastStep(step: WizardStep): boolean { return getStepIndex(step) === STEP_ORDER.length - 1; } function isFirstStep(step: WizardStep): boolean { return getStepIndex(step) === 0; } export function SetupWizard({ isOpen, onClose, onProjectCreated, onRegisterProject }: SetupWizardProps) { const [state, setState] = useState({ step: "directory", directory: "", name: "", isolationMode: "in-process", isValidating: false, validationError: null, hasFusionDir: null, isCreating: false, createError: null, }); // Reset state when modal opens useEffect(() => { if (isOpen) { setState({ step: "directory", directory: "", name: "", isolationMode: "in-process", isValidating: false, validationError: null, hasFusionDir: null, isCreating: false, createError: null, }); } }, [isOpen]); // Auto-suggest name from directory useEffect(() => { if (state.directory && !state.name) { const basename = state.directory.split("/").pop() || state.directory.split("\\").pop() || ""; setState((prev) => ({ ...prev, name: basename })); } }, [state.directory, state.name]); const handleNext = useCallback(() => { const currentIndex = getStepIndex(state.step); if (currentIndex < STEP_ORDER.length - 1) { setState((prev) => ({ ...prev, step: STEP_ORDER[currentIndex + 1], validationError: null, createError: null, })); } }, [state.step]); const handleBack = useCallback(() => { const currentIndex = getStepIndex(state.step); if (currentIndex > 0) { setState((prev) => ({ ...prev, step: STEP_ORDER[currentIndex - 1], validationError: null, createError: null, })); } }, [state.step]); const handleValidate = useCallback(async () => { setState((prev) => ({ ...prev, isValidating: true, validationError: null })); try { // Check if directory exists and has .fusion/ directory // In a real implementation, this would call an API endpoint // For now, we simulate the check await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate validation - assume valid for now const hasFusionDir = true; // Would be determined by API call setState((prev) => ({ ...prev, isValidating: false, hasFusionDir, step: "summary", })); } catch (err: any) { setState((prev) => ({ ...prev, isValidating: false, validationError: err.message || "Validation failed", })); } }, []); const handleCreate = useCallback(async () => { if (!onRegisterProject) { setState((prev) => ({ ...prev, createError: "Project registration not available" })); return; } setState((prev) => ({ ...prev, isCreating: true, createError: null })); try { const input: ProjectCreateInput = { name: state.name, path: state.directory, isolationMode: state.isolationMode, }; const project = await onRegisterProject(input); onProjectCreated(project); onClose(); } catch (err: any) { setState((prev) => ({ ...prev, isCreating: false, createError: err.message || "Failed to create project", })); } }, [onRegisterProject, state.name, state.directory, state.isolationMode, onProjectCreated, onClose]); const canProceed = () => { switch (state.step) { case "directory": return state.directory.trim().length > 0; case "name": return state.name.trim().length > 0; case "isolation": return true; case "validation": return !state.isValidating; case "summary": return !state.isCreating; default: return false; } }; if (!isOpen) return null; return (
e.stopPropagation()}>

Add New Project

{STEP_ORDER.map((step, index) => (
{index + 1} {step === "directory" && "Directory"} {step === "name" && "Name"} {step === "isolation" && "Mode"} {step === "validation" && "Validate"} {step === "summary" && "Confirm"}
))}
{/* Step 1: Directory Selection */} {state.step === "directory" && (

Select Project Directory

Enter the absolute path to your project directory. This should be the root folder containing your project files.

setState((prev) => ({ ...prev, directory: e.target.value })) } placeholder="/path/to/your/project" autoFocus />
The directory must contain a .fusion/ folder. If it doesn't exist, you can initialize it in the next step.
)} {/* Step 2: Project Name */} {state.step === "name" && (

Project Name

Give your project a display name. This will be shown in the dashboard.

setState((prev) => ({ ...prev, name: e.target.value }))} placeholder="My Project" autoFocus />
Suggested from directory name. You can change it if needed.
)} {/* Step 3: Isolation Mode */} {state.step === "isolation" && (

Execution Mode

Choose how tasks should be executed for this project.

)} {/* Step 4: Validation */} {state.step === "validation" && (

Validation

We're checking the project directory and preparing it for use.

{state.isValidating ? (
Validating project directory...
) : state.validationError ? (
{state.validationError}
) : (
Project directory is valid!
)}
)} {/* Step 5: Summary */} {state.step === "summary" && (

Summary

Review your project settings before creating.

Name: {state.name}
Directory: {state.directory}
Execution Mode: {state.isolationMode === "in-process" ? "In-Process" : "Child Process (Isolated)"}
{state.createError && (
{state.createError}
)}
)}
{!isFirstStep(state.step) && ( )}
{state.step === "validation" ? ( ) : isLastStep(state.step) ? ( ) : ( )}
); }