From 3b33d80e2e54177db2eda9810d400960017015f4 Mon Sep 17 00:00:00 2001 From: Cascade Bot Date: Fri, 13 Mar 2026 16:19:22 +0000 Subject: [PATCH 1/2] feat(cancel-button): improve UX with loading spinner and success state - Add loading spinner (Loader2 with animate-spin) during mutation - Show brief success indicator with auto-dismiss after 2 seconds - Replace window.confirm() with shadcn AlertDialog - Update dialog text: 'This will terminate the worker container?' - Improve error display to show full error message - Prevent double-cancellation by disabling button during success --- web/src/components/runs/cancel-run-button.tsx | 117 +++++++++++++----- 1 file changed, 89 insertions(+), 28 deletions(-) diff --git a/web/src/components/runs/cancel-run-button.tsx b/web/src/components/runs/cancel-run-button.tsx index 3440da0e..caa46253 100644 --- a/web/src/components/runs/cancel-run-button.tsx +++ b/web/src/components/runs/cancel-run-button.tsx @@ -1,7 +1,17 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog.js'; import { Button } from '@/components/ui/button.js'; import { trpc, trpcClient } from '@/lib/trpc.js'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { Square } from 'lucide-react'; +import { CheckCircle, Loader2, Square } from 'lucide-react'; +import { useEffect, useState } from 'react'; interface CancelRunButtonProps { runId: string; @@ -11,10 +21,13 @@ interface CancelRunButtonProps { export function CancelRunButton({ runId, status }: CancelRunButtonProps) { const queryClient = useQueryClient(); + const [showDialog, setShowDialog] = useState(false); + const [showSuccess, setShowSuccess] = useState(false); const cancelMutation = useMutation({ mutationFn: () => trpcClient.runs.cancel.mutate({ runId }), onSuccess: () => { + setShowSuccess(true); queryClient.invalidateQueries({ queryKey: trpc.runs.list.queryOptions({}).queryKey }); queryClient.invalidateQueries({ queryKey: trpc.runs.getById.queryOptions({ id: runId }).queryKey, @@ -22,38 +35,86 @@ export function CancelRunButton({ runId, status }: CancelRunButtonProps) { }, }); + // Auto-dismiss success indicator after 2 seconds + useEffect(() => { + if (showSuccess) { + const timer = setTimeout(() => { + setShowSuccess(false); + }, 2000); + return () => clearTimeout(timer); + } + }, [showSuccess]); + if (status !== 'running') { return null; } return ( - - - {cancelMutation.isError && ( - - Failed + <> + + + + {showSuccess && !cancelMutation.isPending && ( + Cancelled + )} + {cancelMutation.isError && !showSuccess && ( + + {cancelMutation.error instanceof Error + ? `Error: ${cancelMutation.error.message}` + : 'Failed'} + + )} - )} - + + + + Cancel Run + + This will terminate the worker container. Are you sure? + + +
+ Cancel + { + cancelMutation.mutate(); + setShowDialog(false); + }} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + {cancelMutation.isPending && } + Terminate + +
+
+ + ); } From 8c9e3ac51eaa6f8b41442fef4ff839cfa9ef2e57 Mon Sep 17 00:00:00 2001 From: Cascade Bot Date: Fri, 13 Mar 2026 16:30:40 +0000 Subject: [PATCH 2/2] fix(cancel-button): remove dead code and use AlertDialogFooter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove dead `disabled={cancelMutation.isPending}` props from AlertDialogCancel and AlertDialogAction — Radix UI auto-closes the dialog on action click, so isPending is never true while the dialog is visible - Remove the unreachable Loader2 spinner inside AlertDialogAction for the same reason - Replace raw `
` footer with `AlertDialogFooter` for consistent responsive layout matching credentials-table, projects-table, and agent-configs-table Co-Authored-By: Claude Opus 4.6 --- web/src/components/runs/cancel-run-button.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/src/components/runs/cancel-run-button.tsx b/web/src/components/runs/cancel-run-button.tsx index caa46253..0bd83c6b 100644 --- a/web/src/components/runs/cancel-run-button.tsx +++ b/web/src/components/runs/cancel-run-button.tsx @@ -4,6 +4,7 @@ import { AlertDialogCancel, AlertDialogContent, AlertDialogDescription, + AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog.js'; @@ -99,20 +100,17 @@ export function CancelRunButton({ runId, status }: CancelRunButtonProps) { This will terminate the worker container. Are you sure? -
- Cancel + + Cancel { cancelMutation.mutate(); - setShowDialog(false); }} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > - {cancelMutation.isPending && } Terminate -
+