import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; import { api } from '@/api/client'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; interface Props { open: boolean; onOpenChange: (open: boolean) => void; } function previewFolderName(raw: string): string { return raw .toLowerCase() .trim() .replace(/\s+/g, '-') .replace(/[^a-z0-9-]/g, '') .replace(/-+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 64); } export function CreateProjectModal({ open, onOpenChange }: Props) { const navigate = useNavigate(); const [name, setName] = useState(''); const [commitMessage, setCommitMessage] = useState('Initial commit'); const [visibility, setVisibility] = useState<'private' | 'public'>('private'); const [createRemote, setCreateRemote] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!open) return; setName(''); setCommitMessage('Initial commit'); setVisibility('private'); setCreateRemote(true); setBusy(false); setError(null); }, [open]); const folderPreview = previewFolderName(name); async function submit() { if (!folderPreview) { setError('Project name must contain at least one letter or digit.'); return; } setBusy(true); setError(null); try { const result = await api.projects.create({ name: name.trim(), commit_message: commitMessage.trim() || 'Initial commit', visibility, create_gitea_remote: createRemote, }); const warnings = result.bootstrap.warnings; if (warnings.length > 0) { toast.warning(`Project created with warnings: ${warnings.join('; ')}`); } else { toast.success(`Project "${result.project.name}" created`); } onOpenChange(false); navigate(`/project/${result.project.id}`); } catch (err) { setError(err instanceof Error ? err.message : 'failed to create project'); } finally { setBusy(false); } } return ( Create New Project Creates a folder under /opt with a git repo, .gitignore, and optionally a Gitea remote.
setName(e.target.value)} disabled={busy} autoFocus /> {name && (
Folder: /opt/projects/{folderPreview || (empty after sanitization)}
)}
setCommitMessage(e.target.value)} disabled={busy} />
{error && (
{error}
)}
); }