"use client"; import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { HotkeyButton } from "@/components/ui/hotkey-button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { FolderPlus, FolderOpen, Rocket, ExternalLink, Check, Loader2, Link, Folder, } from "lucide-react"; import { starterTemplates, type StarterTemplate } from "@/lib/templates"; import { getElectronAPI } from "@/lib/electron"; import { getHttpApiClient } from "@/lib/http-api-client"; import { cn } from "@/lib/utils"; import { useFileBrowser } from "@/contexts/file-browser-context"; interface ValidationErrors { projectName?: boolean; workspaceDir?: boolean; templateSelection?: boolean; customUrl?: boolean; } interface NewProjectModalProps { open: boolean; onOpenChange: (open: boolean) => void; onCreateBlankProject: (projectName: string, parentDir: string) => Promise; onCreateFromTemplate: ( template: StarterTemplate, projectName: string, parentDir: string ) => Promise; onCreateFromCustomUrl: ( repoUrl: string, projectName: string, parentDir: string ) => Promise; isCreating: boolean; } export function NewProjectModal({ open, onOpenChange, onCreateBlankProject, onCreateFromTemplate, onCreateFromCustomUrl, isCreating, }: NewProjectModalProps) { const [activeTab, setActiveTab] = useState<"blank" | "template">("blank"); const [projectName, setProjectName] = useState(""); const [workspaceDir, setWorkspaceDir] = useState(""); const [isLoadingWorkspace, setIsLoadingWorkspace] = useState(false); const [selectedTemplate, setSelectedTemplate] = useState(null); const [useCustomUrl, setUseCustomUrl] = useState(false); const [customUrl, setCustomUrl] = useState(""); const [errors, setErrors] = useState({}); const { openFileBrowser } = useFileBrowser(); // Fetch workspace directory when modal opens useEffect(() => { if (open) { setIsLoadingWorkspace(true); const httpClient = getHttpApiClient(); httpClient.workspace.getConfig() .then((result) => { if (result.success && result.workspaceDir) { setWorkspaceDir(result.workspaceDir); } }) .catch((error) => { console.error("Failed to get workspace config:", error); }) .finally(() => { setIsLoadingWorkspace(false); }); } }, [open]); // Reset form when modal closes useEffect(() => { if (!open) { setProjectName(""); setSelectedTemplate(null); setUseCustomUrl(false); setCustomUrl(""); setActiveTab("blank"); setErrors({}); } }, [open]); // Clear specific errors when user fixes them useEffect(() => { if (projectName && errors.projectName) { setErrors((prev) => ({ ...prev, projectName: false })); } }, [projectName, errors.projectName]); useEffect(() => { if ((selectedTemplate || (useCustomUrl && customUrl)) && errors.templateSelection) { setErrors((prev) => ({ ...prev, templateSelection: false })); } }, [selectedTemplate, useCustomUrl, customUrl, errors.templateSelection]); useEffect(() => { if (customUrl && errors.customUrl) { setErrors((prev) => ({ ...prev, customUrl: false })); } }, [customUrl, errors.customUrl]); const validateAndCreate = async () => { const newErrors: ValidationErrors = {}; // Check project name if (!projectName.trim()) { newErrors.projectName = true; } // Check workspace dir if (!workspaceDir) { newErrors.workspaceDir = true; } // Check template selection (only for template tab) if (activeTab === "template") { if (useCustomUrl) { if (!customUrl.trim()) { newErrors.customUrl = true; } } else if (!selectedTemplate) { newErrors.templateSelection = true; } } // If there are errors, show them and don't proceed if (Object.values(newErrors).some(Boolean)) { setErrors(newErrors); return; } // Clear errors and proceed setErrors({}); if (activeTab === "blank") { await onCreateBlankProject(projectName, workspaceDir); } else if (useCustomUrl && customUrl) { await onCreateFromCustomUrl(customUrl, projectName, workspaceDir); } else if (selectedTemplate) { await onCreateFromTemplate(selectedTemplate, projectName, workspaceDir); } }; const handleOpenRepo = (url: string) => { const api = getElectronAPI(); api.openExternalLink(url); }; const handleSelectTemplate = (template: StarterTemplate) => { setSelectedTemplate(template); setUseCustomUrl(false); setCustomUrl(""); }; const handleToggleCustomUrl = () => { setUseCustomUrl(!useCustomUrl); if (!useCustomUrl) { setSelectedTemplate(null); } }; const handleBrowseDirectory = async () => { const selectedPath = await openFileBrowser({ title: "Select Base Project Directory", description: "Choose the parent directory where your project will be created", }); if (selectedPath) { setWorkspaceDir(selectedPath); // Clear any workspace error when a valid directory is selected if (errors.workspaceDir) { setErrors((prev) => ({ ...prev, workspaceDir: false })); } } }; const projectPath = workspaceDir && projectName ? `${workspaceDir}/${projectName}` : ""; return ( Create New Project Start with a blank project or choose from a starter template. {/* Project Name Input - Always visible at top */}
setProjectName(e.target.value)} className={cn( "bg-input text-foreground placeholder:text-muted-foreground", errors.projectName ? "border-red-500 focus:border-red-500 focus:ring-red-500/20" : "border-border" )} data-testid="project-name-input" autoFocus /> {errors.projectName && (

Project name is required

)}
{/* Workspace Directory Display */}
{isLoadingWorkspace ? ( "Loading workspace..." ) : workspaceDir ? ( <>Will be created at: {projectPath || "..."} ) : ( No workspace configured )}
setActiveTab(v as "blank" | "template")} className="flex-1 flex flex-col overflow-hidden" > Blank Project Starter Kit

Create an empty project with the standard .automaker directory structure. Perfect for starting from scratch or importing an existing codebase.

{/* Error message for template selection */} {errors.templateSelection && (

Please select a template or enter a custom GitHub URL

)} {/* Preset Templates */}
{starterTemplates.map((template) => (
handleSelectTemplate(template)} data-testid={`template-${template.id}`} >

{template.name}

{selectedTemplate?.id === template.id && !useCustomUrl && ( )}

{template.description}

{/* Tech Stack */}
{template.techStack.slice(0, 6).map((tech) => ( {tech} ))} {template.techStack.length > 6 && ( +{template.techStack.length - 6} more )}
{/* Key Features */}
Features: {template.features.slice(0, 3).join(" · ")} {template.features.length > 3 && ` · +${template.features.length - 3} more`}
))} {/* Custom URL Option */}

Custom GitHub URL

{useCustomUrl && }

Clone any public GitHub repository as a starting point.

{useCustomUrl && (
e.stopPropagation()} className="space-y-1"> setCustomUrl(e.target.value)} className={cn( "bg-input text-foreground placeholder:text-muted-foreground", errors.customUrl ? "border-red-500 focus:border-red-500 focus:ring-red-500/20" : "border-border" )} data-testid="custom-url-input" /> {errors.customUrl && (

GitHub URL is required

)}
)}
{isCreating ? ( <> {activeTab === "template" ? "Cloning..." : "Creating..."} ) : ( <>Create Project )}
); }