From f91536c86c4384b3c2dfcab7347ed4dc54ed4a47 Mon Sep 17 00:00:00 2001 From: Jakob Wennberg <149234542+jakobwennberg@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:51:00 +0200 Subject: [PATCH] fix(ui): migrate remaining native confirm() calls to DestructiveConfirmDialog (#1046) Migrates the three remaining bare confirm() sites from issue #1038 to the imperative useDestructiveConfirm() pattern already mounted on both pages: resume-autosend and run-now on the recurring-invoices page, and unapprove on the salary-run page (its dynamically assembled multi-line copy now renders as paragraphs via whitespace-pre-line on DialogDescription). Also adds a togglingId in-flight guard to togglePause, mirroring the deletingId/runningId guards from PR #1036, so the pause/resume button cannot fire a duplicate PATCH while one is pending. Fixes #1038 Co-authored-by: Claude Fable 5 --- app/(dashboard)/invoices/recurring/page.tsx | 60 +++++++++++++------- app/(dashboard)/salary/runs/[id]/page.tsx | 10 +++- components/ui/destructive-confirm-dialog.tsx | 7 ++- messages/en.json | 3 + messages/sv.json | 3 + 5 files changed, 59 insertions(+), 24 deletions(-) diff --git a/app/(dashboard)/invoices/recurring/page.tsx b/app/(dashboard)/invoices/recurring/page.tsx index fd67d5a3..87c89618 100644 --- a/app/(dashboard)/invoices/recurring/page.tsx +++ b/app/(dashboard)/invoices/recurring/page.tsx @@ -35,6 +35,7 @@ export default function RecurringInvoicesPage() { const [schedules, setSchedules] = useState([]) const [isLoading, setIsLoading] = useState(true) const [runningId, setRunningId] = useState(null) + const [togglingId, setTogglingId] = useState(null) const [deletingId, setDeletingId] = useState(null) const { canWrite } = useCanWrite() const { toast } = useToast() @@ -81,31 +82,41 @@ export default function RecurringInvoicesPage() { }, []) async function togglePause(s: ScheduleRow) { + // In-flight guard: the confirm dialog closes before the PATCH settles, + // so a second click would fire a duplicate request. + if (togglingId) return const next = s.status === 'active' ? 'paused' : 'active' // Reactivating an auto-send schedule resumes automatic emails to the // customer, so make the user consciously confirm they mean to turn it on. - if ( - next === 'active' && - s.auto_send && - !confirm(t('resume_autosend_confirm', { name: s.name })) - ) { - return + if (next === 'active' && s.auto_send) { + const ok = await confirmAction({ + title: t('resume_autosend_confirm_title'), + description: t('resume_autosend_confirm', { name: s.name }), + confirmLabel: t('resume'), + variant: 'warning', + }) + if (!ok) return } - const res = await fetch(`/api/invoices/recurring/${s.id}`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ status: next }), - }) - if (res.ok) { - toast({ - title: next === 'paused' ? t('schedule_paused_title') : t('schedule_resumed_title'), - }) - fetchSchedules() - } else { - toast({ - title: t('schedule_update_failed_title'), - variant: 'destructive', + setTogglingId(s.id) + try { + const res = await fetch(`/api/invoices/recurring/${s.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status: next }), }) + if (res.ok) { + toast({ + title: next === 'paused' ? t('schedule_paused_title') : t('schedule_resumed_title'), + }) + fetchSchedules() + } else { + toast({ + title: t('schedule_update_failed_title'), + variant: 'destructive', + }) + } + } finally { + setTogglingId(null) } } @@ -113,7 +124,13 @@ export default function RecurringInvoicesPage() { // In-flight guard: a second click while the request runs would create a // duplicate invoice for the customer. if (runningId) return - if (!confirm(t('run_now_confirm', { name: s.name }))) return + const ok = await confirmAction({ + title: t('run_now_confirm_title'), + description: t('run_now_confirm', { name: s.name }), + confirmLabel: t('run_now'), + variant: 'warning', + }) + if (!ok) return setRunningId(s.id) try { const res = await fetch(`/api/invoices/recurring/${s.id}/run`, { method: 'POST' }) @@ -270,6 +287,7 @@ export default function RecurringInvoicesPage() {