import { defaultJSXConverters, RichText } from '@payloadcms/richtext-lexical/react'; import type { JSXConverters } from '@payloadcms/richtext-lexical/react'; import Image from 'next/image'; // Import all custom React components that were previously mapped via MDX import StickyNarrative from '@/components/blog/StickyNarrative'; import ComparisonGrid from '@/components/blog/ComparisonGrid'; import VisualLinkPreview from '@/components/blog/VisualLinkPreview'; import TechnicalGrid from '@/components/blog/TechnicalGrid'; import HighlightBox from '@/components/blog/HighlightBox'; import AnimatedImage from '@/components/blog/AnimatedImage'; import ChatBubble from '@/components/blog/ChatBubble'; import PowerCTA from '@/components/blog/PowerCTA'; import { Callout } from '@/components/ui/Callout'; import Stats from '@/components/blog/Stats'; import SplitHeading from '@/components/blog/SplitHeading'; import ProductTabs from '@/components/ProductTabs'; import ProductTechnicalData from '@/components/ProductTechnicalData'; 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; if (text && (text.includes('<') || text.includes('data-start'))) { return ; } if (node.format === 1) return {text}; if (node.format === 2) return {text}; return {text}; }, // Scale headings to prevent multiple H1s (H1 -> H2, etc) h1: ({ children }: any) =>

{children}

, h2: ({ children }: any) =>

{children}

, h3: ({ children }: any) =>

{children}

, blocks: { // ... preserved existing blocks ... // Map the custom Payload Blocks created in src/payload/blocks to their React components // Payload Lexical exposes blocks using the 'block-[slug]' pattern stickyNarrative: ({ node }: any) => ( ), 'block-stickyNarrative': ({ node }: any) => ( ), comparisonGrid: ({ node }: any) => ( ), 'block-comparisonGrid': ({ node }: any) => ( ), visualLinkPreview: ({ node }: any) => ( ), 'block-visualLinkPreview': ({ node }: any) => ( ), technicalGrid: ({ node }: any) => ( ), 'block-technicalGrid': ({ node }: any) => { console.log('[PayloadRichText] Rendering block-technicalGrid:', node.fields.title); return ; }, highlightBox: ({ node }: any) => ( ), 'block-highlightBox': ({ node }: any) => ( ), animatedImage: ({ node }: any) => ( ), 'block-animatedImage': ({ node }: any) => ( ), chatBubble: ({ node }: any) => ( ), 'block-chatBubble': ({ node }: any) => ( ), powerCTA: ({ node }: any) => , 'block-powerCTA': ({ node }: any) => , callout: ({ node }: any) => ( ), 'block-callout': ({ node }: any) => ( ), stats: ({ node }: any) => , 'block-stats': ({ node }: any) => , splitHeading: ({ node }: any) => ( {node.fields.title} ), 'block-splitHeading': ({ node }: any) => ( {node.fields.title} ), productTabs: ({ node }: any) => ( } > <> ), 'block-productTabs': ({ node }: any) => ( } > {node.fields.content && } ), }, // Custom converter for the Payload "upload" Lexical node (Media collection) // This natively reconstructs Next.js tags pointing to the focal-point cropped sizes upload: ({ node }: any) => { // Attempt to extract the highly optimized 'card' generated size from Payload, fallback to raw url let src = node?.value?.sizes?.card?.url || node?.value?.url; const alt = node?.value?.alt || 'Blog Post Media'; if (!src) return null; // Strip legacy imgproxy query parameters (e.g. ?ar=16:9) that crash Next.js 14+ localPatterns if (src.includes('?')) { src = src.split('?')[0]; } // Fallback dimensions if unmapped or loading from raw const width = node?.value?.sizes?.card?.width || 800; const height = node?.value?.sizes?.card?.height || 600; return (
{alt} {node?.value?.caption && (
{node.value.caption}
)}
); }, }; export default function PayloadRichText({ data }: { data: any }) { if (!data) return null; return (
); }