Passer au contenu principal

Image architecture

How event images flow from admin upload → S3 → storefront rendering. Cards use the square profile; the detail hero uses the wide banner.

Card image source

The adapter at eventGridAdapter.ts reads:

e.profile_image_url ?? e.banner_image_url ?? ""
Card image source
SourceWhereFallback role
Profile (1:1, 400x400)Event cards, search results, category pages.Primary.
Banner (~2.5:1, 1600x643)Event detail page hero only. Letterboxed on cards.Fallback when no profile uploaded.
No imageGradient placeholder (teal→blue). Preserves grid alignment.Last resort.

Why profile, not banner

  • Aspect ratio. Profile is 400x400 (1:1). Banner is 1600x643 (~2.5:1). A card slot is square; the banner letterboxes or crops the subject.
  • Subject framing. Producers crop identity (logo, face) into the square. The wide canvas is atmosphere/venue for the detail hero at full width.
  • Legacy parity. Legacy storefront cards rendered the profile image. v2 mirrors this behaviour.

Event detail hero

The detail page (/[locale]/events/[slug]/page.tsx) renders the wide banner_image_url as the full-width hero image. When a profile_image_url exists, it overlays at the bottom-left as a small square mark (96x96px on desktop, 64x64px on mobile) — mirroring legacy's layout where the event's identity mark sits over the banner.

Upload flow (admin)

The admin EventForm Visual tab has two uploaders and a default banner picker:

  • Profile image — S3ImageUploader with aspect="square", folder events/profiles. DS ImageUploader molecule with aspect-square h-44.
  • Banner image — S3ImageUploader with aspect="wide", folder events/banners. DS ImageUploader with aspect-[1600/643] h-44.
  • Default banners — DefaultBannerPicker (consumer-layer composition of DS Radio + token utilities). Four legacy stock banners at files/default-banners/banner{1..4}.jpg.
  • YouTube video — video_url stored raw, iframe src rebuilt from parsed 11-char id via youtubeEmbedUrl().

The upload pipe: file picker → POST /producer/uploads/presign (Medusa signs a 15-min PUT URL) → XHR direct-to-S3 PUT with progress → publicUrl stored on the row.

DS ImageUploader molecule changes

The DS ImageUploader molecule was extended with new props:

  • currentSrc — URL of a persisted image. When set and no new file picked, the saved image becomes the control (click to replace).
  • aspect — "square" | "wide" | "auto". Controls both the empty dropzone and the filled preview shape. Height-driven (h-44) so square and wide previews line up on the same row.
  • onRemove / removeLabel — exposed remove control over the preview, positioned top-right via DS IconButton.

The empty dropzone holds the same aspect as the filled preview — the field does not jump size when an image lands. Previously the empty state was a fixed h-24 w-full bar.

DS EventCard organism change

The image slot changed from aspect-[16/9] to aspect-square. The card now renders a 1:1 crop matching the profile image dimensions.

Side-by-side layout call

The EventForm Visual tab places the two uploaders side-by-side (flex-row on sm:, stacks below). This deliberately avoids ResponsiveFieldGroup — that molecule splits into equal flex-1 slots, which would strand the 176px square far to the right of the much wider banner. Both previews share one height (h-44) so they line up regardless of their different aspect ratios.

Backend: profile_image_url not selected

The /web/events listing endpoint previously did SELECT … banner_image_url but omitted profile_image_url. The adapter preferred profile (null every time) and silently fell back to the 2.5:1 banner. This is now fixed — profile_image_url is selected and returned alongside the banner.

Cross-references

  • eventGridAdapter.ts — fallback chain: profile → banner → empty
  • events/[slug]/page.tsx — detail hero (banner + profile overlay) + YouTube embed
  • EventForm.tsx — admin editor Visual tab (side-by-side uploaders + DefaultBannerPicker + video_url)
  • EventDetail.tsx — read-only Visual step mirroring the editor
  • S3ImageUploader.tsx — reusable widget (DS ImageUploader + presign/PUT + ProgressUpload)
  • DefaultBannerPicker.tsx — consumer-layer composition (DS Radio + thumbnails)
  • ImageUploader.tsx (DS molecule) — extended with currentSrc / aspect / onRemove
  • EventCard.tsx (DS organism) — aspect-square image slot
  • youtube.ts — URL → embed id parser (security-critical: iframe src rebuilt from parsed 11-char id)
  • hetzner-storage.ts — presignUpload() method
  • /web/events route.ts — now SELECTs profile_image_url
  • next.config.js — nbg1.your-objectstorage.com added to images.remotePatterns

Captured 2026-08-14 as part of ClickUp task EVENT-UPLOAD-01. Updated 2026-08-14 after visual review with full component audit. Do not re-open without an ADR.

Architecture des images — Systeme de design ePassLive