import { supabase } from './supabase'; const BUCKET = 'profile-avatars'; const MAX_DIM = 512; const QUALITY = 0.85; // Resizes the source image to a centred-cropped square ≤ MAX_DIM and // re-encodes as WebP. Falls back to JPEG if WebP isn't supported (rare). async function resizeToSquare(file: File): Promise { const url = URL.createObjectURL(file); try { const img = await new Promise((resolve, reject) => { const i = new Image(); i.onload = () => resolve(i); i.onerror = () => reject(new Error('image load failed')); i.src = url; }); const minSide = Math.min(img.naturalWidth, img.naturalHeight); const sx = (img.naturalWidth - minSide) / 2; const sy = (img.naturalHeight - minSide) / 2; const target = Math.min(MAX_DIM, minSide); const canvas = document.createElement('canvas'); canvas.width = target; canvas.height = target; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('canvas context unavailable'); ctx.drawImage(img, sx, sy, minSide, minSide, 0, 0, target, target); const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/webp', QUALITY), ); if (blob) return blob; const jpeg = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', QUALITY), ); if (!jpeg) throw new Error('canvas toBlob returned null'); return jpeg; } finally { URL.revokeObjectURL(url); } } export async function uploadAvatar(userId: string, file: File): Promise { if (!file.type.startsWith('image/')) { throw new Error('only image files are accepted'); } const blob = await resizeToSquare(file); const ext = blob.type === 'image/webp' ? 'webp' : 'jpg'; // Random filename so old uploads don't get overwritten before we update // the profile row — Supabase Storage CDN caches by URL, so a fresh path // also forces clients to fetch the new image. const name = userId + '/' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8) + '.' + ext; const { error: upErr } = await supabase.storage.from(BUCKET).upload(name, blob, { contentType: blob.type, cacheControl: '604800', upsert: false, }); if (upErr) throw upErr; const { data: pub } = supabase.storage.from(BUCKET).getPublicUrl(name); return pub.publicUrl; } export async function deleteAvatarObject(publicUrl: string): Promise { // Public URLs look like // https:///storage/v1/object/public/profile-avatars/ // Extract and remove. const marker = '/object/public/' + BUCKET + '/'; const idx = publicUrl.indexOf(marker); if (idx === -1) return; const path = publicUrl.slice(idx + marker.length); const { error } = await supabase.storage.from(BUCKET).remove([path]); if (error) throw error; }