Files
anouma/app/(frontend)/page.tsx
T
maro 45261a0461 Initial commit: ANOUMA website with Payload CMS, WebRTC meetings, and booking system
- Next.js 16 App Router site with the ANOUMA design system
- Payload CMS (PostgreSQL) for offers, events, posts and page content
- WebRTC video-call system with custom signaling server
- SMTP email reminders and booking-request notifications
- Customer accounts, calendar-based availability, and booking workflow
2026-08-25 16:40:51 +02:00

224 lines
8.5 KiB
TypeScript

import type { Metadata } from "next";
import Link from "next/link";
import { Hero } from "@/components/Hero";
import { Button } from "@/components/Button";
import { Section, SectionHeading } from "@/components/Section";
import { AngebotCard } from "@/components/AngebotCard";
import { ImagePlaceholder } from "@/components/ImagePlaceholder";
import { RichText } from "@/components/RichText";
import { CTA } from "@/components/CTA";
import { Reveal } from "@/components/Reveal";
import { EventTeaserCard } from "@/components/EventTeaserCard";
import { getOffers, getPosts, getUpcomingEvents } from "@/lib/payload/content";
import { getHomeGlobal } from "@/lib/payload/globals";
import { groupOffersByCategory, moodForOffer } from "@/lib/angebote";
import { mediaAlt, mediaUrl } from "@/lib/payload/media";
export const dynamic = "force-dynamic";
export async function generateMetadata(): Promise<Metadata> {
const home = await getHomeGlobal();
return {
title: { absolute: home.heroTitle || "Willkommen bei Anouma" },
description:
home.heroSupporting ||
"Prozessbegleitung, Doula-Begleitung, Kindergruppen und Singkreise — Räume für Verbindung mit dir selbst, miteinander und mit der Natur.",
};
}
export default async function Home() {
const [home, offers, posts, events] = await Promise.all([
getHomeGlobal(),
getOffers(),
getPosts(3),
getUpcomingEvents(3),
]);
const groups = groupOffersByCategory(offers);
return (
<>
<Hero
eyebrow={home.heroEyebrow ?? undefined}
title={home.heroTitle || "Willkommen bei Anouma"}
mood="rose"
imageSrc={mediaUrl(home.heroImage, "hero")}
imageAlt={mediaAlt(home.heroImage)}
actions={
<>
<Button href="/termin-buchen">Termin buchen</Button>
<Button href="/angebote" variant="secondary">
Angebote entdecken
</Button>
</>
}
>
{home.heroHighlight && <p>{home.heroHighlight}</p>}
{home.heroSupporting && (
<p className="mt-3 text-base text-anouma-plum/90">{home.heroSupporting}</p>
)}
</Hero>
<Section tone="cream">
<SectionHeading
eyebrow="Angebote"
title="Räume, die zu deinem Weg passen"
lead="Von persönlicher Prozessbegleitung über Doula-Begleitung bis zu Kinder- und Singkreisen — jedes Angebot ist ein eigener, geschützter Raum."
/>
<div className="mt-14 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{groups.map((group, i) => {
const isSingle = group.offers.length === 1;
const first = group.offers[0];
return (
<Reveal key={group.category} delay={i * 0.08}>
<AngebotCard
href={isSingle ? `/angebote/${first.slug}` : group.href}
title={group.label}
tagline={isSingle ? first.shortDescription : `${group.offers.length} Angebote`}
mood={moodForOffer(first)}
imageSrc={mediaUrl(first.image, "card")}
/>
</Reveal>
);
})}
</div>
</Section>
{(home.aboutTitle || home.aboutText) && (
<Section tone="plain">
<div className="grid gap-14 lg:grid-cols-2 lg:items-center">
<Reveal>
<div className="relative mx-auto aspect-[4/5] w-full max-w-md lg:max-w-none">
<ImagePlaceholder
mood="mauve"
label="Anouma"
src={mediaUrl(home.aboutImage, "hero")}
alt={mediaAlt(home.aboutImage)}
className="h-full w-full"
/>
</div>
</Reveal>
<Reveal delay={0.1}>
{home.aboutEyebrow && (
<p className="mb-3 text-xs font-medium uppercase tracking-[0.22em] text-anouma-plum">
{home.aboutEyebrow}
</p>
)}
{home.aboutTitle && (
<h2 className="text-balance font-serif text-4xl font-medium leading-tight text-anouma-plum sm:text-5xl">
{home.aboutTitle}
</h2>
)}
{home.aboutText && (
<div className="mt-6">
<RichText data={home.aboutText} />
</div>
)}
<div className="mt-8">
<Button href="/ueber-mich" variant="ghost">
Mehr über mich lesen
</Button>
</div>
</Reveal>
</div>
</Section>
)}
{home.values && home.values.length > 0 && (
<Section tone="mauve">
<SectionHeading
eyebrow={home.valuesEyebrow ?? undefined}
title={home.valuesTitle || "Philosophie"}
align="center"
className="mx-auto text-anouma-cream-light [&_h2]:text-anouma-cream-light [&_p]:text-anouma-cream-light/90"
/>
<div className="mt-16 grid gap-10 sm:grid-cols-3">
{home.values.map((wert, i) => (
<Reveal key={wert.id ?? wert.title} delay={i * 0.1} className="text-center">
<p className="font-serif text-sm font-medium uppercase tracking-[0.2em] text-white/85">
{wert.title}
</p>
<p className="mt-4 text-balance font-serif text-2xl leading-snug italic">
{wert.quote}
</p>
</Reveal>
))}
</div>
</Section>
)}
{events.length > 0 && (
<Section tone="warm">
<div className="flex flex-wrap items-end justify-between gap-6">
<SectionHeading eyebrow="Termine" title="Aktuelle Termine" className="mb-0" />
<Button href="/termine" variant="ghost">
Alle Termine
</Button>
</div>
<div className="mt-12 grid gap-6 sm:grid-cols-3">
{events.map((event, i) => (
<Reveal key={event.slug} delay={i * 0.08}>
<EventTeaserCard event={event} />
</Reveal>
))}
</div>
</Section>
)}
{posts.length > 0 && (
<Section tone="cream">
<div className="flex flex-wrap items-end justify-between gap-6">
<SectionHeading eyebrow="Aktuelles" title="Neuigkeiten und Inspiration" className="mb-0" />
<Button href="/aktuelles" variant="ghost">
Alle Neuigkeiten
</Button>
</div>
<div className="mt-12 grid gap-6 sm:grid-cols-3">
{posts.map((post, i) => (
<Reveal key={post.slug} delay={i * 0.08}>
<Link href={`/aktuelles/${post.slug}`} className="block rounded-3xl bg-background p-7">
<p className="text-xs font-medium uppercase tracking-[0.18em] text-anouma-plum">
{new Date(post.publishDate ?? post.createdAt).toLocaleDateString("de-DE", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</p>
<h3 className="mt-3 font-serif text-xl font-medium text-anouma-plum">{post.title}</h3>
<p className="mt-3 text-sm leading-relaxed text-anouma-plum">{post.teaser}</p>
</Link>
</Reveal>
))}
</div>
</Section>
)}
<Section tone="plain">
<SectionHeading eyebrow="Impressionen" title="Einblicke in gemeinsame Räume" align="center" className="mx-auto" />
<div className="mt-14 grid grid-cols-2 gap-4 sm:grid-cols-4">
{(["moss", "rose", "peach", "sand"] as const).map((mood, i) => (
<Reveal key={mood} delay={i * 0.06}>
<ImagePlaceholder mood={mood} label="Impression" shape="soft" className="aspect-square" />
</Reveal>
))}
</div>
<div className="mt-10 text-center">
<Link
href="/impressionen"
className="text-sm font-medium text-anouma-mauve-dark underline underline-offset-4"
>
Alle Impressionen ansehen
</Link>
</div>
</Section>
<CTA
title={home.ctaTitle || "Kennenlernen & Termine vereinbaren"}
lead={
home.ctaLead ||
"Ich freue mich, von dir zu hören — schreib mir oder buche direkt ein unverbindliches Kennenlerngespräch."
}
/>
</>
);
}