ifixkart-admin/components/ui/BulkImageUpload.tsx

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>
);
};