11import React , { useCallback , useState , useEffect } from 'react' ;
2+ // MessageBar styles constants
3+ const messageBarErrorStyles = {
4+ root : { display : "flex" , flexDirection : "column" , alignItems : "left" , background : "#fff4f4" } ,
5+ icon : { display : "none" } ,
6+ } ;
7+
8+
9+
10+ const messageBarSuccessStyles = {
11+ root : { display : "flex" , alignItems : "left" } ,
12+ icon : { display : "none" } ,
13+ } ;
14+
15+ const messageBarWarningStyles = {
16+ root : { display : "flex" , alignItems : "center" } ,
17+ } ;
18+ // Helper function to check for .sql extension
19+ const isSqlFile = ( file : File ) : boolean => file . name . toLowerCase ( ) . endsWith ( '.sql' ) ;
20+
21+ // ...existing code...
222import { useDropzone , FileRejection , DropzoneOptions } from 'react-dropzone' ;
323import { CircleCheck , X } from 'lucide-react' ;
424import {
@@ -40,7 +60,7 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
4060 onFileReject,
4161 onUploadStateChange,
4262 maxSize = 200 * 1024 * 1024 ,
43- acceptedFileTypes = { 'application/sql' : [ '.sql' ] } ,
63+ acceptedFileTypes = { 'application/sql' : [ '.sql' ] } , // Accept only .sql files by extension
4464 selectedCurrentLanguage,
4565 selectedTargetLanguage
4666} ) => {
@@ -52,6 +72,7 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
5272 const [ batchId , setBatchId ] = useState < string > ( uuidv4 ( ) ) ;
5373 const [ allUploadsComplete , setAllUploadsComplete ] = useState ( false ) ;
5474 const [ fileLimitExceeded , setFileLimitExceeded ] = useState ( false ) ;
75+ const [ fileRejectionErrors , setFileRejectionErrors ] = useState < string [ ] > ( [ ] ) ;
5576 const [ showFileLimitDialog , setShowFileLimitDialog ] = useState ( false ) ;
5677 const navigate = useNavigate ( ) ;
5778
@@ -162,34 +183,57 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
162183
163184 const onDrop = useCallback (
164185 ( acceptedFiles : File [ ] , fileRejections : FileRejection [ ] ) => {
186+ // Use helper for .sql extension check
187+ const validFiles = acceptedFiles . filter ( isSqlFile ) ;
188+ const invalidFiles = acceptedFiles . filter ( file => ! isSqlFile ( file ) ) ;
189+
165190 // Check current files count and determine how many more can be added
166191 const remainingSlots = MAX_FILES - uploadingFiles . length ;
167192
193+ if ( validFiles . length > 0 ) {
194+ setFileRejectionErrors ( [ ] ) ; // Clear error notification when valid file is selected
195+ }
196+
168197 if ( remainingSlots <= 0 ) {
169- // Already at max files, show dialog
170198 setShowFileLimitDialog ( true ) ;
171199 return ;
172200 }
173201
174202 // If more files are dropped than slots available
175- if ( acceptedFiles . length > remainingSlots ) {
176- // Take only the first `remainingSlots` files
177- const filesToUpload = acceptedFiles . slice ( 0 , remainingSlots ) ;
203+ if ( validFiles . length > remainingSlots ) {
204+ const filesToUpload = validFiles . slice ( 0 , remainingSlots ) ;
178205 filesToUpload . forEach ( file => simulateFileUpload ( file ) ) ;
179-
180206 if ( onFileUpload ) onFileUpload ( filesToUpload ) ;
181-
182- // Show dialog about exceeding limit
183207 setShowFileLimitDialog ( true ) ;
184208 } else {
185- // Normal case, upload all files
186- acceptedFiles . forEach ( file => simulateFileUpload ( file ) ) ;
187- if ( onFileUpload ) onFileUpload ( acceptedFiles ) ;
209+ validFiles . forEach ( file => simulateFileUpload ( file ) ) ;
210+ if ( onFileUpload ) onFileUpload ( validFiles ) ;
188211 }
189212
190- if ( onFileReject && fileRejections . length > 0 ) {
213+ // Efficient error array construction
214+ const errors : string [ ] = [
215+ ...invalidFiles . map ( file =>
216+ `File '${ file . name } ' is not a valid SQL file. Only .sql files are allowed.`
217+ ) ,
218+ ...fileRejections . flatMap ( rejection =>
219+ rejection . errors . map ( err => {
220+ if ( err . code === "file-too-large" ) {
221+ return `File '${ rejection . file . name } ' exceeds the 200MB size limit. Please upload a file smaller than 200MB.` ;
222+ } else if ( err . code === "file-invalid-type" ) {
223+ return `File '${ rejection . file . name } ' is not a valid SQL file. Only .sql files are allowed.` ;
224+ } else {
225+ return `File '${ rejection . file . name } ': ${ err . message } ` ;
226+ }
227+ } )
228+ )
229+ ] ;
230+
231+ if ( fileRejections . length > 0 && onFileReject ) {
191232 onFileReject ( fileRejections ) ;
192233 }
234+ if ( errors . length > 0 ) {
235+ setFileRejectionErrors ( errors ) ;
236+ }
193237 } ,
194238 [ onFileUpload , onFileReject , uploadingFiles . length ]
195239 ) ;
@@ -198,7 +242,7 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
198242 onDrop,
199243 noClick : true ,
200244 maxSize,
201- accept : acceptedFileTypes ,
245+ accept : acceptedFileTypes , // Only .sql files regardless of mime type
202246 //maxFiles: MAX_FILES,
203247 } ;
204248
@@ -230,18 +274,19 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
230274 } ;
231275
232276 const cancelAllUploads = useCallback ( ( ) => {
233- // Clear all upload intervals
234- dispatch ( deleteBatch ( { batchId, headers : null } ) ) ;
235-
236- Object . values ( uploadIntervals ) . forEach ( interval => clearInterval ( interval ) ) ;
237- setUploadIntervals ( { } ) ;
238- setUploadingFiles ( [ ] ) ;
239- setUploadState ( 'IDLE' ) ;
240- onUploadStateChange ?.( 'IDLE' ) ;
241- setShowCancelDialog ( false ) ;
242- setShowLogoCancelDialog ( false ) ;
243- //setBatchId();
244- startNewBatch ( ) ;
277+ // Clear all upload intervals
278+ dispatch ( deleteBatch ( { batchId, headers : null } ) ) ;
279+
280+ Object . values ( uploadIntervals ) . forEach ( interval => clearInterval ( interval ) ) ;
281+ setUploadIntervals ( { } ) ;
282+ setUploadingFiles ( [ ] ) ;
283+ setUploadState ( 'IDLE' ) ;
284+ onUploadStateChange ?.( 'IDLE' ) ;
285+ setShowCancelDialog ( false ) ;
286+ setShowLogoCancelDialog ( false ) ;
287+ setFileRejectionErrors ( [ ] ) ; // Clear error notification when cancel is clicked
288+ //setBatchId();
289+ startNewBatch ( ) ;
245290 } , [ uploadIntervals , onUploadStateChange ] ) ;
246291
247292 useEffect ( ( ) => {
@@ -521,15 +566,28 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
521566 </ div >
522567
523568 < div style = { { display : 'flex' , flexDirection : 'column' , gap : '13px' , width : '837px' , paddingBottom : 10 , borderRadius : '4px' , } } >
569+ { /* Show file rejection errors for invalid type or size */ }
570+ { fileRejectionErrors . length > 0 && (
571+ < MessageBar
572+ messageBarType = { MessageBarType . error }
573+ isMultiline = { true }
574+ styles = { messageBarErrorStyles }
575+ >
576+ < div style = { { display : "flex" , alignItems : "center" } } >
577+ < X strokeWidth = "2.5px" color = "#d83b01" size = "16px" style = { { marginRight : "8px" } } />
578+ < span > { fileRejectionErrors [ 0 ] } </ span >
579+ </ div >
580+ { fileRejectionErrors . slice ( 1 ) . map ( ( err , idx ) => (
581+ < div key = { idx } style = { { marginLeft : "24px" , marginTop : "2px" } } > { err } </ div >
582+ ) ) }
583+ </ MessageBar >
584+ ) }
524585 { /* Show network error message bar if any file has error */ }
525586 { uploadingFiles . some ( f => f . status === 'error' ) && (
526587 < MessageBar
527588 messageBarType = { MessageBarType . error }
528589 isMultiline = { false }
529- styles = { {
530- root : { display : "flex" , alignItems : "left" , background : "#fff4f4" } ,
531- icon : { display : "none" } ,
532- } }
590+ styles = { messageBarErrorStyles }
533591 >
534592 < div style = { { display : "flex" , alignItems : "left" } } >
535593 < X
@@ -548,10 +606,7 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
548606 < MessageBar
549607 messageBarType = { MessageBarType . success }
550608 isMultiline = { false }
551- styles = { {
552- root : { display : "flex" , alignItems : "left" } ,
553- icon : { display : "none" } ,
554- } }
609+ styles = { messageBarSuccessStyles }
555610 >
556611 < div style = { { display : "flex" , alignItems : "left" } } >
557612 < CircleCheck
@@ -571,9 +626,7 @@ const FileUploadZone: React.FC<FileUploadZoneProps> = ({
571626 isMultiline = { false }
572627 onDismiss = { ( ) => setFileLimitExceeded ( false ) }
573628 dismissButtonAriaLabel = "Close"
574- styles = { {
575- root : { display : "flex" , alignItems : "center" } ,
576- } }
629+ styles = { messageBarWarningStyles }
577630 >
578631 < X
579632 strokeWidth = "2.5px"
0 commit comments