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
+13 -76
View File
@@ -1,8 +1,12 @@
import type { Metadata } from "next";
import Link from "next/link";
import Image from "next/image";
import { draftMode } from "next/headers";
import { Footer } from "../components/Footer";
import { Reveal, RevealGroup, RevealItem } from "../components/Reveal";
import { TestimonialsGrid } from "../components/TestimonialsGrid";
import { LiveTestimonialsGrid } from "../components/LiveTestimonialsGrid";
import { getTestimonials } from "../lib/payload";
const title = "7-Tage-Challenge Mehr Klarheit in 7 Tagen";
const description =
@@ -118,27 +122,6 @@ const benefits = [
{ title: "Gelassener leben", desc: "Weniger Stress, mehr Zeit für die Dinge, die dir wichtig sind." },
];
const testimonials = [
{
avatar: "/avatar-1.jpg",
quote: "„Die 7-Tage-Challenge hat mir geholfen, wieder klar zu sehen und mit kleinen Schritten wirklich etwas zu verändern.“",
name: "Sarah M.",
role: "Marketing Managerin",
},
{
avatar: "/avatar-2.jpg",
quote: "„Kurz, konkret und unglaublich wirkungsvoll. Ich habe direkt mehr Fokus und weniger Druck im Kopf.“",
name: "Thomas K.",
role: "Selbständiger Berater",
},
{
avatar: "/avatar-3.jpg",
quote: "„Endlich eine Challenge, die nicht überfordert, sondern genau die richtigen Impulse gibt jeden Tag.“",
name: "Miriam L.",
role: "Projektleiterin",
},
];
function EmailCapture({ buttonLabel = "Challenge starten" }: { buttonLabel?: string }) {
return (
<div className="flex flex-col gap-2 w-full">
@@ -185,7 +168,10 @@ function EmailCapture({ buttonLabel = "Challenge starten" }: { buttonLabel?: str
);
}
export default function ChallengePage() {
export default async function ChallengePage() {
const testimonials = await getTestimonials("challenge");
const { isEnabled: isPreview } = await draftMode();
return (
<>
<main className="flex flex-col flex-1 bg-[#f5f0e8]">
@@ -383,60 +369,11 @@ export default function ChallengePage() {
</section>
{/* ── Was andere sagen ── */}
{/* max-w-[1600px], not this page's other sections' 1280px — a
deliberate compromise with /todo-cards's and /newsletter's
unbounded fluid width, so all three testimonial sections cap
at the same width instead of Challenge's reading narrower on
wide viewports. Only this one section's cap changed, not the
rest of the page. */}
<section className="bg-white w-full py-14 lg:py-20">
<div className="px-8 lg:px-[5rem] max-w-[1600px] mx-auto flex flex-col gap-10">
<Reveal
className="font-semibold text-[#222221] text-center"
style={{ fontFamily: "var(--font-lora)", fontSize: "clamp(1.5rem, 3vw, 2rem)" }}
>
Was andere sagen
</Reveal>
{/* Same style + micro-interactions as /todo-cards's and
/newsletter's identically-styled testimonial cards (kept in
sync deliberately): decorative quote-mark, hover-lift on
the card, avatar scale on the same hover via `group`. */}
<RevealGroup className="flex flex-col lg:flex-row gap-6">
{testimonials.map((t) => (
<RevealItem
key={t.name}
className="group relative flex-1 bg-[#f5f0e8] 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="text-[#222221] text-[0.95rem] leading-[1.6] flex-1 pr-8">{t.quote}</p>
<div className="flex items-center gap-3">
<div className="relative size-10 shrink-0 rounded-full overflow-hidden">
<Image
alt={t.name}
src={t.avatar}
fill
sizes="40px"
className="object-cover transition-transform duration-300 group-hover:scale-110"
/>
</div>
<div>
<p className="font-semibold text-[#222221] text-[0.9rem]">{t.name}</p>
<p className="text-[#777] text-[0.8rem]">{t.role}</p>
</div>
</div>
</RevealItem>
))}
</RevealGroup>
</div>
</section>
{isPreview ? (
<LiveTestimonialsGrid testimonials={testimonials} />
) : (
<TestimonialsGrid testimonials={testimonials} />
)}
{/* ── Bottom CTA ── */}
<section className="w-full py-14 lg:py-16">