import React from 'react' import { useParams, Link } from '@/spa/router' import { motion } from 'framer-motion' import { Calendar, MapPin, Clock, Bell, ArrowLeft, ChevronRight } from 'lucide-react' import { getEventBySlug, EventUpdate, type BmpEvent, type EventEckdaten } from '@/spa/data/events' import { useCmsRoute } from '@/spa/cmsRoute' import { docImageUrl, mediaUrl } from '@/spa/cmsMediaField' import { useIsMobile } from '@/spa/hooks/useIsMobile' import { eventDetailContent } from '@/spa/eventDetailContent' import Image from '@/spa/components/ui/UnoptimizedImage' const NAVY = '#111D55' const GOLD = '#EFBF04' const WHITE = '#fff' type EventDetailCms = Omit, 'updateStyles' | 'statusStyles'> & { fallbackImage?: unknown updateStyles?: unknown statusStyles?: unknown updatesCopy?: Partial } type StyleEntry = { bg: string; color: string; label: string } type StyleMap = Record const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback // ── Timestamp formatter ────────────────────────────────────────────────────── function formatTs(iso: string, detail: EventDetailCms): string { const d = new Date(iso) const monthNames = Array.isArray(detail.dateFormat?.monthNames) && detail.dateFormat.monthNames.length >= 12 ? detail.dateFormat.monthNames : eventDetailContent.dateFormat.monthNames return `${d.getDate()}. ${monthNames[d.getMonth()]} ${d.getFullYear()}, ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')} ${fallbackText(detail.dateFormat?.timeSuffix, eventDetailContent.dateFormat.timeSuffix)}` } const asStyleMap = (value: unknown, fallback: StyleMap): StyleMap => { if (!value || typeof value !== 'object') return fallback return { ...fallback, ...(value as StyleMap) } } const getDetailPage = (value: unknown): EventDetailCms => { const detail = (value && typeof value === 'object' ? value : {}) as EventDetailCms return { ...eventDetailContent, ...detail, notFound: { ...eventDetailContent.notFound, ...detail.notFound }, dateFormat: { ...eventDetailContent.dateFormat, ...detail.dateFormat }, updateStyles: asStyleMap(detail.updateStyles, eventDetailContent.updateStyles), statusStyles: asStyleMap(detail.statusStyles, eventDetailContent.statusStyles), heroBackLink: { ...eventDetailContent.heroBackLink, ...detail.heroBackLink }, fallbackEvent: { ...eventDetailContent.fallbackEvent, ...detail.fallbackEvent }, sections: { ...eventDetailContent.sections, ...detail.sections }, updatesCopy: { ...eventDetailContent.updates, ...(detail.updatesCopy || detail.updates) }, bottomCta: { ...eventDetailContent.bottomCta, ...detail.bottomCta }, } } // ── Update Card ─────────────────────────────────────────────────────────────── function UpdateCard({ update, index, detail }: { update: EventUpdate; index: number; detail: EventDetailCms }) { const styles = asStyleMap(detail.updateStyles, eventDetailContent.updateStyles) const style = styles[update.type] ?? styles.update return ( {/* Timeline dot */}
{/* Card */}
{style.label} {formatTs(update.timestamp, detail)}
{update.title}
{update.body}
) } // ── Main Component ──────────────────────────────────────────────────────────── 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 fallbackEvent = cmsDetail.fallbackEvent || eventDetailContent.fallbackEvent const eventFallbackImage = mediaUrl(cmsDetail.fallbackImage, `/images/${eventDetailContent.fallbackImageFilename}`) const event = getEventBySlug(slug ?? '') || (cmsEvent ? { slug: String(cmsEvent.slug || slug), title: String(cmsEvent.title || fallbackEvent.title || eventDetailContent.fallbackEvent.title), subtitle: String(cmsEvent.subtitle || cmsEvent.description || cmsEvent.meta?.description || ''), date: String(cmsEvent.date || fallbackEvent.date || eventDetailContent.fallbackEvent.date), time: String(cmsEvent.time || fallbackEvent.time || eventDetailContent.fallbackEvent.time), location: String(cmsEvent.location || fallbackEvent.location || eventDetailContent.fallbackEvent.location), venue: String(cmsEvent.venue || fallbackEvent.venue || eventDetailContent.fallbackEvent.venue), category: String(cmsEvent.category || fallbackEvent.category || eventDetailContent.fallbackEvent.category), status: ((cmsEvent.status || fallbackEvent.status || eventDetailContent.fallbackEvent.status) as BmpEvent['status']), img: docImageUrl(cmsEvent, eventFallbackImage), description: String(cmsEvent.description || cmsEvent.meta?.description || ''), longDescription: String(cmsEvent.longDescription || cmsEvent.description || cmsEvent.meta?.description || fallbackEvent.longDescription || eventDetailContent.fallbackEvent.longDescription), 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 : cmsDetail) const notFound = detail.notFound || eventDetailContent.notFound const heroBackLink = detail.heroBackLink || eventDetailContent.heroBackLink const sections = detail.sections || eventDetailContent.sections const updatesCopy = detail.updatesCopy || eventDetailContent.updates const bottomCta = detail.bottomCta || eventDetailContent.bottomCta const isMobile = useIsMobile() if (!event) { return (
404
{fallbackText(notFound.title, eventDetailContent.notFound.title)}
{fallbackText(notFound.backLabel, eventDetailContent.notFound.backLabel)}
) } const statusStyles = asStyleMap(detail.statusStyles, eventDetailContent.statusStyles) const statusStyle = statusStyles[event.status] ?? statusStyles.geplant return (
{/* ── Hero ─────────────────────────────────────────────────────────── */}
{event.title}
{/* Back link */}
(e.currentTarget.style.color = '#fff')} onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.7)')} > {fallbackText(heroBackLink.label, eventDetailContent.heroBackLink.label)}
{/* Hero content */}
{/* Left */}
{statusStyle.label} {event.category}

