page.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import dynamicImport from "next/dynamic";
  2. import Grid from "@/components/theme/ui/grid/Grid";
  3. import NotFound from "@/components/theme/search/not-found";
  4. import { isArray } from "@/utils/type-guards";
  5. import { GET_FILTER_PRODUCTS } from "@/graphql";
  6. import { GET_PRODUCTS, GET_PRODUCTS_PAGINATION } from "@/graphql";
  7. import {
  8. cachedGraphQLRequest,
  9. getFilterAttributes,
  10. } from "@/utils/hooks/useCache";
  11. import { generateMetadataForPage, buildProductFilters } from "@/utils/helper";
  12. import SortOrder from "@/components/theme/filters/SortOrder";
  13. import { SortByFields } from "@/utils/constants";
  14. import MobileFilter from "@/components/theme/filters/MobileFilter";
  15. import FilterList from "@/components/theme/filters/FilterList";
  16. import { ProductsResponse } from "@/components/catalog/type";
  17. import { MobileSearchBar } from "@components/layout/navbar/MobileSearch";
  18. import HotSearch from "./_components/HotSearch"
  19. const Pagination = dynamicImport(
  20. () => import("@/components/catalog/Pagination"),
  21. );
  22. const ProductGridItems = dynamicImport(
  23. () => import("@/components/catalog/product/ProductGridItems"),
  24. );
  25. export const dynamicParams = true;
  26. export async function generateStaticParams() {
  27. try {
  28. const itemsPerPage = 12;
  29. const commonSearches = [""];
  30. const params = [];
  31. for (const query of commonSearches) {
  32. const { data } = await cachedGraphQLRequest<ProductsResponse>(
  33. "search",
  34. GET_PRODUCTS,
  35. {
  36. query: query,
  37. first: 1,
  38. sortKey: "CREATED_AT",
  39. reverse: true,
  40. },
  41. );
  42. const totalCount = data?.products?.totalCount || 0;
  43. const totalPages = Math.ceil(totalCount / itemsPerPage);
  44. let cursor: string | undefined;
  45. for (let i = 0; i < totalPages; i++) {
  46. const pageParams: { page: string; cursor?: string } = {
  47. page: String(i + 1),
  48. };
  49. if (i > 0 && cursor) {
  50. pageParams.cursor = cursor;
  51. }
  52. params.push(pageParams);
  53. if (i < totalPages - 1) {
  54. const { data: pageData } =
  55. await cachedGraphQLRequest<ProductsResponse>(
  56. "search",
  57. GET_PRODUCTS,
  58. {
  59. query: query,
  60. first: itemsPerPage,
  61. sortKey: "CREATED_AT",
  62. reverse: true,
  63. ...(cursor && { after: cursor }),
  64. },
  65. );
  66. cursor = pageData?.products?.pageInfo?.endCursor;
  67. }
  68. }
  69. }
  70. return params;
  71. } catch (error) {
  72. console.error("Error generating static params:", error);
  73. return [];
  74. }
  75. }
  76. export async function generateMetadata({
  77. searchParams,
  78. }: {
  79. searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
  80. }) {
  81. const params = await searchParams;
  82. const searchQuery = params?.q as string | undefined;
  83. return generateMetadataForPage("search", {
  84. title: searchQuery ? `Search: ${searchQuery}` : "Search Products",
  85. description: searchQuery
  86. ? `Search results for "${searchQuery}"`
  87. : "Search for products in our store",
  88. image: "/search-og.jpg",
  89. });
  90. }
  91. export default async function SearchPage({
  92. searchParams,
  93. }: {
  94. searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
  95. }) {
  96. const params = await searchParams;
  97. const {
  98. q: searchValue,
  99. page,
  100. cursor,
  101. before,
  102. } = (params || {}) as {
  103. [key: string]: string;
  104. };
  105. const itemsPerPage = 12;
  106. const currentPage = page ? parseInt(page) - 1 : 0;
  107. const sortValue = params?.sort || "name-asc";
  108. const selectedSort =
  109. SortByFields.find((s) => s.key === sortValue) || SortByFields[0];
  110. const afterCursor: string | undefined = cursor;
  111. const beforeCursor: string | undefined = before;
  112. const { filterInput, isFilterApplied } = buildProductFilters(params || {});
  113. let dataPromise;
  114. if (isFilterApplied) {
  115. dataPromise = cachedGraphQLRequest<ProductsResponse>(
  116. "search",
  117. GET_FILTER_PRODUCTS,
  118. {
  119. query: searchValue,
  120. filter: filterInput,
  121. ...(beforeCursor
  122. ? { last: itemsPerPage, before: beforeCursor }
  123. : { first: itemsPerPage, after: afterCursor }),
  124. sortKey: selectedSort.sortKey,
  125. reverse: selectedSort.reverse,
  126. },
  127. );
  128. } else {
  129. dataPromise = (async () => {
  130. let currentAfterCursor: string | undefined = afterCursor;
  131. if (currentPage > 0 && !afterCursor) {
  132. const { data: cursorData } =
  133. await cachedGraphQLRequest<ProductsResponse>(
  134. "search",
  135. GET_PRODUCTS_PAGINATION,
  136. {
  137. query: searchValue,
  138. first: currentPage * itemsPerPage,
  139. sortKey: selectedSort.sortKey,
  140. reverse: selectedSort.reverse,
  141. },
  142. );
  143. currentAfterCursor = cursorData?.products?.pageInfo?.endCursor;
  144. }
  145. return cachedGraphQLRequest<ProductsResponse>("search", GET_PRODUCTS, {
  146. query: searchValue,
  147. ...(beforeCursor
  148. ? { last: itemsPerPage, before: beforeCursor }
  149. : { first: itemsPerPage, after: currentAfterCursor }),
  150. sortKey: selectedSort.sortKey,
  151. reverse: selectedSort.reverse,
  152. });
  153. })();
  154. }
  155. const [{ data }, filterAttributes] = await Promise.all([
  156. dataPromise,
  157. getFilterAttributes(),
  158. ]);
  159. const products = data?.products?.edges?.map((e) => e.node) || [];
  160. const pageInfo = data?.products?.pageInfo;
  161. const totalCount = data?.products?.totalCount || 0;
  162. return (
  163. <>
  164. <MobileSearchBar />
  165. <HotSearch />
  166. {/* <div className="my-10 hidden gap-4 md:flex md:items-baseline md:justify-between w-full mx-auto max-w-screen-2xl px-4 xss:px-7.5">
  167. <FilterList filterAttributes={filterAttributes} />
  168. <SortOrder sortOrders={SortByFields} title="Sort by" />
  169. </div> */}
  170. {isArray(products) ? (
  171. <div className="flex items-center justify-between gap-4 py-8 md:hidden mx-auto w-full max-w-screen-2xl px-4 xss:px-7.5">
  172. <MobileFilter filterAttributes={filterAttributes} />
  173. <SortOrder sortOrders={SortByFields} title="Sort by" />
  174. </div>
  175. ) : null}
  176. {!isArray(products) && (
  177. // <NotFound
  178. // msg={`${
  179. // searchValue
  180. // ? `There are no products that match Showing : ${searchValue}`
  181. // : "There are no products that match Showing"
  182. // } `}
  183. // />
  184. <div>
  185. <h2>There are no products that match Showing : ${searchValue}</h2>
  186. <h2 className="text-2xl sm:text-4xl font-semibold mx-auto mt-7.5 w-full max-w-screen-2xl my-3 mx-auto px-4 xss:px-7.5">
  187. All Top Products
  188. </h2>
  189. </div>
  190. )}
  191. {isArray(products) ? (
  192. <Grid className="grid grid-flow-row grid-cols-2 gap-5 lg:gap-11.5 w-full max-w-screen-2xl mx-auto md:grid-cols-3 lg:grid-cols-4 px-4 xss:px-7.5">
  193. <ProductGridItems products={products} />
  194. </Grid>
  195. ) : null}
  196. {!isFilterApplied && isArray(products) && totalCount > itemsPerPage && (
  197. <nav
  198. aria-label="Collection pagination"
  199. className="my-10 block items-center sm:flex"
  200. >
  201. <Pagination
  202. itemsPerPage={itemsPerPage}
  203. itemsTotal={totalCount || 0}
  204. currentPage={currentPage}
  205. nextCursor={pageInfo?.endCursor}
  206. prevCursor={pageInfo?.startCursor}
  207. />
  208. </nav>
  209. )}
  210. {isFilterApplied && isArray(products) && pageInfo?.hasNextPage && (
  211. <nav
  212. aria-label="Filtered pagination"
  213. className="my-10 block items-center sm:flex"
  214. >
  215. <Pagination
  216. itemsPerPage={itemsPerPage}
  217. itemsTotal={totalCount || 0}
  218. currentPage={currentPage}
  219. nextCursor={pageInfo?.endCursor}
  220. prevCursor={pageInfo?.startCursor}
  221. />
  222. </nav>
  223. )}
  224. </>
  225. );
  226. }