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
@@ -0,0 +1,73 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { StoreDetailClient } from "@/app/components/store-detail-client";
import {
getProductById,
getProductTechnicalInfo,
getStoreItemVariant,
listComments,
listStoreItems,
} from "@/lib/meshkee/api";
import { storeItemName } from "@/lib/meshkee/format";
type PageProps = {
params: Promise<{ variantId: string; slug?: string[] }>;
};
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { variantId } = await params;
try {
const variant = await getStoreItemVariant(variantId);
const name = storeItemName(variant);
return {
title: `${name} | مبلمان هومت`,
description: `خرید ${name} از فروشگاه مبلمان هومت`,
};
} catch {
return { title: "محصول | مبلمان هومت" };
}
}
export default async function StoreDetailPage({ params }: PageProps) {
const { variantId } = await params;
let variant;
try {
variant = await getStoreItemVariant(variantId);
} catch {
notFound();
}
const [{ product }, technical, comments] = await Promise.all([
getProductById(variant.productId),
getProductTechnicalInfo(variant.productId).catch(() => ({
form: null,
values: [],
})),
listComments({ entityType: "product", entityId: variant.productId }).catch(
() => [],
),
]);
const similarResponse = await listStoreItems({
page: 1,
pageSize: 8,
categoryId: product.categoryId || undefined,
}).catch(() => ({ items: [] as Awaited<ReturnType<typeof listStoreItems>>["items"] }));
const similar = similarResponse.items
.filter((item) => item.id !== variant.id)
.slice(0, 4);
return (
<main className="container">
<StoreDetailClient
variant={variant}
product={product}
technical={technical}
comments={comments}
similar={similar}
/>
</main>
);
}
+110
View File
@@ -0,0 +1,110 @@
import Image from "next/image";
import Link from "next/link";
import { listStoreItems } from "@/lib/meshkee/api";
import {
formatPrice,
storeItemName,
storeItemPrice,
} from "@/lib/meshkee/format";
import { storeItemHref } from "@/lib/meshkee/urls";
type SearchParams = Promise<{
categoryId?: string;
page?: string;
}>;
export const metadata = {
title: "فروشگاه | مبلمان هومت",
description: "لیست محصولات قابل خرید فروشگاه مبلمان هومت",
};
export default async function StorePage({
searchParams,
}: {
searchParams: SearchParams;
}) {
const params = await searchParams;
const page = Math.max(1, Number(params.page) || 1);
const data = await listStoreItems({
page,
pageSize: 24,
categoryId: params.categoryId,
});
return (
<main className="container">
<section className="page-hero">
<p className="eyebrow">Store</p>
<h1>فروشــــــــگاه هومــــــــت</h1>
<p>محصولات قابل سفارش را مشاهده کنید و به سبد خرید اضافه کنید.</p>
</section>
<section className="section store-grid-section">
{data.items.length === 0 ? (
<p className="empty-note">در حال حاضر محصول فروشگاهی فعالی وجود ندارد.</p>
) : (
<div className="store-grid">
{data.items.map((item) => {
const name = storeItemName(item);
const href = storeItemHref(item.id, name);
const price = storeItemPrice(item);
return (
<Link key={item.id} href={href} className="store-card">
<div className="store-card-media">
{item.productImage ? (
<Image
src={item.productImage}
alt={name}
width={480}
height={480}
/>
) : (
<div className="store-card-fallback" />
)}
</div>
<div className="store-card-body">
{item.label ? <p className="eyebrow">{item.label}</p> : null}
<h2>{name}</h2>
<strong className="ltr-number" dir="ltr" lang="en">
{formatPrice(price)}
</strong>
</div>
</Link>
);
})}
</div>
)}
{data.total > data.pageSize ? (
<div className="pager">
{page > 1 ? (
<Link
href={`/store?page=${page - 1}${
params.categoryId
? `&categoryId=${encodeURIComponent(params.categoryId)}`
: ""
}`}
>
قبلی
</Link>
) : null}
<span className="ltr-number" dir="ltr" lang="en">
{page}
</span>
{page * data.pageSize < data.total ? (
<Link
href={`/store?page=${page + 1}${
params.categoryId
? `&categoryId=${encodeURIComponent(params.categoryId)}`
: ""
}`}
>
بعدی
</Link>
) : null}
</div>
) : null}
</section>
</main>
);
}