import { useState, useCallback, useEffect } from 'react'; import { useBaaS, useAuth } from '@picobaas/client/react'; import type { ImageFile, UploadOptions } from '../types'; import { PUBLIC_BUCKET, USER_BUCKET, generateUUID } from '../config'; import { getExpiryFromMetadata } from '../utils/expiry'; import { getSessionToken, trackUpload, untrackUpload } from '../utils/session'; import type { FileMetadata } from '@picobaas/client'; export function useImages(bucket: string = PUBLIC_BUCKET) { const { client } = useBaaS(); const { userId, isAuthenticated } = useAuth(); const [images, setImages] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const fetchImages = useCallback(async () => { setIsLoading(true); setError(null); try { const storage = client.storage.from(bucket); const prefix = bucket === USER_BUCKET && userId ? `${userId}/` : ''; // Fetch from storage const { data, error: listError } = await storage.list(prefix, { limit: 100, }); if (listError) throw listError; // Also fetch from image API to get shortCodes const sessionToken = getSessionToken(); const headers: Record = { 'X-Session-Token': sessionToken, }; const accessToken = client.accessToken; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } let imageMetadata: Record = {}; try { const apiResponse = await fetch('/api/images', { headers }); if (apiResponse.ok) { const apiData = await apiResponse.json(); // Create a lookup by path for (const img of apiData.items || []) { imageMetadata[img.path] = { shortCode: img.short_code, shortUrl: img.short_url, downloadAllowed: img.download_allowed, }; } } } catch (e) { // Non-fatal, continue without shortCodes console.warn('Failed to fetch image metadata:', e); } const imageFiles: ImageFile[] = (data || []).map((file: FileMetadata) => { const meta = imageMetadata[file.path]; return { id: file.id, path: file.path, name: file.filename || file.path.split('/').pop() || 'Unknown', size: file.size, mimeType: file.mime_type, url: storage.getPublicUrl(file.path).data.publicUrl, createdAt: new Date(file.created_at * 1000), expiresAt: getExpiryFromMetadata(file.custom_metadata), isPublic: bucket === PUBLIC_BUCKET, ownerId: file.owner_id, shortCode: meta?.shortCode, shortUrl: meta?.shortUrl, downloadAllowed: meta?.downloadAllowed, }; }); // Sort by created date, newest first imageFiles.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); setImages(imageFiles); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load images'); } finally { setIsLoading(false); } }, [client, bucket, userId]); const uploadImage = useCallback( async (file: File, options: UploadOptions): Promise => { try { const storage = client.storage.from(bucket); // Generate unique filename const ext = file.name.split('.').pop() || 'jpg'; const uniqueId = generateUUID(); const prefix = bucket === USER_BUCKET && userId ? `${userId}/` : ''; const path = `${prefix}${uniqueId}.${ext}`; // Calculate expiry const expiresAt = options.expirySeconds > 0 ? new Date(Date.now() + options.expirySeconds * 1000).toISOString() : undefined; // Upload to storage const { data, error: uploadError } = await storage.upload(path, file, { contentType: file.type, metadata: expiresAt ? { expires_at: expiresAt } : undefined, }); if (uploadError) throw uploadError; // Register with image API for short URL and tracking const sessionToken = getSessionToken(); const headers: Record = { 'Content-Type': 'application/json', 'X-Session-Token': sessionToken, }; // Include auth token if logged in const accessToken = client.accessToken; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const apiResponse = await fetch('/api/images', { method: 'POST', headers, body: JSON.stringify({ bucket, path, storage_object_id: data?.id, download_allowed: options.downloadAllowed, expires_at: expiresAt, }), }); let shortCode: string | undefined; let shortUrl: string | undefined; if (apiResponse.ok) { const imageData = await apiResponse.json(); shortCode = imageData.short_code; shortUrl = `/i/${shortCode}`; // Track anonymous uploads for deletion capability if (!isAuthenticated && shortCode) { trackUpload(shortCode); } } else { console.warn('Failed to register image with API:', await apiResponse.text()); } const newImage: ImageFile = { id: data?.id || uniqueId, path: path, name: file.name, size: file.size, mimeType: file.type, url: storage.getPublicUrl(path).data.publicUrl, createdAt: new Date(), expiresAt: expiresAt ? new Date(expiresAt) : undefined, isPublic: bucket === PUBLIC_BUCKET, ownerId: userId || undefined, shortCode, shortUrl, downloadAllowed: options.downloadAllowed, }; setImages((prev) => [newImage, ...prev]); return newImage; } catch (err) { throw new Error( err instanceof Error ? err.message : 'Upload failed' ); } }, [client, bucket, userId, isAuthenticated] ); const deleteImage = useCallback( async (image: ImageFile): Promise => { try { // If we have a short code, delete via the image API (handles session auth) if (image.shortCode) { const sessionToken = getSessionToken(); const headers: Record = { 'X-Session-Token': sessionToken, }; const accessToken = client.accessToken; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const apiResponse = await fetch(`/api/images/${image.shortCode}`, { method: 'DELETE', headers, }); if (!apiResponse.ok) { const errorData = await apiResponse.json().catch(() => ({})); throw new Error(errorData.error || 'Delete failed'); } // Remove from session tracking untrackUpload(image.shortCode); } else { // Fallback to direct storage deletion const storage = client.storage.from(bucket); const { error: deleteError } = await storage.remove([image.path]); if (deleteError) throw deleteError; } setImages((prev) => prev.filter((img) => img.id !== image.id)); } catch (err) { throw new Error( err instanceof Error ? err.message : 'Delete failed' ); } }, [client, bucket] ); // Auto-fetch when authenticated for user bucket useEffect(() => { if (bucket === USER_BUCKET && isAuthenticated) { fetchImages(); } else if (bucket === PUBLIC_BUCKET) { fetchImages(); } }, [bucket, isAuthenticated, fetchImages]); return { images, isLoading, error, fetchImages, uploadImage, deleteImage, }; }