import { useRef, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { PROJECT_COLORS, type ProjectRepository } from "@paperclipai/shared"; import { X } from "lucide-react"; import { useDialog } from "../context/DialogContext"; import { useCompany } from "../context/CompanyContext"; import { projectsApi } from "../api/projects"; import { describeError } from "../api/errors"; import { queryKeys } from "../lib/queryKeys"; import { trackRecentProject } from "../lib/recent-projects"; import { ProjectTile } from "./ProjectTile"; import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"; import { QueryErrorState, useQueryView } from "./QueryView"; import { NativeSelect } from "./ui/select"; import { Button } from "./ui/button"; import { ProjectRepositoryInput, repositoryOptionsKey } from "./ProjectRepositoryInput"; import { ConnectionSetupFlow } from "@/features/connections/ConnectionSetupFlow"; export function NewProjectDialog() { const { newProjectOpen, closeNewProject } = useDialog(); const { selectedCompanyId } = useCompany(); return selectedCompanyId && newProjectOpen ? : null; } export function NewProjectForm({ companyId, onClose }: { companyId: string; onClose: () => void }) { const client = useQueryClient(); const [name, setName] = useState(""); const [color] = useState(() => PROJECT_COLORS[Math.floor(Math.random() * PROJECT_COLORS.length)]!); const [repos, setRepos] = useState([]); const [connecting, setConnecting] = useState(false); const input = useRef(null); const [selectedEnvironmentId, setSelectedEnvironmentId] = useState(null); const environments = useQuery({ queryKey: queryKeys.projects.environments(companyId), queryFn: () => projectsApi.environmentOptions(companyId), gcTime: 0 }); const environmentView = useQueryView(environments); const options = environmentView.data; const environmentId = selectedEnvironmentId ?? options?.defaultEnvironmentId; const selectedGroup = options?.groups.find(group => group.environments.some(environment => environment.id === environmentId)); const environmentReady = Boolean(environmentView.kind !== "error" && options && (!options.error || selectedGroup) && (!options.groups.length || selectedGroup)); const create = useMutation({ mutationFn: () => projectsApi.create(companyId, { name: name.trim(), color, status: "planned", repositoryIds: repos.map((repo) => repo.id), ...(selectedGroup ? { environmentId, expectedStorageProviderPluginId: selectedGroup.storageProviderPluginId } : {}) }), onSuccess: (project) => { trackRecentProject(project.id, companyId); void client.invalidateQueries({ queryKey: queryKeys.projects.all(companyId) }); onClose(); }, }); return { if (!open && !create.isPending) onClose(); }}> { event.preventDefault(); input.current?.focus(); }}> {connecting ?
Connect GitHub setConnecting(false)} onComplete={() => { void client.invalidateQueries({ queryKey: repositoryOptionsKey(companyId) }); setConnecting(false); }} />
:
{ event.preventDefault(); if (name.trim() && environmentReady && !create.isPending) create.mutate(); }}>
Create project
setName(event.target.value)} placeholder="Project name" required className="h-10 w-full min-w-0 border-0 bg-transparent text-base outline-none placeholder:text-muted-foreground md:text-sm" />
{options && (options.groups.length > 1 || (options.error && options.groups.length > 0)) &&
setSelectedEnvironmentId(event.target.value)}> {!selectedGroup && } {options.groups.map(group => )}

These environments can share this project's working files. The storage choice stays with the project.

} {(environmentView.kind === "loading" || environmentView.kind === "reconnecting") &&

Loading environments…

} {environmentView.kind === "error" && } {options?.error && !selectedGroup &&

{options.error}

} setConnecting(true)} disabled={create.isPending} />
{create.status === "error" &&

{describeError(create.error).body}

}
}
; }