206 lines
5.9 KiB
TypeScript
206 lines
5.9 KiB
TypeScript
import SiteHeader from "@/components/home/SiteHeader";
|
|
import SiteFooter from "@/components/home/SiteFooter";
|
|
import LeatherBanner from "@/components/home/LeatherBanner";
|
|
import PartnerCta from "@/components/home/PartnerCta";
|
|
import ProductListHero from "@/components/products/ProductListHero";
|
|
import ProductFilters from "@/components/products/ProductFilters";
|
|
import ProductGrid from "@/components/products/ProductGrid";
|
|
import ProductPagination from "@/components/products/ProductPagination";
|
|
import ProductEmptyState from "@/components/products/ProductEmptyState";
|
|
import ProductListExtras from "@/components/products/ProductListExtras";
|
|
import { LIST_PAGE_META } from "@/data/products";
|
|
import { mapHomeSlots } from "@/lib/map-home-slots";
|
|
import {
|
|
fetchPublicWebsiteSlots,
|
|
itemsForSlot,
|
|
} from "@/lib/website-slots";
|
|
import { getStorefrontProducts } from "@/lib/products-api";
|
|
import {
|
|
fetchPublicCategories,
|
|
mapCategoriesToMegaTabs,
|
|
resolveHomeCategoryStrip,
|
|
type ApiCategoryNode,
|
|
} from "@/lib/categories-api";
|
|
import {
|
|
paginateItems,
|
|
parseProductListQuery,
|
|
type ProductListQuery,
|
|
} from "@/lib/products-query";
|
|
|
|
type PageProps = {
|
|
searchParams: Promise<ProductListQuery>;
|
|
};
|
|
|
|
function findCategory(
|
|
nodes: ApiCategoryNode[],
|
|
id: string,
|
|
): ApiCategoryNode | null {
|
|
for (const node of nodes) {
|
|
if (String(node.id) === id) return node;
|
|
const child = findCategory(node.children ?? [], id);
|
|
if (child) return child;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function findCategoryName(
|
|
nodes: ApiCategoryNode[],
|
|
id: string,
|
|
): string | null {
|
|
return findCategory(nodes, id)?.nameFa ?? null;
|
|
}
|
|
|
|
export default async function ProductsPage({ searchParams }: PageProps) {
|
|
const raw = await searchParams;
|
|
const query = parseProductListQuery(raw);
|
|
const [slotItems, products, apiCategories] = await Promise.all([
|
|
fetchPublicWebsiteSlots(),
|
|
getStorefrontProducts({
|
|
name: query.q || undefined,
|
|
categoryId: query.cat || undefined,
|
|
minPrice: query.minPrice,
|
|
maxPrice: query.maxPrice,
|
|
featuredSlot: query.sale ? "promo-1" : undefined,
|
|
}),
|
|
fetchPublicCategories(),
|
|
]);
|
|
const home = mapHomeSlots(slotItems);
|
|
const megaTabs = mapCategoriesToMegaTabs(
|
|
apiCategories,
|
|
home.megaTabs.map((t) => ({ image: t.image })),
|
|
);
|
|
|
|
const stripCategories = resolveHomeCategoryStrip(
|
|
apiCategories,
|
|
home.categories,
|
|
itemsForSlot(slotItems, "category-strip").length > 0,
|
|
);
|
|
|
|
const catNode = query.cat ? findCategory(apiCategories, query.cat) : null;
|
|
const categorySeo = catNode?.seoDescription?.trim()
|
|
? {
|
|
nameFa: catNode.nameFa,
|
|
nameEn: catNode.nameEn,
|
|
description: catNode.seoDescription.trim(),
|
|
}
|
|
: null;
|
|
|
|
const { pageItems, totalPages, page, total } = paginateItems(
|
|
products,
|
|
query.page,
|
|
);
|
|
const categoryLabel = query.cat
|
|
? findCategoryName(apiCategories, query.cat)
|
|
: null;
|
|
const hasFilters = Boolean(
|
|
query.q || query.cat || query.min || query.max || query.sale,
|
|
);
|
|
|
|
const heroTitle = query.sale
|
|
? "فروش ویژه"
|
|
: query.q
|
|
? `جستجو: ${query.q}`
|
|
: categoryLabel
|
|
? categoryLabel
|
|
: LIST_PAGE_META.titleFa;
|
|
|
|
const heroEn = query.sale
|
|
? "Special Sale"
|
|
: query.q
|
|
? "Search results"
|
|
: categoryLabel
|
|
? ""
|
|
: LIST_PAGE_META.titleEn;
|
|
|
|
const breadcrumbs = query.sale
|
|
? [
|
|
{ label: "خانه", href: "/" },
|
|
{ label: "محصولات", href: "/products" },
|
|
{ label: "فروش ویژه" },
|
|
]
|
|
: query.q
|
|
? [
|
|
{ label: "خانه", href: "/" },
|
|
{ label: "محصولات", href: "/products" },
|
|
{ label: "جستجو" },
|
|
]
|
|
: LIST_PAGE_META.breadcrumbs;
|
|
|
|
return (
|
|
<main className="home-page min-h-dvh bg-white text-ink">
|
|
<div className="relative">
|
|
<SiteHeader megaTabs={megaTabs.length ? megaTabs : home.megaTabs} />
|
|
<ProductListHero
|
|
titleFa={heroTitle}
|
|
titleEn={heroEn}
|
|
heroImage={home.heroSlides[0]?.image || LIST_PAGE_META.heroImage}
|
|
breadcrumbs={breadcrumbs}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-6 space-y-6 sm:mt-8 lg:mt-[60px]">
|
|
<ProductFilters
|
|
basePath="/products"
|
|
initial={{
|
|
q: query.q,
|
|
cat: query.cat,
|
|
min: query.min,
|
|
max: query.max,
|
|
sale: query.sale,
|
|
}}
|
|
categoryLabel={categoryLabel}
|
|
/>
|
|
<div className="space-y-3 sm:space-y-4">
|
|
{total === 0 ? (
|
|
<ProductEmptyState
|
|
title={
|
|
hasFilters
|
|
? "محصولی با این فیلترها پیدا نشد"
|
|
: "محصولی موجود نیست"
|
|
}
|
|
description={
|
|
hasFilters
|
|
? "فیلترها را تغییر دهید یا همه را پاک کنید تا محصولات بیشتری ببینید."
|
|
: "بهزودی محصولات جدید اضافه میشوند."
|
|
}
|
|
/>
|
|
) : (
|
|
<>
|
|
<ProductGrid products={pageItems} />
|
|
<ProductPagination
|
|
page={page}
|
|
totalPages={totalPages}
|
|
basePath="/products"
|
|
query={{
|
|
q: query.q,
|
|
cat: query.cat,
|
|
min: query.min,
|
|
max: query.max,
|
|
sale: query.sale,
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<LeatherBanner
|
|
image={home.leather.image}
|
|
nameFa={home.leather.nameFa}
|
|
nameEn={home.leather.nameEn}
|
|
linkText={home.leather.linkText}
|
|
link={home.leather.link}
|
|
/>
|
|
<PartnerCta />
|
|
|
|
<ProductListExtras
|
|
stripCategories={stripCategories}
|
|
about={home.about}
|
|
categorySeo={categorySeo}
|
|
/>
|
|
|
|
<SiteFooter paymentBadges={home.paymentBadges} />
|
|
</main>
|
|
);
|
|
}
|