'use client'; import { useState, useCallback, useRef } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { cn } from '@/lib/utils'; import { useUploadFile, useUploadMultipleFiles } from '@/hooks/use-files'; import type { FileUploadOptions } from '@fischerx/types'; interface FileUploaderProps { onUploadSuccess?: (files: any[]) => void; accept?: string; maxSize?: number; multiple?: boolean; category?: string; className?: string; } export function FileUploader({ onUploadSuccess, accept, maxSize = 10 * 1024 * 1024, multiple = false, category, className, }: FileUploaderProps) { const [isDragging, setIsDragging] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const fileInputRef = useRef(null); const uploadFile = useUploadFile(); const uploadMultipleFiles = useUploadMultipleFiles(); const validateFile = (file: File): string | null => { if (maxSize && file.size > maxSize) { return `File size must be less than ${(maxSize / 1024 / 1024).toFixed(0)}MB`; } if (accept) { const acceptedTypes = accept.split(','); const fileType = file.type; const fileName = file.name; const isAccepted = acceptedTypes.some(type => { if (type.startsWith('.')) { return fileName.toLowerCase().endsWith(type.toLowerCase()); } if (type.endsWith('/*')) { return fileType.startsWith(type.replace('/*', '/')); } return fileType === type; }); if (!isAccepted) { return 'File type not accepted'; } } return null; }; const handleFiles = useCallback( async (selectedFiles: FileList | File[]) => { const files = Array.from(selectedFiles); const errors: string[] = []; files.forEach((file) => { const error = validateFile(file); if (error) { errors.push(`${file.name}: ${error}`); } }); if (errors.length > 0) { alert(errors.join('\n')); return; } const options: FileUploadOptions = category ? { category } : {}; try { setUploadProgress(0); let result; if (multiple && files.length > 1) { result = await uploadMultipleFiles.mutateAsync({ files, options }); if (result.success && onUploadSuccess) { onUploadSuccess(result.data); } } else if (files.length === 1) { result = await uploadFile.mutateAsync({ file: files[0], options }); if (result.success && onUploadSuccess) { onUploadSuccess([result.data]); } } setUploadProgress(100); setTimeout(() => setUploadProgress(0), 1000); } catch (error) { console.error('Upload failed:', error); alert('Upload failed. Please try again.'); } }, [uploadFile, uploadMultipleFiles, category, multiple, onUploadSuccess, maxSize, accept] ); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }, []); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); if (e.dataTransfer.files.length > 0) { handleFiles(e.dataTransfer.files); } }, [handleFiles] ); const handleFileSelect = useCallback( (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleFiles(e.target.files); } e.target.value = ''; }, [handleFiles] ); const isUploading = uploadFile.isPending || uploadMultipleFiles.isPending; return ( Upload Files
fileInputRef.current?.click()} className={cn( 'flex flex-col items-center justify-center p-8 border-2 border-dashed rounded-lg cursor-pointer transition-colors', isDragging ? 'border-primary bg-primary/10' : 'border-gray-300 hover:border-primary hover:bg-gray-50' )} > {isUploading ? (
Uploading...
) : ( <>
📁

{isDragging ? 'Drop files here' : 'Drag and drop files here'}

or click to browse

)}
{uploadFile.error && (
Error uploading file: {uploadFile.error.message}
)} {uploadMultipleFiles.error && (
Error uploading files: {uploadMultipleFiles.error.message}
)} ); }