PasstDazu driven by Products.relatedProduct; circle back on Focus icons

- PasstDazu.tsx now takes a productSlug prop and reads its cross-sell
  target from the product's own relatedProduct field (Payload) instead
  of hardcoding "todo-karten" — reusable on any product page now.
- payload.ts: Product/PayloadProduct gained relatedProduct, resolved via
  a bounded-recursion mapPayloadProduct call (safe — the fetch's depth:2
  means the nested relation's own relatedProduct is never populated).
- Focus.tsx icons: added the black stroked circle back around each glyph
  (matches icon-step-3.png exactly) — dropped in the previous pass by
  mistake while fixing the color/fill.
This commit is contained in:
Marco
2026-08-25 13:45:52 +00:00
parent b59c9a0e4b
commit 1f537ccb06
5 changed files with 40 additions and 24 deletions
+11
View File
@@ -251,6 +251,12 @@ export type Product = {
// than "matches none", since an uncategorized product shouldn't just
// disappear the moment a category filter is applied.
categories: string[];
// Optional cross-sell pick (Products.relatedProduct) — powers a "Passt
// dazu" card on the product's own detail page, same field/pattern as
// Posts.relatedProduct. null means no card. Only one level deep (this
// related product's own relatedProduct is never resolved/shown) — see
// mapPayloadProduct's own comment.
relatedProduct: Product | null;
};
type PayloadProduct = {
@@ -288,6 +294,7 @@ type PayloadProduct = {
}[]
| null;
categories: ({ name: string } | number)[] | null;
relatedProduct: PayloadProduct | number | null;
};
// A product/variant is only actually unbuyable when it opted into
@@ -354,6 +361,10 @@ export function mapPayloadProduct(product: PayloadProduct): Product {
categories: (product.categories ?? [])
.map((c) => (typeof c === "object" && c ? c.name : null))
.filter((name): name is string => Boolean(name)),
// Recursive call is safe here — depth=2 on the originating fetch means
// this nested object's OWN relatedProduct is never populated (stays a
// raw id or absent), so the recursion bottoms out after exactly one level.
relatedProduct: typeof product.relatedProduct === "object" && product.relatedProduct ? mapPayloadProduct(product.relatedProduct) : null,
};
}