Files
dealplustech/src/app/blog/[slug]/page.tsx

144 lines
4.6 KiB
TypeScript

import { notFound } from 'next/navigation';
import Image from 'next/image';
import Link from 'next/link';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { remark } from 'remark';
import html from 'remark-html';
import gfm from 'remark-gfm';
interface Props {
params: Promise<{ slug: string }>;
}
async function getPost(slug: string) {
const blogDir = path.join(process.cwd(), 'src/content/blog');
const filePath = path.join(blogDir, `${slug}.md`);
if (!fs.existsSync(filePath)) {
return null;
}
const fileContent = fs.readFileSync(filePath, 'utf-8');
const { data, content } = matter(fileContent);
const processedContent = await remark()
.use(gfm)
.use(html, { sanitize: false })
.process(content);
const contentHtml = processedContent.toString();
// Support both 'category' and 'categories' field names
const category = data.category || (Array.isArray(data.categories) ? data.categories[0] : 'ทั่วไป');
// Support both 'image' and 'featuredImage' field names
const image = data.image || data.featuredImage || '/images/2021/03/ppr-pipe_000C.jpg';
return {
slug,
title: data.title || 'ไม่มีชื่อ',
excerpt: data.excerpt || '',
date: data.date || new Date().toISOString(),
author: data.author || 'ดีลพลัสเทค',
category,
image,
content: contentHtml,
};
}
export async function generateMetadata({ params }: Props) {
const { slug } = await params;
const decodedSlug = decodeURIComponent(slug);
const post = await getPost(decodedSlug);
if (!post) return { title: 'ไม่พบบทความ' };
return {
title: post.title,
description: post.excerpt,
};
}
export async function generateStaticParams() {
const blogDir = path.join(process.cwd(), 'src/content/blog');
const files = fs.readdirSync(blogDir).filter(f => f.endsWith('.md'));
return files.map(filename => ({
slug: filename.replace('.md', ''),
}));
}
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
const decodedSlug = decodeURIComponent(slug);
const post = await getPost(decodedSlug);
if (!post) {
notFound();
}
return (
<div className="pt-32 pb-16">
<article className="container mx-auto px-4">
{/* Header */}
<header className="max-w-3xl mx-auto mb-8">
<Link
href="/blog"
className="inline-flex items-center text-primary-600 hover:text-primary-700 mb-4"
>
<svg className="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
<span className="industrial-badge">{post.category}</span>
<h1 className="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mt-4 mb-4">
{post.title}
</h1>
<div className="flex items-center gap-4 text-secondary-600">
<span>{post.author}</span>
<span></span>
<time>
{new Date(post.date).toLocaleDateString('th-TH', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
</header>
{/* Featured Image */}
<div className="relative aspect-video max-w-4xl mx-auto rounded-xl overflow-hidden mb-8">
<Image
src={post.image}
alt={post.title}
fill
className="object-cover"
/>
</div>
{/* Content */}
<div
className="max-w-3xl mx-auto prose prose-lg prose-headings:font-bold prose-headings:text-secondary-900 prose-p:text-secondary-600 prose-a:text-primary-600 prose-strong:text-secondary-900"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
{/* CTA */}
<div className="max-w-3xl mx-auto mt-12 bg-secondary-800 rounded-2xl p-8 text-center">
<h2 className="text-2xl font-bold text-white mb-4">
?
</h2>
<p className="text-secondary-300 mb-6">
</p>
<Link href="/contact-us" className="btn-primary">
</Link>
</div>
</article>
</div>
);
}