From 1330e3e621fbeadf4771c3387c49c7489f262a21 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 26 Aug 2026 22:26:14 +0000 Subject: [PATCH] Show product subline on cards/cart/PDP hero; brand-color trailing period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows Products.subline (docker/payload commit ad7156e). Cards and cart line items showed nothing but the bare name — added the subline under it. der-eine and todo-cards' hero taglines were hardcoded copy nearly identical to this new field — switched both to read subline instead, so future edits go through the CMS. Also adds a shared ProductName component: every hand-written PDP headline already styled a trailing "." in brand color, but the few places that render product.name as plain text (cards, cart, the Payload-driven tasse-die-pause hero) had silently lost that styling. Centralizes it so it can't drift per call site again. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01J1Hu5bZ1kZUgKhab6yNwCt --- app/cart/components/CartContent.tsx | 6 ++++-- app/components/ProductCard.tsx | 6 ++++-- app/components/ProductName.tsx | 16 ++++++++++++++++ app/der-eine/components/Hero.tsx | 14 ++++++++------ app/lib/__tests__/cartTotals.test.ts | 1 + app/lib/payload.ts | 5 +++++ app/tasse-die-pause/components/Hero.tsx | 20 ++++++++++++++------ app/tasse-die-pause/components/Pricing.tsx | 4 +++- app/todo-cards/components/TodoKartenHero.tsx | 14 ++++++++------ 9 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 app/components/ProductName.tsx diff --git a/app/cart/components/CartContent.tsx b/app/cart/components/CartContent.tsx index 27d8469..f6d8c61 100644 --- a/app/cart/components/CartContent.tsx +++ b/app/cart/components/CartContent.tsx @@ -14,6 +14,7 @@ import { Reveal } from "../../components/Reveal"; import { VersandModal } from "../../components/VersandModal"; import { VatBreakdown } from "../../components/VatBreakdown"; import { ArrowLeftIcon } from "../../components/ArrowLeftIcon"; +import { ProductName } from "../../components/ProductName"; import { FreeShippingBanner } from "./FreeShippingBanner"; import type { TrustBadge, ShippingSettings } from "../../lib/payload"; @@ -260,7 +261,7 @@ export function CartContent({ className="font-semibold text-h-small text-text-primary hover:text-brand transition-colors" style={{ fontFamily: "var(--font-lora)" }} > - {product.name} + {entry.variant ? ` (${entry.variant})` : ""} ) : ( @@ -268,10 +269,11 @@ export function CartContent({ className="font-semibold text-h-small text-text-primary" style={{ fontFamily: "var(--font-lora)" }} > - {product.name} + {entry.variant ? ` (${entry.variant})` : ""}

)} + {product.subline &&

{product.subline}

} {/* Independent stacked rows, not grid siblings — no equal-height pressure from neighboring lines, so a plain conditional line is enough here. */} diff --git a/app/components/ProductCard.tsx b/app/components/ProductCard.tsx index f2839a3..5eb98ea 100644 --- a/app/components/ProductCard.tsx +++ b/app/components/ProductCard.tsx @@ -5,6 +5,7 @@ import { formatPrice, discountPercent } from "../lib/format"; import { effectiveTaxRate } from "../lib/cartTotals"; import { AddToCartInlineButton } from "./AddToCartInlineButton"; import { WishlistButton } from "./WishlistButton"; +import { ProductName } from "./ProductName"; import type { Product } from "../lib/payload"; // Single shared card markup for every product grid (ProductGrid, @@ -108,13 +109,14 @@ export function ProductCard({ className="font-semibold text-h4 text-text-primary w-full hover:text-brand transition-colors" style={{ fontFamily: "var(--font-lora)" }} > - {product.name} + ) : (

- {product.name} +

)} + {product.subline &&

{product.subline}

}

