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:
@@ -1,78 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal";
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
quote:
|
||||
"„Die ToDo-Karten sind mein täglicher Begleiter. Endlich mehr Fokus auf das, was wirklich wichtig ist.“",
|
||||
name: "Sarah M.",
|
||||
role: "Marketing Managerin",
|
||||
avatar: "/avatar-todo-1.png",
|
||||
},
|
||||
{
|
||||
quote: "„Ein simples Konzept, das mein Arbeiten komplett verändert hat. Klare Empfehlung!“",
|
||||
name: "Thomas K.",
|
||||
role: "Selbstständiger Berater",
|
||||
avatar: "/avatar-todo-2.png",
|
||||
},
|
||||
{
|
||||
quote: "„Weniger Chaos im Kopf, mehr Struktur im Alltag. Die Karten helfen mir jeden Tag.“",
|
||||
name: "Miriam L.",
|
||||
role: "Projektleiterin",
|
||||
avatar: "/avatar-todo-3.png",
|
||||
},
|
||||
];
|
||||
|
||||
// Card layout/style matches app/challenge/page.tsx's and
|
||||
// app/newsletter/'s testimonial sections exactly (bg-muted filled card,
|
||||
// no border, decorative quote-mark, quote on top with flex-1 pushing
|
||||
// the avatar/name row to the bottom, hover-lift + avatar-scale) — all
|
||||
// three kept in sync deliberately as one shared visual pattern, overriding
|
||||
// each page's own (differing) Figma spec for this one section. Only the
|
||||
// photos differ per page (each has its own Figma-sourced avatars).
|
||||
// max-w-[1600px] on the inner wrapper matches Challenge's testimonial
|
||||
// container cap — without it this section's fluid px-[layout-padding-x]
|
||||
// alone has no upper bound, so cards kept growing wider than Challenge's
|
||||
// past ~1440px viewports (capped at 1280px there) and read as
|
||||
// inconsistently narrower on Challenge. 1600px is a deliberate compromise
|
||||
// between the two, not either page's original value.
|
||||
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
@@ -1,10 +1,13 @@
|
||||
import type { Metadata } from "next";
|
||||
import { draftMode } from "next/headers";
|
||||
import { TodoKartenHero } from "./components/TodoKartenHero";
|
||||
import { HowItWorks } from "./components/HowItWorks";
|
||||
import { Focus } from "./components/Focus";
|
||||
import { Testimonials } from "./components/Testimonials";
|
||||
import { Pricing } from "./components/Pricing";
|
||||
import { Footer } from "../components/Footer";
|
||||
import { TestimonialsGrid } from "../components/TestimonialsGrid";
|
||||
import { LiveTestimonialsGrid } from "../components/LiveTestimonialsGrid";
|
||||
import { getTestimonials } from "../lib/payload";
|
||||
|
||||
const title = "ToDo-Karten – Kleine Karten. Große Wirkung.";
|
||||
const description =
|
||||
@@ -29,14 +32,21 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function TodoCardsPage() {
|
||||
export default async function TodoCardsPage() {
|
||||
const testimonials = await getTestimonials("todo-cards");
|
||||
const { isEnabled: isPreview } = await draftMode();
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="flex flex-col flex-1">
|
||||
<TodoKartenHero />
|
||||
<HowItWorks />
|
||||
<Focus />
|
||||
<Testimonials />
|
||||
{isPreview ? (
|
||||
<LiveTestimonialsGrid testimonials={testimonials} />
|
||||
) : (
|
||||
<TestimonialsGrid testimonials={testimonials} />
|
||||
)}
|
||||
<Pricing />
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
Reference in New Issue
Block a user