From 446b9aa299fc006dfe54d8ecc99ebaa3f78edc8f Mon Sep 17 00:00:00 2001 From: sax3l Date: Tue, 3 Nov 2026 00:51:42 +0100 Subject: [PATCH] fix(portal): one idempotency key per selected file A double click or a retry after a network error generated a new key each time and therefore a duplicate document. The key is now stable until the file selection changes, so the same upload returns the same verified receipt. --- app/portal/dokument/page.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/portal/dokument/page.tsx b/app/portal/dokument/page.tsx index 7b2bd44..f7fbf75 100644 --- a/app/portal/dokument/page.tsx +++ b/app/portal/dokument/page.tsx @@ -73,6 +73,10 @@ export default function DocumentsPage() { const [uploading, setUploading] = useState(false); const [unknownOutcome, setUnknownOutcome] = useState(null); const inputRef = useRef(null); + // One idempotency key per selected file: a retry (double click, network + // error, reload-and-pick-the-same-file) returns the SAME document instead of + // creating a duplicate. Picking a different file resets it. + const uploadKey = useRef(null); const [instruction, setInstruction] = useState(''); const [fields, setFields] = useState([{ name: 'organization', type: 'string' }]); @@ -119,7 +123,9 @@ export default function DocumentsPage() { const form = new FormData(); form.set('file', file); form.set('project_id', projectId.trim()); - form.set('idempotency_key', crypto.randomUUID()); + const key = uploadKey.current ?? crypto.randomUUID(); + uploadKey.current = key; + form.set('idempotency_key', key); form.set('classification', 'restricted'); try { const response = await fetch('/api/0pt/documents/upload', { @@ -293,7 +299,10 @@ export default function DocumentsPage() { ref={inputRef} type="file" className="hidden" - onChange={(event) => setFile(event.target.files?.[0] ?? null)} + onChange={(event) => { + uploadKey.current = null; + setFile(event.target.files?.[0] ?? null); + }} />