@faststore/core 2.1.84 → 2.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.turbo/turbo-build.log +9 -7
  2. package/.turbo/turbo-test.log +25 -0
  3. package/@generated/graphql/index.ts +183 -79
  4. package/@generated/graphql/persisted.json +5 -5
  5. package/@generated/graphql/schema.graphql +1053 -0
  6. package/api/index.ts +15 -0
  7. package/cms/faststore/sections.json +2 -32
  8. package/codegen.yml +2 -1
  9. package/generate.sh +71 -0
  10. package/index.ts +15 -0
  11. package/jest.config.js +6 -0
  12. package/package.json +33 -18
  13. package/src/components/ThirdPartyScripts/ThirdPartyScripts.tsx +1 -2
  14. package/src/components/cms/RenderSections.tsx +4 -5
  15. package/src/components/navigation/Navbar/Navbar.tsx +4 -3
  16. package/src/components/navigation/NavbarSlider/NavbarSlider.tsx +5 -4
  17. package/src/components/product/ProductGrid/ProductGrid.tsx +4 -3
  18. package/src/components/sections/Breadcrumb/Breadcrumb.tsx +17 -22
  19. package/src/components/sections/CrossSellingShelf/CrossSellingShelf.tsx +8 -6
  20. package/src/components/sections/Footer/Footer.tsx +2 -14
  21. package/src/components/sections/Navbar/Navbar.tsx +0 -4
  22. package/src/components/sections/ProductDetails/ProductDetails.tsx +23 -33
  23. package/src/components/sections/ProductGallery/ProductGallery.tsx +15 -26
  24. package/src/components/sections/ProductShelf/ProductShelf.tsx +1 -1
  25. package/src/components/sections/ProductTiles/ProductTiles.tsx +4 -3
  26. package/src/components/templates/ProductListingPage/ProductListing.tsx +95 -0
  27. package/src/components/templates/ProductListingPage/ProductListingPage.tsx +34 -68
  28. package/src/components/templates/SearchPage/SearchPage.tsx +81 -0
  29. package/src/components/templates/SearchPage/index.ts +2 -0
  30. package/src/components/ui/ProductGallery/ProductGallery.tsx +24 -16
  31. package/src/components/ui/ProductGallery/ProductGalleryPage.tsx +8 -7
  32. package/src/components/ui/ProductGallery/useDelayedFacets.ts +3 -3
  33. package/src/components/ui/ProductShelf/ProductShelf.tsx +3 -2
  34. package/src/customizations/GlobalOverrides.tsx +0 -2
  35. package/src/customizations/fragments/ClientProduct.ts +9 -0
  36. package/src/customizations/fragments/ClientProductGallery.ts +19 -0
  37. package/src/customizations/fragments/ClientProducts.ts +19 -0
  38. package/src/customizations/fragments/ServerCollectionPage.ts +9 -0
  39. package/src/customizations/fragments/ServerProductPage.ts +9 -0
  40. package/src/customizations/graphql/thirdParty/resolvers/index.ts +3 -0
  41. package/src/customizations/graphql/vtex/resolvers/index.ts +3 -0
  42. package/src/pages/[...slug].tsx +5 -3
  43. package/src/pages/[slug]/p.tsx +38 -18
  44. package/src/pages/s.tsx +22 -34
  45. package/src/sdk/graphql/useQuery.ts +17 -13
  46. package/src/sdk/overrides/PageProvider.tsx +78 -0
  47. package/src/sdk/product/useLocalizedVariables.ts +33 -0
  48. package/src/sdk/product/usePageProductsQuery.ts +139 -0
  49. package/src/{components/sections/ProductGallery/useGalleryQuery.ts → sdk/product/useProductGalleryQuery.ts} +12 -13
  50. package/src/sdk/product/{useProduct.ts → useProductQuery.ts} +10 -7
  51. package/src/sdk/product/useProductsPrefetch.ts +72 -0
  52. package/src/sdk/product/useProductsQuery.ts +17 -63
  53. package/src/server/generator/generateGraphQLSchemaFile.ts +3 -0
  54. package/src/server/generator/schema.ts +82 -0
  55. package/src/server/index.ts +34 -21
  56. package/src/server/options.ts +16 -0
  57. package/test/server/index.test.ts +146 -0
  58. package/.next/BUILD_ID +0 -1
  59. package/.next/build-manifest.json +0 -129
  60. package/.next/cache/.tsbuildinfo +0 -1
  61. package/.next/cache/config.json +0 -7
  62. package/.next/cache/eslint/.cache_1gneedd +0 -1
  63. package/.next/cache/next-server.js.nft.json +0 -1
  64. package/.next/cache/webpack/client-production/0.pack +0 -0
  65. package/.next/cache/webpack/client-production/index.pack +0 -0
  66. package/.next/cache/webpack/server-production/0.pack +0 -0
  67. package/.next/cache/webpack/server-production/index.pack +0 -0
  68. package/.next/export-marker.json +0 -1
  69. package/.next/images-manifest.json +0 -1
  70. package/.next/next-server.js.nft.json +0 -1
  71. package/.next/package.json +0 -1
  72. package/.next/prerender-manifest.json +0 -1
  73. package/.next/react-loadable-manifest.json +0 -44
  74. package/.next/required-server-files.json +0 -1
  75. package/.next/routes-manifest.json +0 -1
  76. package/.next/server/chunks/143.js +0 -106
  77. package/.next/server/chunks/177.js +0 -120
  78. package/.next/server/chunks/183.js +0 -94
  79. package/.next/server/chunks/184.js +0 -61
  80. package/.next/server/chunks/186.js +0 -113
  81. package/.next/server/chunks/289.js +0 -239
  82. package/.next/server/chunks/312.js +0 -697
  83. package/.next/server/chunks/350.js +0 -143
  84. package/.next/server/chunks/483.js +0 -650
  85. package/.next/server/chunks/487.js +0 -9142
  86. package/.next/server/chunks/53.js +0 -61
  87. package/.next/server/chunks/530.js +0 -626
  88. package/.next/server/chunks/576.js +0 -94
  89. package/.next/server/chunks/650.js +0 -9142
  90. package/.next/server/chunks/676.js +0 -32
  91. package/.next/server/chunks/693.js +0 -58
  92. package/.next/server/chunks/71.js +0 -1254
  93. package/.next/server/chunks/74.js +0 -4065
  94. package/.next/server/chunks/753.js +0 -509
  95. package/.next/server/chunks/779.js +0 -58
  96. package/.next/server/chunks/825.js +0 -4039
  97. package/.next/server/chunks/854.js +0 -72
  98. package/.next/server/chunks/859.js +0 -959
  99. package/.next/server/chunks/907.js +0 -1911
  100. package/.next/server/chunks/933.js +0 -517
  101. package/.next/server/chunks/98.js +0 -124
  102. package/.next/server/chunks/988.js +0 -211
  103. package/.next/server/chunks/font-manifest.json +0 -1
  104. package/.next/server/font-manifest.json +0 -1
  105. package/.next/server/middleware-build-manifest.js +0 -1
  106. package/.next/server/middleware-manifest.json +0 -6
  107. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  108. package/.next/server/pages/404.js +0 -386
  109. package/.next/server/pages/404.js.nft.json +0 -1
  110. package/.next/server/pages/500.js +0 -388
  111. package/.next/server/pages/500.js.nft.json +0 -1
  112. package/.next/server/pages/[...slug].js +0 -1005
  113. package/.next/server/pages/[...slug].js.nft.json +0 -1
  114. package/.next/server/pages/[slug]/p.js +0 -2269
  115. package/.next/server/pages/[slug]/p.js.nft.json +0 -1
  116. package/.next/server/pages/_app.js +0 -280
  117. package/.next/server/pages/_app.js.nft.json +0 -1
  118. package/.next/server/pages/_document.js +0 -374
  119. package/.next/server/pages/_document.js.nft.json +0 -1
  120. package/.next/server/pages/_error.js +0 -164
  121. package/.next/server/pages/_error.js.nft.json +0 -1
  122. package/.next/server/pages/account.js +0 -363
  123. package/.next/server/pages/account.js.nft.json +0 -1
  124. package/.next/server/pages/api/graphql.js +0 -365
  125. package/.next/server/pages/api/graphql.js.nft.json +0 -1
  126. package/.next/server/pages/api/health/live.js +0 -31
  127. package/.next/server/pages/api/health/live.js.nft.json +0 -1
  128. package/.next/server/pages/api/health/ready.js +0 -31
  129. package/.next/server/pages/api/health/ready.js.nft.json +0 -1
  130. package/.next/server/pages/api/preview.js +0 -148
  131. package/.next/server/pages/api/preview.js.nft.json +0 -1
  132. package/.next/server/pages/checkout.js +0 -363
  133. package/.next/server/pages/checkout.js.nft.json +0 -1
  134. package/.next/server/pages/en-US/404.html +0 -81
  135. package/.next/server/pages/en-US/404.json +0 -1
  136. package/.next/server/pages/en-US/500.html +0 -81
  137. package/.next/server/pages/en-US/500.json +0 -1
  138. package/.next/server/pages/en-US/account.html +0 -81
  139. package/.next/server/pages/en-US/account.json +0 -1
  140. package/.next/server/pages/en-US/checkout.html +0 -81
  141. package/.next/server/pages/en-US/checkout.json +0 -1
  142. package/.next/server/pages/en-US/login.html +0 -81
  143. package/.next/server/pages/en-US/login.json +0 -1
  144. package/.next/server/pages/en-US/s.html +0 -81
  145. package/.next/server/pages/en-US/s.json +0 -1
  146. package/.next/server/pages/en-US.html +0 -81
  147. package/.next/server/pages/en-US.json +0 -1
  148. package/.next/server/pages/index.js +0 -439
  149. package/.next/server/pages/index.js.nft.json +0 -1
  150. package/.next/server/pages/login.js +0 -368
  151. package/.next/server/pages/login.js.nft.json +0 -1
  152. package/.next/server/pages/s.js +0 -466
  153. package/.next/server/pages/s.js.nft.json +0 -1
  154. package/.next/server/pages-manifest.json +0 -18
  155. package/.next/server/webpack-api-runtime.js +0 -229
  156. package/.next/server/webpack-runtime.js +0 -229
  157. package/.next/static/UFCNzAvjHLcTNmP8sQK5l/_buildManifest.js +0 -1
  158. package/.next/static/UFCNzAvjHLcTNmP8sQK5l/_ssgManifest.js +0 -1
  159. package/.next/static/chunks/143.dd8a556e6957baa1.js +0 -1
  160. package/.next/static/chunks/148.582eaa81293ee470.js +0 -1
  161. package/.next/static/chunks/238-39630714f5d70169.js +0 -1
  162. package/.next/static/chunks/243-b4f8e506d156ee41.js +0 -1
  163. package/.next/static/chunks/530.ec68de379130c11e.js +0 -1
  164. package/.next/static/chunks/548-4ee971bf7bdb3e81.js +0 -1
  165. package/.next/static/chunks/603-072c8fb73ac35775.js +0 -1
  166. package/.next/static/chunks/651.7142f31ce1e052b3.js +0 -1
  167. package/.next/static/chunks/709.7bc5a25ce30abda6.js +0 -1
  168. package/.next/static/chunks/738-a5ff304828f20cbf.js +0 -1
  169. package/.next/static/chunks/741.52f7fb873418346f.js +0 -1
  170. package/.next/static/chunks/98.97381d2021f86cd9.js +0 -1
  171. package/.next/static/chunks/988.afda042dd9ba11d1.js +0 -1
  172. package/.next/static/chunks/framework-dfd14d7ce6600b03.js +0 -1
  173. package/.next/static/chunks/main-fd466221927468fd.js +0 -1
  174. package/.next/static/chunks/pages/404-af78f7cd1d3c1f60.js +0 -1
  175. package/.next/static/chunks/pages/500-f6346ca5f9dc4fef.js +0 -1
  176. package/.next/static/chunks/pages/[...slug]-3f4e5f74ff9436ec.js +0 -1
  177. package/.next/static/chunks/pages/[slug]/p-8e20c206fb84d184.js +0 -1
  178. package/.next/static/chunks/pages/_app-895781b1c7b5bf56.js +0 -1
  179. package/.next/static/chunks/pages/_error-a7a0c1d9bfbb4f38.js +0 -1
  180. package/.next/static/chunks/pages/account-05bd79fb78365e88.js +0 -1
  181. package/.next/static/chunks/pages/checkout-c973786e68f25a39.js +0 -1
  182. package/.next/static/chunks/pages/index-73110361a184aa10.js +0 -1
  183. package/.next/static/chunks/pages/login-8deb9243376b6aa1.js +0 -1
  184. package/.next/static/chunks/pages/s-94691e1fdc9521a4.js +0 -1
  185. package/.next/static/chunks/polyfills-c67a75d1b6f99dc8.js +0 -1
  186. package/.next/static/chunks/webpack-504557a0a7373460.js +0 -1
  187. package/.next/static/css/20e4a3a45cdd65f4.css +0 -1
  188. package/.next/static/css/4b7138899cd07c63.css +0 -1
  189. package/.next/static/css/527e334fa69cf40a.css +0 -1
  190. package/.next/static/css/6e1a7434f061d0ef.css +0 -1
  191. package/.next/static/css/9e76fef1c9ca89af.css +0 -1
  192. package/.next/static/css/a2eefb25a4608343.css +0 -1
  193. package/.next/static/css/cb7d1fcea42fab9c.css +0 -1
  194. package/.next/static/css/df588bb98c0b0ca6.css +0 -1
  195. package/.next/static/css/e3b039e8f5daf95f.css +0 -1
  196. package/.next/static/css/f0e2d1b8832e935d.css +0 -1
  197. package/.next/trace +0 -80
  198. package/public/~partytown/debug/partytown-atomics.js +0 -556
  199. package/public/~partytown/debug/partytown-media.js +0 -374
  200. package/public/~partytown/debug/partytown-sandbox-sw.js +0 -543
  201. package/public/~partytown/debug/partytown-sw.js +0 -59
  202. package/public/~partytown/debug/partytown-ww-atomics.js +0 -1789
  203. package/public/~partytown/debug/partytown-ww-sw.js +0 -1781
  204. package/public/~partytown/debug/partytown.js +0 -72
  205. package/public/~partytown/partytown-atomics.js +0 -2
  206. package/public/~partytown/partytown-media.js +0 -2
  207. package/public/~partytown/partytown-sw.js +0 -2
  208. package/public/~partytown/partytown.js +0 -2
  209. package/src/components/ui/ProductGallery/usePageProducts.ts +0 -48
  210. package/src/customizations/components/overrides/ThirdPartyScripts.tsx +0 -9
  211. package/src/customizations/scripts/ThirdPartyScripts.tsx +0 -8
