crossSell: match der-eine's/blog's own "Passt dazu" card style

ProductCard (price, wishlist heart, add-to-cart button) read as a
commerce grid, too busy for a single lightweight recommendation slot —
der-eine's own hand-coded PasstDazu.tsx (and the blog's "Passend dazu"
card) already had the right, more editorial look: bordered card,
thumbnail + name + description + "Entdecken" arrow, no price/buy
button. Reused that exact markup here instead, stacked rather than a
grid. getWishlistEnabled/ProductCard now unused in this file, removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Eg6h91yngXmSnM51wxXM8
This commit is contained in:
Marco
2026-08-29 23:42:35 +00:00
parent c98294a6a4
commit 48e73796b1
+38 -20
View File
@@ -8,7 +8,6 @@ import {
getTestimonials,
getProducts,
getProductsByIds,
getWishlistEnabled,
} from "../lib/payload";
import { formatPrice, discountPercent } from "../lib/format";
import { effectiveTaxRate } from "../lib/cartTotals";
@@ -17,7 +16,6 @@ import { ProductName } from "./ProductName";
import { RichText } from "./RichText";
import { Reveal } from "./Reveal";
import { ProductGallery } from "./ProductGallery";
import { ProductCard } from "./ProductCard";
import { STEP_ICONS } from "./icons/StepIcons";
import { renderPageBlockSync } from "./PageBlocks";
import { TestimonialsGrid } from "./TestimonialsGrid";
@@ -38,11 +36,10 @@ const CROSS_SELL_COUNT = 3;
// 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, wishlistEnabled] = await Promise.all([
const [shipping, defaultTaxRate, kleinunternehmer] = await Promise.all([
getShippingSettings(),
getDefaultTaxRatePercent(),
getKleinunternehmer(),
getWishlistEnabled(),
]);
const ctx: ProductBlockContext = { product, shipping, taxRate: effectiveTaxRate(product, defaultTaxRate), kleinunternehmer };
@@ -68,23 +65,44 @@ export async function ProductBlocks({ product }: { product: Product }) {
if (block.blockType === "crossSell") {
const recommended = await getCrossSellProducts(block, product);
if (recommended.length === 0) return null;
// Same bordered "Passt dazu" card as der-eine's own hand-coded
// PasstDazu.tsx (and the blog's "Passend dazu" card) — thumbnail +
// name + description + "Entdecken" arrow, no price/wishlist/buy
// button. Reads as editorial, not a commerce grid; ProductCard
// (price, wishlist heart, add-to-cart) was too busy here
// (reported 2026-08-29). Stacked, not a grid — PasstDazu's own
// design was always a single full-width card; this just allows
// more than one recommendation without losing that feel.
return (
<div key={block.id} className="w-full px-[var(--layout-padding-x)] py-8 sm:py-12">
<p className="font-semibold text-h-small text-text-primary mb-6" style={{ fontFamily: "var(--font-lora)" }}>
Das passt dazu
</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{recommended.map((p) => (
<ProductCard
key={p.id}
product={p}
defaultTaxRate={defaultTaxRate}
kleinunternehmer={kleinunternehmer}
wishlistEnabled={wishlistEnabled}
wishlistRevealOnHover
/>
))}
</div>
<div key={block.id} className="w-full px-[var(--layout-padding-x)] py-8 sm:py-12 flex flex-col gap-4 max-w-[48rem] mx-auto">
{recommended.map((p) => (
<Link
key={p.id}
href={p.href ?? `/${p.id}`}
className="group flex items-center gap-4 sm:gap-6 border border-border rounded-md px-5 py-5 sm:px-9 sm:py-7 hover:border-brand transition-colors"
>
<div className="relative w-16 h-[4.6875rem] shrink-0 rounded-sm overflow-hidden">
<Image src={p.image} alt="" fill sizes="64px" className="object-cover" />
</div>
<div className="flex-1 min-w-0 flex flex-col gap-2.5">
<p className="font-bold text-[0.8125rem] text-brand">Passt dazu:</p>
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 w-full">
<div className="flex flex-col gap-2 items-start w-full sm:w-[19rem] sm:shrink-0">
<p className="font-semibold text-[1.375rem] text-text-primary sm:whitespace-nowrap" style={{ fontFamily: "var(--font-lora)" }}>
{p.name}
</p>
<p className="text-[0.9375rem] text-text-muted leading-[1.45]">{p.descriptionText}</p>
</div>
<span className="flex items-center gap-1.5 font-bold text-[0.875rem] text-text-primary whitespace-nowrap mt-1 sm:mt-0">
Entdecken
<svg viewBox="0 0 20 20" className="size-3.5 transition-transform duration-200 group-hover:translate-x-1" fill="none" aria-hidden="true">
<path d="M4 10h12m0 0-5-5m5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</div>
</div>
</Link>
))}
</div>
);
}