“vinit5112”
Add all code
deb090d
raw
history blame
7.68 kB
import React, { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { motion, AnimatePresence } from 'framer-motion';
import {
CloudArrowUpIcon,
DocumentIcon,
CheckCircleIcon,
XCircleIcon,
XMarkIcon
} from '@heroicons/react/24/outline';
import { uploadDocument } from '../services/api';
import toast from 'react-hot-toast';
const FileUploader = ({ darkMode, onClose }) => {
const [uploading, setUploading] = useState(false);
const [uploadedFiles, setUploadedFiles] = useState([]);
const onDrop = useCallback(async (acceptedFiles) => {
setUploading(true);
for (const file of acceptedFiles) {
try {
const formData = new FormData();
formData.append('file', file);
await uploadDocument(formData);
setUploadedFiles(prev => [...prev, {
name: file.name,
size: file.size,
status: 'success'
}]);
toast.success(`${file.name} uploaded successfully!`);
} catch (error) {
setUploadedFiles(prev => [...prev, {
name: file.name,
size: file.size,
status: 'error'
}]);
toast.error(`Failed to upload ${file.name}`);
}
}
setUploading(false);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'application/pdf': ['.pdf'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
'text/plain': ['.txt']
},
multiple: true
});
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const removeFile = (index) => {
setUploadedFiles(prev => prev.filter((_, i) => i !== index));
};
return (
<div className="space-y-4">
{/* Dropzone */}
<motion.div
{...getRootProps()}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className={`file-drop-zone border-2 border-dashed rounded-2xl p-8 text-center cursor-pointer transition-all ${
isDragActive
? darkMode
? 'border-primary-400 bg-primary-900/20'
: 'border-primary-500 bg-primary-50'
: darkMode
? 'border-gray-600 hover:border-gray-500 bg-gray-800'
: 'border-gray-300 hover:border-gray-400 bg-gray-50'
}`}
>
<input {...getInputProps()} />
<CloudArrowUpIcon className={`w-12 h-12 mx-auto mb-4 ${
isDragActive
? darkMode ? 'text-primary-400' : 'text-primary-500'
: darkMode ? 'text-gray-400' : 'text-gray-500'
}`} />
<h3 className={`text-lg font-semibold mb-2 ${
darkMode ? 'text-white' : 'text-gray-900'
}`}>
{isDragActive ? 'Drop files here' : 'Upload study materials'}
</h3>
<p className={`mb-4 ${
darkMode ? 'text-gray-400' : 'text-gray-600'
}`}>
Drag & drop files here, or click to browse
</p>
<div className="flex justify-center space-x-2">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
darkMode
? 'bg-blue-900/30 text-blue-400'
: 'bg-blue-100 text-blue-700'
}`}>
PDF
</span>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
darkMode
? 'bg-green-900/30 text-green-400'
: 'bg-green-100 text-green-700'
}`}>
DOCX
</span>
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
darkMode
? 'bg-purple-900/30 text-purple-400'
: 'bg-purple-100 text-purple-700'
}`}>
TXT
</span>
</div>
</motion.div>
{/* Upload Progress */}
{uploading && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className={`p-4 rounded-lg ${
darkMode ? 'bg-gray-800' : 'bg-gray-100'
}`}
>
<div className="flex items-center space-x-3">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-primary-500"></div>
<span className={`${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
Uploading files...
</span>
</div>
</motion.div>
)}
{/* Uploaded Files List */}
<AnimatePresence>
{uploadedFiles.length > 0 && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="space-y-2"
>
<h4 className={`font-medium ${
darkMode ? 'text-gray-300' : 'text-gray-700'
}`}>
Uploaded Files
</h4>
{uploadedFiles.map((file, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className={`flex items-center justify-between p-3 rounded-lg ${
darkMode ? 'bg-gray-800' : 'bg-gray-100'
}`}
>
<div className="flex items-center space-x-3">
<DocumentIcon className={`w-5 h-5 ${
darkMode ? 'text-gray-400' : 'text-gray-500'
}`} />
<div>
<p className={`text-sm font-medium ${
darkMode ? 'text-white' : 'text-gray-900'
}`}>
{file.name}
</p>
<p className={`text-xs ${
darkMode ? 'text-gray-500' : 'text-gray-400'
}`}>
{formatFileSize(file.size)}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
{file.status === 'success' ? (
<CheckCircleIcon className="w-5 h-5 text-green-500" />
) : (
<XCircleIcon className="w-5 h-5 text-red-500" />
)}
<button
onClick={() => removeFile(index)}
className={`p-1 rounded transition-colors ${
darkMode
? 'hover:bg-gray-700 text-gray-400'
: 'hover:bg-gray-200 text-gray-500'
}`}
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>
</motion.div>
))}
</motion.div>
)}
</AnimatePresence>
{/* Close Button */}
{onClose && (
<div className="flex justify-end">
<button
onClick={onClose}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
darkMode
? 'bg-gray-700 hover:bg-gray-600 text-white'
: 'bg-gray-200 hover:bg-gray-300 text-gray-700'
}`}
>
Close
</button>
</div>
)}
</div>
);
};
export default FileUploader;