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')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user