{discount !== null && ( diff --git a/app/components/ProductName.tsx b/app/components/ProductName.tsx new file mode 100644 index 0000000..76d4ffe --- /dev/null +++ b/app/components/ProductName.tsx @@ -0,0 +1,16 @@ +// Every hand-written PDP headline styles a trailing "." in brand color +// (e.g. der-eine/Pricing.tsx's "Der Eine."), +// matching how every product's own Payload `name` is actually written +// ("Der Eine.", "Die Pause.", "Einfach anfangen."). Anywhere `product.name` +// is rendered as plain text instead (cards, cart line items, the one +// Payload-driven PDP hero) silently lost that styling — this centralizes +// the same trailing-period split so it can't drift per call site again. +export function ProductName({ name }: { name: string }) { + if (!name.endsWith(".")) return <>{name}; + return ( + <> + {name.slice(0, -1)} + . + + ); +} diff --git a/app/der-eine/components/Hero.tsx b/app/der-eine/components/Hero.tsx index 7d6a229..2141af9 100644 --- a/app/der-eine/components/Hero.tsx +++ b/app/der-eine/components/Hero.tsx @@ -63,12 +63,14 @@ export async function Hero() { > Der Eine.

-

- Für die eine Sache, die gerade zählt. -

+ {product?.subline && ( +

+ {product.subline} +

+ )}

diff --git a/app/lib/__tests__/cartTotals.test.ts b/app/lib/__tests__/cartTotals.test.ts index 540a408..9e7c141 100644 --- a/app/lib/__tests__/cartTotals.test.ts +++ b/app/lib/__tests__/cartTotals.test.ts @@ -6,6 +6,7 @@ const product = (overrides: Partial = {}): Product => ({ id: "todo-karten", numericId: 1, name: "ToDo-Karten", + subline: null, description: null, descriptionText: "", sku: null, diff --git a/app/lib/payload.ts b/app/lib/payload.ts index 867e871..28378f4 100644 --- a/app/lib/payload.ts +++ b/app/lib/payload.ts @@ -181,6 +181,9 @@ export type Product = { // slug-based call site. numericId: number; name: string; + // Short tagline shown under the name on cards/cart/PDP hero — see + // Products.ts's own field comment. null/"" means show nothing extra. + subline: string | null; // Raw Lexical richText JSON — rendered formatted (bold/italic/multiple // paragraphs) on the PDP via the shared component. Plain-text // consumers (Passend-dazu cards, JSON-LD) use `descriptionText` below @@ -271,6 +274,7 @@ export type Product = { type PayloadProduct = { id: number; name: string; + subline: string | null; slug: string; description: unknown | null; sku: string | null; @@ -356,6 +360,7 @@ export function mapPayloadProduct(product: PayloadProduct): Product { id: product.slug, numericId: product.id, name: product.name, + subline: product.subline || null, description: product.description ?? null, descriptionText: extractPlainText(descriptionRoot), sku: product.sku || null, diff --git a/app/tasse-die-pause/components/Hero.tsx b/app/tasse-die-pause/components/Hero.tsx index 82248fe..9d7a22c 100644 --- a/app/tasse-die-pause/components/Hero.tsx +++ b/app/tasse-die-pause/components/Hero.tsx @@ -3,6 +3,7 @@ import Image from "next/image"; import { AddToCartButton } from "../../components/AddToCartButton"; import { Reveal } from "../../components/Reveal"; import { RichText } from "../../components/RichText"; +import { ProductName } from "../../components/ProductName"; import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload"; import { formatPrice, discountPercent } from "../../lib/format"; import { effectiveTaxRate } from "../../lib/cartTotals"; @@ -44,12 +45,19 @@ export async function Hero() {

-

- {product?.name ?? "Tasse"} -

+
+

+ {product ? : "Tasse"} +

+ {product?.subline && ( +

+ {product.subline} +

+ )} +
{product?.description ? : null} diff --git a/app/tasse-die-pause/components/Pricing.tsx b/app/tasse-die-pause/components/Pricing.tsx index 5bab60a..cea47b9 100644 --- a/app/tasse-die-pause/components/Pricing.tsx +++ b/app/tasse-die-pause/components/Pricing.tsx @@ -2,6 +2,7 @@ import Image from "next/image"; import { AddToCartButton } from "../../components/AddToCartButton"; import { Reveal } from "../../components/Reveal"; import { RichText } from "../../components/RichText"; +import { ProductName } from "../../components/ProductName"; import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload"; import { formatPrice, discountPercent } from "../../lib/format"; import { effectiveTaxRate } from "../../lib/cartTotals"; @@ -47,8 +48,9 @@ export async function Pricing() { className="font-semibold text-h-small text-text-primary" style={{ fontFamily: "var(--font-lora)" }} > - {product.name} +

+ {product.subline &&

{product.subline}

} {product.description ? : null}
diff --git a/app/todo-cards/components/TodoKartenHero.tsx b/app/todo-cards/components/TodoKartenHero.tsx index ca0d74c..7a68556 100644 --- a/app/todo-cards/components/TodoKartenHero.tsx +++ b/app/todo-cards/components/TodoKartenHero.tsx @@ -97,12 +97,14 @@ export async function TodoKartenHero() { > ToDo-Karten.

-

- Kleine Karten. Große Wirkung. -

+ {product?.subline && ( +

+ {product.subline} +

+ )}