import { IconLoader2 as Loader2, IconUpload as Upload, IconX as X } from '@tabler/icons-react'; import { useCallback, useRef, useState } from 'react'; import { supabase } from '@/lib/supabase'; import { cn } from '@/lib/utils'; import { Button } from './ui/button'; interface FileUploadProps { bucket: string; path: string; accept?: string; maxSize?: number; onUpload: (url: string) => void; onError?: (error: string) => void; className?: string; children?: React.ReactNode; } /** * Drag-and-drop file upload component using Supabase Storage. * * Usage: * setAvatarUrl(url)} * /> */ export function FileUpload({ bucket, path, accept, maxSize = 10 * 1024 * 1024, onUpload, onError, className, children, }: FileUploadProps) { const [uploading, setUploading] = useState(false); const [dragOver, setDragOver] = useState(false); const inputRef = useRef(null); const upload = useCallback( async (file: File) => { if (file.size > maxSize) { onError?.(`File too large. Max size: ${Math.round(maxSize / 1024 / 1024)}MB`); return; } setUploading(true); try { const ext = file.name.split('.').pop(); const filePath = ext ? `${path}.${ext}` : path; const { error } = await supabase.storage.from(bucket).upload(filePath, file, { upsert: true, }); if (error) throw error; const { data: { publicUrl }, } = supabase.storage.from(bucket).getPublicUrl(filePath); onUpload(publicUrl); } catch (err) { onError?.(err instanceof Error ? err.message : 'Upload failed'); } finally { setUploading(false); } }, [bucket, path, maxSize, onUpload, onError] ); function handleDrop(e: React.DragEvent) { e.preventDefault(); setDragOver(false); const file = e.dataTransfer.files[0]; if (file) upload(file); } function handleChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (file) upload(file); // Reset input so same file can be re-selected e.target.value = ''; } if (children) { return ( <> ); } return ( ); } /** * Avatar upload component with preview. */ export function AvatarUpload({ userId, currentUrl, onUpload, size = 'lg', }: { userId: string; currentUrl?: string | null; onUpload: (url: string) => void; size?: 'sm' | 'lg'; }) { const [url, setUrl] = useState(currentUrl || ''); const [error, setError] = useState(''); function handleUpload(newUrl: string) { setUrl(newUrl); setError(''); onUpload(newUrl); } const sizeClass = size === 'sm' ? 'size-16' : 'size-20'; return (
{url ? ( Avatar ) : ( ? )}
{error ? (

{error}

) : (

JPG, PNG or GIF. Max 2MB.

)}
); }