From 7602d8112012a099cc0fb93fb7f1facacfc149f0 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 2 Mar 2026 19:05:50 -0800 Subject: [PATCH] =?UTF-8?q?Simplify=20project=20creation:=20name=20?= =?UTF-8?q?=E2=86=92=20create=20=E2=86=92=20redirect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove GitHub step entirely; single input + Next button - Creates project immediately, redirects to /overview on success - Rewritten in Stackless inline style (no shadcn Dialog/Button/Input) Made-with: Cursor --- components/project-creation-modal.tsx | 465 ++++++++------------------ 1 file changed, 144 insertions(+), 321 deletions(-) diff --git a/components/project-creation-modal.tsx b/components/project-creation-modal.tsx index 44a7d83..0d8a2d2 100644 --- a/components/project-creation-modal.tsx +++ b/components/project-creation-modal.tsx @@ -1,362 +1,185 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useRouter } from 'next/navigation'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { - Github, - Loader2, - CheckCircle2, - XCircle, - ExternalLink, - ChevronLeft, -} from 'lucide-react'; import { toast } from 'sonner'; -import { Card, CardContent } from '@/components/ui/card'; -import { Separator } from '@/components/ui/separator'; interface ProjectCreationModalProps { open: boolean; onOpenChange: (open: boolean) => void; - initialWorkspacePath?: string; workspace: string; + initialWorkspacePath?: string; } -export function ProjectCreationModal({ - open, - onOpenChange, - initialWorkspacePath, - workspace, -}: ProjectCreationModalProps) { +export function ProjectCreationModal({ open, onOpenChange, workspace }: ProjectCreationModalProps) { const router = useRouter(); - - const [step, setStep] = useState(1); const [productName, setProductName] = useState(''); - const [selectedRepo, setSelectedRepo] = useState(null); - const [createdProjectId, setCreatedProjectId] = useState(null); - const [createdGiteaRepo, setCreatedGiteaRepo] = useState<{ repo: string; repoUrl: string } | null>(null); - const [createdTheiaUrl, setCreatedTheiaUrl] = useState(null); - const [loading, setLoading] = useState(false); - const [githubConnected, setGithubConnected] = useState(false); - const [githubRepos, setGithubRepos] = useState([]); - const [loadingGithub, setLoadingGithub] = useState(false); + const inputRef = useRef(null); useEffect(() => { - async function checkGitHub() { - if (!open || step !== 2) return; - setLoadingGithub(true); - try { - const statusResponse = await fetch('/api/github/connect'); - if (statusResponse.ok) { - const statusData = await statusResponse.json(); - const isConnected = statusData.connected || false; - setGithubConnected(isConnected); - if (isConnected) { - const reposResponse = await fetch('/api/github/repos'); - if (reposResponse.ok) { - const repos = await reposResponse.json(); - setGithubRepos(Array.isArray(repos) ? repos : []); - } - } - } else { - setGithubConnected(false); - } - } catch (error) { - console.error('GitHub check error:', error); - setGithubConnected(false); - } finally { - setLoadingGithub(false); - } + if (open) { + setProductName(''); + setLoading(false); + setTimeout(() => inputRef.current?.focus(), 80); } - checkGitHub(); - }, [open, step]); - - useEffect(() => { - if (!open) setTimeout(resetModal, 200); }, [open]); - const resetModal = () => { - setStep(1); - setProductName(''); - setSelectedRepo(null); - setCreatedProjectId(null); - setCreatedGiteaRepo(null); - setCreatedTheiaUrl(null); - setLoading(false); - }; + // Close on Escape + useEffect(() => { + if (!open) return; + const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onOpenChange(false); }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [open, onOpenChange]); - const handleCreateProject = async () => { - if (!productName.trim()) { - toast.error('Product name is required'); - return; - } + const handleCreate = async () => { + const name = productName.trim(); + if (!name) return; setLoading(true); try { - const projectData = { - projectName: productName.trim(), - projectType: selectedRepo ? 'existing' : 'scratch', - slug: productName.toLowerCase().replace(/[^a-z0-9]+/g, '-'), - product: { name: productName }, - ...(selectedRepo && { - githubRepo: selectedRepo.full_name, - githubRepoId: selectedRepo.id, - githubRepoUrl: selectedRepo.html_url, - githubDefaultBranch: selectedRepo.default_branch, - }), - }; - - const response = await fetch('/api/projects/create', { + const res = await fetch('/api/projects/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(projectData), + body: JSON.stringify({ + projectName: name, + projectType: 'scratch', + slug: name.toLowerCase().replace(/[^a-z0-9]+/g, '-'), + product: { name }, + }), }); - - if (response.ok) { - const data = await response.json(); - setCreatedProjectId(data.projectId); - if (data.gitea) setCreatedGiteaRepo(data.gitea); - if (data.theiaWorkspaceUrl) setCreatedTheiaUrl(data.theiaWorkspaceUrl); - toast.success('Project created!'); - setStep(3); - } else { - const error = await response.json(); - toast.error(error.error || 'Failed to create project'); + if (!res.ok) { + const err = await res.json(); + toast.error(err.error || 'Failed to create project'); + return; } - } catch (error) { - console.error('Error creating project:', error); - toast.error('An error occurred'); + const data = await res.json(); + onOpenChange(false); + router.push(`/${workspace}/project/${data.projectId}/overview`); + } catch { + toast.error('Something went wrong'); } finally { setLoading(false); } }; - const handleFinish = () => { - onOpenChange(false); - if (createdProjectId) { - router.push(`/${workspace}/project/${createdProjectId}/overview`); - } - }; + if (!open) return null; return ( - - - - - {step === 1 && 'Create New Project'} - {step === 2 && 'Connect GitHub Repository'} - {step === 3 && 'Setup Complete!'} - - - {step === 1 && 'Give your project a name to get started.'} - {step === 2 && 'Select a GitHub repository to connect (optional).'} - {step === 3 && 'Add the .vibn file to your project to enable tracking.'} - - + <> + {/* Backdrop */} +
onOpenChange(false)} + style={{ + position: 'fixed', inset: 0, zIndex: 50, + background: 'rgba(26,26,26,0.35)', + animation: 'fadeIn 0.15s ease', + }} + /> -
- {step === 1 && ( -
-
- - setProductName(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter' && productName.trim()) { - setStep(2); - } - }} - autoFocus - /> -
- -

- Connect GitHub or skip to continue + {/* Modal */} +

+
e.stopPropagation()} + > + + + {/* Header */} +
+
+

+ New project +

+

+ Give your project a name to get started.

- )} + +
- {step === 2 && ( -
- + {/* Name input */} +
+ + setProductName(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter' && productName.trim() && !loading) handleCreate(); }} + placeholder="e.g. Foxglove, Meridian, OpsAI…" + style={{ + width: '100%', padding: '11px 14px', + borderRadius: 8, border: '1px solid #e0dcd4', + background: '#faf8f5', fontSize: '0.9rem', + fontFamily: 'Outfit, sans-serif', color: '#1a1a1a', + outline: 'none', transition: 'border-color 0.12s', + boxSizing: 'border-box', + }} + onFocus={e => (e.currentTarget.style.borderColor = '#1a1a1a')} + onBlur={e => (e.currentTarget.style.borderColor = '#e0dcd4')} + /> +
- {loadingGithub ? ( -
- -

Checking GitHub connection...

-
- ) : ( - <> -
- - - {githubRepos.length > 0 ? ( - githubRepos.map((repo) => ( - - )) - ) : !githubConnected ? ( -
-

GitHub not connected yet

- -
- ) : ( -

No repositories found

- )} -
- - - )} -
- )} - - {step === 3 && createdProjectId && ( -
- - -
- -
-

- {productName} is ready -

- {createdGiteaRepo ? ( - - - {createdGiteaRepo.repo} - - - ) : ( -

- Gitea repo provisioning skipped -

- )} -
-
-
-
- -
-

What was provisioned:

-
    -
  • - - Project record saved to database -
  • -
  • - {createdGiteaRepo ? ( - - ) : ( - - )} - Gitea repo created at git.vibnai.com -
  • -
  • - {createdGiteaRepo ? ( - - ) : ( - - )} - Webhook registered (push, PR, issues → Vibn) -
  • -
  • - {createdTheiaUrl ? ( - - ) : ( - - )} - Dedicated IDE workspace{createdTheiaUrl - ? ` at ${createdTheiaUrl.replace('https://', '')}` - : ' — provisioning in background (ready in ~30s)'} -
  • -
-
- -
- - - - -
-
- )} + {/* Create button */} +
- -
+ + ); }