The Component
import { useState, useCallback } from 'react';
export default function FileUpload({ onUpload }) {
const [progress, setProgress] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const [file, setFile] = useState(null);
const handleDrop = useCallback((e) => {
e.preventDefault();
setIsDragging(false);
const droppedFile = e.dataTransfer.files[0];
if (droppedFile) {
setFile(droppedFile);
uploadFile(droppedFile);
}
}, []);
const uploadFile = async (file) => {
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
setProgress(percent);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
onUpload?.(response);
}
setProgress(0);
};
xhr.onerror = () => {
alert('Upload failed');
setProgress(0);
};
xhr.open('POST', '/api/upload');
xhr.send(formData);
};
return (
<div
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
onDragLeave={() => setIsDragging(false)}
onDrop={handleDrop}
className={`border-2 border-dashed rounded-lg p-8 text-center
${isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
>
<input
type="file"
onChange={(e) => {
const f = e.target.files[0];
if (f) { setFile(f); uploadFile(f); }
}}
className="hidden"
id="file-input"
/>
<label htmlFor="file-input" className="cursor-pointer">
<p>Drag & drop or click to upload</p>
</label>
{progress > 0 && (
<div className="mt-4">
<div className="bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<p className="mt-2">{progress}%</p>
</div>
)}
</div>
);
}
Laravel Backend
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|max:10240', // 10MB
]);
$path = $request->file('file')->store('uploads', 'public');
return response()->json([
'path' => $path,
'url' => Storage::url($path),
]);
}
Comments (0)
Leave a Comment
No comments yet. Be the first to share your thoughts!