import type { Metadata } from "next"; import { notFound, permanentRedirect, redirect } from "next/navigation"; import Link from "next/link"; import { headers } from "next/headers"; import { getPayload } from "payload"; import config from "@payload-config"; import { PageHeader } from "@/components/PageHeader"; import { Section } from "@/components/Section"; import { ImagePlaceholder } from "@/components/ImagePlaceholder"; import { RichText } from "@/components/RichText"; import { CTA } from "@/components/CTA"; import { RegisterForm } from "@/components/meeting/RegisterForm"; import { getEventBySlug } from "@/lib/payload/content"; import { getBookingSettingsGlobal, getSEOSettingsGlobal } from "@/lib/payload/globals"; import { mediaAlt, mediaUrl } from "@/lib/payload/media"; import { formatFullDate, formatTimeRange } from "@/lib/format"; import { EVENT_CATEGORIES } from "@/collections/Events"; import { canJoinMeeting, combineDateAndTime, getMeetingStatus, statusLabel } from "@/lib/meeting/status"; import { resolveSeo } from "@/lib/seo/resolve"; import { buildMetadata } from "@/lib/seo/metadata"; import { getSiteUrl } from "@/lib/seo/config"; import { excerptFromRichText } from "@/lib/seo/textExcerpt"; import { JsonLd, eventJsonLd } from "@/lib/seo/jsonld"; import { resolveRedirect } from "@/lib/seo/redirects"; import { siteConfig } from "@/lib/site"; export const dynamic = "force-dynamic"; type Args = { params: Promise<{ slug: string }> }; async function resolveEventOrRedirect(slug: string) { const event = await getEventBySlug(slug); if (event) return event; const match = await resolveRedirect(`/termine/${slug}`); if (match) { if (match.permanent) permanentRedirect(match.to); redirect(match.to); } return null; } export async function generateMetadata({ params }: Args): Promise { const { slug } = await params; const [event, seoSettings] = await Promise.all([getEventBySlug(slug), getSEOSettingsGlobal()]); if (!event) return {}; const resolved = resolveSeo({ seo: event.seo, fallbackTitle: `${event.title} – ${siteConfig.name}`, fallbackDescription: () => excerptFromRichText(event.description) || `${event.title} bei ${siteConfig.name}.`, fallbackImage: event.image, defaultOgImage: seoSettings.defaultOgImage, }); return buildMetadata({ title: resolved.title, description: resolved.description, path: `/termine/${event.slug}`, ogImageUrl: resolved.ogImageUrl, keywords: resolved.keywords, }); } export default async function EventDetailPage({ params }: Args) { const { slug } = await params; const event = await resolveEventOrRedirect(slug); if (!event) notFound(); const categoryLabel = EVENT_CATEGORIES.find((c) => c.value === event.category)?.label; const time = formatTimeRange(event.startTime, event.endTime); const [siteUrl, bookingSettings] = await Promise.all([getSiteUrl(), getBookingSettingsGlobal()]); let meetingCard = null; if (event.isOnline) { const payload = await getPayload({ config }); const { user } = await payload.auth({ headers: await headers() }); // Must check the collection, not just presence — a logged-in customer // (collection "customers") must never be treated as host. const isHost = user?.collection === "users"; const settings = await payload.findGlobal({ slug: "meeting-settings" }); const start = combineDateAndTime(event.date, event.startTime); const end = event.endTime ? combineDateAndTime(event.date, event.endTime) : new Date(start.getTime() + 60 * 60_000); const status = getMeetingStatus({ start, end, joinWindowMinutes: isHost ? settings.hostJoinWindowMinutes : settings.participantJoinWindowMinutes, closeAfterMinutes: settings.meetingCloseAfterMinutes, }); const joinable = canJoinMeeting(status); meetingCard = (

Online-Termin

{joinable ? ( {statusLabel[status]} ) : (

{statusLabel[status]}

)}
); } return ( <>
{event.description && } {event.registrationRequired && (

Anmeldung

)}
{meetingCard}
Datum
{formatFullDate(event.date)}
{time && (
Uhrzeit
{time}
)} {event.location && (
Ort
{event.location}
)} {event.maxParticipants && (
Teilnehmerzahl
max. {event.maxParticipants}
)} {event.registrationRequired && event.registrationInfo && (
Anmeldung
{event.registrationInfo}
)}
); }