diff --git a/app/components/LiveProductContent.tsx b/app/components/LiveProductContent.tsx new file mode 100644 index 0000000..703e7cb --- /dev/null +++ b/app/components/LiveProductContent.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useLivePreview } from "@payloadcms/live-preview-react"; +import { renderProductBlock, type ProductBlockContext } from "./ProductBlocks"; +import { mapPayloadProduct, type PayloadProduct, type Product } from "../lib/payload"; + +const PAYLOAD_URL = process.env.NEXT_PUBLIC_PAYLOAD_URL || "https://payload.mk360.de"; + +// Live-previewable subset of a Products document's `layout` — same +// "editable subset only" scope as LivePageContent.tsx/LivePostContent.tsx: +// `testimonialsRef` and `crossSell` are skipped here (both need an async +// fetch a client component can't perform inline — testimonialsRef isn't +// even registered on Products.layout anymore, kept out of the switch for +// the same reason; crossSell's related-product card just doesn't update +// live, shows correctly once saved and reloaded outside the iframe). +// shipping/tax/Kleinunternehmer are passed in once from the server page +// component instead of re-fetched on every keystroke — see +// getProductBlockContext's own comment. +export function LiveProductContent({ + initialProduct, + shipping, + taxRate, + kleinunternehmer, +}: { + initialProduct: Product; +} & Omit) { + const { data } = useLivePreview({ + initialData: initialProduct as unknown as PayloadProduct, + serverURL: PAYLOAD_URL, + depth: 2, + }); + const product = data?.slug ? mapPayloadProduct(data) : initialProduct; + const ctx: ProductBlockContext = { product, shipping, taxRate, kleinunternehmer }; + + return <>{product.layout.map((block) =>
{renderProductBlock(block, ctx)}
)}; +} diff --git a/app/components/ProductBlocks.tsx b/app/components/ProductBlocks.tsx index fb38273..62bb2bf 100644 --- a/app/components/ProductBlocks.tsx +++ b/app/components/ProductBlocks.tsx @@ -30,12 +30,7 @@ import { TestimonialsGrid } from "./TestimonialsGrid"; // need the live product/shipping/tax context that a generic content page // doesn't have. export async function ProductBlocks({ product }: { product: Product }) { - const [shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([ - getShippingSettings(), - getDefaultTaxRatePercent(), - getKleinunternehmer(), - ]); - const ctx: ProductBlockContext = { product, shipping, taxRate: effectiveTaxRate(product, defaultTaxRate), kleinunternehmer }; + const ctx = await getProductBlockContext(product); const rendered = await Promise.all( product.layout.map(async (block) => { @@ -114,14 +109,28 @@ export async function ProductBlocks({ product }: { product: Product }) { return <>{rendered}; } -type ProductBlockContext = { +export type ProductBlockContext = { product: Product; shipping: Awaited>; taxRate: number; kleinunternehmer: boolean; }; -function renderProductBlock(block: ProductBlock, ctx: ProductBlockContext): React.ReactNode { +// Split out so LiveProductContent.tsx (the "use client" live-preview +// counterpart, same pattern as LivePageContent.tsx/renderPageBlockSync) +// can fetch this once itself instead of duplicating the three calls — +// shipping/tax/Kleinunternehmer status essentially never change between +// keystrokes in a preview session, unlike the product doc itself. +export async function getProductBlockContext(product: Product): Promise { + const [shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([ + getShippingSettings(), + getDefaultTaxRatePercent(), + getKleinunternehmer(), + ]); + return { product, shipping, taxRate: effectiveTaxRate(product, defaultTaxRate), kleinunternehmer }; +} + +export function renderProductBlock(block: ProductBlock, ctx: ProductBlockContext): React.ReactNode { switch (block.blockType) { case "productHero": return ; diff --git a/app/der-eine/page.tsx b/app/der-eine/page.tsx index 08bb455..12dd7a8 100644 --- a/app/der-eine/page.tsx +++ b/app/der-eine/page.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; -import { ProductBlocks } from "../components/ProductBlocks"; +import { draftMode } from "next/headers"; +import { ProductBlocks, getProductBlockContext } from "../components/ProductBlocks"; +import { LiveProductContent } from "../components/LiveProductContent"; import { Footer } from "../components/Footer"; import { getProductBySlug, getCompanySettings } from "../lib/payload"; import { buildProductSchema } from "../lib/structuredData"; @@ -30,18 +32,24 @@ export async function generateMetadata(): Promise { } export default async function DerEinePage() { + const { isEnabled: isPreview } = await draftMode(); const [product, seller] = await Promise.all([ getProductBySlug("stift-kugelschreiber"), getCompanySettings(), ]); if (!product) notFound(); const productSchema = buildProductSchema(product, "https://einfach-produktiv.mk360.de/der-eine", seller); + const ctx = isPreview ? await getProductBlockContext(product) : null; return ( <>