mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
fix(autopilot): confirm before deleting autopilot or trigger (#1604)
Destructive actions in the autopilot detail page fired immediately on click. Wrap "Delete autopilot" and per-trigger delete with AlertDialog confirmation, matching the existing issue-delete pattern. Also fix a latent bug in trigger deletion where the success toast was shown synchronously after mutate(), so failures still reported success — switch to mutateAsync + try/catch.
This commit is contained in:
@@ -27,6 +27,16 @@ import {
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from "@multica/ui/components/ui/dialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@multica/ui/components/ui/alert-dialog";
|
||||
import {
|
||||
TriggerConfigSection,
|
||||
getDefaultTriggerConfig,
|
||||
@@ -91,6 +101,21 @@ function RunRow({ run }: { run: AutopilotRun }) {
|
||||
|
||||
function TriggerRow({ trigger, autopilotId }: { trigger: AutopilotTrigger; autopilotId: string }) {
|
||||
const deleteTrigger = useDeleteAutopilotTrigger();
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
const handleDelete = async () => {
|
||||
setDeleting(true);
|
||||
try {
|
||||
await deleteTrigger.mutateAsync({ autopilotId, triggerId: trigger.id });
|
||||
toast.success("Trigger deleted");
|
||||
setConfirmOpen(false);
|
||||
} catch {
|
||||
toast.error("Failed to delete trigger");
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 rounded-md border px-3 py-2">
|
||||
@@ -121,13 +146,30 @@ function TriggerRow({ trigger, autopilotId }: { trigger: AutopilotTrigger; autop
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 shrink-0"
|
||||
onClick={() => {
|
||||
deleteTrigger.mutate({ autopilotId, triggerId: trigger.id });
|
||||
toast.success("Trigger deleted");
|
||||
}}
|
||||
onClick={() => setConfirmOpen(true)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</Button>
|
||||
<AlertDialog open={confirmOpen} onOpenChange={(v) => { if (!v && !deleting) setConfirmOpen(false); }}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete trigger</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This trigger will be removed and the autopilot will stop firing on this schedule. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
disabled={deleting}
|
||||
className="bg-destructive text-white hover:bg-destructive/90"
|
||||
>
|
||||
{deleting ? "Deleting..." : "Delete"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -211,6 +253,8 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
|
||||
|
||||
const [triggerDialogOpen, setTriggerDialogOpen] = useState(false);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -271,12 +315,14 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
setDeleting(true);
|
||||
try {
|
||||
await deleteAutopilot.mutateAsync(autopilotId);
|
||||
toast.success("Autopilot deleted");
|
||||
router.push(wsPaths.autopilots());
|
||||
} catch {
|
||||
toast.error("Failed to delete autopilot");
|
||||
setDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -401,7 +447,7 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
|
||||
{/* Danger zone */}
|
||||
<section className="space-y-3 pt-4 border-t">
|
||||
<h2 className="text-sm font-medium text-destructive uppercase tracking-wider">Danger Zone</h2>
|
||||
<Button size="sm" variant="destructive" onClick={handleDelete}>
|
||||
<Button size="sm" variant="destructive" onClick={() => setDeleteConfirmOpen(true)}>
|
||||
<Trash2 className="h-3.5 w-3.5 mr-1" />
|
||||
Delete autopilot
|
||||
</Button>
|
||||
@@ -429,6 +475,29 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
|
||||
triggers={triggers}
|
||||
/>
|
||||
)}
|
||||
<AlertDialog
|
||||
open={deleteConfirmOpen}
|
||||
onOpenChange={(v) => { if (!v && !deleting) setDeleteConfirmOpen(false); }}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete autopilot</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will permanently delete “{autopilot.title}”, along with its triggers and run history. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
disabled={deleting}
|
||||
className="bg-destructive text-white hover:bg-destructive/90"
|
||||
>
|
||||
{deleting ? "Deleting..." : "Delete"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user