From 355723c5666c8613b9e371b1544016d7da768a66 Mon Sep 17 00:00:00 2001 From: Mattsson <111893710+mattssonn@users.noreply.github.com> Date: Sun, 30 Aug 2026 12:25:30 +0200 Subject: [PATCH] fix(import): make SIE upload Safari-safe with visible file-type errors (#2032) Safari maps the accept attribute's extensions to system file types; unregistered extensions like .sie/.se grey out valid files in the picker, making SIE import impossible in Safari. Drop the accept filter and validate after selection instead. The drag-and-drop path previously ignored non-matching files silently; both paths now show a visible Swedish error (with a dedicated hint for zip files) and the server-side filename gate in /api/import/sie/parse remains as the backstop. Claude-Session: https://claude.ai/code/session_019xZqhrhqoGLLbsKtCAxyGc Co-authored-by: Claude Fable 5 --- components/import/SIEUploadStep.tsx | 49 ++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/components/import/SIEUploadStep.tsx b/components/import/SIEUploadStep.tsx index 2cf538bb..5e5a89c7 100644 --- a/components/import/SIEUploadStep.tsx +++ b/components/import/SIEUploadStep.tsx @@ -29,6 +29,7 @@ export default function SIEUploadStep({ onFileSelect, isLoading, error, errorTyp const [isDragging, setIsDragging] = useState(false) const [selectedFile, setSelectedFile] = useState(null) const [loadingPhase, setLoadingPhase] = useState(0) + const [fileTypeError, setFileTypeError] = useState(null) // Cycle through loading phases on timers useEffect(() => { @@ -56,27 +57,43 @@ export default function SIEUploadStep({ onFileSelect, isLoading, error, errorTyp setIsDragging(false) }, []) + // No `accept` attribute on the input and no silent rejection here: Safari + // maps accept extensions to system file types, and unregistered extensions + // like .sie/.se grey out perfectly valid files in the picker. All filtering + // happens after selection, with a visible error instead of a dead drop. + const trySelectFile = useCallback((file: File) => { + const name = file.name.toLowerCase() + if (name.endsWith('.sie') || name.endsWith('.se')) { + setFileTypeError(null) + setSelectedFile(file) + onFileSelect(file) + return + } + if (name.endsWith('.zip')) { + setFileTypeError(`Filen "${file.name}" är en zip-fil. Packa upp den först och välj SIE-filen inuti (slutar på .sie eller .se).`) + } else { + setFileTypeError(`Filen "${file.name}" stöds inte. Välj en SIE-fil som slutar på .sie eller .se.`) + } + }, [onFileSelect]) + const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() setIsDragging(false) const files = e.dataTransfer.files if (files.length > 0) { - const file = files[0] - if (file.name.toLowerCase().endsWith('.sie') || file.name.toLowerCase().endsWith('.se')) { - setSelectedFile(file) - onFileSelect(file) - } + trySelectFile(files[0]) } - }, [onFileSelect]) + }, [trySelectFile]) const handleFileInput = useCallback((e: React.ChangeEvent) => { const files = e.target.files if (files && files.length > 0) { - setSelectedFile(files[0]) - onFileSelect(files[0]) + trySelectFile(files[0]) } - }, [onFileSelect]) + // Allow re-picking the same file after a rejection + e.target.value = '' + }, [trySelectFile]) const phase = LOADING_PHASES[loadingPhase] @@ -124,7 +141,7 @@ export default function SIEUploadStep({ onFileSelect, isLoading, error, errorTyp className={` relative border-2 border-dashed rounded-lg p-8 text-center transition-colors cursor-pointer hover:border-primary/50 ${isDragging ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'} - ${error ? 'border-destructive bg-destructive/5' : ''} + ${error || fileTypeError ? 'border-destructive bg-destructive/5' : ''} `} onDragOver={handleDragOver} onDragLeave={handleDragLeave} @@ -134,7 +151,6 @@ export default function SIEUploadStep({ onFileSelect, isLoading, error, errorTyp + {/* Rejected file type (client-side, before upload) */} + {fileTypeError && ( +
+ +
+

Filen kan inte användas

+

{fileTypeError}

+
+
+ )} + {/* Error display */} {error && (