Compare commits
2 Commits
82696cb259
...
21b5f41127
| Author | SHA1 | Date | |
|---|---|---|---|
| 21b5f41127 | |||
| 7489f83564 |
@@ -162,7 +162,7 @@ export default async function BlogDetailPage({
|
||||
>
|
||||
{post.relatedProduct.name}
|
||||
</p>
|
||||
<p className="text-[0.9375rem] text-text-muted leading-[1.45]">{post.relatedProduct.description}</p>
|
||||
<p className="text-[0.9375rem] text-text-muted leading-[1.45]">{post.relatedProduct.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
|
||||
|
||||
@@ -276,7 +276,6 @@ export function CartContent({
|
||||
equal-height pressure from neighboring lines, so a
|
||||
plain conditional line is enough here. */}
|
||||
{lowStock && <p className="text-label font-bold text-warning">Nur noch wenige verfügbar</p>}
|
||||
<p className="font-bold text-body-sm text-text-muted">{product.description}</p>
|
||||
<div className="flex flex-col gap-0.5 items-start">
|
||||
<p className="text-label text-text-muted">Einzelpreis</p>
|
||||
<p className="flex items-baseline gap-1.5">
|
||||
|
||||
@@ -56,11 +56,14 @@ export function renderPageBlockSync(block: PageBlock): React.ReactNode {
|
||||
return (
|
||||
<div key={block.id} className="flex flex-col gap-4 items-start">
|
||||
{/* Optional round portrait beside the headline — e.g. /ueber-mich's
|
||||
"Hallo, ich bin Björn." */}
|
||||
"Hallo, ich bin Björn." size-20 (80px) read as barely-there/
|
||||
easy to miss next to a full headline — bumped to size-32
|
||||
(128px) so it actually registers as a real photo, not a
|
||||
small decorative blob. */}
|
||||
{block.image ? (
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="relative size-20 shrink-0 rounded-full overflow-hidden">
|
||||
<Image alt="" src={block.image} fill sizes="80px" className="object-cover" />
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="relative size-32 shrink-0 rounded-full overflow-hidden ring-1 ring-border">
|
||||
<Image alt="" src={block.image} fill sizes="128px" className="object-cover" />
|
||||
</div>
|
||||
{headline}
|
||||
</div>
|
||||
|
||||
@@ -134,7 +134,7 @@ export async function ProductSpotlight() {
|
||||
>
|
||||
{product.spotlightHeadline || product.name}
|
||||
</p>
|
||||
<SpotlightText content={product.spotlightText} fallback={product.description} />
|
||||
<SpotlightText content={product.spotlightText} fallback={product.descriptionText} />
|
||||
{/* MwSt./Versand disclosure on its own line, not crammed into
|
||||
the price row itself — same reasoning as todo-cards'
|
||||
Pricing.tsx (identical text, same narrow-column risk). */}
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function PasstDazu({ productSlug }: { productSlug: string }) {
|
||||
>
|
||||
{related.name}
|
||||
</p>
|
||||
<p className="text-[0.9375rem] text-text-muted leading-[1.45]">{related.description}</p>
|
||||
<p className="text-[0.9375rem] text-text-muted leading-[1.45]">{related.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
|
||||
|
||||
@@ -18,7 +18,7 @@ const title = "Der Eine. – Für die eine Sache, die gerade zählt.";
|
||||
// the cart/checkout/JSON-LD elsewhere) — same field, one source now.
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const product = await getProductBySlug("stift-kugelschreiber");
|
||||
const description = product?.description ?? undefined;
|
||||
const description = product?.descriptionText || undefined;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
|
||||
@@ -6,7 +6,8 @@ const product = (overrides: Partial<Product> = {}): Product => ({
|
||||
id: "todo-karten",
|
||||
numericId: 1,
|
||||
name: "ToDo-Karten",
|
||||
description: "",
|
||||
description: null,
|
||||
descriptionText: "",
|
||||
sku: null,
|
||||
price: 12.9,
|
||||
compareAtPrice: null,
|
||||
|
||||
+26
-3
@@ -181,7 +181,12 @@ export type Product = {
|
||||
// slug-based call site.
|
||||
numericId: number;
|
||||
name: string;
|
||||
description: string;
|
||||
// Raw Lexical richText JSON — rendered formatted (bold/italic/multiple
|
||||
// paragraphs) on the PDP via the shared <RichText> component. Plain-text
|
||||
// consumers (Passend-dazu cards, JSON-LD) use `descriptionText` below
|
||||
// instead of re-deriving text from this themselves.
|
||||
description: unknown | null;
|
||||
descriptionText: string;
|
||||
// Not shown anywhere in the UI today — only consumed by
|
||||
// buildProductSchema (JSON-LD's Product.sku), a real Schema.org property
|
||||
// Google's rich-result validator looks for.
|
||||
@@ -267,7 +272,7 @@ type PayloadProduct = {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
description: unknown | null;
|
||||
sku: string | null;
|
||||
price: number;
|
||||
compareAtPrice: number | null;
|
||||
@@ -325,16 +330,34 @@ function maxPurchasableQty(trackInventory: boolean, stock: number | null, allowB
|
||||
return trackInventory && !allowBackorder ? (stock ?? 0) : null;
|
||||
}
|
||||
|
||||
type LexicalTextNode = {
|
||||
text?: string;
|
||||
children?: LexicalTextNode[];
|
||||
};
|
||||
|
||||
// Mirrors the backend's own Posts.ts extractPlainText (same shape, same
|
||||
// space-joined fallback for multiple paragraphs/children) — kept as a
|
||||
// separate copy rather than a shared package since it's a few lines and
|
||||
// the two run in different runtimes (Payload server vs. this frontend).
|
||||
function extractPlainText(node: LexicalTextNode | null | undefined): string {
|
||||
if (!node) return "";
|
||||
if (typeof node.text === "string") return node.text;
|
||||
if (Array.isArray(node.children)) return node.children.map(extractPlainText).join(" ");
|
||||
return "";
|
||||
}
|
||||
|
||||
// Shared by getProducts() and getPostBySlug()'s relatedProduct — kept in
|
||||
// one place instead of duplicating the same field mapping, which is
|
||||
// exactly the kind of drift this session's Shipping Settings work was
|
||||
// about eliminating elsewhere.
|
||||
export function mapPayloadProduct(product: PayloadProduct): Product {
|
||||
const descriptionRoot = (product.description as { root?: LexicalTextNode } | null)?.root;
|
||||
return {
|
||||
id: product.slug,
|
||||
numericId: product.id,
|
||||
name: product.name,
|
||||
description: product.description ?? "",
|
||||
description: product.description ?? null,
|
||||
descriptionText: extractPlainText(descriptionRoot),
|
||||
sku: product.sku || null,
|
||||
price: product.price,
|
||||
compareAtPrice: product.compareAtPrice ?? null,
|
||||
|
||||
@@ -53,7 +53,7 @@ export function buildProductSchema(product: Product, url: string, seller: Compan
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Product",
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
description: product.descriptionText,
|
||||
image: product.gallery.length > 0 ? [product.image, ...product.gallery] : product.image,
|
||||
url,
|
||||
...(product.sku ? { sku: product.sku } : {}),
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { AddToCartButton } from "../../components/AddToCartButton";
|
||||
import { Reveal } from "../../components/Reveal";
|
||||
import { RichText } from "../../components/RichText";
|
||||
import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload";
|
||||
import { formatPrice, discountPercent } from "../../lib/format";
|
||||
import { effectiveTaxRate } from "../../lib/cartTotals";
|
||||
@@ -50,7 +51,7 @@ export async function Hero() {
|
||||
{product?.name ?? "Tasse"}
|
||||
</p>
|
||||
|
||||
{product?.description && <p className="text-body text-text-body">{product.description}</p>}
|
||||
{product?.description ? <RichText content={product.description} /> : null}
|
||||
|
||||
<div className="flex flex-col gap-1 items-start">
|
||||
{product && (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Image from "next/image";
|
||||
import { AddToCartButton } from "../../components/AddToCartButton";
|
||||
import { Reveal } from "../../components/Reveal";
|
||||
import { RichText } from "../../components/RichText";
|
||||
import { getProductBySlug, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload";
|
||||
import { formatPrice, discountPercent } from "../../lib/format";
|
||||
import { effectiveTaxRate } from "../../lib/cartTotals";
|
||||
@@ -48,7 +49,7 @@ export async function Pricing() {
|
||||
>
|
||||
{product.name}
|
||||
</p>
|
||||
{product.description && <p className="text-body-sm text-text-primary">{product.description}</p>}
|
||||
{product.description ? <RichText content={product.description} /> : null}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 items-start w-full lg:w-[18.75rem] lg:shrink-0">
|
||||
|
||||
@@ -11,7 +11,7 @@ import { buildProductSchema } from "../lib/structuredData";
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const product = await getProductBySlug("tasse-die-pause");
|
||||
const title = product ? `${product.name} – einfach produktiv.` : "Tasse – einfach produktiv.";
|
||||
const description = product?.description ?? undefined;
|
||||
const description = product?.descriptionText || undefined;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
|
||||
Reference in New Issue
Block a user