Files
thermcool/app/sklep/[slug]/page.tsx
T

150 lines
4.2 KiB
TypeScript

import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { getProducts, getProductBySlug } from '@/content/products';
import { ProductGallery } from '@/components/shop/ProductGallery';
import { ProductInfo } from '@/components/shop/ProductInfo';
import { Tabs } from '@/components/shop/Tabs';
import { ProductCard } from '@/components/shop/ProductCard';
import { ArrowLeft } from 'lucide-react';
import Link from 'next/link';
interface ProductPageProps {
params: {
slug: string;
};
}
export async function generateStaticParams() {
const products = getProducts();
return products.map((product) => ({
slug: product.slug,
}));
}
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) {
return { title: 'Produkt nie znaleziony' };
}
return {
title: `${product.brand} ${product.name} | Sklep AirProject`,
description: product.description,
alternates: {
canonical: `/sklep/${product.slug}`,
},
openGraph: {
title: `${product.brand} ${product.name}`,
description: product.description,
type: 'website',
images: [
{
url: product.image || '/hero.jpg',
},
],
},
};
}
export default async function ProductPage({ params }: ProductPageProps) {
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) {
notFound();
}
const allProducts = getProducts();
// Dobieramy 4 podobne produkty
const relatedProducts = allProducts
.filter(p => p.category === product.category && p.slug !== product.slug)
.slice(0, 4);
// JSON-LD
const jsonLdProduct = {
'@context': 'https://schema.org',
'@type': 'Product',
name: `${product.brand} ${product.name}`,
image: product.image,
description: product.description,
brand: {
'@type': 'Brand',
name: product.brand,
},
offers: {
'@type': 'Offer',
url: `https://aeroservice-demo.pl/sklep/${product.slug}`,
priceCurrency: 'PLN',
price: product.price,
itemCondition: 'https://schema.org/NewCondition',
availability: 'https://schema.org/InStock',
},
};
const jsonLdBreadcrumb = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'Sklep',
item: 'https://aeroservice-demo.pl/sklep',
},
{
'@type': 'ListItem',
position: 2,
name: product.name,
item: `https://aeroservice-demo.pl/sklep/${product.slug}`,
},
],
};
return (
<main className="bg-slate-50 pt-24 pb-16 min-h-screen">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLdProduct) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLdBreadcrumb) }}
/>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Nawigacja powrotu */}
<Link
href="/sklep"
className="inline-flex items-center gap-2 text-sm font-semibold text-text-muted hover:text-primary transition-colors mb-8"
>
<ArrowLeft className="w-4 h-4" />
Wróć do sklepu
</Link>
{/* Górna sekcja */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16">
<ProductGallery image={product.image || ''} name={product.name} />
<ProductInfo product={product} />
</div>
{/* Zakładki z detalami */}
<Tabs product={product} />
{/* Podobne produkty */}
{relatedProducts.length > 0 && (
<section className="mt-24">
<h2 className="text-2xl font-bold text-primary font-heading mb-8">Podobne produkty</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{relatedProducts.map(relProduct => (
<ProductCard key={relProduct.slug} product={relProduct} />
))}
</div>
</section>
)}
</div>
</main>
);
}