v0.1 — Venue Image Upload & Crop

Branch: develop (commit diretto) Data: 24/03/2026 Tipo: feat (vendor dashboard, storage, UX)


Contesto

La pagina “Le tue Venue” permetteva solo di inserire un URL immagine manualmente. Con questa modifica il vendor può scegliere tra:

  1. Link URL — comportamento precedente, incolla un URL diretto
  2. Carica file — upload di un file locale con ritaglio visuale integrato

Componente ImageUploadCrop

File: components/ui/ImageUploadCrop.tsx

Componente riutilizzabile basato su react-easy-crop e Canvas API.

Props:

PropTipoDefaultDescrizione
onCropComplete(dataUrl: string) => voidCallback con dataUrl JPEG del ritaglio
aspectRationumber16/9Rapporto d’aspetto del crop

Flusso:

  1. Mostra un’area di drop/click per il file (<input type="file" accept="image/jpeg,image/png,image/webp">)
  2. Dopo la selezione: apre l’interfaccia di crop con slider zoom (1×–3×)
  3. Click “Applica ritaglio” → estrae l’area selezionata via Canvas API → chiama onCropComplete con la dataUrl JPEG (qualità 0.9)
  4. Il parent riceve il dataUrl e lo usa per upload/preview

Modifica app/vendor/(dashboard)/venues/page.tsx

Tab di selezione modalità

<div className="flex gap-1 p-1 bg-gray-100 rounded-xl w-fit mb-3">
  <button onClick={() => setImageMode('url')}>Link URL</button>
  <button onClick={() => setImageMode('file')}>Carica file</button>
</div>

Upload su Supabase Storage

Quando imageMode === 'file' e il crop è completato, prima del salvataggio viene eseguito:

async function uploadCroppedImage(dataUrl: string, userId: string): Promise<string> {
  const blob = await fetch(dataUrl).then(r => r.blob())
  const fileName = `${userId}/${Date.now()}.jpg`
  const { data, error } = await supabase.storage
    .from('venue-images')
    .upload(fileName, blob, { contentType: 'image/jpeg', upsert: false })
  if (error) throw error
  const { data: { publicUrl } } = supabase.storage
    .from('venue-images')
    .getPublicUrl(data.path)
  return publicUrl
}

Il URL pubblico restituito viene salvato in venues.image_url.


Storage bucket venue-images

Migration: supabase/migrations/20260324000005_venue_images_storage.sql

ParametroValore
IDvenue-images
Pubblico
Max file size5 MB
Tipi ammessiimage/jpeg, image/png, image/webp

RLS policies:

PolicyOperazioneCondizione
venue_images_public_readSELECTbucket_id = 'venue-images'
venue_images_auth_insertINSERTautenticato
venue_images_owner_updateUPDATEfolder = auth.uid()
venue_images_owner_deleteDELETEfolder = auth.uid()

I file vengono organizzati per cartella {userId}/{timestamp}.jpg.


Dipendenza aggiunta

  • react-easy-crop: ^5.5.7

File modificati / creati

  • components/ui/ImageUploadCrop.tsx (nuovo)
  • app/vendor/(dashboard)/venues/page.tsx
  • supabase/migrations/20260324000005_venue_images_storage.sql (nuovo)
  • package.json