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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a5e37d3510
commit
f91536c86c
@@ -35,6 +35,7 @@ export default function RecurringInvoicesPage() {
|
||||
const [schedules, setSchedules] = useState<ScheduleRow[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [runningId, setRunningId] = useState<string | null>(null)
|
||||
const [togglingId, setTogglingId] = useState<string | null>(null)
|
||||
const [deletingId, setDeletingId] = useState<string | null>(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() {
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={togglingId !== null}
|
||||
onClick={() => togglePause(s)}
|
||||
>
|
||||
{s.status === 'active' ? t('pause') : t('resume')}
|
||||
|
||||
@@ -224,13 +224,19 @@ export default function SalaryRunPage({ params }: { params: Promise<{ id: string
|
||||
// could be sitting at the bank, sent payslips, a generated AGI — so the
|
||||
// confirm spells out exactly the ones that apply to this run. The API
|
||||
// refuses outright once the AGI has been filed with Skatteverket.
|
||||
function handleUnapprove() {
|
||||
async function handleUnapprove() {
|
||||
if (!run) return
|
||||
const lines = [t('confirm_unapprove_intro')]
|
||||
if (run.payment_file_generated_at) lines.push(t('confirm_unapprove_payment_file'))
|
||||
if ((run.payslip_deliveries_summary?.sent ?? 0) > 0) lines.push(t('confirm_unapprove_payslips'))
|
||||
if (run.agi_generated_at) lines.push(t('confirm_unapprove_agi'))
|
||||
if (!confirm(lines.join('\n\n'))) return
|
||||
const ok = await confirmAction({
|
||||
title: t('confirm_unapprove_title'),
|
||||
description: lines.join('\n\n'),
|
||||
confirmLabel: t('action_unapprove'),
|
||||
variant: 'warning',
|
||||
})
|
||||
if (!ok) return
|
||||
handleAction('unapprove')
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,12 @@ export function DestructiveConfirmDialog({
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
{/* pre-line so callers can pass newline-separated paragraphs
|
||||
(e.g. the salary unapprove confirm assembles its copy
|
||||
dynamically); single-line descriptions render unchanged. */}
|
||||
<DialogDescription className="whitespace-pre-line">
|
||||
{description}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -2960,9 +2960,11 @@
|
||||
"delete_confirm_title": "Remove the schedule?",
|
||||
"run_now": "Create invoice now",
|
||||
"run_now_confirm": "Create an invoice for \"{name}\" now? If the schedule has automatic sending, it is emailed to the customer immediately. The next scheduled run is not affected.",
|
||||
"run_now_confirm_title": "Create invoice now?",
|
||||
"run_now_success_title": "Invoice created",
|
||||
"run_now_failed_title": "Could not create invoice",
|
||||
"resume_autosend_confirm": "Reactivate \"{name}\"? This resumes automatic monthly emails to the customer.",
|
||||
"resume_autosend_confirm_title": "Resume automatic sending?",
|
||||
"send_time": "at {time}"
|
||||
},
|
||||
"invoice_recurring_new": {
|
||||
@@ -4952,6 +4954,7 @@
|
||||
"approve_override_confirm": "Approve anyway",
|
||||
"action_revert": "Back to draft",
|
||||
"action_unapprove": "Recall approval",
|
||||
"confirm_unapprove_title": "Recall approval?",
|
||||
"confirm_unapprove_intro": "The approval is recalled and the run goes back to review.",
|
||||
"confirm_unapprove_payment_file": "A payment file has been generated. If it has already been uploaded to the bank, the payment must be cancelled there — otherwise the old amounts will be paid out.",
|
||||
"confirm_unapprove_payslips": "Payslips have already been sent and need to be re-sent after recalculation.",
|
||||
|
||||
@@ -2960,9 +2960,11 @@
|
||||
"delete_confirm_title": "Ta bort schemat?",
|
||||
"run_now": "Skapa faktura nu",
|
||||
"run_now_confirm": "Skapa en faktura för \"{name}\" nu? Om schemat har automatiskt utskick skickas den direkt till kunden. Nästa schemalagda körning påverkas inte.",
|
||||
"run_now_confirm_title": "Skapa faktura nu?",
|
||||
"run_now_success_title": "Faktura skapad",
|
||||
"run_now_failed_title": "Kunde inte skapa faktura",
|
||||
"resume_autosend_confirm": "Aktivera \"{name}\" igen? Detta återupptar automatiska månatliga utskick till kunden.",
|
||||
"resume_autosend_confirm_title": "Återuppta automatiska utskick?",
|
||||
"send_time": "kl {time}"
|
||||
},
|
||||
"invoice_recurring_new": {
|
||||
@@ -4952,6 +4954,7 @@
|
||||
"approve_override_confirm": "Godkänn ändå",
|
||||
"action_revert": "Tillbaka till utkast",
|
||||
"action_unapprove": "Ångra godkännande",
|
||||
"confirm_unapprove_title": "Ångra godkännande?",
|
||||
"confirm_unapprove_intro": "Godkännandet återkallas och körningen går tillbaka till granskning.",
|
||||
"confirm_unapprove_payment_file": "En betalfil har genererats. Om den redan laddats upp till banken måste betalningen makuleras där — annars betalas de gamla beloppen ut.",
|
||||
"confirm_unapprove_payslips": "Lönebesked har redan skickats och behöver skickas om efter ny beräkning.",
|
||||
|
||||
Reference in New Issue
Block a user