@faststore/core 2.1.83 → 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 (206) 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/codegen.yml +2 -1
  8. package/generate.sh +71 -0
  9. package/index.ts +15 -0
  10. package/jest.config.js +6 -0
  11. package/package.json +33 -18
  12. package/src/components/ThirdPartyScripts/ThirdPartyScripts.tsx +1 -2
  13. package/src/components/cms/RenderSections.tsx +4 -5
  14. package/src/components/product/ProductGrid/ProductGrid.tsx +4 -3
  15. package/src/components/sections/Breadcrumb/Breadcrumb.tsx +17 -22
  16. package/src/components/sections/CrossSellingShelf/CrossSellingShelf.tsx +8 -6
  17. package/src/components/sections/ProductDetails/ProductDetails.tsx +23 -33
  18. package/src/components/sections/ProductGallery/ProductGallery.tsx +15 -26
  19. package/src/components/sections/ProductShelf/ProductShelf.tsx +1 -1
  20. package/src/components/sections/ProductTiles/ProductTiles.tsx +4 -3
  21. package/src/components/templates/ProductListingPage/ProductListing.tsx +95 -0
  22. package/src/components/templates/ProductListingPage/ProductListingPage.tsx +34 -68
  23. package/src/components/templates/SearchPage/SearchPage.tsx +81 -0
  24. package/src/components/templates/SearchPage/index.ts +2 -0
  25. package/src/components/ui/ProductGallery/ProductGallery.tsx +24 -16
  26. package/src/components/ui/ProductGallery/ProductGalleryPage.tsx +8 -7
  27. package/src/components/ui/ProductGallery/useDelayedFacets.ts +3 -3
  28. package/src/components/ui/ProductShelf/ProductShelf.tsx +3 -2
  29. package/src/customizations/GlobalOverrides.tsx +0 -2
  30. package/src/customizations/fragments/ClientProduct.ts +9 -0
  31. package/src/customizations/fragments/ClientProductGallery.ts +19 -0
  32. package/src/customizations/fragments/ClientProducts.ts +19 -0
  33. package/src/customizations/fragments/ServerCollectionPage.ts +9 -0
  34. package/src/customizations/fragments/ServerProductPage.ts +9 -0
  35. package/src/customizations/graphql/thirdParty/resolvers/index.ts +3 -0
  36. package/src/customizations/graphql/vtex/resolvers/index.ts +3 -0
  37. package/src/pages/[...slug].tsx +5 -3
  38. package/src/pages/[slug]/p.tsx +38 -18
  39. package/src/pages/s.tsx +22 -34
  40. package/src/sdk/graphql/useQuery.ts +17 -13
  41. package/src/sdk/overrides/PageProvider.tsx +78 -0
  42. package/src/sdk/product/useLocalizedVariables.ts +33 -0
  43. package/src/sdk/product/usePageProductsQuery.ts +139 -0
  44. package/src/{components/sections/ProductGallery/useGalleryQuery.ts → sdk/product/useProductGalleryQuery.ts} +12 -13
  45. package/src/sdk/product/{useProduct.ts → useProductQuery.ts} +10 -7
  46. package/src/sdk/product/useProductsPrefetch.ts +72 -0
  47. package/src/sdk/product/useProductsQuery.ts +17 -63
  48. package/src/server/generator/generateGraphQLSchemaFile.ts +3 -0
  49. package/src/server/generator/schema.ts +82 -0
  50. package/src/server/index.ts +34 -21
  51. package/src/server/options.ts +16 -0
  52. package/test/server/index.test.ts +146 -0
  53. package/.next/BUILD_ID +0 -1
  54. package/.next/build-manifest.json +0 -129
  55. package/.next/cache/.tsbuildinfo +0 -1
  56. package/.next/cache/config.json +0 -7
  57. package/.next/cache/eslint/.cache_1gneedd +0 -1
  58. package/.next/cache/next-server.js.nft.json +0 -1
  59. package/.next/cache/webpack/client-production/0.pack +0 -0
  60. package/.next/cache/webpack/client-production/index.pack +0 -0
  61. package/.next/cache/webpack/server-production/0.pack +0 -0
  62. package/.next/cache/webpack/server-production/index.pack +0 -0
  63. package/.next/export-marker.json +0 -1
  64. package/.next/images-manifest.json +0 -1
  65. package/.next/next-server.js.nft.json +0 -1
  66. package/.next/package.json +0 -1
  67. package/.next/prerender-manifest.json +0 -1
  68. package/.next/react-loadable-manifest.json +0 -44
  69. package/.next/required-server-files.json +0 -1
  70. package/.next/routes-manifest.json +0 -1
  71. package/.next/server/chunks/143.js +0 -106
  72. package/.next/server/chunks/177.js +0 -120
  73. package/.next/server/chunks/183.js +0 -94
  74. package/.next/server/chunks/184.js +0 -61
  75. package/.next/server/chunks/186.js +0 -113
  76. package/.next/server/chunks/289.js +0 -239
  77. package/.next/server/chunks/312.js +0 -697
  78. package/.next/server/chunks/350.js +0 -143
  79. package/.next/server/chunks/483.js +0 -650
  80. package/.next/server/chunks/487.js +0 -9142
  81. package/.next/server/chunks/53.js +0 -61
  82. package/.next/server/chunks/530.js +0 -626
  83. package/.next/server/chunks/576.js +0 -94
  84. package/.next/server/chunks/650.js +0 -9142
  85. package/.next/server/chunks/676.js +0 -32
  86. package/.next/server/chunks/693.js +0 -58
  87. package/.next/server/chunks/71.js +0 -1254
  88. package/.next/server/chunks/74.js +0 -4054
  89. package/.next/server/chunks/753.js +0 -509
  90. package/.next/server/chunks/779.js +0 -58
  91. package/.next/server/chunks/825.js +0 -4039
  92. package/.next/server/chunks/854.js +0 -72
  93. package/.next/server/chunks/859.js +0 -959
  94. package/.next/server/chunks/907.js +0 -1911
  95. package/.next/server/chunks/933.js +0 -517
  96. package/.next/server/chunks/98.js +0 -124
  97. package/.next/server/chunks/988.js +0 -211
  98. package/.next/server/chunks/font-manifest.json +0 -1
  99. package/.next/server/font-manifest.json +0 -1
  100. package/.next/server/middleware-build-manifest.js +0 -1
  101. package/.next/server/middleware-manifest.json +0 -6
  102. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  103. package/.next/server/pages/404.js +0 -386
  104. package/.next/server/pages/404.js.nft.json +0 -1
  105. package/.next/server/pages/500.js +0 -388
  106. package/.next/server/pages/500.js.nft.json +0 -1
  107. package/.next/server/pages/[...slug].js +0 -1005
  108. package/.next/server/pages/[...slug].js.nft.json +0 -1
  109. package/.next/server/pages/[slug]/p.js +0 -2269
  110. package/.next/server/pages/[slug]/p.js.nft.json +0 -1
  111. package/.next/server/pages/_app.js +0 -280
  112. package/.next/server/pages/_app.js.nft.json +0 -1
  113. package/.next/server/pages/_document.js +0 -374
  114. package/.next/server/pages/_document.js.nft.json +0 -1
  115. package/.next/server/pages/_error.js +0 -164
  116. package/.next/server/pages/_error.js.nft.json +0 -1
  117. package/.next/server/pages/account.js +0 -363
  118. package/.next/server/pages/account.js.nft.json +0 -1
  119. package/.next/server/pages/api/graphql.js +0 -365
  120. package/.next/server/pages/api/graphql.js.nft.json +0 -1
  121. package/.next/server/pages/api/health/live.js +0 -31
  122. package/.next/server/pages/api/health/live.js.nft.json +0 -1
  123. package/.next/server/pages/api/health/ready.js +0 -31
  124. package/.next/server/pages/api/health/ready.js.nft.json +0 -1
  125. package/.next/server/pages/api/preview.js +0 -148
  126. package/.next/server/pages/api/preview.js.nft.json +0 -1
  127. package/.next/server/pages/checkout.js +0 -363
  128. package/.next/server/pages/checkout.js.nft.json +0 -1
  129. package/.next/server/pages/en-US/404.html +0 -81
  130. package/.next/server/pages/en-US/404.json +0 -1
  131. package/.next/server/pages/en-US/500.html +0 -81
  132. package/.next/server/pages/en-US/500.json +0 -1
  133. package/.next/server/pages/en-US/account.html +0 -81
  134. package/.next/server/pages/en-US/account.json +0 -1
  135. package/.next/server/pages/en-US/checkout.html +0 -81
  136. package/.next/server/pages/en-US/checkout.json +0 -1
  137. package/.next/server/pages/en-US/login.html +0 -81
  138. package/.next/server/pages/en-US/login.json +0 -1
  139. package/.next/server/pages/en-US/s.html +0 -81
  140. package/.next/server/pages/en-US/s.json +0 -1
  141. package/.next/server/pages/en-US.html +0 -81
  142. package/.next/server/pages/en-US.json +0 -1
  143. package/.next/server/pages/index.js +0 -439
  144. package/.next/server/pages/index.js.nft.json +0 -1
  145. package/.next/server/pages/login.js +0 -368
  146. package/.next/server/pages/login.js.nft.json +0 -1
  147. package/.next/server/pages/s.js +0 -466
  148. package/.next/server/pages/s.js.nft.json +0 -1
  149. package/.next/server/pages-manifest.json +0 -18
  150. package/.next/server/webpack-api-runtime.js +0 -229
  151. package/.next/server/webpack-runtime.js +0 -229
  152. package/.next/static/chunks/143.dd8a556e6957baa1.js +0 -1
  153. package/.next/static/chunks/148.3bb7e05cc5d1c1c4.js +0 -1
  154. package/.next/static/chunks/238-e5e4b2094f0e1df8.js +0 -1
  155. package/.next/static/chunks/243-8f5650ed908aa75c.js +0 -1
  156. package/.next/static/chunks/530.848b014622932b93.js +0 -1
  157. package/.next/static/chunks/548-ab84e9e8b49413ab.js +0 -1
  158. package/.next/static/chunks/603-d1c069aa8a349c86.js +0 -1
  159. package/.next/static/chunks/651.7142f31ce1e052b3.js +0 -1
  160. package/.next/static/chunks/709.7bc5a25ce30abda6.js +0 -1
  161. package/.next/static/chunks/738-67a288ca3569cdbb.js +0 -1
  162. package/.next/static/chunks/741.52f7fb873418346f.js +0 -1
  163. package/.next/static/chunks/98.97381d2021f86cd9.js +0 -1
  164. package/.next/static/chunks/988.d10040040cdfebbb.js +0 -1
  165. package/.next/static/chunks/framework-dfd14d7ce6600b03.js +0 -1
  166. package/.next/static/chunks/main-fd466221927468fd.js +0 -1
  167. package/.next/static/chunks/pages/404-af78f7cd1d3c1f60.js +0 -1
  168. package/.next/static/chunks/pages/500-f6346ca5f9dc4fef.js +0 -1
  169. package/.next/static/chunks/pages/[...slug]-ca533c74c22cb787.js +0 -1
  170. package/.next/static/chunks/pages/[slug]/p-7bb760bb3fcfc0bb.js +0 -1
  171. package/.next/static/chunks/pages/_app-895781b1c7b5bf56.js +0 -1
  172. package/.next/static/chunks/pages/_error-a7a0c1d9bfbb4f38.js +0 -1
  173. package/.next/static/chunks/pages/account-05bd79fb78365e88.js +0 -1
  174. package/.next/static/chunks/pages/checkout-c973786e68f25a39.js +0 -1
  175. package/.next/static/chunks/pages/index-d521ce4f4e2b89a6.js +0 -1
  176. package/.next/static/chunks/pages/login-8deb9243376b6aa1.js +0 -1
  177. package/.next/static/chunks/pages/s-0e516ab36bb49c99.js +0 -1
  178. package/.next/static/chunks/polyfills-c67a75d1b6f99dc8.js +0 -1
  179. package/.next/static/chunks/webpack-062512aedeb4b39e.js +0 -1
  180. package/.next/static/css/0d056c673d2869bb.css +0 -1
  181. package/.next/static/css/4b7138899cd07c63.css +0 -1
  182. package/.next/static/css/527e334fa69cf40a.css +0 -1
  183. package/.next/static/css/6e1a7434f061d0ef.css +0 -1
  184. package/.next/static/css/9e76fef1c9ca89af.css +0 -1
  185. package/.next/static/css/a2eefb25a4608343.css +0 -1
  186. package/.next/static/css/cb7d1fcea42fab9c.css +0 -1
  187. package/.next/static/css/df588bb98c0b0ca6.css +0 -1
  188. package/.next/static/css/e3b039e8f5daf95f.css +0 -1
  189. package/.next/static/css/f0e2d1b8832e935d.css +0 -1
  190. package/.next/static/jxv75E0IlXYBYk71TulQP/_buildManifest.js +0 -1
  191. package/.next/static/jxv75E0IlXYBYk71TulQP/_ssgManifest.js +0 -1
  192. package/.next/trace +0 -80
  193. package/public/~partytown/debug/partytown-atomics.js +0 -556
  194. package/public/~partytown/debug/partytown-media.js +0 -374
  195. package/public/~partytown/debug/partytown-sandbox-sw.js +0 -543
  196. package/public/~partytown/debug/partytown-sw.js +0 -59
  197. package/public/~partytown/debug/partytown-ww-atomics.js +0 -1789
  198. package/public/~partytown/debug/partytown-ww-sw.js +0 -1781
  199. package/public/~partytown/debug/partytown.js +0 -72
  200. package/public/~partytown/partytown-atomics.js +0 -2
  201. package/public/~partytown/partytown-media.js +0 -2
  202. package/public/~partytown/partytown-sw.js +0 -2
  203. package/public/~partytown/partytown.js +0 -2
  204. package/src/components/ui/ProductGallery/usePageProducts.ts +0 -48
  205. package/src/customizations/components/overrides/ThirdPartyScripts.tsx +0 -9
  206. package/src/customizations/scripts/ThirdPartyScripts.tsx +0 -8
