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 using SDK's request method const sessionToken = getSessionToken(); let imageMetadata: Record = {}; try { const apiResponse = await client.request<{ items: Array<{ path: string; short_code: string; short_url: string; download_allowed: boolean }> }>( '/images', { headers: { 'X-Session-Token': sessionToken }, } ); if (!apiResponse.error && apiResponse.data) { // Create a lookup by path for (const img of apiResponse.data.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; let shortCode: string | undefined; let shortUrl: string | undefined; // Only register with image API for anonymous users (auto short link generation) // Logged-in users manage share links manually via the dashboard if (!isAuthenticated) { const sessionToken = getSessionToken(); const apiResponse = await client.request<{ short_code: string }>( '/images', { method: 'POST', headers: { 'X-Session-Token': sessionToken }, body: JSON.stringify({ bucket, path, storage_object_id: data?.id, download_allowed: options.downloadAllowed, expires_at: expiresAt, }), } ); if (!apiResponse.error && apiResponse.data) { shortCode = apiResponse.data.short_code; shortUrl = `/i/${shortCode}`; // Track anonymous uploads for deletion capability trackUpload(shortCode); } else { console.warn('Failed to register image with API:', apiResponse.error); } } 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 using SDK if (image.shortCode) { const sessionToken = getSessionToken(); const apiResponse = await client.request( `/images/${image.shortCode}`, { method: 'DELETE', headers: { 'X-Session-Token': sessionToken }, } ); if (apiResponse.error) { throw new Error(apiResponse.error.message || '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, }; }