From aa4e3aab4f1de7efacb6c05a1bb83dabc01d4c09 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 28 Feb 2026 10:51:58 +0100 Subject: [PATCH] fix: product texts, mobile nav background, mobile product page layout - Fix PayloadRichText: migrate custom JSX converters to Lexical v3 nodesToJSX API - paragraph, heading, list, listitem, quote, link converters now use nodesToJSX - Resolves missing product texts since PayloadCMS migration - Fix mobile navigation: move overlay outside
to prevent fixed-position clipping - Header transform/backdrop-filter was containing the fixed overlay - Use bg-primary/95 backdrop-blur-3xl for premium blue background - Fix product image mobile layout: use md:-mt-32 responsive prefix - Negative margin only applies on md+ to avoid overlap on mobile - Improve mobile product page UX: - Breadcrumbs: flex-wrap, truncate, reduced separator spacing - Hero: reduced top padding pt-28 on mobile - Product image card: 4/3 aspect ratio and smaller padding on mobile - Section spacing: use responsive md: prefixes throughout - Data tables: 2-col grid on mobile, smaller card padding/radius - Tables: add right-edge scroll hint gradient on mobile --- .env | 1 + app/[locale]/products/[...slug]/page.tsx | 44 ++-- components/Header.tsx | 201 +++++++++--------- components/PayloadRichText.tsx | 122 +++-------- components/ProductTechnicalData.tsx | 14 +- docker-compose.dev.yml | 2 +- ...ent.config.ts => instrumentation-client.ts | 3 +- lib/products.ts | 3 + next-env.d.ts | 2 +- next.config.mjs | 1 + 10 files changed, 171 insertions(+), 222 deletions(-) rename sentry.client.config.ts => instrumentation-client.ts (64%) diff --git a/.env b/.env index 2272a516..99eaf41c 100644 --- a/.env +++ b/.env @@ -7,6 +7,7 @@ SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1 LOG_LEVEL=info NEXT_PUBLIC_FEEDBACK_ENABLED=false NEXT_PUBLIC_RECORD_MODE_ENABLED=false +NPM_TOKEN=263e7f75d8ada27f3a2e71fd6bd9d95298d48a4d # SMTP Configuration MAIL_HOST=smtp.eu.mailgun.org diff --git a/app/[locale]/products/[...slug]/page.tsx b/app/[locale]/products/[...slug]/page.tsx index d42d6a3d..c4be5c33 100644 --- a/app/[locale]/products/[...slug]/page.tsx +++ b/app/[locale]/products/[...slug]/page.tsx @@ -322,6 +322,8 @@ export default async function ProductPage({ params }: ProductPageProps) { } } + console.log(`[DEBUG PAGE] Slug: ${productSlug}, children count: ${descriptionChildren.length}`); + const descriptionContent = { root: { ...product.content.root, @@ -353,29 +355,31 @@ export default async function ProductPage({ params }: ProductPageProps) { categories={product.frontmatter.categories} sku={product.frontmatter.sku} /> -
+
{/* Background Decorative Elements */}
-
+ + + {/* Bottom Branding */} +
+ {t('home')} +
+ + ); } diff --git a/components/PayloadRichText.tsx b/components/PayloadRichText.tsx index a8c471ef..598441f8 100644 --- a/components/PayloadRichText.tsx +++ b/components/PayloadRichText.tsx @@ -39,95 +39,17 @@ import CTA from '@/components/home/CTA'; const jsxConverters: JSXConverters = { ...defaultJSXConverters, // Let the default converters handle text nodes to preserve valid formatting - // If the text node contains raw HTML (from messy migrations), render it as HTML instead of escaping it - text: ({ node }: any) => { - const text = node.text; - // Handle markdown-style lists embedded in text nodes from Markdown migration - if (text && text.includes('\n- ')) { - const parts = text.split('\n- ').filter((p: string) => p.trim() !== ''); - // If first part doesn't start with "- ", it's a prefix paragraph - const startsWithDash = text.trimStart().startsWith('- '); - const prefix = startsWithDash ? null : parts.shift(); - return ( -
- {prefix && ( -
- {!prefix.includes('<') ? prefix : undefined} -
- )} - -
- ); - } - - if (text && (text.includes('<') || text.includes('data-start'))) { - return ; - } - - // Handle markdown-style links [text](url) from Markdown migration - if (text && /\[([^\]]+)\]\(([^)]+)\)/.test(text)) { - const parts: React.ReactNode[] = []; - const remaining = text; - let key = 0; - const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; - let match; - let lastIndex = 0; - while ((match = linkRegex.exec(remaining)) !== null) { - if (match.index > lastIndex) { - parts.push({remaining.slice(lastIndex, match.index)}); - } - parts.push( - - {match[1]} - , - ); - lastIndex = match.index + match[0].length; - } - if (lastIndex < remaining.length) { - parts.push({remaining.slice(lastIndex)}); - } - return <>{parts}; - } - - // Handle newlines in text nodes — convert to
for proper line breaks - if (text && text.includes('\n')) { - const lines = text.split('\n'); - return ( - <> - {lines.map((line: string, i: number) => ( - - {line} - {i < lines.length - 1 &&
} -
- ))} - - ); - } - - if (node.format === 1) return {text}; - if (node.format === 2) return {text}; - return {text}; - }, // Use div instead of p for paragraphs to allow nested block elements (like the lists above) - paragraph: ({ children }: any) => ( -
{children}
- ), + paragraph: ({ node, nodesToJSX }: any) => { + return ( +
+ {nodesToJSX({ nodes: node.children })} +
+ ); + }, // Scale headings to prevent multiple H1s (H1 -> H2, etc) and style natively - heading: ({ node, children }: any) => { + heading: ({ node, nodesToJSX }: any) => { + const children = nodesToJSX({ nodes: node.children }); const tag = node?.tag; if (tag === 'h1') return ( @@ -151,7 +73,8 @@ const jsxConverters: JSXConverters = { ); return
{children}
; }, - list: ({ node, children }: any) => { + list: ({ node, nodesToJSX }: any) => { + const children = nodesToJSX({ nodes: node.children }); if (node?.listType === 'number') { return (
    @@ -168,7 +91,8 @@ const jsxConverters: JSXConverters = { ); }, - listitem: ({ node, children }: any) => { + listitem: ({ node, nodesToJSX }: any) => { + const children = nodesToJSX({ nodes: node.children }); if (node?.checked != null) { return (
  1. @@ -184,12 +108,16 @@ const jsxConverters: JSXConverters = { } return
  2. {children}
  3. ; }, - quote: ({ children }: any) => ( -
    - {children} -
    - ), - link: ({ node, children }: any) => { + quote: ({ node, nodesToJSX }: any) => { + const children = nodesToJSX({ nodes: node.children }); + return ( +
    + {children} +
    + ); + }, + link: ({ node, nodesToJSX }: any) => { + const children = nodesToJSX({ nodes: node.children }); // Handling Payload CMS link nodes const href = node?.fields?.url || node?.url || '#'; const newTab = node?.fields?.newTab || node?.newTab; @@ -1090,6 +1018,10 @@ export default function PayloadRichText({ if (!data) return null; + if (data.root?.children?.length > 0) { + console.log('[PayloadRichText DEBUG] received children', data.root.children.length); + } + const dynamicConverters: JSXConverters = { ...jsxConverters, blocks: { diff --git a/components/ProductTechnicalData.tsx b/components/ProductTechnicalData.tsx index 7679385d..d3672e87 100644 --- a/components/ProductTechnicalData.tsx +++ b/components/ProductTechnicalData.tsx @@ -38,14 +38,14 @@ export default function ProductTechnicalData({ data }: ProductTechnicalDataProps }; return ( -
    +
    {technicalItems.length > 0 && ( -
    +

    General Data

    -
    +
    {technicalItems.map((item, idx) => (
    @@ -72,7 +72,7 @@ export default function ProductTechnicalData({ data }: ProductTechnicalDataProps return (

    @@ -83,7 +83,7 @@ export default function ProductTechnicalData({ data }: ProductTechnicalDataProps

    {table.metaItems.length > 0 && ( -
    +
    {table.metaItems.map((item, mIdx) => (
    @@ -98,9 +98,11 @@ export default function ProductTechnicalData({ data }: ProductTechnicalDataProps )}
    + {/* Scroll hint gradient on right edge for mobile */} +
    diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 13c8aa66..2d504f82 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -29,7 +29,7 @@ services: NEXT_TELEMETRY_DISABLED: "1" POSTGRES_URI: postgres://${PAYLOAD_DB_USER:-payload}:${PAYLOAD_DB_PASSWORD:-120in09oenaoinsd9iaidon}@klz-db:5432/${PAYLOAD_DB_NAME:-payload} PAYLOAD_SECRET: ${PAYLOAD_SECRET:-fallback-secret-for-dev} - NODE_OPTIONS: "--max-old-space-size=4096" + NODE_OPTIONS: "--max-old-space-size=8192" UV_THREADPOOL_SIZE: "4" NPM_TOKEN: ${NPM_TOKEN:-} CI: "true" diff --git a/sentry.client.config.ts b/instrumentation-client.ts similarity index 64% rename from sentry.client.config.ts rename to instrumentation-client.ts index 1db7d0ef..cb479e76 100644 --- a/sentry.client.config.ts +++ b/instrumentation-client.ts @@ -1,4 +1,5 @@ // Sentry initialization move to GlitchtipErrorReportingService to allow lazy-loading // for PageSpeed 100 optimizations. This file is now empty to prevent the SDK // from being included in the initial JS bundle. -export {}; +import * as Sentry from '@sentry/nextjs'; +export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/lib/products.ts b/lib/products.ts index 5b53080e..9e693702 100644 --- a/lib/products.ts +++ b/lib/products.ts @@ -18,6 +18,7 @@ export interface ProductData { slug: string; frontmatter: ProductFrontmatter; content: any; // Lexical AST from Payload + application?: any; // Lexical AST for Application field } export async function getProductMetadata( @@ -113,6 +114,7 @@ export async function getProductBySlug(slug: string, locale: string): Promise { : 50, }, content: null, + application: null, }; }); diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.mjs b/next.config.mjs index dc5814df..103c4165 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -17,6 +17,7 @@ const nextConfig = { workerThreads: false, }, reactStrictMode: false, + swcMinify: true, productionBrowserSourceMaps: false, logging: { fetches: {