92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { notFound, redirect } from "next/navigation";
|
|
|
|
import SlotImage from "@/components/SlotImage";
|
|
import { plainText, sanitizeHtml } from "@/lib/format";
|
|
import {
|
|
fetchPortfolioById,
|
|
portfolioHref,
|
|
portfolioImage,
|
|
portfolioTitle,
|
|
} from "@/lib/portfolios";
|
|
import { pageMetadata } from "@/lib/seo";
|
|
import { slugifyForUrl } from "@/lib/slug";
|
|
|
|
export const revalidate = 60;
|
|
|
|
type Params = { id: string; slug: string };
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<Params>;
|
|
}): Promise<Metadata> {
|
|
const { id } = await params;
|
|
const item = await fetchPortfolioById(id);
|
|
if (!item) {
|
|
return {
|
|
title: "نمونه کار یافت نشد",
|
|
description: "این نمونه کار در گالری پنجره فرخ موجود نیست.",
|
|
robots: { index: false },
|
|
};
|
|
}
|
|
const title = portfolioTitle(item);
|
|
return pageMetadata({
|
|
title,
|
|
description: item.abstract?.trim() || plainText(item.mainTextHtml).slice(0, 160) || `${title} | نمونه کارهای پنجره فرخ`,
|
|
path: portfolioHref(item),
|
|
image: portfolioImage(item),
|
|
});
|
|
}
|
|
|
|
export default async function PortfolioDetailPage({ params }: { params: Promise<Params> }) {
|
|
const { id, slug } = await params;
|
|
const item = await fetchPortfolioById(id);
|
|
if (!item) notFound();
|
|
|
|
const title = portfolioTitle(item);
|
|
if (decodeURIComponent(slug) !== slugifyForUrl(title)) {
|
|
redirect(portfolioHref(item));
|
|
}
|
|
|
|
const html = sanitizeHtml(item.mainTextHtml);
|
|
|
|
return (
|
|
<>
|
|
<section className="page-hero">
|
|
<div className="container page-hero-inner">
|
|
<div className="page-hero-en">
|
|
<span>PORTFOLIO</span>
|
|
<i />
|
|
</div>
|
|
<div className="page-hero-fa">
|
|
<nav className="crumbs">
|
|
<Link href="/">خانه</Link>
|
|
{" > "}
|
|
<Link href="/portfolio">نمونه کارها</Link>
|
|
{" > "}
|
|
<span>{title}</span>
|
|
</nav>
|
|
<h1>{title}</h1>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="section">
|
|
<div className="container detail-article">
|
|
<div className="article-cover">
|
|
<SlotImage src={portfolioImage(item)} alt={title} />
|
|
</div>
|
|
{item.abstract ? <p className="detail-lead">{item.abstract}</p> : null}
|
|
{html ? <div className="rich-text" dangerouslySetInnerHTML={{ __html: html }} /> : null}
|
|
|
|
<Link href="/portfolio" className="text-link">
|
|
بازگشت به نمونه کارها
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|