@@ -0,0 +1,9 @@
1
+ import { gql } from '@faststore/graphql-utils'
2
+
3
+ export const fragment = gql`
4
+ fragment ServerProductPage on Query {
5
+ product(locator: $locator) {
6
+ id: productID
7
+ }
8
+ }
9
+ `
@@ -0,0 +1,3 @@
1
+ const resolvers = {}
2
+
3
+ export default resolvers
@@ -0,0 +1,3 @@
1
+ const resolvers = {}
2
+
3
+ export default resolvers
@@ -29,10 +29,11 @@ type BaseProps = {
29
29
 
30
30
  type Props = BaseProps &
31
31
  (
32
- | ({
32
+ | {
33
33
  type: 'plp'
34
34
  page: PLPContentType
35
- } & ServerCollectionPageQueryQuery)
35
+ data: ServerCollectionPageQueryQuery
36
+ }
36
37
  | {
37
38
  type: 'page'
38
39
  page: PageContentType
@@ -52,6 +53,7 @@ function Page({ globalSections, type, ...otherProps }: Props) {
52
53
 
53
54
  const query = gql`
54
55
  query ServerCollectionPageQuery($slug: String!) {
56
+ ...ServerCollectionPage
55
57
  collection(slug: $slug) {
56
58
  seo {
57
59
  title
@@ -123,7 +125,7 @@ export const getStaticProps: GetStaticProps<
123
125
 
124
126
  return {
125
127
  props: {
126
- ...data,
128
+ data,
127
129
  page,
128
130
  globalSections: await globalSectionsPromise,
129
131
  type: 'plp',
@@ -4,8 +4,10 @@ import type { Locator } from '@vtex/client-cms'
4
4
  import type { GetStaticPaths, GetStaticProps } from 'next'
5
5
  import { BreadcrumbJsonLd, NextSeo, ProductJsonLd } from 'next-seo'
6
6
  import type { ComponentType } from 'react'
7
+ import deepmerge from 'deepmerge'
7
8
 
8
9
  import type {
10
+ ClientProductQueryQuery,
9
11
  ServerProductPageQueryQuery,
10
12
  ServerProductPageQueryQueryVariables,
11
13
  } from '@generated/graphql'
@@ -26,6 +28,8 @@ import GlobalSections, {
26
28
  getGlobalSectionsData,
27
29
  } from 'src/components/cms/GlobalSections'
28
30
  import storeConfig from '../../../faststore.config'
31
+ import { useProductQuery } from 'src/sdk/product/useProductQuery'
32
+ import PageProvider, { PDPContext } from 'src/sdk/overrides/PageProvider'
29
33
 
30
34
  /**
31
35
  * Sections: Components imported from each store's custom components and '../components/sections' only.
@@ -39,19 +43,36 @@ const COMPONENTS: Record<string, ComponentType<any>> = {
39
43
  ...CUSTOM_COMPONENTS,
40
44
  }
41
45
 
42
- type Props = ServerProductPageQueryQuery &
43
- PDPContentType & {
44
- globalSections: GlobalSectionsData
45
- meta: {
46
- title: string
47
- description: string
48
- canonical: string
49
- }
46
+ type Props = PDPContentType & {
47
+ data: ServerProductPageQueryQuery
48
+ globalSections: GlobalSectionsData
49
+ meta: {
50
+ title: string
51
+ description: string
52
+ canonical: string
50
53
  }
54
+ }
51
55
 
52
- function Page({ product, sections, globalSections, offers, meta }: Props) {
56
+ // Array merging strategy from deepmerge that makes client arrays overwrite server array
57
+ // https://www.npmjs.com/package/deepmerge
58
+ const overwriteMerge = (_, sourceArray) => sourceArray
59
+
60
+ function Page({ data: server, sections, globalSections, offers, meta }: Props) {
61
+ const { product } = server
53
62
  const { currency } = useSession()
54
63
 
64
+ // Stale while revalidate the product for fetching the new price etc
65
+ const { data: client, isValidating } = useProductQuery(product.id, {
66
+ product: product,
67
+ })
68
+
69
+ const context = {
70
+ data: {
71
+ ...deepmerge(server, client, { arrayMerge: overwriteMerge }),
72
+ isValidating,
73
+ },
74
+ } as PDPContext
75
+
55
76
  return (
56
77
  <GlobalSections {...globalSections}>
57
78
  {/* SEO */}
@@ -105,18 +126,17 @@ function Page({ product, sections, globalSections, offers, meta }: Props) {
105
126
  If needed, wrap your component in a <Section /> component
106
127
  (not the HTML tag) before rendering it here.
107
128
  */}
108
- <RenderSections
109
- context={product}
110
- sections={sections}
111
- components={COMPONENTS}
112
- />
129
+ <PageProvider context={context}>
130
+ <RenderSections sections={sections} components={COMPONENTS} />
131
+ </PageProvider>
113
132
  </GlobalSections>
114
133
  )
115
134
  }
116
135
 
117
136
  const query = gql`
118
- query ServerProductPageQuery($slug: String!) {
119
- product(locator: [{ key: "slug", value: $slug }]) {
137
+ query ServerProductPageQuery($locator: [IStoreSelectedFacet!]!) {
138
+ ...ServerProductPage
139
+ product(locator: $locator) {
120
140
  id: productID
121
141
 
122
142
  seo {
@@ -181,7 +201,7 @@ export const getStaticProps: GetStaticProps<
181
201
  const slug = params?.slug ?? ''
182
202
  const [searchResult, cmsPage, globalSections] = await Promise.all([
183
203
  execute<ServerProductPageQueryQueryVariables, ServerProductPageQueryQuery>({
184
- variables: { slug },
204
+ variables: { locator: [{ key: 'slug', value: slug }] },
185
205
  operationName: query,
186
206
  }),
187
207
  getPage<PDPContentType>({
@@ -228,7 +248,7 @@ export const getStaticProps: GetStaticProps<
228
248
 
229
249
  return {
230
250
  props: {
231
- ...data,
251
+ data,
232
252
  ...cmsPage,
233
253
  meta,
234
254
  offers,
package/src/pages/s.tsx CHANGED
@@ -1,15 +1,15 @@
1
1
  import type { SearchState } from '@faststore/sdk'
2
- import { parseSearchState, SearchProvider } from '@faststore/sdk'
2
+ import {
3
+ formatSearchState,
4
+ parseSearchState,
5
+ SearchProvider,
6
+ } from '@faststore/sdk'
3
7
  import { SROnly as UISROnly } from '@faststore/ui'
4
8
  import { NextSeo } from 'next-seo'
5
9
  import { useRouter } from 'next/router'
6
- import type { ComponentType } from 'react'
7
- import { useEffect, useState } from 'react'
10
+ import { useMemo } from 'react'
8
11
 
9
- import Breadcrumb from 'src/components/sections/Breadcrumb'
10
- import ProductGallery from 'src/components/sections/ProductGallery'
11
12
  import { ITEMS_PER_PAGE } from 'src/constants'
12
- import CUSTOM_COMPONENTS from 'src/customizations/components'
13
13
  import { useApplySearchState } from 'src/sdk/search/state'
14
14
  import { mark } from 'src/sdk/tests/mark'
15
15
 
@@ -19,19 +19,9 @@ import GlobalSections, {
19
19
  getGlobalSectionsData,
20
20
  GlobalSectionsData,
21
21
  } from 'src/components/cms/GlobalSections'
22
- import RenderSections from 'src/components/cms/RenderSections'
23
22
  import { getPage, SearchContentType } from 'src/server/cms'
24
23
  import storeConfig from '../../faststore.config'
25
-
26
- /**
27
- * Sections: Components imported from each store's custom components and '../components/sections' only.
28
- * Do not import or render components from any other folder in here.
29
- */
30
- const COMPONENTS: Record<string, ComponentType<any>> = {
31
- Breadcrumb,
32
- ProductGallery,
33
- ...CUSTOM_COMPONENTS,
34
- }
24
+ import SearchPage from 'src/components/templates/SearchPage/SearchPage'
35
25
 
36
26
  type Props = {
37
27
  page: SearchContentType
@@ -48,10 +38,9 @@ type UseSearchParams = {
48
38
  }
49
39
 
50
40
  const useSearchParams = ({ sort: defaultSort }: UseSearchParams) => {
51
- const [params, setParams] = useState<SearchState | null>(null)
52
41
  const { asPath } = useRouter()
53
42
 
54
- useEffect(() => {
43
+ return useMemo(() => {
55
44
  const url = new URL(asPath, 'http://localhost')
56
45
 
57
46
  const shouldUpdateDefaultSort = defaultSort && !url.searchParams.has('sort')
@@ -59,13 +48,14 @@ const useSearchParams = ({ sort: defaultSort }: UseSearchParams) => {
59
48
  url.searchParams.set('sort', defaultSort)
60
49
  }
61
50
 
62
- setParams(parseSearchState(url))
51
+ const newState = parseSearchState(url)
52
+ const hrefState = formatSearchState(newState).href
53
+ return parseSearchState(new URL(hrefState))
63
54
  }, [asPath, defaultSort])
64
-
65
- return params
66
55
  }
67
56
 
68
- function Page({ page: { sections, settings }, globalSections }: Props) {
57
+ function Page({ page: searchContentType, globalSections }: Props) {
58
+ const { settings } = searchContentType
69
59
  const searchParams = useSearchParams({
70
60
  sort: settings?.productGallery?.sortBySelection as SearchState['sort'],
71
61
  })
@@ -73,15 +63,22 @@ function Page({ page: { sections, settings }, globalSections }: Props) {
73
63
  const title = 'Search Results'
74
64
  const { description, titleTemplate } = storeConfig.seo
75
65
 
66
+ const itemsPerPage = settings?.productGallery?.itemsPerPage ?? ITEMS_PER_PAGE
67
+
76
68
  if (!searchParams) {
77
69
  return null
78
70
  }
79
71
 
72
+ const server = {
73
+ title,
74
+ searchTerm: searchParams.term ?? undefined,
75
+ } as SearchPageContextType
76
+
80
77
  return (
81
78
  <GlobalSections {...globalSections}>
82
79
  <SearchProvider
83
80
  onChange={applySearchState}
84
- itemsPerPage={settings?.productGallery?.itemsPerPage ?? ITEMS_PER_PAGE}
81
+ itemsPerPage={itemsPerPage}
85
82
  {...searchParams}
86
83
  >
87
84
  {/* SEO */}
@@ -110,16 +107,7 @@ function Page({ page: { sections, settings }, globalSections }: Props) {
110
107
  If needed, wrap your component in a <Section /> component
111
108
  (not the HTML tag) before rendering it here.
112
109
  */}
113
- <RenderSections
114
- sections={sections}
115
- components={COMPONENTS}
116
- context={
117
- {
118
- title,
119
- searchTerm: searchParams.term ?? undefined,
120
- } as SearchPageContextType
121
- }
122
- />
110
+ <SearchPage page={searchContentType} data={server}></SearchPage>
123
111
  </SearchProvider>
124
112
  </GlobalSections>
125
113
  )
@@ -4,7 +4,8 @@ import type { SWRConfiguration } from 'swr'
4
4
  import { request } from './request'
5
5
  import type { RequestOptions } from './request'
6
6
 
7
- export type QueryOptions = SWRConfiguration & RequestOptions
7
+ export type QueryOptions = SWRConfiguration &
8
+ RequestOptions & { doNotRun?: boolean }
8
9
 
9
10
  export const getKey = <Variables>(
10
11
  operationName: string,
@@ -25,16 +26,19 @@ export const useQuery = <Data, Variables = Record<string, unknown>>(
25
26
  variables: Variables,
26
27
  options?: QueryOptions
27
28
  ) =>
28
- useSWR<Data>(getKey(operationName, variables), {
29
- fetcher: () => {
30
- return new Promise((resolve) => {
31
- setTimeout(async () => {
32
- resolve(
33
- await request<Data, Variables>(operationName, variables, options)
34
- )
29
+ useSWR<Data>(
30
+ () => (options?.doNotRun ? null : getKey(operationName, variables)),
31
+ {
32
+ fetcher: () => {
33
+ return new Promise((resolve) => {
34
+ setTimeout(async () => {
35
+ resolve(
36
+ await request<Data, Variables>(operationName, variables, options)
37
+ )
38
+ })
35
39
  })
36
- })
37
- },
38
- ...DEFAULT_OPTIONS,
39
- ...options,
40
- })
40
+ },
41
+ ...DEFAULT_OPTIONS,
42
+ ...options,
43
+ }
44
+ )
@@ -0,0 +1,78 @@
1
+ import {
2
+ ClientProductGalleryQueryQuery,
3
+ ClientProductQueryQuery,
4
+ ClientProductsQueryQuery,
5
+ ServerCollectionPageQueryQuery,
6
+ ServerProductPageQueryQuery,
7
+ } from '@generated/graphql'
8
+ import type { PropsWithChildren } from 'react'
9
+ import { createContext, useContext, useMemo } from 'react'
10
+ import { SearchPageContextType } from 'src/pages/s'
11
+
12
+ export interface PDPContext {
13
+ data?: ServerProductPageQueryQuery &
14
+ ClientProductQueryQuery['product'] & { isValidating?: boolean }
15
+ }
16
+
17
+ export interface PLPContext {
18
+ data?: ServerCollectionPageQueryQuery &
19
+ ClientProductGalleryQueryQuery & { pages: ClientProductsQueryQuery[] }
20
+ }
21
+
22
+ export interface SearchPageContext {
23
+ data?: SearchPageContextType &
24
+ ClientProductGalleryQueryQuery & { pages: ClientProductsQueryQuery[] }
25
+ }
26
+
27
+ export const isPDP = (x: any): x is PDPContext =>
28
+ x?.data?.product?.sku != undefined && x?.data?.product?.sku != null
29
+
30
+ export const isPLP = (x: any): x is PLPContext =>
31
+ x?.data?.collection?.seo != undefined &&
32
+ x?.data?.collection?.seo != null &&
33
+ x?.data?.collection?.sku == undefined
34
+
35
+ export const isSearchPage = (x: any): x is SearchPageContext =>
36
+ x === undefined ||
37
+ x?.data?.title != undefined ||
38
+ x?.data?.searchTerm != undefined
39
+
40
+ export interface PageProviderContextValue {
41
+ context?: PDPContext | PLPContext | SearchPageContext
42
+ }
43
+
44
+ const PageContext = createContext<PageProviderContextValue | null>(null)
45
+
46
+ function PageProvider({
47
+ context,
48
+ children,
49
+ }: PropsWithChildren<PageProviderContextValue>) {
50
+ const value = useMemo(
51
+ () => ({
52
+ context,
53
+ }),
54
+ [context]
55
+ )
56
+
57
+ return <PageContext.Provider value={value}>{children}</PageContext.Provider>
58
+ }
59
+
60
+ export function usePage<
61
+ T extends PLPContext | SearchPageContext | PDPContext
62
+ >(): T {
63
+ const { context } = useContext(PageContext)
64
+
65
+ if (context == null) {
66
+ throw new Error('Missing Overrides context on React tree')
67
+ }
68
+
69
+ return context as T
70
+ }
71
+
72
+ export const usePDP = () => usePage<PDPContext>()
73
+
74
+ export const usePLP = () => usePage<PLPContext>()
75
+
76
+ export const useSearchPage = () => usePage<SearchPageContext>()
77
+
78
+ export default PageProvider
@@ -0,0 +1,33 @@
1
+ import { useMemo } from 'react'
2
+ import { ClientProductsQueryQueryVariables } from '@generated/graphql'
3
+ import { useSession } from '../session'
4
+ import { ITEMS_PER_SECTION } from 'src/constants'
5
+
6
+ const toArray = <T>(x: T[] | T | undefined) =>
7
+ Array.isArray(x) ? x : x ? [x] : []
8
+
9
+ export const useLocalizedVariables = ({
10
+ first,
11
+ after,
12
+ sort,
13
+ term,
14
+ selectedFacets,
15
+ }: Partial<ClientProductsQueryQueryVariables>) => {
16
+ const { channel, locale } = useSession()
17
+
18
+ return useMemo(() => {
19
+ const facets = toArray(selectedFacets)
20
+
21
+ return {
22
+ first: first ?? ITEMS_PER_SECTION,
23
+ after: after ?? '0',
24
+ sort: sort ?? ('score_desc' as const),
25
+ term: term ?? '',
26
+ selectedFacets: [
27
+ ...facets,
28
+ { key: 'channel', value: channel ?? '' },
29
+ { key: 'locale', value: locale },
30
+ ],
31
+ }
32
+ }, [selectedFacets, first, after, sort, term, channel, locale])
33
+ }
@@ -0,0 +1,139 @@
1
+ import { gql } from '@faststore/graphql-utils'
2
+ import { useSearch } from '@faststore/sdk'
3
+ import {
4
+ ClientProductsQueryQuery,
5
+ ClientProductsQueryQueryVariables,
6
+ } from '@generated/graphql'
7
+ import {
8
+ useEffect,
9
+ useCallback,
10
+ createContext,
11
+ useContext,
12
+ useRef,
13
+ useMemo,
14
+ useState,
15
+ } from 'react'
16
+ import { useQuery } from 'src/sdk/graphql/useQuery'
17
+ import { useLocalizedVariables } from './useLocalizedVariables'
18
+
19
+ export const UseGalleryPageContext = createContext<
20
+ ReturnType<typeof useCreateUseGalleryPage>['useGalleryPage']
21
+ >((_: number) => {
22
+ return { data: null }
23
+ })
24
+
25
+ export const useGalleryPage = (page: number) => {
26
+ const useGalleryPageCallback = useContext(UseGalleryPageContext)
27
+ if (!useGalleryPageCallback) {
28
+ throw new Error('Missing UseGalleryPageContext on React tree')
29
+ }
30
+ return useGalleryPageCallback(page)
31
+ }
32
+
33
+ export const query = gql`
34
+ query ClientProductsQuery(
35
+ $first: Int!
36
+ $after: String
37
+ $sort: StoreSort!
38
+ $term: String!
39
+ $selectedFacets: [IStoreSelectedFacet!]!
40
+ ) {
41
+ ...ClientProducts
42
+ search(
43
+ first: $first
44
+ after: $after
45
+ sort: $sort
46
+ term: $term
47
+ selectedFacets: $selectedFacets
48
+ ) {
49
+ products {
50
+ pageInfo {
51
+ totalCount
52
+ }
53
+ edges {
54
+ node {
55
+ ...ProductSummary_product
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ `
62
+
63
+ const getKey = (object: any) => JSON.stringify(object)
64
+
65
+ /**
66
+ * Use this hook for managed pages state and creating useGalleryPage hook that will be used for fetching a list of products per pages in PLP or Search
67
+ */
68
+ export const useCreateUseGalleryPage = () => {
69
+ const [pages, setPages] = useState<ClientProductsQueryQuery[]>([])
70
+ // We create pagesRef as a mirror of the pages state so we don't have to add pages as a dependency of the useGalleryPage hook
71
+ const pagesRef = useRef<ClientProductsQueryQuery[]>([])
72
+ const pagesCache = useRef<string[]>([])
73
+
74
+ const useGalleryPage = useCallback(function useGalleryPage(page: number) {
75
+ const {
76
+ state: { sort, term, selectedFacets },
77
+ itemsPerPage,
78
+ } = useSearch()
79
+
80
+ const localizedVariables = useLocalizedVariables({
81
+ first: itemsPerPage,
82
+ after: (itemsPerPage * page).toString(),
83
+ sort,
84
+ term: term ?? '',
85
+ selectedFacets,
86
+ })
87
+
88
+ const hasSameVariables =
89
+ pagesCache.current[page] === getKey(localizedVariables)
90
+
91
+ const { data } = useQuery<
92
+ ClientProductsQueryQuery,
93
+ ClientProductsQueryQueryVariables
94
+ >(query, localizedVariables, {
95
+ fallbackData: null,
96
+ suspense: true,
97
+ doNotRun: hasSameVariables,
98
+ })
99
+
100
+ const shouldUpdatePages = !hasSameVariables && data !== null
101
+
102
+ if (shouldUpdatePages) {
103
+ pagesCache.current[page] = getKey(localizedVariables)
104
+
105
+ // Update refs
106
+ const newPages = [...pagesRef.current]
107
+ newPages[page] = data
108
+ pagesRef.current = newPages
109
+ }
110
+
111
+ // Prevents error: Cannot update a component (`ProductListing`) while rendering a different component (`ProductGalleryPage`).
112
+ useEffect(() => {
113
+ if (shouldUpdatePages) {
114
+ // Update state
115
+ setPages((oldPages) => {
116
+ const newPages = [...oldPages]
117
+ newPages[page] = data
118
+ return newPages
119
+ })
120
+ }
121
+ }, [data, page, shouldUpdatePages])
122
+
123
+ return useMemo(() => {
124
+ if (hasSameVariables) {
125
+ return { data: pagesRef.current[page] }
126
+ }
127
+
128
+ return { data }
129
+ }, [hasSameVariables, data, page])
130
+ }, [])
131
+
132
+ return useMemo(
133
+ () => ({
134
+ pages,
135
+ useGalleryPage,
136
+ }),
137
+ [pages, useGalleryPage]
138
+ )
139
+ }
@@ -1,26 +1,25 @@
1
- import { useSearch } from '@faststore/sdk'
2
1
  import { gql } from '@faststore/graphql-utils'
3
2
 
4
- import { useQuery } from 'src/sdk/graphql/useQuery'
5
3
  import type {
6
- ProductGalleryQueryQuery as Query,
7
- ProductGalleryQueryQueryVariables as Variables,
4
+ ClientProductGalleryQueryQuery as Query,
5
+ ClientProductGalleryQueryQueryVariables as Variables,
8
6
  } from '@generated/graphql'
9
-
10
- import { useLocalizedVariables } from '../../../sdk/product/useProductsQuery'
7
+ import { useQuery } from 'src/sdk/graphql/useQuery'
8
+ import { useLocalizedVariables } from './useLocalizedVariables'
11
9
 
12
10
  /**
13
11
  * This query is run on the browser and contains
14
12
  * the current search state of the user
15
13
  */
16
14
  export const query = gql`
17
- query ProductGalleryQuery(
15
+ query ClientProductGalleryQuery(
18
16
  $first: Int!
19
17
  $after: String!
20
18
  $sort: StoreSort!
21
19
  $term: String!
22
20
  $selectedFacets: [IStoreSelectedFacet!]!
23
21
  ) {
22
+ ...ClientProductGallery
24
23
  search(
25
24
  first: $first
26
25
  after: $after
@@ -40,12 +39,12 @@ export const query = gql`
40
39
  }
41
40
  `
42
41
 
43
- export const useGalleryQuery = () => {
44
- const {
45
- state: { term, sort, selectedFacets },
46
- itemsPerPage,
47
- } = useSearch()
48
-
42
+ export const useProductGalleryQuery = ({
43
+ term,
44
+ sort,
45
+ selectedFacets,
46
+ itemsPerPage,
47
+ }) => {
49
48
  const localizedVariables = useLocalizedVariables({
50
49
  first: itemsPerPage,
51
50
  after: '0',
@@ -2,29 +2,32 @@ import { gql } from '@faststore/graphql-utils'
2
2
  import { useMemo } from 'react'
3
3
 
4
4
  import type {
5
- BrowserProductQueryQuery,
6
- BrowserProductQueryQueryVariables,
5
+ ClientProductQueryQuery,
6
+ ClientProductQueryQueryVariables,
7
7
  } from '@generated/graphql'
8
8
 
9
9
  import { useQuery } from '../graphql/useQuery'
10
10
  import { useSession } from '../session'
11
11
 
12
12
  const query = gql`
13
- query BrowserProductQuery($locator: [IStoreSelectedFacet!]!) {
13
+ query ClientProductQuery($locator: [IStoreSelectedFacet!]!) {
14
+ ...ClientProduct
14
15
  product(locator: $locator) {
15
16
  ...ProductDetailsFragment_product
16
17
  }
17
18
  }
18
19
  `
19
20
 
20
- export const useProduct = <T extends BrowserProductQueryQuery>(
21
+ export const useProductQuery = <T extends ClientProductQueryQuery>(
21
22
  productID: string,
22
23
  fallbackData?: T
23
24
  ) => {
24
25
  const { channel, locale } = useSession()
25
26
  const variables = useMemo(() => {
26
27
  if (!channel) {
27
- throw new Error(`useProduct: 'channel' from session is an empty string.`)
28
+ throw new Error(
29
+ `useProductQuery: 'channel' from session is an empty string.`
30
+ )
28
31
  }
29
32
 
30
33
  return {
@@ -37,8 +40,8 @@ export const useProduct = <T extends BrowserProductQueryQuery>(
37
40
  }, [channel, locale, productID])
38
41
 
39
42
  return useQuery<
40
- BrowserProductQueryQuery & T,
41
- BrowserProductQueryQueryVariables
43
+ ClientProductQueryQuery & T,
44
+ ClientProductQueryQueryVariables
42
45
  >(query, variables, {
43
46
  fallbackData,
44
47
  revalidateOnMount: true,