134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useRef } from 'react';
|
|
import { Upload, Loader2, Images } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { getAccessToken } from '@/services/api/client';
|
|
import { API_BASE_URL } from '@/services/api/config';
|
|
|
|
interface BulkImageUploadProps {
|
|
entityType: string;
|
|
onUploadBatch: (urls: string[]) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export const BulkImageUpload: React.FC<BulkImageUploadProps> = ({
|
|
entityType,
|
|
onUploadBatch,
|
|
className = '',
|
|
}) => {
|
|
const [uploading, setUploading] = useState(false);
|
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const handleFiles = async (fileList: FileList | File[]) => {
|
|
const files = Array.from(fileList);
|
|
if (files.length === 0) return;
|
|
|
|
const allowedExtensions = ['jpg', 'jpeg', 'png', 'webp'];
|
|
const validFiles = files.filter((f) => {
|
|
const ext = f.name.split('.').pop()?.toLowerCase() || '';
|
|
return (f.type && f.type.startsWith('image/')) || allowedExtensions.includes(ext);
|
|
});
|
|
|
|
if (validFiles.length === 0) {
|
|
toast.error('Only JPG, JPEG, PNG, or WebP images are supported.');
|
|
return;
|
|
}
|
|
|
|
setUploading(true);
|
|
const token = getAccessToken();
|
|
const headers: Record<string, string> = {};
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
try {
|
|
const uploads = await Promise.all(
|
|
validFiles.map(async (file) => {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('entity_type', entityType);
|
|
formData.append('entity_id', 'temp_' + Math.random().toString(36).substring(2, 11));
|
|
|
|
const res = await fetch(`${API_BASE_URL}/api/v1/files/upload`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: formData,
|
|
});
|
|
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
const rawPath = data?.webp_path || data?.storage_path || data?.raw_path || data?.url || '';
|
|
if (rawPath) {
|
|
return rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
|
|
}
|
|
}
|
|
return null;
|
|
})
|
|
);
|
|
|
|
const uploadedUrls = uploads.filter((u): u is string => Boolean(u));
|
|
|
|
if (uploadedUrls.length > 0) {
|
|
toast.success(`Successfully uploaded ${uploadedUrls.length} image(s)!`);
|
|
onUploadBatch(uploadedUrls);
|
|
} else {
|
|
toast.error('Failed to upload image(s).');
|
|
}
|
|
} catch (err) {
|
|
console.error('Bulk image upload error:', err);
|
|
toast.error('Bulk image upload failed.');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
onDragOver={(e) => {
|
|
e.preventDefault();
|
|
setIsDragOver(true);
|
|
}}
|
|
onDragLeave={() => setIsDragOver(false)}
|
|
onDrop={(e) => {
|
|
e.preventDefault();
|
|
setIsDragOver(false);
|
|
if (e.dataTransfer.files) handleFiles(e.dataTransfer.files);
|
|
}}
|
|
onClick={() => fileInputRef.current?.click()}
|
|
className={`relative border-2 border-dashed rounded-xl p-4 transition-all cursor-pointer flex items-center justify-center text-center select-none ${
|
|
isDragOver
|
|
? 'border-red-500 bg-red-500/10'
|
|
: 'border-slate-700 hover:border-slate-600 bg-slate-800/40 hover:bg-slate-800/80'
|
|
} ${className}`}
|
|
>
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
multiple
|
|
accept="image/jpeg,image/png,image/webp,image/jpg"
|
|
onChange={(e) => e.target.files && handleFiles(e.target.files)}
|
|
className="hidden"
|
|
/>
|
|
|
|
<div className="flex items-center gap-3">
|
|
{uploading ? (
|
|
<Loader2 className="w-5 h-5 text-red-500 animate-spin" />
|
|
) : (
|
|
<div className="p-2 bg-slate-700/50 rounded-lg text-red-400">
|
|
<Images size={20} />
|
|
</div>
|
|
)}
|
|
<div className="text-left">
|
|
<p className="text-xs font-bold text-slate-200">
|
|
{uploading ? 'Uploading multiple images...' : 'Bulk Drag & Drop Images'}
|
|
</p>
|
|
<p className="text-[10px] text-slate-400">
|
|
Drag & drop multiple files or click to multi-select PNG, JPG, WEBP
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|