Add CMS website and Qom landing pages with responsive layout fixes.

Ship blog/portfolio routes, shared content components, and mobile-safe typography so the marketing site can deploy cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-07-29 21:57:44 +03:30
co-authored by Cursor
parent 47bee1ce66
commit 112e5eb46d
29 changed files with 1584 additions and 147 deletions
+102
View File
@@ -0,0 +1,102 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { ContentPageShell } from "@/components/ContentPageShell";
import { HtmlContent } from "@/components/HtmlContent";
import {
ApiNotFoundError,
authorName,
formatFaDate,
getBlog,
getBlogCoverImage,
} from "@/lib/api";
type BlogDetailPageProps = {
params: Promise<{ slug: string }>;
};
export async function generateMetadata({
params,
}: BlogDetailPageProps): Promise<Metadata> {
const { slug } = await params;
try {
const blog = await getBlog(slug);
return {
title: `${blog.title} | مشکی`,
description: blog.abstract ?? undefined,
};
} catch {
return { title: "مقاله | مشکی" };
}
}
export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
const { slug } = await params;
let blog;
try {
blog = await getBlog(slug);
} catch (error) {
if (error instanceof ApiNotFoundError) notFound();
throw error;
}
const cover = getBlogCoverImage(blog);
const date = formatFaDate(blog.publishedAt);
const author = authorName(blog.author);
return (
<ContentPageShell>
<article className="pb-20 lg:pb-28">
<div className="mx-auto max-w-3xl px-5 lg:px-8">
<div className="reveal mb-8">
<Link
href="/blog"
className="link-hover mb-6 inline-flex text-sm text-muted"
>
بازگشت به مقالات
</Link>
{blog.categoryName ? (
<p className="latin mb-3 text-[11px] uppercase tracking-widest text-muted">
{blog.categoryName}
</p>
) : null}
<h1 className="whitespace-normal text-[clamp(1.35rem,3.8vw,2.5rem)] font-normal leading-tight tracking-tight text-ink">
{blog.title}
</h1>
{blog.abstract ? (
<p className="mt-5 text-sm leading-8 text-muted md:text-base">
{blog.abstract}
</p>
) : null}
<div className="mt-5 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted">
{date ? <time dateTime={blog.publishedAt ?? undefined}>{date}</time> : null}
{author ? <span>نویسنده: {author}</span> : null}
</div>
</div>
{cover && blog.titleImageUrl ? (
<div className="reveal reveal-delay-1 mb-10 overflow-hidden rounded-2xl bg-paper-deep">
<img
src={cover}
alt=""
className="mx-auto max-h-[28rem] w-full object-contain"
/>
</div>
) : null}
{blog.mainTextHtml ? (
<div className="reveal reveal-delay-2">
<HtmlContent html={blog.mainTextHtml} />
</div>
) : null}
</div>
</article>
</ContentPageShell>
);
}