Initial commit: Hoomat storefront with Meshkee API and static-image slots.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-04 15:00:41 +03:30
co-authored by Cursor
commit cfabf3e327
95 changed files with 12651 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import type { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
import { getBlogById } from "@/lib/meshkee/api";
import { formatDate } from "@/lib/meshkee/format";
type PageProps = {
params: Promise<{ id: string; slug: string }>;
};
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { id } = await params;
try {
const post = await getBlogById(id);
return {
title: `${post.title} | مبلمان هومت`,
description: post.abstract || post.title,
};
} catch {
return { title: "مقاله | مبلمان هومت" };
}
}
export default async function BlogDetailPage({ params }: PageProps) {
const { id } = await params;
let post;
try {
post = await getBlogById(id);
} catch {
notFound();
}
return (
<main className="container">
<article className="blog-detail">
<section className="page-hero">
<p className="eyebrow">{post.categoryName || "Article"}</p>
<h1>{post.title}</h1>
<time>{formatDate(post.publishedAt || post.createdAt)}</time>
</section>
{post.titleImageUrl ? (
<div className="blog-detail-cover">
<Image
src={post.titleImageUrl}
alt={post.title}
width={1200}
height={720}
priority
/>
</div>
) : null}
{post.abstract ? <p className="blog-abstract">{post.abstract}</p> : null}
{post.mainTextHtml ? (
<div
className="rich-html blog-content"
dangerouslySetInnerHTML={{ __html: post.mainTextHtml }}
/>
) : (
<p className="empty-note">متن مقاله در دسترس نیست.</p>
)}
</article>
</main>
);
}
+74
View File
@@ -0,0 +1,74 @@
import Image from "next/image";
import Link from "next/link";
import { listBlogs } from "@/lib/meshkee/api";
import { formatDate } from "@/lib/meshkee/format";
import { blogHref } from "@/lib/meshkee/urls";
type SearchParams = Promise<{ page?: string }>;
export const metadata = {
title: "مجله دکوراسیون | مبلمان هومت",
description: "مقالات و راهنمای خرید مبلمان و دکوراسیون از مبلمان هومت",
};
export default async function BlogPage({
searchParams,
}: {
searchParams: SearchParams;
}) {
const params = await searchParams;
const page = Math.max(1, Number(params.page) || 1);
const data = await listBlogs({ page, pageSize: 12 });
return (
<main className="container">
<section className="page-hero">
<p className="eyebrow">Blog</p>
<h1>مجله دکوراســـــــــیون</h1>
<p>راهنماها و مقالات تخصصی برای انتخاب بهتر مبلمان و چیدمان خانه.</p>
</section>
<section className="section blog-grid-section">
{data.items.length === 0 ? (
<p className="empty-note">مقاله‌ای برای نمایش وجود ندارد.</p>
) : (
<div className="blog-grid">
{data.items.map((post) => (
<Link key={post.id} href={blogHref(post.id, post.title)} className="blog-card">
<div className="blog-card-media">
{post.titleImageUrl ? (
<Image
src={post.titleImageUrl}
alt={post.title}
width={640}
height={420}
/>
) : (
<div className="store-card-fallback" />
)}
</div>
<div className="blog-card-body">
<time>{formatDate(post.publishedAt || post.createdAt)}</time>
<h2>{post.title}</h2>
{post.abstract ? <p>{post.abstract}</p> : null}
</div>
</Link>
))}
</div>
)}
{data.total > data.pageSize ? (
<div className="pager">
{page > 1 ? <Link href={`/blog?page=${page - 1}`}>قبلی</Link> : null}
<span className="ltr-number" dir="ltr" lang="en">
{page}
</span>
{page * data.pageSize < data.total ? (
<Link href={`/blog?page=${page + 1}`}>بعدی</Link>
) : null}
</div>
) : null}
</section>
</main>
);
}