Files
anouma/app/(frontend)/termin-buchen/page.tsx
T

135 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import { PageHeader } from "@/components/PageHeader";
import { Section, SectionHeading } from "@/components/Section";
import { ContactForm } from "@/components/ContactForm";
import { AngebotCard } from "@/components/AngebotCard";
import { Reveal } from "@/components/Reveal";
import { getBookingGlobal, getSEOSettingsGlobal } from "@/lib/payload/globals";
import { getOffers } from "@/lib/payload/content";
import { moodForOffer } from "@/lib/angebote";
import { mediaUrl } from "@/lib/payload/media";
import { resolveSeo } from "@/lib/seo/resolve";
import { buildMetadata } from "@/lib/seo/metadata";
import { getSiteUrl } from "@/lib/seo/config";
import { JsonLd, webPageJsonLd } from "@/lib/seo/jsonld";
import { siteConfig } from "@/lib/site";
export const dynamic = "force-dynamic";
const FALLBACK_DESCRIPTION = "Kennenlernen & Termine vereinbaren.";
export async function generateMetadata(): Promise<Metadata> {
const [booking, seoSettings] = await Promise.all([getBookingGlobal(), getSEOSettingsGlobal()]);
const resolved = resolveSeo({
seo: booking.seo,
fallbackTitle: `Termin buchen – ${siteConfig.name}`,
fallbackDescription: () => booking.lead || seoSettings.defaultDescription || FALLBACK_DESCRIPTION,
defaultOgImage: seoSettings.defaultOgImage,
});
return buildMetadata({
title: resolved.title,
description: resolved.description,
path: "/termin-buchen",
ogImageUrl: resolved.ogImageUrl,
keywords: resolved.keywords,
});
}
export default async function TerminBuchenPage() {
const [booking, offers] = await Promise.all([getBookingGlobal(), getOffers()]);
const bookableOffers = offers.filter((offer) => offer.bookable);
const siteUrl = await getSiteUrl();
return (
<>
<JsonLd
data={webPageJsonLd({
name: booking.title || "Termin buchen",
description: booking.lead || FALLBACK_DESCRIPTION,
url: `${siteUrl}/termin-buchen`,
})}
/>
<PageHeader
eyebrow={booking.eyebrow || "Termin buchen"}
title={booking.title || "Kennenlernen & Termine vereinbaren"}
lead={booking.lead}
crumbs={[{ title: "Termin buchen" }]}
/>
<Section tone="cream">
<div className="mx-auto grid max-w-4xl gap-5 md:grid-cols-2">
<a href="#freie-termine" className="rounded-3xl bg-white p-7 transition-transform hover:-translate-y-1">
<p className="text-xs font-medium uppercase tracking-[0.18em] text-anouma-dustyrose">Direkt buchen</p>
<h2 className="mt-3 font-serif text-2xl font-medium text-anouma-plum">Freien Termin auswählen</h2>
<p className="mt-3 text-sm leading-relaxed text-anouma-plum/80">Wähle ein Angebot, einen freien Tag und eine passende Uhrzeit im Kalender.</p>
<span className="mt-5 inline-flex rounded-full bg-anouma-mauve-dark px-5 py-2.5 text-sm font-medium text-white">Zum Kalender</span>
</a>
<a href="/kontakt?subject=Terminanfrage" className="rounded-3xl bg-anouma-plum p-7 transition-transform hover:-translate-y-1">
<p className="text-xs font-medium uppercase tracking-[0.18em] text-anouma-cream-light/75">Persönlich abstimmen</p>
<h2 className="mt-3 font-serif text-2xl font-medium text-white">Anfrage stellen</h2>
<p className="mt-3 text-sm leading-relaxed text-white/80">Wenn du keinen freien Kalendertermin brauchst oder etwas Besonderes besprechen möchtest.</p>
<span className="mt-5 inline-flex rounded-full bg-white px-5 py-2.5 text-sm font-medium text-anouma-plum">Zur Kontaktseite</span>
</a>
</div>
</Section>
{bookableOffers.length > 0 && (
<Section tone="plain" id="freie-termine">
<SectionHeading eyebrow="Angebot auswählen" title="Wofür möchtest du einen Termin?" />
<div className="mt-14 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{bookableOffers.map((offer, i) => (
<Reveal key={offer.slug} delay={i * 0.08}>
<AngebotCard
href={`/angebote/${offer.slug}`}
title={offer.title}
tagline={offer.shortDescription}
mood={moodForOffer(offer)}
imageSrc={mediaUrl(offer.image, "card")}
/>
</Reveal>
))}
</div>
</Section>
)}
{booking.steps && booking.steps.length > 0 && (
<Section tone="cream">
<SectionHeading eyebrow="Ablauf" title="So findest du zu deinem Termin" />
<div className="mt-14 grid gap-8 sm:grid-cols-3">
{booking.steps.map((s, i) => (
<Reveal key={s.id ?? s.title} delay={i * 0.08}>
<p className="font-serif text-5xl font-medium text-anouma-rose">
{String(i + 1).padStart(2, "0")}
</p>
<h3 className="mt-4 font-serif text-xl font-medium text-anouma-plum">{s.title}</h3>
<p className="mt-3 text-base leading-relaxed text-anouma-plum">{s.text}</p>
</Reveal>
))}
</div>
</Section>
)}
<Section tone="plain">
<div className="mx-auto max-w-xl">
<SectionHeading
title="Allgemeine Anfrage"
lead={
bookableOffers.length > 0
? "Für einen konkreten Termin wähle oben ein Angebot – hier kannst du auch einfach eine allgemeine Nachricht schreiben."
: undefined
}
align="left"
/>
<div className="mt-10">
<ContactForm
subjectPrefix="Terminanfrage über anouma.org"
type="appointment"
submitLabel="Terminanfrage senden"
/>
</div>
</div>
</Section>
</>
);
}