/** * New Project Modal Component * * Multi-step modal for creating new projects: * 1. Enter project name * 2. Select project folder * 3. Choose spec method (Claude or manual) * 4a. If Claude: Show SpecCreationChat * 4b. If manual: Create project and close */ import { useState } from 'react' import { X, Bot, FileEdit, ArrowRight, ArrowLeft, Loader2, CheckCircle2, Folder } from 'lucide-react' import { useCreateProject } from '../hooks/useProjects' import { SpecCreationChat } from './SpecCreationChat' import { FolderBrowser } from './FolderBrowser' import { startAgent } from '../lib/api' type InitializerStatus = 'idle' | 'starting' | 'error' type Step = 'name' | 'folder' | 'method' | 'chat' | 'complete' type SpecMethod = 'claude' | 'manual' interface NewProjectModalProps { isOpen: boolean onClose: () => void onProjectCreated: (projectName: string) => void onStepChange?: (step: Step) => void } export function NewProjectModal({ isOpen, onClose, onProjectCreated, onStepChange, }: NewProjectModalProps) { const [step, setStep] = useState('name') const [projectName, setProjectName] = useState('') const [projectPath, setProjectPath] = useState(null) const [_specMethod, setSpecMethod] = useState(null) const [error, setError] = useState(null) const [initializerStatus, setInitializerStatus] = useState('idle') const [initializerError, setInitializerError] = useState(null) const [yoloModeSelected, setYoloModeSelected] = useState(false) // Suppress unused variable warning - specMethod may be used in future void _specMethod const createProject = useCreateProject() // Wrapper to notify parent of step changes const changeStep = (newStep: Step) => { setStep(newStep) onStepChange?.(newStep) } if (!isOpen) return null const handleNameSubmit = (e: React.FormEvent) => { e.preventDefault() const trimmed = projectName.trim() if (!trimmed) { setError('Please enter a project name') return } if (!/^[a-zA-Z0-9_-]+$/.test(trimmed)) { setError('Project name can only contain letters, numbers, hyphens, and underscores') return } setError(null) changeStep('folder') } const handleFolderSelect = (path: string) => { setProjectPath(path) // Use selected path directly - no subfolder creation changeStep('method') } const handleFolderCancel = () => { changeStep('name') } const handleMethodSelect = async (method: SpecMethod) => { setSpecMethod(method) if (!projectPath) { setError('Please select a project folder first') changeStep('folder') return } if (method === 'manual') { // Create project immediately with manual method try { const project = await createProject.mutateAsync({ name: projectName.trim(), path: projectPath, specMethod: 'manual', }) changeStep('complete') setTimeout(() => { onProjectCreated(project.name) handleClose() }, 1500) } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Failed to create project') } } else { // Create project then show chat try { await createProject.mutateAsync({ name: projectName.trim(), path: projectPath, specMethod: 'claude', }) changeStep('chat') } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Failed to create project') } } } const handleSpecComplete = async (_specPath: string, yoloMode: boolean = false) => { // Save yoloMode for retry setYoloModeSelected(yoloMode) // Auto-start the initializer agent setInitializerStatus('starting') try { await startAgent(projectName.trim(), { yoloMode }) // Success - navigate to project changeStep('complete') setTimeout(() => { onProjectCreated(projectName.trim()) handleClose() }, 1500) } catch (err) { setInitializerStatus('error') setInitializerError(err instanceof Error ? err.message : 'Failed to start agent') } } const handleRetryInitializer = () => { setInitializerError(null) setInitializerStatus('idle') handleSpecComplete('', yoloModeSelected) } const handleChatCancel = () => { // Go back to method selection but keep the project changeStep('method') setSpecMethod(null) } const handleExitToProject = () => { // Exit chat and go directly to project - user can start agent manually onProjectCreated(projectName.trim()) handleClose() } const handleClose = () => { changeStep('name') setProjectName('') setProjectPath(null) setSpecMethod(null) setError(null) setInitializerStatus('idle') setInitializerError(null) setYoloModeSelected(false) onClose() } const handleBack = () => { if (step === 'method') { changeStep('folder') setSpecMethod(null) } else if (step === 'folder') { changeStep('name') setProjectPath(null) } } // Full-screen chat view if (step === 'chat') { return (
) } // Folder step uses larger modal if (step === 'folder') { return (
e.stopPropagation()} > {/* Header */}

Select Project Location

Select the folder to use for project {projectName}. Create a new folder or choose an existing one.

{/* Folder Browser */}
) } return (
e.stopPropagation()} > {/* Header */}

{step === 'name' && 'Create New Project'} {step === 'method' && 'Choose Setup Method'} {step === 'complete' && 'Project Created!'}

{/* Content */}
{/* Step 1: Project Name */} {step === 'name' && (
setProjectName(e.target.value)} placeholder="my-awesome-app" className="neo-input" pattern="^[a-zA-Z0-9_-]+$" autoFocus />

Use letters, numbers, hyphens, and underscores only.

{error && (
{error}
)}
)} {/* Step 2: Spec Method */} {step === 'method' && (

How would you like to define your project?

{/* Claude option */} {/* Manual option */}
{error && (
{error}
)} {createProject.isPending && (
Creating project...
)}
)} {/* Step 3: Complete */} {step === 'complete' && (

{projectName}

Your project has been created successfully!

Redirecting...
)}
) }