|
@@ -0,0 +1,301 @@
|
|
|
|
|
+import { Metadata } from "next";
|
|
|
|
|
+import { notFound } from "next/navigation";
|
|
|
|
|
+import { isArray } from "@/utils/type-guards";
|
|
|
|
|
+import FilterList from "@components/theme/filters/FilterList";
|
|
|
|
|
+import Pagination from "@components/catalog/Pagination";
|
|
|
|
|
+import ProductListing from "./_components/ProductListing";
|
|
|
|
|
+import { ProductsResponse } from "@components/catalog/type";
|
|
|
|
|
+import {
|
|
|
|
|
+ GET_FILTER_PRODUCTS,
|
|
|
|
|
+ GET_TREE_CATEGORIES,
|
|
|
|
|
+ GET_CATEHORY_BY_ID,
|
|
|
|
|
+} from "@/graphql";
|
|
|
|
|
+import {
|
|
|
|
|
+ cachedGraphQLRequest,
|
|
|
|
|
+ cachedCategoryRequest,
|
|
|
|
|
+ getFilterAttributes,
|
|
|
|
|
+} from "@/utils/hooks/useCache";
|
|
|
|
|
+import { SortByFields } from "@utils/constants";
|
|
|
|
|
+// import { CategoryDetail } from "@components/theme/search/CategoryDetail";
|
|
|
|
|
+import CategoryDesc from "./_components/CategoryDesc";
|
|
|
|
|
+import FaqList, { FaqItem } from "./_components/FaqList";
|
|
|
|
|
+import { Suspense } from "react";
|
|
|
|
|
+import FilterListSkeleton from "@components/common/skeleton/FilterSkeleton";
|
|
|
|
|
+import { TreeCategoriesResponse } from "@/types/theme/category-tree";
|
|
|
|
|
+// import { MobileSearchBar } from "@components/layout/navbar/MobileSearch";
|
|
|
|
|
+import {
|
|
|
|
|
+ extractNumericId,
|
|
|
|
|
+ findCategoryBySlug,
|
|
|
|
|
+ buildProductFilters,
|
|
|
|
|
+} from "@utils/helper";
|
|
|
|
|
+import { serverGraphqlFetch } from "@/utils/bagisto/index";
|
|
|
|
|
+import {
|
|
|
|
|
+ CategoryNode,
|
|
|
|
|
+ CategorySingleResponse,
|
|
|
|
|
+} from "@/types/theme/category-tree";
|
|
|
|
|
+
|
|
|
|
|
+/**列表页 */
|
|
|
|
|
+export async function generateMetadata({
|
|
|
|
|
+ params,
|
|
|
|
|
+}: {
|
|
|
|
|
+ params: Promise<{ slug: string; id: string }>;
|
|
|
|
|
+}): Promise<Metadata> {
|
|
|
|
|
+ const { slug: categorySlug, id: categoryId } = await params;
|
|
|
|
|
+ const { data: categoryData } = await serverGraphqlFetch<
|
|
|
|
|
+ CategorySingleResponse,
|
|
|
|
|
+ { id: number }
|
|
|
|
|
+ >({
|
|
|
|
|
+ query: GET_CATEHORY_BY_ID,
|
|
|
|
|
+ variables: {
|
|
|
|
|
+ id: Number(categoryId),
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // const {data:treeData} = await cachedGraphQLRequest<TreeCategoriesResponse>(
|
|
|
|
|
+ // "category",
|
|
|
|
|
+ // GET_TREE_CATEGORIES,
|
|
|
|
|
+ // { parentId: 1 }
|
|
|
|
|
+ // );
|
|
|
|
|
+
|
|
|
|
|
+ // const categories = treeData?.treeCategories || [];
|
|
|
|
|
+ // const categoryItem = findCategoryBySlug(categories, categorySlug);
|
|
|
|
|
+ const categoryItem = categoryData.category;
|
|
|
|
|
+ if (!categoryItem) return notFound();
|
|
|
|
|
+
|
|
|
|
|
+ const translation = categoryItem.translation;
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ title: translation?.metaTitle || translation?.name,
|
|
|
|
|
+ description: translation?.description || `${translation?.name} products`,
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default async function CategoryPage({
|
|
|
|
|
+ searchParams,
|
|
|
|
|
+ params,
|
|
|
|
|
+}: {
|
|
|
|
|
+ params: Promise<{ slug: string; id: string }>;
|
|
|
|
|
+ searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
|
|
|
+}) {
|
|
|
|
|
+ // const { collection: categorySlug } = await params;
|
|
|
|
|
+ const { slug: categorySlug, id: categoryId } = await params;
|
|
|
|
|
+ const resolvedParams = await searchParams;
|
|
|
|
|
+ function getSearchParam(
|
|
|
|
|
+ value: string | string[] | undefined,
|
|
|
|
|
+ ): string | undefined {
|
|
|
|
|
+ return Array.isArray(value) ? value[0] : value;
|
|
|
|
|
+ }
|
|
|
|
|
+ /*
|
|
|
|
|
+ const [{data:treeData}, filterAttributes] = await Promise.all([
|
|
|
|
|
+ cachedGraphQLRequest<TreeCategoriesResponse>(
|
|
|
|
|
+ "category",
|
|
|
|
|
+ GET_TREE_CATEGORIES,
|
|
|
|
|
+ { parentId: 1 }
|
|
|
|
|
+ ),
|
|
|
|
|
+ getFilterAttributes(), // 这个不能用
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ const categories = treeData?.treeCategories || [];
|
|
|
|
|
+ const categoryItem = findCategoryBySlug(categories, categorySlug);
|
|
|
|
|
+ */
|
|
|
|
|
+ const filterAttributes = await getFilterAttributes(); // 这个不能用
|
|
|
|
|
+
|
|
|
|
|
+ console.log("filterAttributes---------------:", filterAttributes);
|
|
|
|
|
+ const { data: categoryData } = await serverGraphqlFetch<
|
|
|
|
|
+ CategorySingleResponse,
|
|
|
|
|
+ { id: number }
|
|
|
|
|
+ >({
|
|
|
|
|
+ query: GET_CATEHORY_BY_ID,
|
|
|
|
|
+ variables: {
|
|
|
|
|
+ id: Number(categoryId),
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ const categoryItem = categoryData.category;
|
|
|
|
|
+ if (!categoryItem) return notFound();
|
|
|
|
|
+
|
|
|
|
|
+ console.log("categoryItem +++++++++++", categoryItem);
|
|
|
|
|
+ // const numericId = extractNumericId(categoryItem.id);
|
|
|
|
|
+ const numericId = String(categoryItem._id);
|
|
|
|
|
+
|
|
|
|
|
+ // const {
|
|
|
|
|
+ // q: searchValue,
|
|
|
|
|
+ // page,
|
|
|
|
|
+ // cursor,
|
|
|
|
|
+ // before,
|
|
|
|
|
+ // } = (resolvedParams || {}) as {
|
|
|
|
|
+ // [key: string]: string;
|
|
|
|
|
+ // };
|
|
|
|
|
+ const searchValue = getSearchParam(resolvedParams?.q) ?? "";
|
|
|
|
|
+
|
|
|
|
|
+ const page = getSearchParam(resolvedParams?.page);
|
|
|
|
|
+
|
|
|
|
|
+ const cursor = getSearchParam(resolvedParams?.cursor);
|
|
|
|
|
+
|
|
|
|
|
+ const before = getSearchParam(resolvedParams?.before);
|
|
|
|
|
+ const itemsPerPage = 12;
|
|
|
|
|
+ const currentPage = page ? parseInt(page) - 1 : 0;
|
|
|
|
|
+ const sortValue = getSearchParam(resolvedParams?.sort) ?? "name-asc";
|
|
|
|
|
+ const selectedSort =
|
|
|
|
|
+ SortByFields.find((s) => s.key === sortValue) || SortByFields[0];
|
|
|
|
|
+
|
|
|
|
|
+ const { filterObject: baseFilterObject } = buildProductFilters(
|
|
|
|
|
+ resolvedParams || {},
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const filterObject: Record<string, string> = {
|
|
|
|
|
+ ...baseFilterObject,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (numericId) {
|
|
|
|
|
+ filterObject.category_id = numericId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const filterInput = JSON.stringify(filterObject);
|
|
|
|
|
+
|
|
|
|
|
+ // 这个也改成 serverGraphqlFetch
|
|
|
|
|
+ const productVariables = {
|
|
|
|
|
+ query: searchValue || "",
|
|
|
|
|
+ filter: filterInput,
|
|
|
|
|
+ ...(before
|
|
|
|
|
+ ? {
|
|
|
|
|
+ last: itemsPerPage,
|
|
|
|
|
+ before,
|
|
|
|
|
+ }
|
|
|
|
|
+ : {
|
|
|
|
|
+ first: itemsPerPage,
|
|
|
|
|
+ after: cursor || null,
|
|
|
|
|
+ }),
|
|
|
|
|
+ sortKey: selectedSort.sortKey,
|
|
|
|
|
+ reverse: selectedSort.reverse,
|
|
|
|
|
+ };
|
|
|
|
|
+ const { data } = await serverGraphqlFetch<
|
|
|
|
|
+ ProductsResponse,
|
|
|
|
|
+ typeof productVariables
|
|
|
|
|
+ >({
|
|
|
|
|
+ query: GET_FILTER_PRODUCTS,
|
|
|
|
|
+ variables: productVariables,
|
|
|
|
|
+ });
|
|
|
|
|
+ // const [{ data }] = await Promise.all([
|
|
|
|
|
+ // cachedCategoryRequest<ProductsResponse>(categorySlug, GET_FILTER_PRODUCTS, {
|
|
|
|
|
+ // query: searchValue || "",
|
|
|
|
|
+ // filter: filterInput,
|
|
|
|
|
+ // ...(before
|
|
|
|
|
+ // ? { last: itemsPerPage, before: before }
|
|
|
|
|
+ // : { first: itemsPerPage, after: cursor }),
|
|
|
|
|
+ // sortKey: selectedSort.sortKey,
|
|
|
|
|
+ // reverse: selectedSort.reverse,
|
|
|
|
|
+ // }),
|
|
|
|
|
+ // ]);
|
|
|
|
|
+ console.log(
|
|
|
|
|
+ "GET_FILTER_PRODUCTS ----- ",
|
|
|
|
|
+ searchValue,
|
|
|
|
|
+ filterInput,
|
|
|
|
|
+ itemsPerPage,
|
|
|
|
|
+ before,
|
|
|
|
|
+ cursor,
|
|
|
|
|
+ selectedSort,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const products = data?.products?.edges?.map((e) => e.node) || [];
|
|
|
|
|
+ const pageInfo = data?.products?.pageInfo;
|
|
|
|
|
+ const totalCount = data?.products?.totalCount || 0;
|
|
|
|
|
+ const translation = categoryItem.translation;
|
|
|
|
|
+ console.log("initialQuery------------", {
|
|
|
|
|
+ search: searchValue,
|
|
|
|
|
+ sort: sortValue,
|
|
|
|
|
+ filters: baseFilterObject,
|
|
|
|
|
+ });
|
|
|
|
|
+ // 模拟FAQ假数据
|
|
|
|
|
+ const mockFaqData: FaqItem[] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: "1",
|
|
|
|
|
+ question: "How long is the delivery time for human hair wigs?",
|
|
|
|
|
+ answer:
|
|
|
|
|
+ "Standard shipping takes 7-12 working days, expedited shipping supports 3-5 days delivery worldwide.",
|
|
|
|
|
+ likeCount: 248,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: "2",
|
|
|
|
|
+ question: "Is the HD lace wig invisible on all skin tones?",
|
|
|
|
|
+ answer:
|
|
|
|
|
+ "Our HD transparent lace matches all skin tones, melts perfectly without foundation.",
|
|
|
|
|
+ likeCount: 186,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: "3",
|
|
|
|
|
+ question: "Can I dye and bleach the hair extensions?",
|
|
|
|
|
+ answer:
|
|
|
|
|
+ "100% human hair can be dyed darker or bleached lighter, we recommend professional hairdresser operation.",
|
|
|
|
|
+ likeCount: 312,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: "4",
|
|
|
|
|
+ question: "What payment methods do you support?",
|
|
|
|
|
+ answer:
|
|
|
|
|
+ "Credit card, PayPal, Apple Pay, Google Pay and bank transfer are all available.",
|
|
|
|
|
+ likeCount: 97,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: "5",
|
|
|
|
|
+ question: "How to return or exchange products?",
|
|
|
|
|
+ answer:
|
|
|
|
|
+ "Contact our customer service within 30 days after receiving the package for free exchange.",
|
|
|
|
|
+ likeCount: 154,
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+ return (
|
|
|
|
|
+ <>
|
|
|
|
|
+ {/* <MobileSearchBar /> */}
|
|
|
|
|
+ <section>
|
|
|
|
|
+ <Suspense fallback={<FilterListSkeleton />}>
|
|
|
|
|
+ {/* <CategoryDetail
|
|
|
|
|
+ categoryItem={{
|
|
|
|
|
+ description: translation?.description ?? "",
|
|
|
|
|
+ name: translation?.name ?? "",
|
|
|
|
|
+ }}
|
|
|
|
|
+ /> */}
|
|
|
|
|
+ <CategoryDesc
|
|
|
|
|
+ title={translation?.name ?? ""}
|
|
|
|
|
+ description={
|
|
|
|
|
+ "Alipearl Hair provides many style best quality but cheap lace wigs,including lace front wigs, lace closure wigs, full lace human hair wigs, HD transparent lace wigs, 13x4 frontal wigs, 13x6 lace front wigs, 360 lace wigs, all textures available straight, body wave, deep wave, water wave, curly hair wigs, all lengths from 8 inch to 40 inch, different hair colors natural black, brown, blonde, highlight, ginger, burgundy etc."
|
|
|
|
|
+ } //translation?.description ?? ""}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Suspense>
|
|
|
|
|
+ {/* <div className="my-10 hidden gap-4 md:flex md:items-baseline md:justify-between w-full max-w-screen-2xl mx-auto px-4">
|
|
|
|
|
+ <FilterList filterAttributes={filterAttributes} />
|
|
|
|
|
+ <SortOrder sortOrders={SortByFields} title="Sort by" />
|
|
|
|
|
+ </div> */}
|
|
|
|
|
+ <ProductListing
|
|
|
|
|
+ categoryId={numericId}
|
|
|
|
|
+ filterAttributes={filterAttributes}
|
|
|
|
|
+ initialProducts={products}
|
|
|
|
|
+ initialPageInfo={pageInfo}
|
|
|
|
|
+ initialTotal={totalCount}
|
|
|
|
|
+ initialQuery={{
|
|
|
|
|
+ search: searchValue,
|
|
|
|
|
+ sort: sortValue,
|
|
|
|
|
+ filters: baseFilterObject,
|
|
|
|
|
+ cursor,
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ {/* {isArray(products) &&
|
|
|
|
|
+ (totalCount > itemsPerPage || pageInfo?.hasNextPage) && (
|
|
|
|
|
+ <nav
|
|
|
|
|
+ aria-label="Collection pagination"
|
|
|
|
|
+ className="my-10 block items-center sm:flex"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ itemsPerPage={itemsPerPage}
|
|
|
|
|
+ itemsTotal={totalCount || 0}
|
|
|
|
|
+ currentPage={currentPage}
|
|
|
|
|
+ nextCursor={pageInfo?.endCursor}
|
|
|
|
|
+ prevCursor={pageInfo?.startCursor}
|
|
|
|
|
+ />
|
|
|
|
|
+ </nav>
|
|
|
|
|
+ )} */}
|
|
|
|
|
+ <FaqList faqList={mockFaqData} />
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|