import type { Metadata } from "next"; import { notFound, permanentRedirect, redirect } from "next/navigation"; 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 { BookingWidget } from "@/components/booking/BookingWidget"; import { getOfferBySlug } from "@/lib/payload/content"; import { moodForOffer } from "@/lib/angebote"; import { mediaAlt, mediaUrl } from "@/lib/payload/media"; import { OFFER_CATEGORIES } from "@/collections/Offers"; import { getCurrentCustomer } from "@/lib/auth/customer"; import { getSEOSettingsGlobal } from "@/lib/payload/globals"; 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, serviceJsonLd } 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 resolveOfferOrRedirect(slug: string) { const offer = await getOfferBySlug(slug); if (offer) return offer; const match = await resolveRedirect(`/angebote/${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 [offer, seoSettings] = await Promise.all([getOfferBySlug(slug), getSEOSettingsGlobal()]); if (!offer) return {}; const resolved = resolveSeo({ seo: offer.seo, fallbackTitle: `${offer.title} – ${siteConfig.name}`, fallbackDescription: () => offer.shortDescription, fallbackImage: offer.image, defaultOgImage: seoSettings.defaultOgImage, }); return buildMetadata({ title: resolved.title, description: resolved.description, path: `/angebote/${offer.slug}`, ogImageUrl: resolved.ogImageUrl, keywords: resolved.keywords, // Private single-session offers (e.g. "Einzelbegleitung") stay reachable // via their direct link but must never be indexed or listed. noindex: offer.visibility === "private", }); } export default async function OfferDetailPage({ params }: Args) { const { slug } = await params; const offer = await resolveOfferOrRedirect(slug); if (!offer) notFound(); const categoryLabel = OFFER_CATEGORIES.find((c) => c.value === offer.category)?.label; const customer = offer.bookable ? await getCurrentCustomer() : null; const siteUrl = await getSiteUrl(); return ( <> {offer.visibility !== "private" && ( )}
{(offer.price || offer.durationMinutes) && (
{offer.price && (
Preis {offer.price}
)} {offer.durationMinutes && (
Dauer {offer.durationMinutes} Minuten
)}
)}
{offer.bookable ? (

Termin auswählen

) : ( )} ); }