Wire testimonials CMS collection and Payload Live Preview

Testimonials on /todo-cards, /newsletter, /challenge now come from the new
Payload testimonials collection via a shared TestimonialsGrid component,
instead of 3 separately hardcoded arrays.

Adds Next.js Draft Mode (/api/preview) plus Live-Preview-aware client
wrappers (LiveRichText, LiveTestimonialsGrid, LivePostContent) for posts,
legal pages, and testimonials — mounted only while Draft Mode is enabled,
so ordinary visitors keep getting the plain static components.
This commit is contained in:
Marco
2026-07-21 19:02:20 +00:00
parent 2d6cff9f40
commit 26ae4a15f4
18 changed files with 420 additions and 305 deletions
@@ -1,74 +0,0 @@
import Image from "next/image";
import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal";
const testimonials = [
{
quote:
"„Die wöchentlichen Impulse sind mein fester Start in die Woche. Kurz, hilfreich und immer genau richtig.“",
name: "Sarah M.",
role: "Marketing Managerin",
avatar: "/avatar-weekly-1.png",
},
{
quote: "„Endlich mal Tipps, die man wirklich umsetzen kann. Jeden Mittwoch freue ich mich auf die Mail.“",
name: "Thomas K.",
role: "Selbstständiger Berater",
avatar: "/avatar-weekly-2.png",
},
{
quote: "„Die Impulse helfen mir, den Fokus zu behalten und das Wesentliche nicht aus den Augen zu verlieren.“",
name: "Miriam L.",
role: "Projektleiterin",
avatar: "/avatar-weekly-3.png",
},
];
// Same card style as /challenge and /todo-cards's testimonials (bg-muted,
// no border, decorative quote-mark, quote on top with flex-1,
// avatar+name+role row pinned to the bottom, hover-lift + avatar scale) —
// this page's own Figma spec actually used a different bordered pattern,
// but site-wide consistency across all three testimonial sections was
// explicitly requested over per-page Figma fidelity here. No pagination
// dots either, for the same reason — neither of the other two pages has
// them. max-w-[1600px] matches Challenge's testimonial container cap —
// see the identical comment in /todo-cards's Testimonials.tsx.
export function Testimonials() {
return (
<section className="w-full bg-bg-base flex flex-col gap-8 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
<div className="max-w-[1600px] mx-auto w-full flex flex-col gap-8 items-center">
<Reveal
className="font-semibold text-h-emphasis text-text-primary text-center"
style={{ fontFamily: "var(--font-lora)" }}
>
Was andere sagen
</Reveal>
<RevealGroup className="grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-[var(--layout-grid-gap)] w-full">
{testimonials.map((t) => (
<RevealItem
key={t.name}
className="group relative md:col-span-4 bg-bg-muted rounded-xl p-6 flex flex-col gap-4 transition-transform duration-300 hover:-translate-y-1"
>
<span
aria-hidden
className="absolute top-4 right-6 font-bold text-[2.5rem] text-[#ccc] leading-none select-none"
>
</span>
<p className="flex-1 text-body-sm text-text-primary leading-[1.6] pr-8">{t.quote}</p>
<div className="flex items-center gap-3">
<div className="relative size-10 shrink-0 rounded-full overflow-hidden transition-transform duration-300 group-hover:scale-110">
<Image src={t.avatar} alt={t.name} fill sizes="40px" className="object-cover" />
</div>
<div>
<p className="font-semibold text-body-sm text-text-primary">{t.name}</p>
<p className="text-body-sm text-text-muted">{t.role}</p>
</div>
</div>
</RevealItem>
))}
</RevealGroup>
</div>
</section>
);
}
+13 -3
View File
@@ -1,10 +1,13 @@
import type { Metadata } from "next";
import { draftMode } from "next/headers";
import { WeeklyImpulsesHero } from "./components/WeeklyImpulsesHero";
import { HowItWorks } from "./components/HowItWorks";
import { WeeklyBenefits } from "./components/WeeklyBenefits";
import { Testimonials } from "./components/Testimonials";
import { Newsletter } from "../components/Newsletter";
import { Footer } from "../components/Footer";
import { TestimonialsGrid } from "../components/TestimonialsGrid";
import { LiveTestimonialsGrid } from "../components/LiveTestimonialsGrid";
import { getTestimonials } from "../lib/payload";
const title = "Impulse & Tipps Wöchentliche Klarheit für deinen Alltag";
const description =
@@ -35,14 +38,21 @@ export const metadata: Metadata = {
// card). Both point at the same underlying thing (signing up for the
// weekly newsletter), so this page resolves both at once instead of
// picking a new URL and leaving one of them still dangling.
export default function NewsletterPage() {
export default async function NewsletterPage() {
const testimonials = await getTestimonials("newsletter");
const { isEnabled: isPreview } = await draftMode();
return (
<>
<main className="flex flex-col flex-1">
<WeeklyImpulsesHero />
<HowItWorks />
<WeeklyBenefits />
<Testimonials />
{isPreview ? (
<LiveTestimonialsGrid testimonials={testimonials} />
) : (
<TestimonialsGrid testimonials={testimonials} />
)}
<Newsletter
title="Wöchentliche Impulse für mehr Klarheit"
description="Melde dich jetzt an und erhalte jeden Mittwoch neue Impulse & Tipps direkt in dein Postfach."