From 5f76d03913b4ee93494eb0244b83115a3913d72c Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 10 Mar 2026 21:06:16 -0500 Subject: [PATCH] ui: smooth new issue submit state --- ui/src/components/NewIssueDialog.tsx | 57 +++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/ui/src/components/NewIssueDialog.tsx b/ui/src/components/NewIssueDialog.tsx index 9a9076bc..108e282b 100644 --- a/ui/src/components/NewIssueDialog.tsx +++ b/ui/src/components/NewIssueDialog.tsx @@ -34,6 +34,7 @@ import { Tag, Calendar, Paperclip, + Loader2, } from "lucide-react"; import { cn } from "../lib/utils"; import { extractProviderIdWithFallback } from "../lib/model-utils"; @@ -420,7 +421,7 @@ export function NewIssueDialog() { } function handleSubmit() { - if (!effectiveCompanyId || !title.trim()) return; + if (!effectiveCompanyId || !title.trim() || createIssue.isPending) return; const assigneeAdapterOverrides = buildAssigneeAdapterOverrides({ adapterType: assigneeAdapterType, modelOverride: assigneeModelOverride, @@ -516,6 +517,11 @@ export function NewIssueDialog() { })), [orderedProjects], ); + const savedDraft = loadDraft(); + const hasSavedDraft = Boolean(savedDraft?.title.trim() || savedDraft?.description.trim()); + const canDiscardDraft = hasDraft || hasSavedDraft; + const createIssueErrorMessage = + createIssue.error instanceof Error ? createIssue.error.message : "Failed to create issue. Try again."; const handleProjectChange = useCallback((nextProjectId: string) => { setProjectId(nextProjectId); @@ -563,7 +569,7 @@ export function NewIssueDialog() { { - if (!open) closeNewIssue(); + if (!open && !createIssue.isPending) closeNewIssue(); }} > { + if (createIssue.isPending) { + event.preventDefault(); + } + }} onPointerDownOutside={(event) => { + if (createIssue.isPending) { + event.preventDefault(); + return; + } // Radix Dialog's modal DismissableLayer calls preventDefault() on // pointerdown events that originate outside the Dialog DOM tree. // Popover portals render at the body level (outside the Dialog), so @@ -654,6 +669,7 @@ export function NewIssueDialog() { size="icon-xs" className="text-muted-foreground" onClick={() => setExpanded(!expanded)} + disabled={createIssue.isPending} > {expanded ? : } @@ -662,6 +678,7 @@ export function NewIssueDialog() { size="icon-xs" className="text-muted-foreground" onClick={() => closeNewIssue()} + disabled={createIssue.isPending} > × @@ -680,6 +697,7 @@ export function NewIssueDialog() { e.target.style.height = "auto"; e.target.style.height = `${e.target.scrollHeight}px`; }} + readOnly={createIssue.isPending} onKeyDown={(e) => { if (e.key === "Enter" && !e.metaKey && !e.ctrlKey) { e.preventDefault(); @@ -998,17 +1016,36 @@ export function NewIssueDialog() { size="sm" className="text-muted-foreground" onClick={discardDraft} - disabled={!hasDraft && !loadDraft()} + disabled={createIssue.isPending || !canDiscardDraft} > Discard Draft - +
+
+ {createIssue.isPending ? ( + + + Creating issue... + + ) : createIssue.isError ? ( + {createIssueErrorMessage} + ) : canDiscardDraft ? ( + Draft autosaves locally + ) : null} +
+ +