@@ -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,
@@ -0,0 +1,72 @@
1
+ import { gql } from '@faststore/graphql-utils'
2
+ import { useSearch } from '@faststore/sdk'
3
+ import { ClientProductsQueryQueryVariables } from '@generated/graphql'
4
+ import { useEffect, useCallback } from 'react'
5
+ import type { QueryOptions } from '../graphql/useQuery'
6
+ import { useSWRConfig } from 'swr'
7
+ import { prefetchQuery } from '../graphql/prefetchQuery'
8
+ import { useLocalizedVariables } from './useLocalizedVariables'
9
+
10
+ export const query = gql`
11
+ query ClientProductsQuery(
12
+ $first: Int!
13
+ $after: String
14
+ $sort: StoreSort!
15
+ $term: String!
16
+ $selectedFacets: [IStoreSelectedFacet!]!
17
+ ) {
18
+ ...ClientProducts
19
+ search(
20
+ first: $first
21
+ after: $after
22
+ sort: $sort
23
+ term: $term
24
+ selectedFacets: $selectedFacets
25
+ ) {
26
+ products {
27
+ pageInfo {
28
+ totalCount
29
+ }
30
+ edges {
31
+ node {
32
+ ...ProductSummary_product
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ `
39
+
40
+ export const useProductsQueryPrefetch = (
41
+ variables: ClientProductsQueryQueryVariables,
42
+ options?: QueryOptions
43
+ ) => {
44
+ const localizedVariables = useLocalizedVariables(variables)
45
+ const { cache } = useSWRConfig()
46
+
47
+ return useCallback(
48
+ () => prefetchQuery(query, localizedVariables, { cache, ...options }),
49
+ [localizedVariables, cache, options]
50
+ )
51
+ }
52
+
53
+ export const useProductsPrefetch = (page: number | null) => {
54
+ const {
55
+ itemsPerPage,
56
+ state: { sort, term, selectedFacets },
57
+ } = useSearch()
58
+
59
+ const prefetch = useProductsQueryPrefetch({
60
+ first: itemsPerPage,
61
+ after: (itemsPerPage * (page ?? 0)).toString(),
62
+ sort,
63
+ term: term ?? '',
64
+ selectedFacets,
65
+ })
66
+
67
+ useEffect(() => {
68
+ if (page !== null) {
69
+ prefetch()
70
+ }
71
+ }, [page, prefetch])
72
+ }