import { useRef, useState } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { queryOptions } from '@tanstack/react-query' import { api } from '@/lib/api-client' import { useAuthStore } from '@/stores/auth.store' import { Button } from '@/components/ui/button' import { Camera, User } from 'lucide-react' import { toast } from 'sonner' interface FileRecord { id: string path: string url: string filename: string } function entityFilesOptions(entityType: string, entityId: string) { return queryOptions({ queryKey: ['files', entityType, entityId], queryFn: () => api.get<{ data: FileRecord[] }>('/v1/files', { entityType, entityId }), enabled: !!entityId, }) } interface AvatarUploadProps { entityType: 'user' | 'member' | 'company' entityId: string size?: 'sm' | 'md' | 'lg' category?: string placeholderIcon?: React.ComponentType<{ className?: string }> } const sizeClasses = { sm: 'h-8 w-8', md: 'h-16 w-16', lg: 'h-24 w-24', } const iconSizes = { sm: 'h-4 w-4', md: 'h-8 w-8', lg: 'h-12 w-12', } export function AvatarUpload({ entityType, entityId, size = 'lg', category = 'profile', placeholderIcon: PlaceholderIcon }: AvatarUploadProps) { const queryClient = useQueryClient() const token = useAuthStore((s) => s.token) const fileInputRef = useRef(null) const [uploading, setUploading] = useState(false) const IconComponent = PlaceholderIcon ?? User const { data: filesData } = useQuery(entityFilesOptions(entityType, entityId)) // Find image by category const profileFile = filesData?.data?.find((f) => f.path.includes(`/${category}-`)) const imageUrl = profileFile ? `/v1/files/serve/${profileFile.path}` : null async function handleUpload(file: File) { setUploading(true) try { const formData = new FormData() formData.append('file', file) formData.append('entityType', entityType) formData.append('entityId', entityId) formData.append('category', category) // Delete existing profile image first if (profileFile) { await api.del(`/v1/files/${profileFile.id}`) } const res = await fetch('/v1/files', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: formData, }) if (!res.ok) { const err = await res.json() throw new Error(err.error?.message ?? 'Upload failed') } queryClient.invalidateQueries({ queryKey: ['files', entityType, entityId] }) toast.success('Profile picture updated') } catch (err) { toast.error(err instanceof Error ? err.message : 'Upload failed') } finally { setUploading(false) } } function handleFileSelect(e: React.ChangeEvent) { const file = e.target.files?.[0] if (file) handleUpload(file) // Reset input so same file can be re-selected e.target.value = '' } return (
{imageUrl ? ( Profile ) : ( )}
) } /** Display-only avatar (no upload button) */ export function Avatar({ entityType, entityId, size = 'sm' }: AvatarUploadProps) { const { data: filesData } = useQuery(entityFilesOptions(entityType, entityId)) const profileFile = filesData?.data?.find((f) => f.path.includes('/profile-')) const imageUrl = profileFile ? `/v1/files/serve/${profileFile.path}` : null return (
{imageUrl ? ( Profile ) : ( )}
) }