Frontend: render storySplit/faq/crossSell, gallery+badge+usps, qty stepper
Wires up the new PDP page-builder gaps (Products.layout backend already built, migrated in a companion payload repo commit): - storySplit/faq render via PageBlocks.tsx (shared with Pages.layout); crossSell is Products-only, resolved server-side in ProductBlocks.tsx (manual: getProductsByIds; automatic: active products sharing a category, falling back to any other active product). - ProductGallery gets an optional `badge` overlay slot; ProductHero now uses it (previously a single plain <Image>, no badge at all) instead of duplicating a second image element. - New shared ProductBadge helper (discount % / Ausverkauft / Neu) used by both ProductHero and ProductPricingPanel — only the pricing panel showed this before. - product.usps (max-3 icon+text) renders in the hero, reusing StepRowBlock's icon set via STEP_ICONS. - AddToCartButton gets an opt-in showQuantityStepper prop (default off, no behavior change for existing callers); enabled on both PDP buy buttons. - quote gets a PDP-specific full-bleed brand-background treatment, scoped to ProductBlocks.tsx's own switch case so Pages.layout's existing quote styling (e.g. /ueber-mich) is untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
This commit is contained in:
@@ -209,6 +209,62 @@ export function renderPageBlockSync(block: PageBlock): React.ReactNode {
|
||||
</Link>
|
||||
);
|
||||
|
||||
case "storySplit":
|
||||
return (
|
||||
<div
|
||||
key={block.id}
|
||||
className={`flex flex-col gap-8 lg:gap-10 items-center w-full ${block.imagePosition === "right" ? "lg:flex-row-reverse" : "lg:flex-row"}`}
|
||||
>
|
||||
{block.image && (
|
||||
<div className="relative w-full lg:w-[44%] lg:shrink-0 rounded-xl overflow-hidden bg-bg-muted" style={{ minHeight: "16rem" }}>
|
||||
<Image alt="" src={block.image} fill sizes="(min-width: 1024px) 44vw, 100vw" className="object-cover" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 w-full flex flex-col gap-4">
|
||||
{block.heading && (
|
||||
<p className="font-semibold text-h-small text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
{block.heading}
|
||||
</p>
|
||||
)}
|
||||
{block.content ? <RichText content={block.content} /> : null}
|
||||
{block.bullets.length > 0 && (
|
||||
<ul className="flex flex-col gap-2">
|
||||
{block.bullets.map((bullet) => (
|
||||
<li key={bullet.id} className="flex items-start gap-3">
|
||||
<CheckIcon />
|
||||
<p className="text-body text-text-body">{bullet.text}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case "faq":
|
||||
return (
|
||||
<div key={block.id} className="flex flex-col gap-3 w-full">
|
||||
{block.items.map((item) => (
|
||||
<details key={item.id} className="group border border-border rounded-md px-5 py-4 open:border-brand transition-colors">
|
||||
<summary className="flex items-center justify-between gap-4 cursor-pointer list-none font-semibold text-body text-text-primary">
|
||||
{item.question}
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
className="size-4 shrink-0 text-text-muted transition-transform duration-200 group-open:rotate-45"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M10 4v12M4 10h12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
</summary>
|
||||
<div className="pt-3 text-body-sm text-text-muted">
|
||||
<RichText content={item.answer} />
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
// Unknown/malformed block — same "skip rather than crash" convention
|
||||
// RichText.tsx's own blocks use.
|
||||
|
||||
Reference in New Issue
Block a user