diff --git a/src/collections/Events.ts b/src/collections/Events.ts index 19ef165..1ef17c2 100644 --- a/src/collections/Events.ts +++ b/src/collections/Events.ts @@ -130,6 +130,15 @@ export const Events: CollectionConfig = { 'Shared labels, status/update labels, subscription copy, CTAs, and formatting copy for the Event detail route.', }, fields: [ + { + name: 'fallbackImage', + type: 'upload', + relationTo: 'media', + label: 'Fallback hero image', + admin: { + description: `Current frontend fallback image: /images/${eventDetailContent.fallbackImageFilename}`, + }, + }, { name: 'notFound', label: 'Not found state', diff --git a/src/payload-types.ts b/src/payload-types.ts index 3941f10..927372b 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -2845,6 +2845,10 @@ export interface Event { * Shared labels, status/update labels, subscription copy, CTAs, and formatting copy for the Event detail route. */ detailPage?: { + /** + * Current frontend fallback image: /images/buehne-moderatoren.jpg + */ + fallbackImage?: (number | null) | Media; notFound?: { title?: string | null; backLabel?: string | null; @@ -5088,6 +5092,7 @@ export interface EventsSelect { detailPage?: | T | { + fallbackImage?: T; notFound?: | T | { diff --git a/src/scripts/preload-event-detail-cms.ts b/src/scripts/preload-event-detail-cms.ts index 9d3592a..4b2fe46 100644 --- a/src/scripts/preload-event-detail-cms.ts +++ b/src/scripts/preload-event-detail-cms.ts @@ -1,12 +1,24 @@ import 'dotenv/config' import config from '@payload-config' -import { getPayload } from 'payload' +import { getPayload, type Payload } from 'payload' import { eventDetailContent } from '@/spa/eventDetailContent' +const mediaByFilename = async (payload: Payload, filename: string) => { + const result = await payload.find({ + collection: 'media', + limit: 1, + pagination: false, + where: { filename: { equals: filename } }, + }) + return result.docs[0]?.id +} + async function main() { const payload = await getPayload({ config }) + const fallbackImage = await mediaByFilename(payload, eventDetailContent.fallbackImageFilename) + const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = eventDetailContent const events = await payload.find({ collection: 'events', @@ -25,7 +37,8 @@ async function main() { context: { disableRevalidate: true }, data: { detailPage: { - ...eventDetailContent, + ...detailContent, + fallbackImage, updatesCopy: eventDetailContent.updates, }, } as never, diff --git a/src/spa/eventDetailContent.ts b/src/spa/eventDetailContent.ts index 9b18016..8e00002 100644 --- a/src/spa/eventDetailContent.ts +++ b/src/spa/eventDetailContent.ts @@ -1,4 +1,5 @@ export const eventDetailContent = { + fallbackImageFilename: 'buehne-moderatoren.jpg', notFound: { title: 'Event nicht gefunden', backLabel: 'Zurück zur Presse-Seite', diff --git a/src/spa/pages/EventDetail.tsx b/src/spa/pages/EventDetail.tsx index c07b240..9b522e2 100644 --- a/src/spa/pages/EventDetail.tsx +++ b/src/spa/pages/EventDetail.tsx @@ -4,7 +4,7 @@ import { motion } from 'framer-motion' import { Calendar, MapPin, Clock, Bell, BellOff, ArrowLeft, ChevronRight } from 'lucide-react' import { getEventBySlug, EventUpdate, type BmpEvent, type EventEckdaten } from '@/spa/data/events' import { useCmsRoute } from '@/spa/cmsRoute' -import { docImageUrl } from '@/spa/cmsMediaField' +import { docImageUrl, mediaUrl } from '@/spa/cmsMediaField' import { useIsMobile } from '@/spa/hooks/useIsMobile' import { eventDetailContent } from '@/spa/eventDetailContent' import Image from '@/spa/components/ui/UnoptimizedImage' @@ -14,6 +14,7 @@ const GOLD = '#EFBF04' const CREAM = '#EFE5E3' type EventDetailCms = Omit, 'updateStyles' | 'statusStyles'> & { + fallbackImage?: unknown updateStyles?: unknown statusStyles?: unknown updatesCopy?: Partial @@ -96,6 +97,8 @@ export default function EventDetail() { const { slug } = useParams<{ slug: string }>() const route = useCmsRoute() const cmsEvent = route?.collection === 'events' ? route.doc : undefined + const cmsDetail = getDetailPage(cmsEvent?.detailPage) + const eventFallbackImage = mediaUrl(cmsDetail.fallbackImage, `/images/${eventDetailContent.fallbackImageFilename}`) const event = getEventBySlug(slug ?? '') || (cmsEvent ? { slug: String(cmsEvent.slug || slug), title: String(cmsEvent.title || 'Event'), @@ -106,14 +109,14 @@ export default function EventDetail() { venue: String(cmsEvent.venue || 'Ort folgt'), category: String(cmsEvent.category || 'Event'), status: (cmsEvent.status as BmpEvent['status']) || 'geplant', - img: docImageUrl(cmsEvent, '/images/buehne-moderatoren.jpg'), + img: docImageUrl(cmsEvent, eventFallbackImage), description: String(cmsEvent.description || cmsEvent.meta?.description || ''), longDescription: String(cmsEvent.longDescription || cmsEvent.description || cmsEvent.meta?.description || 'Dieses Event wird aus Payload CMS geladen.'), eckdaten: Array.isArray(cmsEvent.eckdaten) ? cmsEvent.eckdaten as EventEckdaten[] : [], updates: Array.isArray(cmsEvent.updates) ? cmsEvent.updates as EventUpdate[] : [], detailPage: cmsEvent.detailPage, } as BmpEvent : undefined) - const detail = getDetailPage(event && 'detailPage' in event ? event.detailPage : undefined) + const detail = getDetailPage(event && 'detailPage' in event ? event.detailPage : cmsDetail) const notFound = detail.notFound || eventDetailContent.notFound const heroBackLink = detail.heroBackLink || eventDetailContent.heroBackLink const sections = detail.sections || eventDetailContent.sections