"use client"; import * as React from "react"; import { Check } from "lucide-react"; import { ResponsiveDialog } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; import { Input, Textarea, Field } from "@/components/ui/input"; import { toast } from "@/components/ui/toast"; import { api } from "@/lib/client/api"; import { errorMessage } from "@/lib/client/humanize"; import { invalidateProjects } from "@/components/app/store"; import { ProjectIcon, PROJECT_COLORS, PROJECT_EMOJIS } from "./project-icon"; import type { PublicProject } from "@/lib/client/types"; import { cn } from "@/lib/utils"; interface Draft { name: string; icon: string; color: string; description: string; } /** * Create / edit a project (name, emoji icon, colour, description). Bottom sheet on phones, dialog on desktop. * Instructions, models, settings and notes live on the project page. */ export function ProjectFormSheet({ open, onOpenChange, project, onSaved }: { open: boolean; onOpenChange: (o: boolean) => void; project?: PublicProject | null; onSaved?: (p: PublicProject) => void }) { const [draft, setDraft] = React.useState({ name: "", icon: "", color: "", description: "" }); const [saving, setSaving] = React.useState(false); const nameRef = React.useRef(null); React.useEffect(() => { if (!open) return; // eslint-disable-next-line react-hooks/set-state-in-effect setDraft({ name: project?.name ?? "", icon: project?.icon ?? "", color: project?.color ?? "", description: project?.description ?? "" }); const t = setTimeout(() => nameRef.current?.focus(), 80); return () => clearTimeout(t); }, [open, project]); const set = (patch: Partial) => setDraft((d) => ({ ...d, ...patch })); const valid = draft.name.trim().length > 0; const save = async () => { if (!valid || saving) return; setSaving(true); const body = { name: draft.name.trim(), icon: draft.icon.trim() || null, color: draft.color || null, description: draft.description.trim() || null }; try { const res = project ? await api<{ project: PublicProject }>(`/api/projects/${project.id}`, { method: "PATCH", json: body }) : await api<{ project: PublicProject }>("/api/projects", { method: "POST", json: body }); await invalidateProjects(); toast.success(project ? "Project updated" : "Project created"); onOpenChange(false); onSaved?.(res.project); } catch (e) { toast.error(project ? "Could not update project" : "Could not create project", errorMessage(e)); } finally { setSaving(false); } }; return ( } >
{ e.preventDefault(); void save(); }} >
set({ name: e.target.value })} placeholder="Research, Client X, Side project…" maxLength={80} required className="h-11 sm:h-9" />
set({ icon: e.target.value.slice(0, 4) })} placeholder="✨" maxLength={4} aria-label="Custom emoji" className="h-10 w-14 text-center text-lg sm:h-9" /> {PROJECT_EMOJIS.map((e) => ( ))}
{PROJECT_COLORS.map((c) => ( ))}