import { useState, useCallback, useMemo } from "react"; import { Folder, Check, AlertCircle, Loader2 } from "lucide-react"; import { validateProjectPath, validateProjectName, suggestProjectName } from "../utils/projectDetection"; import type { ProjectCreateInput } from "../api"; export interface SetupProjectFormProps { /** Called when the form is submitted with valid data */ onSubmit: (input: ProjectCreateInput) => void; /** Called when validation state changes */ onValidationChange?: (isValid: boolean) => void; /** Existing projects for duplicate checking */ existingProjects?: { name: string; path: string }[]; /** Loading state while submitting */ isSubmitting?: boolean; /** Optional default path value */ defaultPath?: string; } /** * SetupProjectForm - Manual project registration form * * Form for manually registering a new project with: * - Path input with validation * - Name input with auto-suggestion * - Isolation mode selector * - Real-time validation */ export function SetupProjectForm({ onSubmit, onValidationChange, existingProjects = [], isSubmitting = false, defaultPath = "", }: SetupProjectFormProps) { const [path, setPath] = useState(defaultPath); const [name, setName] = useState(""); const [isolationMode, setIsolationMode] = useState<"in-process" | "child-process">("in-process"); const [pathError, setPathError] = useState(null); const [nameError, setNameError] = useState(null); const [touched, setTouched] = useState({ path: false, name: false }); // Validate path const validatePath = useCallback((value: string) => { const result = validateProjectPath(value); setPathError(result.valid ? null : (result.error ?? null)); return result.valid; }, []); // Validate name const validateNameField = useCallback((value: string) => { const result = validateProjectName(value, existingProjects); setNameError(result.valid ? null : (result.error ?? null)); return result.valid; }, [existingProjects]); // Auto-suggest name from path const handlePathChange = useCallback((value: string) => { setPath(value); setTouched((prev) => ({ ...prev, path: true })); const isValid = validatePath(value); // Auto-suggest name if name is empty and path is valid if (isValid && !name && value) { const suggested = suggestProjectName(value); if (suggested) { setName(suggested); } } }, [name, validatePath]); const handleNameChange = useCallback((value: string) => { setName(value); setTouched((prev) => ({ ...prev, name: true })); validateNameField(value); }, [validateNameField]); // Check overall form validity const isFormValid = useMemo(() => { const pathResult = validateProjectPath(path); const nameResult = validateProjectName(name, existingProjects); return pathResult.valid && nameResult.valid && !isSubmitting; }, [path, name, existingProjects, isSubmitting]); // Report validation state to parent useMemo(() => { onValidationChange?.(isFormValid); }, [isFormValid, onValidationChange]); const handleSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); const isPathValid = validatePath(path); const isNameValid = validateNameField(name); if (isPathValid && isNameValid) { onSubmit({ name, path, isolationMode, }); } }, [path, name, isolationMode, onSubmit, validatePath, validateNameField]); return (
{/* Path input */}
handlePathChange(e.target.value)} onBlur={() => { setTouched((prev) => ({ ...prev, path: true })); validatePath(path); }} placeholder="/path/to/your/project" disabled={isSubmitting} />
{pathError && touched.path && (
{pathError}
)}
Enter the absolute path to your project directory
{/* Name input */}
handleNameChange(e.target.value)} onBlur={() => { setTouched((prev) => ({ ...prev, name: true })); validateNameField(name); }} placeholder="my-project" disabled={isSubmitting} /> {name && !nameError && touched.name && ( )}
{nameError && touched.name && (
{nameError}
)}
Use letters, numbers, hyphens, and underscores only
{/* Isolation mode */}
{/* Submit button */}
); }