Connect storefront to backend catalog, search, and home slots.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
2f857f1a90
commit
0074ab6629
@@ -0,0 +1,122 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import SiteHeader from "@/components/home/SiteHeader";
|
||||
import SiteFooter from "@/components/home/SiteFooter";
|
||||
import ProductDetailHero from "@/components/product-detail/ProductDetailHero";
|
||||
import ProductGallery from "@/components/product-detail/ProductGallery";
|
||||
import ProductBuyBox from "@/components/product-detail/ProductBuyBox";
|
||||
import ProductTabs from "@/components/product-detail/ProductTabs";
|
||||
import SimilarProducts from "@/components/product-detail/SimilarProducts";
|
||||
import { getProductDetail } from "@/data/product-detail";
|
||||
import { mapHomeSlots } from "@/lib/map-home-slots";
|
||||
import { fetchPublicWebsiteSlots } from "@/lib/website-slots";
|
||||
import {
|
||||
fetchPublicProduct,
|
||||
getStorefrontProducts,
|
||||
mapApiProductToDetail,
|
||||
} from "@/lib/products-api";
|
||||
import {
|
||||
fetchPublicCategories,
|
||||
mapCategoriesToMegaTabs,
|
||||
} from "@/lib/categories-api";
|
||||
import { productPath, productSlug } from "@/lib/product-path";
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<{ path: string[] }>;
|
||||
};
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: PageProps): Promise<Metadata> {
|
||||
const { path } = await params;
|
||||
const id = path?.[0];
|
||||
if (!id) return { title: "محصول | مانا کالکشن" };
|
||||
const api = await fetchPublicProduct(id);
|
||||
if (api) {
|
||||
return {
|
||||
title: `${api.nameFa} | مانا کالکشن`,
|
||||
description: api.summary || api.description || api.nameFa,
|
||||
};
|
||||
}
|
||||
const fallback = getProductDetail(id);
|
||||
if (!fallback) return { title: "محصول | مانا کالکشن" };
|
||||
return {
|
||||
title: `${fallback.nameFa} | مانا کالکشن`,
|
||||
description: fallback.summary,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductDetailPage({ params }: PageProps) {
|
||||
const { path } = await params;
|
||||
if (!path?.length || path.length > 2) notFound();
|
||||
|
||||
const [id, slug] = path;
|
||||
const [slotItems, apiProduct, similar, apiCategories] = await Promise.all([
|
||||
fetchPublicWebsiteSlots(),
|
||||
fetchPublicProduct(id),
|
||||
getStorefrontProducts(),
|
||||
fetchPublicCategories(),
|
||||
]);
|
||||
|
||||
const product = apiProduct
|
||||
? mapApiProductToDetail(apiProduct)
|
||||
: getProductDetail(id);
|
||||
if (!product) notFound();
|
||||
|
||||
const expectedSlug = productSlug({
|
||||
id: product.id,
|
||||
nameEn: product.nameEn,
|
||||
nameFa: product.nameFa,
|
||||
});
|
||||
const canonical = productPath({
|
||||
id: product.id,
|
||||
nameEn: product.nameEn,
|
||||
nameFa: product.nameFa,
|
||||
});
|
||||
|
||||
// Legacy `/products/:id` or wrong slug → canonical `/products/:id/:slug`
|
||||
if (path.length === 1 || slug !== expectedSlug) {
|
||||
redirect(canonical);
|
||||
}
|
||||
|
||||
const home = mapHomeSlots(slotItems);
|
||||
const megaTabs = mapCategoriesToMegaTabs(
|
||||
apiCategories,
|
||||
home.megaTabs.map((t) => ({ image: t.image })),
|
||||
);
|
||||
const similarCards = similar
|
||||
.filter((p) => p.id !== product.id)
|
||||
.slice(0, 8);
|
||||
|
||||
return (
|
||||
<main className="home-page min-h-dvh bg-white text-ink">
|
||||
<div className="relative">
|
||||
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
||||
<ProductDetailHero
|
||||
heroImage={home.heroSlides[0]?.image || undefined}
|
||||
breadcrumbs={product.breadcrumbs}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-[1200px] px-4 py-8 sm:px-6 sm:py-10 lg:px-8 lg:py-12">
|
||||
<div className="grid grid-cols-1 gap-10 lg:grid-cols-2 lg:gap-14 lg:items-start">
|
||||
<ProductGallery images={product.gallery} alt={product.nameFa} />
|
||||
<ProductBuyBox product={product} />
|
||||
</div>
|
||||
|
||||
<div className="mt-12 space-y-14 lg:mt-16 lg:space-y-16">
|
||||
<ProductTabs product={product} />
|
||||
<SimilarProducts
|
||||
products={
|
||||
similarCards.length
|
||||
? similarCards
|
||||
: similar.slice(0, 8)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SiteFooter paymentBadges={home.paymentBadges} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user