253 lines
8.0 KiB
TypeScript
253 lines
8.0 KiB
TypeScript
import { Metadata } from 'next';
|
|
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
import { notFound } from 'next/navigation';
|
|
import { getPosts, getPostBySlug, getRelatedPosts } from '@/content/posts';
|
|
import { CtaBanner } from '@/components/sections/CtaBanner';
|
|
import { Calendar, User, ArrowLeft, ArrowRight } from 'lucide-react';
|
|
import { companyDetails } from '@/content/company';
|
|
|
|
interface BlogPostPageProps {
|
|
params: {
|
|
slug: string;
|
|
};
|
|
}
|
|
|
|
// Generowanie ścieżek statycznych dla wpisów
|
|
export async function generateStaticParams() {
|
|
const posts = getPosts();
|
|
return posts.map((post) => ({
|
|
slug: post.slug,
|
|
}));
|
|
}
|
|
|
|
// Generowanie metadanych dla danego wpisu
|
|
export async function generateMetadata({ params }: BlogPostPageProps): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const post = getPostBySlug(slug);
|
|
|
|
if (!post) {
|
|
return {
|
|
title: 'Nie znaleziono wpisu',
|
|
};
|
|
}
|
|
|
|
return {
|
|
title: `${post.title} | Blog AirProject`,
|
|
description: post.excerpt,
|
|
alternates: {
|
|
canonical: `/blog/${post.slug}`,
|
|
},
|
|
openGraph: {
|
|
title: post.title,
|
|
description: post.excerpt,
|
|
type: 'article',
|
|
publishedTime: post.date,
|
|
authors: [post.author || companyDetails.shortName],
|
|
images: [
|
|
{
|
|
url: post.image || '/hero.jpg',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function BlogPostPage({ params }: BlogPostPageProps) {
|
|
const { slug } = await params;
|
|
const post = getPostBySlug(slug);
|
|
|
|
if (!post) {
|
|
notFound();
|
|
}
|
|
|
|
const relatedPosts = getRelatedPosts(post.slug, post.category, 3);
|
|
|
|
const dateObj = new Date(post.date);
|
|
const formattedDate = dateObj.toLocaleDateString('pl-PL', {
|
|
day: 'numeric', month: 'long', year: 'numeric'
|
|
});
|
|
|
|
const categoryLabels: Record<string, string> = {
|
|
'klimatyzacja': 'Klimatyzacja',
|
|
'pompy-ciepla': 'Pompy Ciepła',
|
|
'remonty': 'Remonty',
|
|
'hydraulika': 'Hydraulika',
|
|
'porady': 'Porady'
|
|
};
|
|
|
|
// Ustawienie CTA dopasowanego do kategorii wpisu
|
|
const getCtaLink = () => {
|
|
switch(post.category) {
|
|
case 'klimatyzacja': return '/klimatyzacja';
|
|
case 'pompy-ciepla': return '/pompy-ciepla';
|
|
case 'remonty': return '/uslugi-domowe/remonty';
|
|
case 'hydraulika': return '/uslugi-domowe/hydraulika';
|
|
default: return '/kontakt';
|
|
}
|
|
};
|
|
|
|
// --- JSON-LD ---
|
|
const jsonLdArticle = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BlogPosting',
|
|
headline: post.title,
|
|
image: post.image,
|
|
datePublished: post.date,
|
|
dateModified: post.date,
|
|
author: {
|
|
'@type': 'Person',
|
|
name: post.author || companyDetails.name,
|
|
},
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: companyDetails.name,
|
|
logo: {
|
|
'@type': 'ImageObject',
|
|
url: 'https://aeroservice-demo.pl/logo.png', // MOCK URL
|
|
},
|
|
},
|
|
description: post.excerpt,
|
|
};
|
|
|
|
const jsonLdBreadcrumb = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: [
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 1,
|
|
name: 'Strona Główna',
|
|
item: 'https://aeroservice-demo.pl',
|
|
},
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 2,
|
|
name: 'Blog',
|
|
item: 'https://aeroservice-demo.pl/blog',
|
|
},
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 3,
|
|
name: post.title,
|
|
item: `https://aeroservice-demo.pl/blog/${post.slug}`,
|
|
},
|
|
],
|
|
};
|
|
|
|
return (
|
|
<main className="bg-slate-50 pt-24 pb-16">
|
|
{/* Skrypty JSON-LD dla SEO */}
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLdArticle) }}
|
|
/>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLdBreadcrumb) }}
|
|
/>
|
|
|
|
<article className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
{/* Powrót */}
|
|
<Link
|
|
href="/blog"
|
|
className="inline-flex items-center gap-2 text-sm font-semibold text-primary/70 hover:text-primary transition-colors mb-8"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
Wróć do listy artykułów
|
|
</Link>
|
|
|
|
{/* Header artykułu */}
|
|
<header className="mb-10 text-center md:text-left">
|
|
<div className="flex flex-wrap items-center justify-center md:justify-start gap-4 mb-6 text-sm font-medium text-text-muted">
|
|
<span className="px-3 py-1 bg-accent/10 text-accent font-bold rounded-full uppercase tracking-wider">
|
|
{categoryLabels[post.category] || post.category}
|
|
</span>
|
|
<div className="flex items-center gap-1.5">
|
|
<Calendar className="w-4 h-4" />
|
|
<time dateTime={post.date}>{formattedDate}</time>
|
|
</div>
|
|
{post.author && (
|
|
<div className="flex items-center gap-1.5">
|
|
<User className="w-4 h-4" />
|
|
<span>{post.author}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<h1 className="text-3xl md:text-5xl font-extrabold text-primary font-heading leading-tight mb-6">
|
|
{post.title}
|
|
</h1>
|
|
|
|
<p className="text-lg md:text-xl text-text-muted leading-relaxed max-w-3xl">
|
|
{post.excerpt}
|
|
</p>
|
|
</header>
|
|
|
|
{/* Główny obraz */}
|
|
<div className="relative w-full aspect-video rounded-2xl overflow-hidden mb-12 shadow-md">
|
|
<Image
|
|
src={post.image || 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=1600&q=80'}
|
|
alt={post.title}
|
|
fill
|
|
priority
|
|
className="object-cover"
|
|
/>
|
|
</div>
|
|
|
|
{/* Treść */}
|
|
<div className="prose prose-lg md:prose-xl prose-slate max-w-none mb-16
|
|
prose-headings:font-heading prose-headings:text-primary prose-headings:font-bold
|
|
prose-a:text-accent hover:prose-a:text-accent-hover prose-a:font-semibold
|
|
prose-img:rounded-xl prose-img:shadow-md
|
|
marker:text-accent"
|
|
>
|
|
<div dangerouslySetInnerHTML={{ __html: post.content }} />
|
|
</div>
|
|
|
|
</article>
|
|
|
|
{/* CTA do usługi */}
|
|
<CtaBanner
|
|
title="Podobał Ci się ten artykuł?"
|
|
subtitle="Jeśli potrzebujesz pomocy specjalistów w podobnej sprawie, sprawdź naszą ofertę lub skontaktuj się z nami bezpośrednio."
|
|
ctaText="Zobacz naszą ofertę"
|
|
ctaLink={getCtaLink()}
|
|
/>
|
|
|
|
{/* Powiązane artykuły */}
|
|
{relatedPosts.length > 0 && (
|
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-16 md:mt-24">
|
|
<div className="flex items-center justify-between border-b border-border pb-4 mb-8">
|
|
<h2 className="text-2xl font-bold text-primary font-heading">Powiązane artykuły</h2>
|
|
<Link href="/blog" className="text-sm font-semibold text-accent hover:text-accent-hover flex items-center gap-1">
|
|
Więcej wpisów <ArrowRight className="w-4 h-4" />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
{relatedPosts.map(related => (
|
|
<Link key={related.slug} href={`/blog/${related.slug}`} className="group block">
|
|
<div className="relative aspect-[16/10] rounded-xl overflow-hidden mb-4">
|
|
<Image
|
|
src={related.image || 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=800&q=80'}
|
|
alt={related.title}
|
|
fill
|
|
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
|
/>
|
|
</div>
|
|
<h3 className="text-lg font-bold text-primary font-heading group-hover:text-accent transition-colors mb-2 line-clamp-2">
|
|
{related.title}
|
|
</h3>
|
|
<p className="text-sm text-text-muted line-clamp-2">
|
|
{related.excerpt}
|
|
</p>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|