From c12d40dc02f06a887d6e45a8ebe79655f6f83372 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Thu, 3 Sep 2026 18:31:16 -0500 Subject: [PATCH] fix(admin): stop the upload links table claiming delivery it never recorded (#260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The table headed its address column "Sent to", but contact_email records only the intent an admin typed in, never whether delivery happened — that outcome is shown once, at creation, and is not persisted. In QA, where every send is blocked by design, every row read "Sent to ..." for links that were never emailed, and the honest warning that appears at creation is guarded on `issued`, so it vanishes on refresh, leaving the false heading as the only surviving statement. Renamed to "Email", which is true of what the column actually stores. Separately, create() called setMailed(null) before every request, including one that would go on to 400. If an admin creates link A while mail is down (warning shown, token A still on screen) and then mistypes an address on a second attempt, the 400 path returned early — but the reset had already run, so the warning for link A disappeared while token A was still displayed on the same screen. setMailed is now only called after a successful create, alongside setIssued, so a request that never produces a new link can no longer clear a warning that belongs to the one still shown. Co-Authored-By: Claude Opus 5 --- frontend/src/admin/UploadLinks.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/admin/UploadLinks.tsx b/frontend/src/admin/UploadLinks.tsx index 06efb82..6e3ea9f 100644 --- a/frontend/src/admin/UploadLinks.tsx +++ b/frontend/src/admin/UploadLinks.tsx @@ -63,7 +63,6 @@ export default function UploadLinks() { async function create() { setCreating(true); setError(null); - setMailed(null); const res = await fetch('/api/admin/upload-links', { method: 'POST', @@ -87,8 +86,12 @@ export default function UploadLinks() { } const created = await res.json(); - setIssued(created.url); + // Reset here, not before the request: a 400 returns before this point, so + // rejecting a second link (say, a mistyped address) can no longer clear + // the warning that belongs to a still-displayed token from an earlier, + // successful create. setMailed(created.mail.sent); + setIssued(created.url); setLabel(''); setEmail(''); setCap(DEFAULT_CAP); @@ -182,7 +185,12 @@ export default function UploadLinks() { pagination={false} columns={[ { title: 'Label', dataIndex: 'label' }, - { title: 'Sent to', dataIndex: 'contact_email' }, + // Not "Sent to": contact_email records only the address the admin + // gave when the link was created, never whether delivery actually + // happened — that outcome is shown once, at creation, and is not + // persisted. In QA, where every send is blocked by design, "Sent + // to" would be false for every row on the page. + { title: 'Email', dataIndex: 'contact_email' }, { title: 'Used', render: (_, row) =>