feat(media): enable full page drag and drop for all media types with progress bar
All checks were successful
Deploy Canina / deploy (push) Successful in 59s

This commit is contained in:
parsa aghaei 2026-08-17 16:30:16 +03:30
parent 813c96742d
commit 387519ff6b
2 changed files with 35 additions and 8 deletions

View File

@ -46,6 +46,7 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [uploadProgress, setUploadProgress] = useState<number | null>(null);
const [filterType, setFilterType] = useState<'all' | 'image' | 'video' | 'audio' | 'pdf'>('all');
const [dragOver, setDragOver] = useState(false);
const fetchMedia = useCallback(async () => {
try {
@ -187,13 +188,11 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
}
};
const [dragOver, setDragOver] = useState(false);
const handleDrop = async (e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
if (!e.dataTransfer.files || e.dataTransfer.files.length === 0) return;
const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
const files = Array.from(e.dataTransfer.files);
if (files.length === 0) return;
setIsUploading(true);
@ -203,7 +202,12 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
const formData = new FormData();
formData.append('file', file);
return api.post('/admin/media/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const total = progressEvent.total || file.size;
const percent = Math.round((progressEvent.loaded * 100) / total);
setUploadProgress(percent);
}
});
});
const results = await Promise.allSettled(uploadPromises);
@ -212,14 +216,18 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
});
if (successCount > 0) {
toast.success(`${successCount} تصویر با موفقیت آپلود شد`);
toast.success(`${successCount} فایل با موفقیت بارگذاری شد`);
await fetchMedia();
}
if (successCount < files.length) {
toast.error(`خطا در بارگذاری ${files.length - successCount} فایل`);
}
} catch (error) {
console.error('Drop upload failed', error);
toast.error('خطا در آپلود تصاویر رها شده');
toast.error('خطا در آپلود فایل‌های رها شده');
} finally {
setIsUploading(false);
setUploadProgress(null);
}
};
@ -243,7 +251,7 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
{dragOver && (
<div className="absolute inset-0 z-50 bg-purple-600/90 backdrop-blur-xs flex flex-col items-center justify-center text-white pointer-events-none animate-in fade-in">
<Upload className="w-16 h-16 mb-2 animate-bounce" />
<p className="text-xl font-bold">تصاویر را اینجا رها کنید تا آپلود شوند</p>
<p className="text-xl font-bold">فایلها (تصویر، ویدئو، پادکست، PDF) را اینجا رها کنید تا آپلود شوند</p>
</div>
)}

View File

@ -257,7 +257,26 @@ export default function MediaManager() {
const totalSize = mediaList.reduce((sum, m) => sum + m.size, 0);
return (
<div className="space-y-6 font-vazir" dir="rtl">
<div
className="space-y-6 font-vazir relative min-h-[80vh]"
dir="rtl"
onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
onDragLeave={(e) => {
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDragOver(false);
}
}}
onDrop={handleDrop}
>
{/* Full Page Drag & Drop Overlay */}
{isDragOver && (
<div className="fixed inset-0 z-50 bg-purple-600/90 backdrop-blur-xs flex flex-col items-center justify-center text-white pointer-events-none animate-in fade-in">
<Upload className="w-16 h-16 mb-4 animate-bounce" />
<p className="text-2xl font-black mb-2">فایلها را در هر جای صفحه رها کنید تا آپلود شوند</p>
<p className="text-sm font-medium opacity-80">پشتیبانی از تصاویر، ویدئوها، پادکستهای صوتی و اسناد PDF</p>
</div>
)}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">