feat: manage event detail fallback media via payload

This commit is contained in:
syntaxbullet
2026-06-22 12:35:05 +02:00
parent 9d2994ecdf
commit a2c4df74e9
5 changed files with 36 additions and 5 deletions

View File

@@ -130,6 +130,15 @@ export const Events: CollectionConfig = {
'Shared labels, status/update labels, subscription copy, CTAs, and formatting copy for the Event detail route.', 'Shared labels, status/update labels, subscription copy, CTAs, and formatting copy for the Event detail route.',
}, },
fields: [ fields: [
{
name: 'fallbackImage',
type: 'upload',
relationTo: 'media',
label: 'Fallback hero image',
admin: {
description: `Current frontend fallback image: /images/${eventDetailContent.fallbackImageFilename}`,
},
},
{ {
name: 'notFound', name: 'notFound',
label: 'Not found state', label: 'Not found state',

View File

@@ -2845,6 +2845,10 @@ export interface Event {
* Shared labels, status/update labels, subscription copy, CTAs, and formatting copy for the Event detail route. * Shared labels, status/update labels, subscription copy, CTAs, and formatting copy for the Event detail route.
*/ */
detailPage?: { detailPage?: {
/**
* Current frontend fallback image: /images/buehne-moderatoren.jpg
*/
fallbackImage?: (number | null) | Media;
notFound?: { notFound?: {
title?: string | null; title?: string | null;
backLabel?: string | null; backLabel?: string | null;
@@ -5088,6 +5092,7 @@ export interface EventsSelect<T extends boolean = true> {
detailPage?: detailPage?:
| T | T
| { | {
fallbackImage?: T;
notFound?: notFound?:
| T | T
| { | {

View File

@@ -1,12 +1,24 @@
import 'dotenv/config' import 'dotenv/config'
import config from '@payload-config' import config from '@payload-config'
import { getPayload } from 'payload' import { getPayload, type Payload } from 'payload'
import { eventDetailContent } from '@/spa/eventDetailContent' 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() { async function main() {
const payload = await getPayload({ config }) const payload = await getPayload({ config })
const fallbackImage = await mediaByFilename(payload, eventDetailContent.fallbackImageFilename)
const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = eventDetailContent
const events = await payload.find({ const events = await payload.find({
collection: 'events', collection: 'events',
@@ -25,7 +37,8 @@ async function main() {
context: { disableRevalidate: true }, context: { disableRevalidate: true },
data: { data: {
detailPage: { detailPage: {
...eventDetailContent, ...detailContent,
fallbackImage,
updatesCopy: eventDetailContent.updates, updatesCopy: eventDetailContent.updates,
}, },
} as never, } as never,

View File

@@ -1,4 +1,5 @@
export const eventDetailContent = { export const eventDetailContent = {
fallbackImageFilename: 'buehne-moderatoren.jpg',
notFound: { notFound: {
title: 'Event nicht gefunden', title: 'Event nicht gefunden',
backLabel: 'Zurück zur Presse-Seite', backLabel: 'Zurück zur Presse-Seite',

View File

@@ -4,7 +4,7 @@ import { motion } from 'framer-motion'
import { Calendar, MapPin, Clock, Bell, BellOff, ArrowLeft, ChevronRight } from 'lucide-react' import { Calendar, MapPin, Clock, Bell, BellOff, ArrowLeft, ChevronRight } from 'lucide-react'
import { getEventBySlug, EventUpdate, type BmpEvent, type EventEckdaten } from '@/spa/data/events' import { getEventBySlug, EventUpdate, type BmpEvent, type EventEckdaten } from '@/spa/data/events'
import { useCmsRoute } from '@/spa/cmsRoute' import { useCmsRoute } from '@/spa/cmsRoute'
import { docImageUrl } from '@/spa/cmsMediaField' import { docImageUrl, mediaUrl } from '@/spa/cmsMediaField'
import { useIsMobile } from '@/spa/hooks/useIsMobile' import { useIsMobile } from '@/spa/hooks/useIsMobile'
import { eventDetailContent } from '@/spa/eventDetailContent' import { eventDetailContent } from '@/spa/eventDetailContent'
import Image from '@/spa/components/ui/UnoptimizedImage' import Image from '@/spa/components/ui/UnoptimizedImage'
@@ -14,6 +14,7 @@ const GOLD = '#EFBF04'
const CREAM = '#EFE5E3' const CREAM = '#EFE5E3'
type EventDetailCms = Omit<Partial<typeof eventDetailContent>, 'updateStyles' | 'statusStyles'> & { type EventDetailCms = Omit<Partial<typeof eventDetailContent>, 'updateStyles' | 'statusStyles'> & {
fallbackImage?: unknown
updateStyles?: unknown updateStyles?: unknown
statusStyles?: unknown statusStyles?: unknown
updatesCopy?: Partial<typeof eventDetailContent.updates> updatesCopy?: Partial<typeof eventDetailContent.updates>
@@ -96,6 +97,8 @@ export default function EventDetail() {
const { slug } = useParams<{ slug: string }>() const { slug } = useParams<{ slug: string }>()
const route = useCmsRoute() const route = useCmsRoute()
const cmsEvent = route?.collection === 'events' ? route.doc : undefined 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 ? { const event = getEventBySlug(slug ?? '') || (cmsEvent ? {
slug: String(cmsEvent.slug || slug), slug: String(cmsEvent.slug || slug),
title: String(cmsEvent.title || 'Event'), title: String(cmsEvent.title || 'Event'),
@@ -106,14 +109,14 @@ export default function EventDetail() {
venue: String(cmsEvent.venue || 'Ort folgt'), venue: String(cmsEvent.venue || 'Ort folgt'),
category: String(cmsEvent.category || 'Event'), category: String(cmsEvent.category || 'Event'),
status: (cmsEvent.status as BmpEvent['status']) || 'geplant', 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 || ''), description: String(cmsEvent.description || cmsEvent.meta?.description || ''),
longDescription: String(cmsEvent.longDescription || cmsEvent.description || cmsEvent.meta?.description || 'Dieses Event wird aus Payload CMS geladen.'), 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[] : [], eckdaten: Array.isArray(cmsEvent.eckdaten) ? cmsEvent.eckdaten as EventEckdaten[] : [],
updates: Array.isArray(cmsEvent.updates) ? cmsEvent.updates as EventUpdate[] : [], updates: Array.isArray(cmsEvent.updates) ? cmsEvent.updates as EventUpdate[] : [],
detailPage: cmsEvent.detailPage, detailPage: cmsEvent.detailPage,
} as BmpEvent : undefined) } 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 notFound = detail.notFound || eventDetailContent.notFound
const heroBackLink = detail.heroBackLink || eventDetailContent.heroBackLink const heroBackLink = detail.heroBackLink || eventDetailContent.heroBackLink
const sections = detail.sections || eventDetailContent.sections const sections = detail.sections || eventDetailContent.sections