summaryrefslogtreecommitdiff
path: root/frontend/components/FileUpload.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/components/FileUpload.tsx')
-rw-r--r--frontend/components/FileUpload.tsx262
1 files changed, 0 insertions, 262 deletions
diff --git a/frontend/components/FileUpload.tsx b/frontend/components/FileUpload.tsx
deleted file mode 100644
index 8fbb919..0000000
--- a/frontend/components/FileUpload.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
1'use client'
2
3import { useState, useRef } from 'react'
4import { UPLOAD_MAX_FILES, UPLOAD_MAX_FILE_SIZE } from '@/lib/constants'
5
6// Client-side file validation function
7function validateFile(file: File): { allowed: boolean; reason?: string } {
8 if (file.size > UPLOAD_MAX_FILE_SIZE) {
9 return { allowed: false, reason: `File size exceeds ${UPLOAD_MAX_FILE_SIZE / (1024 * 1024)}MB limit` };
10 }
11
12 return { allowed: true };
13}
14
15interface FileUploadProps {
16 targetPath: string
17 onUploadComplete?: () => void
18}
19
20interface UploadResult {
21 filename: string
22 success: boolean
23 message: string
24}
25
26export default function FileUpload({ targetPath, onUploadComplete }: FileUploadProps) {
27 const [isDragOver, setIsDragOver] = useState(false)
28 const [isUploading, setIsUploading] = useState(false)
29 const [selectedFiles, setSelectedFiles] = useState<File[]>([])
30 const [uploadResults, setUploadResults] = useState<UploadResult[]>([])
31 const [showResults, setShowResults] = useState(false)
32 const fileInputRef = useRef<HTMLInputElement>(null)
33
34 const handleFileSelect = (files: FileList) => {
35 const fileArray = Array.from(files)
36
37 // Validate file count
38 if (fileArray.length > UPLOAD_MAX_FILES) {
39 alert(`Too many files selected. Maximum ${UPLOAD_MAX_FILES} files allowed.`)
40 return
41 }
42
43 // Validate each file
44 const validFiles: File[] = []
45 for (const file of fileArray) {
46 const validation = validateFile(file)
47 if (!validation.allowed) {
48 alert(`File '${file.name}': ${validation.reason}`)
49 continue
50 }
51 validFiles.push(file)
52 }
53
54 setSelectedFiles(validFiles)
55 setUploadResults([])
56 setShowResults(false)
57 }
58
59 const handleDragOver = (e: React.DragEvent) => {
60 e.preventDefault()
61 setIsDragOver(true)
62 }
63
64 const handleDragLeave = (e: React.DragEvent) => {
65 e.preventDefault()
66 setIsDragOver(false)
67 }
68
69 const handleDrop = (e: React.DragEvent) => {
70 e.preventDefault()
71 setIsDragOver(false)
72
73 if (e.dataTransfer.files) {
74 handleFileSelect(e.dataTransfer.files)
75 }
76 }
77
78 const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
79 if (e.target.files) {
80 handleFileSelect(e.target.files)
81 }
82 }
83
84 const handleUpload = async () => {
85 if (selectedFiles.length === 0) return
86
87 setIsUploading(true)
88 setUploadResults([])
89
90 try {
91 const formData = new FormData()
92 selectedFiles.forEach(file => {
93 formData.append('files', file)
94 })
95 formData.append('targetPath', targetPath)
96
97 const response = await fetch('/api/upload', {
98 method: 'POST',
99 body: formData,
100 })
101
102 const result = await response.json()
103
104 if (response.ok) {
105 setUploadResults(result.results || [])
106 setShowResults(true)
107 setSelectedFiles([])
108
109 // Clear file input
110 if (fileInputRef.current) {
111 fileInputRef.current.value = ''
112 }
113
114 // Refresh the page after successful upload
115 setTimeout(() => {
116 window.location.reload()
117 }, 1000)
118 } else {
119 alert(`Upload failed: ${result.error}`)
120 }
121 } catch (error) {
122 console.error('Upload error:', error)
123 alert('Upload failed: Network error')
124 } finally {
125 setIsUploading(false)
126 }
127 }
128
129 const removeFile = (index: number) => {
130 setSelectedFiles(prev => prev.filter((_, i) => i !== index))
131 }
132
133 const clearResults = () => {
134 setShowResults(false)
135 setUploadResults([])
136 }
137
138 return (
139 <div className="mb-6 p-4 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800">
140 <h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-gray-100">Upload Files</h2>
141
142 {/* Upload Results */}
143 {showResults && uploadResults.length > 0 && (
144 <div className="mb-4 p-3 bg-white dark:bg-gray-900 rounded border">
145 <div className="flex justify-between items-center mb-2">
146 <h3 className="font-medium text-gray-900 dark:text-gray-100">Upload Results</h3>
147 <button
148 onClick={clearResults}
149 className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
150 >
151 Clear
152 </button>
153 </div>
154 <div className="space-y-1">
155 {uploadResults.map((result, index) => (
156 <div
157 key={index}
158 className={`text-sm flex items-center gap-2 ${
159 result.success
160 ? 'text-green-600 dark:text-green-400'
161 : 'text-red-600 dark:text-red-400'
162 }`}
163 >
164 <span>{result.success ? '✓' : '✗'}</span>
165 <span className="font-medium">{result.filename}:</span>
166 <span>{result.message}</span>
167 </div>
168 ))}
169 </div>
170 </div>
171 )}
172
173 {/* File Drop Zone */}
174 <div
175 className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
176 isDragOver
177 ? 'border-blue-400 bg-blue-50 dark:bg-blue-900/20'
178 : 'border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500'
179 }`}
180 onDragOver={handleDragOver}
181 onDragLeave={handleDragLeave}
182 onDrop={handleDrop}
183 >
184 <div className="space-y-2">
185 <div className="text-4xl">📁</div>
186 <div className="text-gray-600 dark:text-gray-300">
187 <p className="font-medium">Drop files here or click to browse</p>
188 <p className="text-sm">
189 Maximum {UPLOAD_MAX_FILES} files, {UPLOAD_MAX_FILE_SIZE / (1024 * 1024)}MB each
190 </p>
191 </div>
192 <input
193 ref={fileInputRef}
194 type="file"
195 multiple
196 className="hidden"
197 onChange={handleFileInputChange}
198 />
199 <button
200 type="button"
201 onClick={() => fileInputRef.current?.click()}
202 disabled={isUploading}
203 className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-400 transition-colors"
204 >
205 Browse Files
206 </button>
207 </div>
208 </div>
209
210 {/* Selected Files */}
211 {selectedFiles.length > 0 && (
212 <div className="mt-4">
213 <h3 className="font-medium mb-2 text-gray-900 dark:text-gray-100">
214 Selected Files ({selectedFiles.length})
215 </h3>
216 <div className="space-y-2">
217 {selectedFiles.map((file, index) => (
218 <div
219 key={index}
220 className="flex items-center justify-between p-2 bg-white dark:bg-gray-900 rounded border"
221 >
222 <div className="flex items-center gap-2">
223 <span className="text-gray-400">📄</span>
224 <div>
225 <div className="text-sm font-medium text-gray-900 dark:text-gray-100">
226 {file.name}
227 </div>
228 <div className="text-xs text-gray-500 dark:text-gray-400">
229 {(file.size / 1024 / 1024).toFixed(2)} MB
230 </div>
231 </div>
232 </div>
233 <button
234 onClick={() => removeFile(index)}
235 disabled={isUploading}
236 className="text-red-500 hover:text-red-700 disabled:text-gray-400 text-sm"
237 >
238 Remove
239 </button>
240 </div>
241 ))}
242 </div>
243
244 <button
245 onClick={handleUpload}
246 disabled={isUploading || selectedFiles.length === 0}
247 className="mt-3 px-6 py-2 bg-green-500 text-white rounded hover:bg-green-600 disabled:bg-gray-400 transition-colors flex items-center gap-2"
248 >
249 {isUploading ? (
250 <>
251 <div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>
252 Uploading...
253 </>
254 ) : (
255 `Upload ${selectedFiles.length} file${selectedFiles.length > 1 ? 's' : ''}`
256 )}
257 </button>
258 </div>
259 )}
260 </div>
261 )
262} \ No newline at end of file