Add portfolio projects to catch-all route
- Updated [...slug] to handle both product categories and portfolio projects - Added 15 portfolio project pages - Added PortfolioProject type to types/index.ts - Build now generates 64 static pages (38 products + 15 portfolio + 3 blog + 8 main)
This commit is contained in:
@@ -1,51 +1,97 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { productCategories } from '@/data/site-config';
|
||||
import { productCategories, portfolioProjects } from '@/data/site-config';
|
||||
|
||||
|
||||
interface Props {
|
||||
params: { slug: string[] };
|
||||
}
|
||||
|
||||
// Generate all possible paths from product categories
|
||||
// Generate all possible paths from product categories and portfolio projects
|
||||
export async function generateStaticParams() {
|
||||
const paths: { slug: string[] }[] = [];
|
||||
|
||||
// Add product category paths
|
||||
productCategories.forEach((product) => {
|
||||
// Remove leading slash and split the href
|
||||
const pathParts = product.href.replace(/^\//, '').split('/');
|
||||
const pathParts = product.href.replace(/^\//, '').replace(/\/$/, '').split('/');
|
||||
paths.push({ slug: pathParts });
|
||||
});
|
||||
|
||||
// Add portfolio project paths
|
||||
portfolioProjects.forEach((project) => {
|
||||
const pathParts = project.href.replace(/^\//, '').replace(/\/$/, '').split('/');
|
||||
paths.push({ slug: pathParts });
|
||||
});
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
function findProductBySlug(slug: string[]) {
|
||||
const fullPath = '/' + slug.join('/');
|
||||
return productCategories.find((p) => p.href === fullPath);
|
||||
type ContentType = 'product' | 'portfolio';
|
||||
|
||||
function findContentBySlug(slug: string[]): { type: ContentType; data: typeof productCategories[0] | typeof portfolioProjects[0] } | null {
|
||||
const fullPath = '/' + slug.join('/') + '/';
|
||||
|
||||
// Check products first
|
||||
const product = productCategories.find((p) => p.href === fullPath);
|
||||
if (product) {
|
||||
return { type: 'product', data: product };
|
||||
}
|
||||
|
||||
// Check portfolio projects
|
||||
const portfolio = portfolioProjects.find((p) => p.href === fullPath);
|
||||
if (portfolio) {
|
||||
return { type: 'portfolio', data: portfolio };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props) {
|
||||
const product = findProductBySlug(params.slug);
|
||||
const content = findContentBySlug(params.slug);
|
||||
|
||||
if (!product) {
|
||||
if (!content) {
|
||||
return { title: 'ไม่พบหน้า' };
|
||||
}
|
||||
|
||||
const { type, data } = content;
|
||||
|
||||
if (type === 'product') {
|
||||
const product = data as typeof productCategories[0];
|
||||
return {
|
||||
title: `${product.name} - ${product.nameEn}`,
|
||||
description: product.description,
|
||||
keywords: product.keywords,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: `${product.name} - ${product.nameEn}`,
|
||||
description: product.description,
|
||||
keywords: product.keywords,
|
||||
title: data.name,
|
||||
description: data.description,
|
||||
};
|
||||
}
|
||||
|
||||
export default function ProductDetailPage({ params }: Props) {
|
||||
const product = findProductBySlug(params.slug);
|
||||
export default function DynamicPage({ params }: Props) {
|
||||
const content = findContentBySlug(params.slug);
|
||||
|
||||
if (!product) {
|
||||
if (!content) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { type, data } = content;
|
||||
|
||||
// Render portfolio project page
|
||||
if (type === 'portfolio') {
|
||||
return <PortfolioPage project={data as typeof portfolioProjects[0]} />;
|
||||
}
|
||||
|
||||
// Render product page
|
||||
return <ProductPage product={data as typeof productCategories[0]} />;
|
||||
}
|
||||
|
||||
// Product Page Component
|
||||
function ProductPage({ product }: { product: typeof productCategories[0] }) {
|
||||
// Find related products in same category
|
||||
const relatedProducts = productCategories
|
||||
.filter((p) => p.slug === product.slug && p.id !== product.id)
|
||||
@@ -110,18 +156,6 @@ export default function ProductDetailPage({ params }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO Content */}
|
||||
{product.seoContent && (
|
||||
<div className="mt-12">
|
||||
<div className="bg-white rounded-xl p-8 shadow-card">
|
||||
<div
|
||||
className="prose prose-lg max-w-none prose-headings:font-bold prose-headings:text-secondary-900 prose-p:text-secondary-600 prose-a:text-primary-600 prose-strong:text-secondary-900 prose-ul:text-secondary-600 prose-li:text-secondary-600"
|
||||
dangerouslySetInnerHTML={{ __html: product.seoContent }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Related Products */}
|
||||
{relatedProducts.length > 0 && (
|
||||
<div className="mt-16">
|
||||
@@ -160,3 +194,94 @@ export default function ProductDetailPage({ params }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Portfolio Page Component
|
||||
function PortfolioPage({ project }: { project: typeof portfolioProjects[0] }) {
|
||||
return (
|
||||
<div className="pt-32 pb-16">
|
||||
<div className="container mx-auto px-4">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="mb-6">
|
||||
<ol className="flex items-center gap-2 text-sm">
|
||||
<li>
|
||||
<Link href="/" className="text-secondary-500 hover:text-primary-600">
|
||||
หน้าแรก
|
||||
</Link>
|
||||
</li>
|
||||
<li className="text-secondary-400">/</li>
|
||||
<li>
|
||||
<Link href="/portfolio" className="text-secondary-500 hover:text-primary-600">
|
||||
ผลงาน
|
||||
</Link>
|
||||
</li>
|
||||
<li className="text-secondary-400">/</li>
|
||||
<li className="text-primary-600 font-medium">{project.name}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Project Image */}
|
||||
<div className="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden">
|
||||
<Image
|
||||
src={project.image}
|
||||
alt={project.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Project Info */}
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-secondary-900 mb-4">
|
||||
{project.name}
|
||||
</h1>
|
||||
<p className="text-secondary-600 text-lg mb-6">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link href="/contact-us" className="btn-primary">
|
||||
ติดต่อเรา
|
||||
</Link>
|
||||
<Link href="/portfolio" className="btn-outline">
|
||||
ดูผลงานอื่นๆ
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Other Projects */}
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-bold text-secondary-900 mb-6">
|
||||
ผลงานอื่นๆ
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{portfolioProjects.filter(p => p.id !== project.id).slice(0, 4).map((other) => (
|
||||
<Link
|
||||
key={other.id}
|
||||
href={other.href}
|
||||
className="card group"
|
||||
>
|
||||
<div className="relative aspect-video bg-secondary-100">
|
||||
<Image
|
||||
src={other.image}
|
||||
alt={other.name}
|
||||
fill
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="text-lg font-bold text-secondary-900 group-hover:text-primary-600 transition-colors">
|
||||
{other.name}
|
||||
</h3>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,14 @@ export interface PortfolioItem {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface PortfolioProject {
|
||||
id: string;
|
||||
name: string;
|
||||
href: string;
|
||||
image: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Contact Form Types
|
||||
export interface ContactFormData {
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user