From f69ca010b80703389fffe75fc6dca907e53df74d Mon Sep 17 00:00:00 2001 From: diogo464 Date: Mon, 11 Aug 2025 13:40:27 +0100 Subject: basic file upload --- frontend/components/FileUpload.tsx | 262 +++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 frontend/components/FileUpload.tsx (limited to 'frontend/components') diff --git a/frontend/components/FileUpload.tsx b/frontend/components/FileUpload.tsx new file mode 100644 index 0000000..8fbb919 --- /dev/null +++ b/frontend/components/FileUpload.tsx @@ -0,0 +1,262 @@ +'use client' + +import { useState, useRef } from 'react' +import { UPLOAD_MAX_FILES, UPLOAD_MAX_FILE_SIZE } from '@/lib/constants' + +// Client-side file validation function +function validateFile(file: File): { allowed: boolean; reason?: string } { + if (file.size > UPLOAD_MAX_FILE_SIZE) { + return { allowed: false, reason: `File size exceeds ${UPLOAD_MAX_FILE_SIZE / (1024 * 1024)}MB limit` }; + } + + return { allowed: true }; +} + +interface FileUploadProps { + targetPath: string + onUploadComplete?: () => void +} + +interface UploadResult { + filename: string + success: boolean + message: string +} + +export default function FileUpload({ targetPath, onUploadComplete }: FileUploadProps) { + const [isDragOver, setIsDragOver] = useState(false) + const [isUploading, setIsUploading] = useState(false) + const [selectedFiles, setSelectedFiles] = useState([]) + const [uploadResults, setUploadResults] = useState([]) + const [showResults, setShowResults] = useState(false) + const fileInputRef = useRef(null) + + const handleFileSelect = (files: FileList) => { + const fileArray = Array.from(files) + + // Validate file count + if (fileArray.length > UPLOAD_MAX_FILES) { + alert(`Too many files selected. Maximum ${UPLOAD_MAX_FILES} files allowed.`) + return + } + + // Validate each file + const validFiles: File[] = [] + for (const file of fileArray) { + const validation = validateFile(file) + if (!validation.allowed) { + alert(`File '${file.name}': ${validation.reason}`) + continue + } + validFiles.push(file) + } + + setSelectedFiles(validFiles) + setUploadResults([]) + setShowResults(false) + } + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault() + setIsDragOver(true) + } + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault() + setIsDragOver(false) + } + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault() + setIsDragOver(false) + + if (e.dataTransfer.files) { + handleFileSelect(e.dataTransfer.files) + } + } + + const handleFileInputChange = (e: React.ChangeEvent) => { + if (e.target.files) { + handleFileSelect(e.target.files) + } + } + + const handleUpload = async () => { + if (selectedFiles.length === 0) return + + setIsUploading(true) + setUploadResults([]) + + try { + const formData = new FormData() + selectedFiles.forEach(file => { + formData.append('files', file) + }) + formData.append('targetPath', targetPath) + + const response = await fetch('/api/upload', { + method: 'POST', + body: formData, + }) + + const result = await response.json() + + if (response.ok) { + setUploadResults(result.results || []) + setShowResults(true) + setSelectedFiles([]) + + // Clear file input + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + + // Refresh the page after successful upload + setTimeout(() => { + window.location.reload() + }, 1000) + } else { + alert(`Upload failed: ${result.error}`) + } + } catch (error) { + console.error('Upload error:', error) + alert('Upload failed: Network error') + } finally { + setIsUploading(false) + } + } + + const removeFile = (index: number) => { + setSelectedFiles(prev => prev.filter((_, i) => i !== index)) + } + + const clearResults = () => { + setShowResults(false) + setUploadResults([]) + } + + return ( +
+

Upload Files

+ + {/* Upload Results */} + {showResults && uploadResults.length > 0 && ( +
+
+

Upload Results

+ +
+
+ {uploadResults.map((result, index) => ( +
+ {result.success ? '✓' : '✗'} + {result.filename}: + {result.message} +
+ ))} +
+
+ )} + + {/* File Drop Zone */} +
+
+
📁
+
+

Drop files here or click to browse

+

+ Maximum {UPLOAD_MAX_FILES} files, {UPLOAD_MAX_FILE_SIZE / (1024 * 1024)}MB each +

+
+ + +
+
+ + {/* Selected Files */} + {selectedFiles.length > 0 && ( +
+

+ Selected Files ({selectedFiles.length}) +

+
+ {selectedFiles.map((file, index) => ( +
+
+ 📄 +
+
+ {file.name} +
+
+ {(file.size / 1024 / 1024).toFixed(2)} MB +
+
+
+ +
+ ))} +
+ + +
+ )} +
+ ) +} \ No newline at end of file -- cgit