import type { ProjectRecord } from "@issue/shared"; import { type FormEvent, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { project } from "@/lib/server"; import { cn } from "@/lib/utils"; const keyify = (value: string) => value .toUpperCase() .replace(/[^A-Z0-9]/g, "") .slice(0, 4); export function CreateProject({ organisationId, trigger, completeAction, }: { organisationId?: number; trigger?: React.ReactNode; completeAction?: (projectId: number) => void | Promise; }) { const userId = JSON.parse(localStorage.getItem("user") || "{}").id as number | undefined; const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [key, setKey] = useState(""); const [nameTouched, setNameTouched] = useState(false); const [keyTouched, setKeyTouched] = useState(false); const [keyManuallyEdited, setKeyManuallyEdited] = useState(false); const [submitAttempted, setSubmitAttempted] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const nameInvalid = useMemo( () => ((nameTouched || submitAttempted) && name.trim() === "" ? "Cannot be empty" : ""), [nameTouched, submitAttempted, name], ); const keyInvalid = useMemo(() => { if (!(keyTouched || submitAttempted)) return ""; if (key.trim() === "") return "Cannot be empty"; if (key.length > 4) return "Must be 4 or less characters"; return ""; }, [keyTouched, submitAttempted, key]); const reset = () => { setName(""); setKey(""); setNameTouched(false); setKeyTouched(false); setKeyManuallyEdited(false); setSubmitAttempted(false); setSubmitting(false); setError(null); }; const onOpenChange = (nextOpen: boolean) => { setOpen(nextOpen); if (!nextOpen) { reset(); } }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setError(null); setSubmitAttempted(true); if (name.trim() === "" || key.length > 4) { return; } if (!userId) { setError("you must be logged in to create a project"); return; } if (!organisationId) { setError("select an organisation first"); return; } setSubmitting(true); try { await project.create({ key, name, creatorId: userId, organisationId, onSuccess: async (data) => { const project = data as ProjectRecord; setOpen(false); reset(); try { await completeAction?.(project.id); } catch (actionErr) { console.error(actionErr); } }, onError: (message) => { setError(message); setSubmitting(false); }, }); } catch (err) { console.error(err); setError("failed to create project"); setSubmitting(false); } }; return ( {trigger || ( )} Create Project
{ const nextName = e.target.value; setName(nextName); if (!keyManuallyEdited) { setKey(keyify(nextName)); } }} onBlur={() => setNameTouched(true)} aria-invalid={nameInvalid !== ""} placeholder="Demo Project" required />
{nameInvalid !== "" ? ( ) : ( )}
{ setKey(keyify(e.target.value)); setKeyManuallyEdited(true); }} onBlur={() => setKeyTouched(true)} aria-invalid={keyInvalid !== ""} placeholder="DEMO" required />
{keyInvalid !== "" ? ( ) : ( )}
{error ? ( ) : ( )}
); }