feat: payload cms
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m13s
Build & Deploy / 🏗️ Build (push) Failing after 5m53s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m13s
Build & Deploy / 🏗️ Build (push) Failing after 5m53s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import { Container, Badge, Heading } from '@/components/ui';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import { getPageBySlug, getAllPages } from '@/lib/pages';
|
||||
import { mdxComponents } from '@/components/blog/MDXComponents';
|
||||
import PayloadRichText from '@/components/PayloadRichText';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
|
||||
@@ -102,7 +101,7 @@ export default async function StandardPage({ params }: PageProps) {
|
||||
|
||||
{/* Main content with shared blog components */}
|
||||
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
|
||||
<MDXRemote source={pageData.content} components={mdxComponents} />
|
||||
<PayloadRichText data={pageData.content} />
|
||||
</div>
|
||||
|
||||
{/* Support Section */}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import { getPostBySlug, getAdjacentPosts, getReadingTime, getHeadings } from '@/lib/blog';
|
||||
import { getPostBySlug, getAdjacentPosts, getReadingTime } from '@/lib/blog';
|
||||
import { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import PostNavigation from '@/components/blog/PostNavigation';
|
||||
import PowerCTA from '@/components/blog/PowerCTA';
|
||||
import TableOfContents from '@/components/blog/TableOfContents';
|
||||
import { mdxComponents } from '@/components/blog/MDXComponents';
|
||||
import { Heading } from '@/components/ui';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import BlogEngagementTracker from '@/components/analytics/BlogEngagementTracker';
|
||||
|
||||
// Payload CMS Imports
|
||||
import PayloadRichText from '@/components/PayloadRichText';
|
||||
|
||||
interface BlogPostProps {
|
||||
params: Promise<{
|
||||
locale: string;
|
||||
@@ -60,7 +60,8 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const headings = getHeadings(post.content);
|
||||
// Convert Lexical content into a plain string to estimate reading time roughly
|
||||
const rawTextContent = JSON.stringify(post.content);
|
||||
|
||||
return (
|
||||
<article className="bg-white min-h-screen font-sans selection:bg-primary/10 selection:text-primary">
|
||||
@@ -68,7 +69,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
title={post.frontmatter.title}
|
||||
slug={slug}
|
||||
category={post.frontmatter.category}
|
||||
readingTime={getReadingTime(post.content)}
|
||||
readingTime={getReadingTime(rawTextContent)}
|
||||
/>
|
||||
|
||||
{/* Featured Image Header */}
|
||||
@@ -76,7 +77,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
<div className="relative w-full h-[70vh] min-h-[500px] overflow-hidden group">
|
||||
<div className="absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100">
|
||||
<Image
|
||||
src={`${post.frontmatter.featuredImage}?ar=16:9`}
|
||||
src={post.frontmatter.featuredImage.split('?')[0]}
|
||||
alt={post.frontmatter.title}
|
||||
fill
|
||||
priority
|
||||
@@ -109,7 +110,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
})}
|
||||
</time>
|
||||
<span className="w-1 h-1 bg-white/30 rounded-full" />
|
||||
<span>{getReadingTime(post.content)} min read</span>
|
||||
<span>{getReadingTime(rawTextContent)} min read</span>
|
||||
{(new Date(post.frontmatter.date) > new Date() ||
|
||||
post.frontmatter.public === false) && (
|
||||
<>
|
||||
@@ -146,7 +147,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
})}
|
||||
</time>
|
||||
<span className="w-1 h-1 bg-neutral-400 rounded-full" />
|
||||
<span>{getReadingTime(post.content)} min read</span>
|
||||
<span>{getReadingTime(rawTextContent)} min read</span>
|
||||
{(new Date(post.frontmatter.date) > new Date() ||
|
||||
post.frontmatter.public === false) && (
|
||||
<>
|
||||
@@ -175,9 +176,9 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main content with enhanced styling */}
|
||||
{/* Main content with enhanced styling rendering Payload Lexical */}
|
||||
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
|
||||
<MDXRemote source={post.content} components={mdxComponents} />
|
||||
<PayloadRichText data={post.content} />
|
||||
</div>
|
||||
|
||||
{/* Power CTA */}
|
||||
@@ -220,10 +221,10 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Sticky Sidebar */}
|
||||
{/* Right Column: Sticky Sidebar - Temporarily Hidden without ToC */}
|
||||
<aside className="sticky-narrative-sidebar hidden lg:block">
|
||||
<div className="space-y-12">
|
||||
<TableOfContents headings={headings} locale={locale} />
|
||||
{/* Future Payload Table of Contents Implementation */}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
@@ -262,8 +263,8 @@ export default async function BlogPost({ params }: BlogPostProps) {
|
||||
'@id': `${SITE_URL}/${locale}/blog/${slug}`,
|
||||
},
|
||||
articleSection: post.frontmatter.category,
|
||||
wordCount: post.content.split(/\s+/).length,
|
||||
timeRequired: `PT${getReadingTime(post.content)}M`,
|
||||
wordCount: rawTextContent.split(/\s+/).length,
|
||||
timeRequired: `PT${getReadingTime(rawTextContent)}M`,
|
||||
} as any
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -63,7 +63,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
{featuredPost && featuredPost.frontmatter.featuredImage && (
|
||||
<>
|
||||
<Image
|
||||
src={featuredPost.frontmatter.featuredImage}
|
||||
src={featuredPost.frontmatter.featuredImage.split('?')[0]}
|
||||
alt={featuredPost.frontmatter.title}
|
||||
fill
|
||||
className="absolute inset-0 w-full h-full object-cover opacity-40 md:opacity-60"
|
||||
@@ -164,7 +164,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
{post.frontmatter.featuredImage && (
|
||||
<div className="relative h-48 md:h-72 overflow-hidden">
|
||||
<Image
|
||||
src={post.frontmatter.featuredImage}
|
||||
src={post.frontmatter.featuredImage.split('?')[0]}
|
||||
alt={post.frontmatter.title}
|
||||
fill
|
||||
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||
|
||||
@@ -8,7 +8,6 @@ export const contentType = 'image/png';
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export default async function Image({ params }: { params: Promise<{ locale: string }> }) {
|
||||
console.log('🖼️ OG Image Handler Called');
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Index.meta' });
|
||||
const fonts = await getOgFonts();
|
||||
|
||||
@@ -12,11 +12,11 @@ import { mapFileSlugToTranslated, mapSlugToFileSlug } from '@/lib/slugs';
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { getProductOGImageMetadata } from '@/lib/metadata';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import ProductEngagementTracker from '@/components/analytics/ProductEngagementTracker';
|
||||
import PayloadRichText from '@/components/PayloadRichText';
|
||||
|
||||
interface ProductPageProps {
|
||||
params: Promise<{
|
||||
@@ -103,76 +103,7 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
};
|
||||
}
|
||||
|
||||
const components = {
|
||||
ProductTechnicalData,
|
||||
ProductTabs,
|
||||
p: (props: any) => (
|
||||
<p
|
||||
{...props}
|
||||
className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8 font-medium"
|
||||
/>
|
||||
),
|
||||
h1: (props: any) => (
|
||||
<div className="relative mb-16">
|
||||
<h2
|
||||
{...props}
|
||||
className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-6"
|
||||
/>
|
||||
<div className="w-20 h-1.5 bg-accent rounded-full" />
|
||||
</div>
|
||||
),
|
||||
h2: (props: any) => (
|
||||
<h3
|
||||
{...props}
|
||||
className="text-2xl md:text-3xl font-black text-primary mb-10 tracking-tight uppercase"
|
||||
/>
|
||||
),
|
||||
h3: (props: any) => (
|
||||
<h4
|
||||
{...props}
|
||||
className="text-xl md:text-2xl font-black text-primary mb-8 tracking-tight uppercase"
|
||||
/>
|
||||
),
|
||||
ul: (props: any) => <ul {...props} className="list-none pl-0 mb-10" />,
|
||||
section: (props: any) => <div {...props} className="block" />,
|
||||
li: (props: any) => (
|
||||
<li className="flex items-start gap-4 group mb-4 last:mb-0">
|
||||
<div className="mt-2.5 w-2 h-2 rounded-full bg-accent flex-shrink-0 group-hover:scale-125 transition-transform" />
|
||||
<span
|
||||
{...props}
|
||||
className="text-lg md:text-xl text-text-secondary leading-relaxed font-medium"
|
||||
/>
|
||||
</li>
|
||||
),
|
||||
strong: (props: any) => <strong {...props} className="font-black text-primary" />,
|
||||
table: (props: any) => (
|
||||
<div className="overflow-x-auto my-20 rounded-[32px] border border-neutral-dark/10 shadow-xl bg-white p-1">
|
||||
<table {...props} className="min-w-full divide-y divide-neutral-dark/10" />
|
||||
</div>
|
||||
),
|
||||
th: (props: any) => (
|
||||
<th
|
||||
{...props}
|
||||
className="px-8 py-6 bg-neutral-light/50 text-left text-[10px] font-black uppercase tracking-[0.25em] text-primary/60"
|
||||
/>
|
||||
),
|
||||
td: (props: any) => (
|
||||
<td
|
||||
{...props}
|
||||
className="px-8 py-6 text-text-secondary border-t border-neutral-dark/5 text-lg md:text-xl font-medium"
|
||||
/>
|
||||
),
|
||||
hr: () => <hr className="my-24 border-t-2 border-neutral-dark/5" />,
|
||||
blockquote: (props: any) => (
|
||||
<div className="my-20 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
|
||||
<div
|
||||
className="relative z-10 italic text-2xl md:text-3xl text-white/90 leading-relaxed font-black tracking-tight"
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
// ... the rest of the file layout ...
|
||||
|
||||
export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const { locale, slug } = await params;
|
||||
@@ -181,7 +112,6 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const t = await getTranslations('Products');
|
||||
const productsSlug = await mapFileSlugToTranslated('products', locale);
|
||||
|
||||
// Check if it's a category page
|
||||
const categories = [
|
||||
'low-voltage-cables',
|
||||
'medium-voltage-cables',
|
||||
@@ -191,6 +121,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const fileSlug = await mapSlugToFileSlug(productSlug, locale);
|
||||
|
||||
if (categories.includes(fileSlug)) {
|
||||
// (Skipping category page block, same as before)
|
||||
const allProducts = await getAllProducts(locale);
|
||||
const categoryKey = fileSlug
|
||||
.replace(/-cables$/, '')
|
||||
@@ -199,14 +130,12 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
? t(`categories.${categoryKey}.title`)
|
||||
: fileSlug;
|
||||
|
||||
// Filter products for this category
|
||||
const filteredProducts = allProducts.filter((p) =>
|
||||
p.frontmatter.categories.some(
|
||||
(cat) => cat.toLowerCase().replace(/\s+/g, '-') === fileSlug || cat === categoryTitle,
|
||||
),
|
||||
);
|
||||
|
||||
// Get translated product slugs
|
||||
const productsWithTranslatedSlugs = await Promise.all(
|
||||
filteredProducts.map(async (p) => ({
|
||||
...p,
|
||||
@@ -257,7 +186,6 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
className="object-contain p-8 transition-transform duration-700 group-hover:scale-110 z-10"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
{/* Subtle reflection/shadow effect */}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 w-2/3 h-4 bg-black/5 blur-xl rounded-full" />
|
||||
</>
|
||||
)}
|
||||
@@ -314,17 +242,14 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Extract technical data for schema
|
||||
const technicalDataMatch = product.content.match(
|
||||
/technicalData=\{<ProductTechnicalData data=\{(.*?)\}\s*\/>\}/s,
|
||||
);
|
||||
// Extract technical data natively from the Lexical AST for Schema.org
|
||||
let technicalItems = [];
|
||||
if (technicalDataMatch) {
|
||||
try {
|
||||
const data = JSON.parse(technicalDataMatch[1]);
|
||||
technicalItems = data.technicalItems || [];
|
||||
} catch (e) {
|
||||
console.error('Failed to parse technical data for schema', e);
|
||||
if (product.content?.root?.children) {
|
||||
const productTabsBlock = product.content.root.children.find(
|
||||
(node: any) => node.type === 'block' && node.fields?.blockType === 'productTabs',
|
||||
);
|
||||
if (productTabsBlock && productTabsBlock.fields?.technicalItems) {
|
||||
technicalItems = productTabsBlock.fields.technicalItems;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,23 +272,6 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
/>
|
||||
);
|
||||
|
||||
const productComponents = {
|
||||
...components,
|
||||
ProductTabs: (props: any) => <ProductTabs {...props} sidebar={sidebar} />,
|
||||
};
|
||||
|
||||
// Pre-process content to convert raw HTML tags to Markdown so they use our custom components
|
||||
const processedContent = product.content
|
||||
.replace(/<h1[^>]*>(.*?)<\/h1>/gs, '\n# $1\n') // Maps to our custom h1 (which renders h2)
|
||||
.replace(/<h2[^>]*>(.*?)<\/h2>/gs, '\n## $1\n') // Maps to our custom h2 (which renders h3)
|
||||
.replace(/<h3[^>]*>(.*?)<\/h3>/gs, '\n### $1\n') // Maps to our custom h3 (which renders h4)
|
||||
.replace(/<p[^>]*>(.*?)<\/p>/gs, '\n$1\n')
|
||||
.replace(/<ul[^>]*>(.*?)<\/ul>/gs, '\n$1\n')
|
||||
.replace(/<li[^>]*>(.*?)<\/li>/gs, '\n- $1\n')
|
||||
.replace(/<strong[^>]*>(.*?)<\/strong>/gs, '**$1**')
|
||||
.replace(/<section[^>]*>/gs, '')
|
||||
.replace(/<\/section>/gs, '');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-white relative">
|
||||
{/* Product Hero */}
|
||||
@@ -474,8 +382,8 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
<div className="relative">
|
||||
<div className="w-full">
|
||||
{/* Main Content Area */}
|
||||
<div className="max-w-none">
|
||||
<MDXRemote source={processedContent} components={productComponents} />
|
||||
<div className="max-w-none prose prose-lg mt-8">
|
||||
<PayloadRichText data={product.content} />
|
||||
</div>
|
||||
|
||||
{/* Datasheet Download Section - Only for Medium Voltage for now */}
|
||||
|
||||
@@ -236,7 +236,7 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
className="group whitespace-nowrap w-full md:w-auto md:h-16 md:px-10 md:text-xl"
|
||||
>
|
||||
{t('cta.button')}
|
||||
<span className="ml-4 transition-transform group-hover:translate-x-2">
|
||||
<span className="ml-2 md:ml-4 transition-transform group-hover:translate-x-2">
|
||||
→
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user