diff --git a/src/components/shared/requirement-upload-slot.tsx b/src/components/shared/requirement-upload-slot.tsx index ce7c5de..d125937 100644 --- a/src/components/shared/requirement-upload-slot.tsx +++ b/src/components/shared/requirement-upload-slot.tsx @@ -166,26 +166,46 @@ export function RequirementUploadSlot({ requirementId: requirement.id, }) - // Upload file with progress tracking - await new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest() - xhr.upload.addEventListener('progress', (event) => { - if (event.lengthComputable) { - setProgress(Math.round((event.loaded / event.total) * 100)) - } - }) - xhr.addEventListener('load', () => { - if (xhr.status >= 200 && xhr.status < 300) { - resolve() + // Upload file with progress tracking and auto-retry + const maxRetries = 3 + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest() + xhr.upload.addEventListener('progress', (event) => { + if (event.lengthComputable) { + setProgress(Math.round((event.loaded / event.total) * 100)) + } + }) + xhr.addEventListener('load', () => { + if (xhr.status >= 200 && xhr.status < 300) { + resolve() + } else { + reject(new Error(`Upload failed with status ${xhr.status}`)) + } + }) + xhr.addEventListener('error', () => + reject(new Error('Network error during upload')) + ) + xhr.addEventListener('abort', () => + reject(new Error('Upload was aborted')) + ) + xhr.open('PUT', url) + xhr.setRequestHeader('Content-Type', file.type) + xhr.send(file) + }) + break // Success — exit retry loop + } catch (uploadErr) { + if (attempt < maxRetries) { + const delay = attempt * 2000 + toast.info(`Upload interrupted, retrying... (${attempt}/${maxRetries})`) + setProgress(0) + await new Promise((r) => setTimeout(r, delay)) } else { - reject(new Error(`Upload failed with status ${xhr.status}`)) + throw uploadErr } - }) - xhr.addEventListener('error', () => reject(new Error('Upload failed'))) - xhr.open('PUT', url) - xhr.setRequestHeader('Content-Type', file.type) - xhr.send(file) - }) + } + } // Save metadata await saveFileMetadata.mutateAsync({