"use client"; import { useState, useRef, useEffect } from "react"; import { DirectoryPickerModal } from "./directory-picker-modal"; type CreateWorkspaceDialogProps = { isOpen: boolean; onClose: () => void; onCreated?: () => void; }; function shortenPath(p: string): string { return p.replace(/^\/Users\/[^/]+/, "~").replace(/^\/home\/[^/]+/, "~"); } export function CreateWorkspaceDialog({ isOpen, onClose, onCreated }: CreateWorkspaceDialogProps) { const [profileName, setProfileName] = useState(""); const [customPath, setCustomPath] = useState(""); const [useCustomPath, setUseCustomPath] = useState(false); const [showDirPicker, setShowDirPicker] = useState(false); const [seedBootstrap, setSeedBootstrap] = useState(true); const [creating, setCreating] = useState(false); const [error, setError] = useState(null); const [result, setResult] = useState<{ workspaceDir: string; seededFiles: string[] } | null>(null); const inputRef = useRef(null); const dialogRef = useRef(null); // Focus input on open useEffect(() => { if (isOpen) { setProfileName(""); setCustomPath(""); setUseCustomPath(false); setShowDirPicker(false); setError(null); setResult(null); setTimeout(() => inputRef.current?.focus(), 100); } }, [isOpen]); // Close on Escape (only if dir picker is not open) useEffect(() => { function handleKey(e: KeyboardEvent) { if (e.key === "Escape" && !showDirPicker) {onClose();} } if (isOpen) { document.addEventListener("keydown", handleKey); return () => document.removeEventListener("keydown", handleKey); } }, [isOpen, onClose, showDirPicker]); const handleCreate = async () => { const name = profileName.trim(); if (!name) { setError("Please enter a workspace name."); return; } if (!/^[a-zA-Z0-9_-]+$/.test(name)) { setError("Name must use only letters, numbers, hyphens, or underscores."); return; } setCreating(true); setError(null); try { const body: Record = { profile: name, seedBootstrap, }; if (useCustomPath && customPath.trim()) { body.path = customPath.trim(); } const res = await fetch("/api/workspace/init", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok) { setError(data.error || "Failed to create workspace."); return; } setResult({ workspaceDir: data.workspaceDir, seededFiles: data.seededFiles ?? [], }); onCreated?.(); } catch (err) { setError((err as Error).message); } finally { setCreating(false); } }; if (!isOpen) {return null;} return (
{ if (e.target === e.currentTarget) {onClose();} }} >
{/* Header */}

New Workspace

{/* Body */}
{result ? ( /* Success state */

Workspace created

{result.workspaceDir.replace(/^\/Users\/[^/]+/, "~")} {result.seededFiles.length > 0 && (

Seeded: {result.seededFiles.join(", ")}

)}
) : ( /* Form */ <> {/* Profile name */}
{ setProfileName(e.target.value); setError(null); }} onKeyDown={(e) => { if (e.key === "Enter" && !creating) {void handleCreate();} }} placeholder="e.g. work, personal, project-x" className="w-full px-3 py-2 text-sm rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]" style={{ background: "var(--color-bg)", border: "1px solid var(--color-border)", color: "var(--color-text)", }} />

This creates a new profile with its own workspace directory.

{/* Custom path toggle */}
{useCustomPath && (
{customPath ? (

{customPath.split("/").pop()}

{shortenPath(customPath)}

) : ( )}
)}
{/* Bootstrap toggle */} {error && (

{error}

)} )}
{/* Footer */}
{result ? ( ) : ( <> )}
{/* Directory picker modal */} setShowDirPicker(false)} onSelect={(path) => setCustomPath(path)} startDir="~" />
); }