wip
This commit is contained in:
135
app/[locale]/products/[...slug]/opengraph-image.tsx
Normal file
135
app/[locale]/products/[...slug]/opengraph-image.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { getProductBySlug } from '@/lib/mdx';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string[] } }) {
|
||||
const productSlug = slug[slug.length - 1];
|
||||
const product = await getProductBySlug(productSlug, locale);
|
||||
|
||||
if (!product) {
|
||||
return new ImageResponse(
|
||||
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
|
||||
);
|
||||
}
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: 'white',
|
||||
padding: '60px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{/* Left Side: Content */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '55%' }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '20px',
|
||||
fontWeight: 'bold',
|
||||
color: '#001a4d',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.2em',
|
||||
marginBottom: '20px',
|
||||
opacity: 0.6,
|
||||
}}
|
||||
>
|
||||
KLZ Cables | {product.frontmatter.categories[0]}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '72px',
|
||||
fontWeight: '900',
|
||||
color: '#001a4d',
|
||||
lineHeight: '1.1',
|
||||
marginBottom: '30px',
|
||||
}}
|
||||
>
|
||||
{product.frontmatter.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '28px',
|
||||
color: '#4a5568',
|
||||
lineHeight: '1.4',
|
||||
}}
|
||||
>
|
||||
{product.frontmatter.description}
|
||||
</div>
|
||||
|
||||
{/* Accent Line */}
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
width: '100px',
|
||||
height: '8px',
|
||||
backgroundColor: '#00ff99',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right Side: Product Image */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '40%',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{product.frontmatter.images?.[0] && (
|
||||
<img
|
||||
src={product.frontmatter.images[0].startsWith('http') ? product.frontmatter.images[0] : `https://klz-cables.com${product.frontmatter.images[0]}`}
|
||||
alt=""
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '80%',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Subtle shadow under product */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '10%',
|
||||
width: '60%',
|
||||
height: '20px',
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
borderRadius: '50%',
|
||||
filter: 'blur(10px)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Branding Corner */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '40px',
|
||||
right: '60px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', fontWeight: 'bold', color: '#001a4d' }}>KLZ</div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 'normal', color: '#00ff99', marginLeft: '4px' }}>Cables</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import Image from 'next/image';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { Section, Container, Heading, Badge, Button } from '@/components/ui';
|
||||
import Scribble from '@/components/Scribble';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
interface ProductPageProps {
|
||||
params: {
|
||||
@@ -18,6 +19,49 @@ interface ProductPageProps {
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
|
||||
const { locale, slug } = params;
|
||||
const productSlug = slug[slug.length - 1];
|
||||
const t = await getTranslations('Products');
|
||||
|
||||
// Check if it's a category page
|
||||
const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
|
||||
if (categories.includes(productSlug)) {
|
||||
const categoryKey = productSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
const categoryTitle = t(`categories.${categoryKey}.title`);
|
||||
const categoryDesc = t(`categories.${categoryKey}.description`);
|
||||
|
||||
return {
|
||||
title: categoryTitle,
|
||||
description: categoryDesc,
|
||||
openGraph: {
|
||||
title: categoryTitle,
|
||||
description: categoryDesc,
|
||||
url: `https://klz-cables.com/${locale}/products/${productSlug}`,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const product = await getProductBySlug(productSlug, locale);
|
||||
if (!product) return {};
|
||||
|
||||
return {
|
||||
title: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
openGraph: {
|
||||
title: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
type: 'website',
|
||||
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const components = {
|
||||
ProductTechnicalData,
|
||||
ProductTabs,
|
||||
@@ -223,6 +267,31 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
<div className="prose prose-lg md:prose-xl prose-slate max-w-none prose-headings:text-primary prose-headings:font-extrabold prose-a:text-primary prose-strong:text-primary prose-img:rounded-3xl prose-img:shadow-2xl">
|
||||
<MDXRemote source={product.content} components={components} />
|
||||
</div>
|
||||
|
||||
{/* Structured Data */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Product',
|
||||
name: product.frontmatter.title,
|
||||
description: product.frontmatter.description,
|
||||
sku: product.frontmatter.sku,
|
||||
image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined,
|
||||
brand: {
|
||||
'@type': 'Brand',
|
||||
name: 'KLZ Cables',
|
||||
},
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
availability: 'https://schema.org/InStock',
|
||||
priceCurrency: 'EUR',
|
||||
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Related Products */}
|
||||
<RelatedProducts
|
||||
|
||||
Reference in New Issue
Block a user