{event.title}

{event.subtitle}

{/* Right – key facts */}
{[ { icon: Calendar, label: event.date }, { icon: Clock, label: event.time }, { icon: MapPin, label: `${event.venue}, ${event.location}` }, ].map(({ icon: Icon, label }, i) => (
{label}
))}
{/* Gold bottom line */}
{/* ── Main Content ─────────────────────────────────────────────────── */}
{/* Left – Description + Eckdaten */}
{/* Description */}

{fallbackText(sections.aboutEyebrow, eventDetailContent.sections.aboutEyebrow)}

{event.longDescription.split('\n\n').map((para, i) => (

{para}

))}
{/* Eckdaten */}

{fallbackText(sections.factsEyebrow, eventDetailContent.sections.factsEyebrow)}

{event.eckdaten.map((item, i) => (
{item.label}
{item.value}
))}
{/* CTA */}
{event.status === 'anmeldung-offen' && ( {fallbackText(sections.registerLabel, eventDetailContent.sections.registerLabel)} )} {fallbackText(sections.questionLabel, eventDetailContent.sections.questionLabel)}
{/* Right – Live Updates Feed (sticky on desktop, static on mobile) */}
{/* Header */}
{fallbackText(updatesCopy.heading, eventDetailContent.updates.heading)} {event.updates.length}
{/* Timeline */}
{event.updates.map((update, i) => ( ))}
{/* ── Bottom CTA ───────────────────────────────────────────────────── */}

{fallbackText(bottomCta.eyebrow, eventDetailContent.bottomCta.eyebrow)}

{fallbackText(bottomCta.heading, eventDetailContent.bottomCta.heading)}

{ const el = e.currentTarget as HTMLElement; el.style.background = '#FFD130'; el.style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }} onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = GOLD; el.style.boxShadow = 'none'; }} > {fallbackText(bottomCta.label, eventDetailContent.bottomCta.label)}
) }