import { ISSUE_DESCRIPTION_MAX_LENGTH, ISSUE_TITLE_MAX_LENGTH, type SprintRecord, type UserRecord, } from "@issue/shared"; import { type FormEvent, useState } from "react"; import { useAuthenticatedSession } from "@/components/session-provider"; import { StatusSelect } from "@/components/status-select"; import StatusTag from "@/components/status-tag"; import { Button } from "@/components/ui/button"; import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Field } from "@/components/ui/field"; import { Label } from "@/components/ui/label"; import { SelectTrigger } from "@/components/ui/select"; import { UserSelect } from "@/components/user-select"; import { issue } from "@/lib/server"; import { cn } from "@/lib/utils"; import { SprintSelect } from "./sprint-select"; export function CreateIssue({ projectId, sprints, members, statuses, trigger, completeAction, }: { projectId?: number; sprints?: SprintRecord[]; members?: UserRecord[]; statuses: Record; trigger?: React.ReactNode; completeAction?: (issueId: number) => void | Promise; }) { const { user } = useAuthenticatedSession(); const [open, setOpen] = useState(false); const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [sprintId, setSprintId] = useState("unassigned"); const [assigneeId, setAssigneeId] = useState("unassigned"); const [status, setStatus] = useState(Object.keys(statuses)[0] ?? ""); const [submitAttempted, setSubmitAttempted] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const reset = () => { setTitle(""); setDescription(""); setSprintId("unassigned"); setAssigneeId("unassigned"); setStatus(statuses?.[0] ?? ""); 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 ( title.trim() === "" || description.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH || title.trim().length > ISSUE_TITLE_MAX_LENGTH ) { return; } if (!user.id) { setError("you must be logged in to create an issue"); return; } if (!projectId) { setError("select a project first"); return; } setSubmitting(true); try { await issue.create({ projectId, title, description, sprintId: sprintId === "unassigned" ? null : Number(sprintId), assigneeId: assigneeId === "unassigned" ? null : Number(assigneeId), status: status.trim() === "" ? undefined : status, onSuccess: async (data) => { setOpen(false); reset(); try { await completeAction?.(data.id); } catch (actionErr) { console.error(actionErr); } }, onError: (message) => { setError(message); setSubmitting(false); }, }); } catch (err) { console.error(err); setError("failed to create issue"); setSubmitting(false); } }; return ( {trigger || ( )} Create Issue
{statuses && Object.keys(statuses).length > 0 && (
{ if (newValue.trim() === "") return; // TODO: handle this better // unsure why an empty value is being sent, but preventing it this way for now setStatus(newValue); }} trigger={({ isOpen, value }) => ( )} />
)} setTitle(e.target.value)} validate={(v) => v.trim() === "" ? "Cannot be empty" : v.trim().length > ISSUE_TITLE_MAX_LENGTH ? `Too long (${ISSUE_TITLE_MAX_LENGTH} character limit)` : undefined } submitAttempted={submitAttempted} placeholder="Demo Issue" maxLength={ISSUE_TITLE_MAX_LENGTH} /> setDescription(e.target.value)} validate={(v) => v.trim().length > ISSUE_DESCRIPTION_MAX_LENGTH ? `Too long (${ISSUE_DESCRIPTION_MAX_LENGTH} character limit)` : undefined } submitAttempted={submitAttempted} placeholder="Optional details" maxLength={ISSUE_DESCRIPTION_MAX_LENGTH} /> {sprints && sprints.length > 0 && (
)} {members && members.length > 0 && (
)}
{error ? ( ) : ( )}
); }