'use client'; import { useState, useEffect } from 'react'; import { assetsApi } from '@/lib/api'; import { X, Search, Check, FileImage, FileVideo, FileAudio, FileText, Loader2 } from 'lucide-react'; import { toast } from 'react-hot-toast'; interface Asset { id: string; original_filename: string; mime_type: string; file_size_bytes: number; created_at: string; } interface AssetPickerModalProps { isOpen: boolean; onClose: () => void; onConfirm: (assets: Asset[]) => void; allowedTypes?: string[]; // e.g. ['image/', 'video/'] prefixes maxSelect?: number; title?: string; } export default function AssetPickerModal({ isOpen, onClose, onConfirm, allowedTypes = [], maxSelect, title = "Select from Library" }: AssetPickerModalProps) { const [assets, setAssets] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(''); const [selected, setSelected] = useState>(new Set()); useEffect(() => { if (isOpen) { loadAssets(); setSelected(new Set()); // Reset selection on open } }, [isOpen]); const loadAssets = async () => { setLoading(true); try { // Fetch reasonably large number of recent assets // In a real app, implement pagination or infinite scroll const response = await assetsApi.list({ limit: 100, sort: 'created_at', order: 'desc' }); setAssets(response.data); } catch (error) { console.error('Failed to load assets', error); toast.error('Failed to load library'); } finally { setLoading(false); } }; const toggleSelection = (asset: Asset) => { const newSelected = new Set(selected); if (newSelected.has(asset.id)) { newSelected.delete(asset.id); } else { if (maxSelect && newSelected.size >= maxSelect) { toast.error(`Maximum ${maxSelect} items allowed`); return; } newSelected.add(asset.id); } setSelected(newSelected); }; const handleConfirm = () => { const selectedAssets = assets.filter(a => selected.has(a.id)); onConfirm(selectedAssets); onClose(); }; const filteredAssets = assets.filter(asset => { // Type filter if (allowedTypes.length > 0) { if (!allowedTypes.some(type => asset.mime_type.startsWith(type))) return false; } // Search filter if (search) { return asset.original_filename.toLowerCase().includes(search.toLowerCase()); } return true; }); if (!isOpen) return null; return (
{/* Header */}

{title}

{/* Toolbar */}
setSearch(e.target.value)} className="w-full bg-black/40 border border-gray-700 rounded-lg pl-9 pr-4 py-2 text-sm text-white focus:ring-1 focus:ring-forge-yellow focus:border-forge-yellow outline-none" />
{selected.size} selected
{/* content */}
{loading ? (
) : filteredAssets.length === 0 ? (

No matching files found

) : (
{filteredAssets.map(asset => { const isSelected = selected.has(asset.id); return (
toggleSelection(asset)} className={`group relative aspect-square rounded-xl overflow-hidden cursor-pointer border-2 transition-all ${isSelected ? 'border-forge-yellow ring-2 ring-forge-yellow/20' : 'border-transparent hover:border-gray-600' }`} > {/* Thumbnail */}
{asset.mime_type.startsWith('image/') ? ( {asset.original_filename} ) : (
{asset.mime_type.startsWith('video/') ? : asset.mime_type.startsWith('audio/') ? : }
)}
{/* Overlay */}

{asset.original_filename}

); })}
)}
{/* Footer */}
); }