Initial commit: Hoomat storefront with Meshkee API and static-image slots.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user