@faststore/core 3.67.0 → 3.68.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 (246) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +74 -74
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/config.json +3 -3
  5. package/.next/cache/webpack/client-production/0.pack +0 -0
  6. package/.next/cache/webpack/client-production/index.pack +0 -0
  7. package/.next/cache/webpack/server-production/0.pack +0 -0
  8. package/.next/cache/webpack/server-production/index.pack +0 -0
  9. package/.next/prerender-manifest.js +1 -1
  10. package/.next/prerender-manifest.json +1 -1
  11. package/.next/react-loadable-manifest.json +86 -71
  12. package/.next/routes-manifest.json +1 -1
  13. package/.next/server/chunks/1333.js +1 -0
  14. package/.next/server/chunks/2778.js +2 -2
  15. package/.next/server/chunks/2792.js +1 -1
  16. package/.next/server/chunks/3006.js +1 -1
  17. package/.next/server/chunks/3836.js +1 -1
  18. package/.next/server/chunks/3918.js +1 -1
  19. package/.next/server/chunks/3963.js +1 -1
  20. package/.next/server/chunks/6789.js +1 -1
  21. package/.next/server/chunks/7178.js +1 -1
  22. package/.next/server/chunks/7228.js +1 -1
  23. package/.next/server/chunks/7794.js +1 -1
  24. package/.next/server/chunks/83.js +1 -1
  25. package/.next/server/chunks/831.js +1 -1
  26. package/.next/server/chunks/839.js +1 -0
  27. package/.next/server/chunks/8569.js +1 -1
  28. package/.next/server/chunks/8687.js +1 -1
  29. package/.next/server/chunks/948.js +2 -2
  30. package/.next/server/chunks/9563.js +2 -2
  31. package/.next/server/chunks/9630.js +4 -4
  32. package/.next/server/chunks/UIBannerText.js +1 -1
  33. package/.next/server/functions-config-manifest.json +1 -1
  34. package/.next/server/middleware-build-manifest.js +1 -1
  35. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  36. package/.next/server/pages/404.js +1 -1
  37. package/.next/server/pages/404.js.nft.json +1 -1
  38. package/.next/server/pages/500.js +1 -1
  39. package/.next/server/pages/500.js.nft.json +1 -1
  40. package/.next/server/pages/[...slug].js +1 -1
  41. package/.next/server/pages/[...slug].js.nft.json +1 -1
  42. package/.next/server/pages/[slug]/p.js +1 -1
  43. package/.next/server/pages/[slug]/p.js.nft.json +1 -1
  44. package/.next/server/pages/_app.js.nft.json +1 -1
  45. package/.next/server/pages/_document.js.nft.json +1 -1
  46. package/.next/server/pages/_error.js.nft.json +1 -1
  47. package/.next/server/pages/account/403.js +1 -1
  48. package/.next/server/pages/account/403.js.nft.json +1 -1
  49. package/.next/server/pages/account/404.js +1 -1
  50. package/.next/server/pages/account/404.js.nft.json +1 -1
  51. package/.next/server/pages/account/[...unknown].js.nft.json +1 -1
  52. package/.next/server/pages/account/orders/[id].js +1 -1
  53. package/.next/server/pages/account/orders/[id].js.nft.json +1 -1
  54. package/.next/server/pages/account/orders.js +1 -1
  55. package/.next/server/pages/account/orders.js.nft.json +1 -1
  56. package/.next/server/pages/account/profile.js +1 -1
  57. package/.next/server/pages/account/profile.js.nft.json +1 -1
  58. package/.next/server/pages/account/security.js +1 -1
  59. package/.next/server/pages/account/security.js.nft.json +1 -1
  60. package/.next/server/pages/account/user-details.js +1 -1
  61. package/.next/server/pages/account/user-details.js.nft.json +1 -1
  62. package/.next/server/pages/account.js.nft.json +1 -1
  63. package/.next/server/pages/api/graphql.js +2 -2
  64. package/.next/server/pages/api/graphql.js.nft.json +1 -1
  65. package/.next/server/pages/api/health/live.js.nft.json +1 -1
  66. package/.next/server/pages/api/health/ready.js.nft.json +1 -1
  67. package/.next/server/pages/api/preview.js.nft.json +1 -1
  68. package/.next/server/pages/checkout.js +1 -1
  69. package/.next/server/pages/checkout.js.nft.json +1 -1
  70. package/.next/server/pages/en-US/404.html +1 -1
  71. package/.next/server/pages/en-US/500.html +1 -1
  72. package/.next/server/pages/en-US/checkout.html +1 -1
  73. package/.next/server/pages/en-US/login.html +1 -1
  74. package/.next/server/pages/en-US/s.html +1 -1
  75. package/.next/server/pages/en-US.html +1 -1
  76. package/.next/server/pages/index.js +1 -1
  77. package/.next/server/pages/index.js.nft.json +1 -1
  78. package/.next/server/pages/login.js +1 -1
  79. package/.next/server/pages/login.js.nft.json +1 -1
  80. package/.next/server/pages/s.js +1 -1
  81. package/.next/server/pages/s.js.nft.json +1 -1
  82. package/.next/server/pages-manifest.json +1 -1
  83. package/.next/static/chunks/2284.6dd050e60172189a.js +1 -0
  84. package/.next/static/chunks/3155.243c7558a71f0695.js +1 -0
  85. package/.next/static/chunks/3166-6af5e854c2f2913a.js +1 -0
  86. package/.next/static/chunks/3399.93804fb74f79436c.js +1 -0
  87. package/.next/static/chunks/353.7f2181843462717d.js +1 -0
  88. package/.next/static/chunks/4803.412bf2a7e15626a6.js +1 -0
  89. package/.next/static/chunks/5781.28d03feacead66ad.js +1 -0
  90. package/.next/static/chunks/6355.57d1a07f50ee6cc9.js +1 -0
  91. package/.next/static/chunks/{6393.361c44eb0818eb7e.js → 6393.53e9ea4f29d1bf23.js} +1 -1
  92. package/.next/static/chunks/6700.f046aa86e2c83b53.js +1 -0
  93. package/.next/static/chunks/6857.b2c06171638955ea.js +1 -0
  94. package/.next/static/chunks/7191-9bdd5f0c18fbd942.js +1 -0
  95. package/.next/static/chunks/{6410.bd3fa399df59cc80.js → 7351.e90a4cc21797c136.js} +1 -1
  96. package/.next/static/chunks/7481.3c4ad3642e346232.js +1 -0
  97. package/.next/static/chunks/7498-0dc4f9a9ed199d3a.js +1 -0
  98. package/.next/static/chunks/83.ee1fdbe283ac65b6.js +1 -0
  99. package/.next/static/chunks/9173-88b7ddf38554a5cf.js +1 -0
  100. package/.next/static/chunks/BannerNewsletter.a9ea51c53885c80f.js +1 -0
  101. package/.next/static/chunks/{BannerText.695d4d4b6a3f7309.js → BannerText.21f106b180339df1.js} +1 -1
  102. package/.next/static/chunks/CartSidebar.55cc31a37ffa6ee6.js +1 -0
  103. package/.next/static/chunks/{Footer.5ed205d931401110.js → Footer.09dddd47ce6c816f.js} +1 -1
  104. package/.next/static/chunks/Newsletter.2c79d1813e9f9c95.js +1 -0
  105. package/.next/static/chunks/ProductShelf.299d0989eea49a79.js +1 -0
  106. package/.next/static/chunks/ProductTiles.ab99b919f3c0215f.js +1 -0
  107. package/.next/static/chunks/RegionModal.503f063f2e19b936.js +1 -0
  108. package/.next/static/chunks/RegionSlider.00de4571775d04cc.js +1 -0
  109. package/.next/static/chunks/Toast.75a18f47eb23b703.js +1 -0
  110. package/.next/static/chunks/UIBannerText.f4167ceafb96cf67.js +1 -0
  111. package/.next/static/chunks/UISKUMatrixSidebar.8b6fac58c48f999c.js +1 -0
  112. package/.next/static/chunks/UIToast.a49584c87d3adc17.js +1 -0
  113. package/.next/static/chunks/pages/{404-3582ed9196afdf1e.js → 404-dca50618ea3e6fb6.js} +1 -1
  114. package/.next/static/chunks/pages/{500-1b4eca062588da7f.js → 500-ae6697c7631fb07a.js} +1 -1
  115. package/.next/static/chunks/pages/[...slug]-debd8b208a0e3d02.js +1 -0
  116. package/.next/static/chunks/pages/[slug]/p-d782ecb21200f200.js +1 -0
  117. package/.next/static/chunks/pages/_app-728289774860e9d9.js +1 -0
  118. package/.next/static/chunks/pages/account/{403-c791997011f970b6.js → 403-a3d8b31b4e9ee8a6.js} +1 -1
  119. package/.next/static/chunks/pages/account/{404-74e64bb12e8f5a68.js → 404-22b789f04fcdce39.js} +1 -1
  120. package/.next/static/chunks/pages/account/orders/[id]-a2f44ba3963b81cd.js +1 -0
  121. package/.next/static/chunks/pages/account/orders-ec040e06c4b516d0.js +1 -0
  122. package/.next/static/chunks/pages/account/profile-29f93f4c5a55bd87.js +1 -0
  123. package/.next/static/chunks/pages/account/security-94874fc477520f74.js +1 -0
  124. package/.next/static/chunks/pages/account/{user-details-6f9fe72e02f5c5df.js → user-details-143cb45d5080d1d9.js} +1 -1
  125. package/.next/static/chunks/pages/checkout-b0637ee59b1cdca8.js +1 -0
  126. package/.next/static/chunks/pages/index-b45c9535696b5ab1.js +1 -0
  127. package/.next/static/chunks/pages/login-bae3a4cdaaed110c.js +1 -0
  128. package/.next/static/chunks/pages/s-011eedb19dcdccc6.js +1 -0
  129. package/.next/static/chunks/webpack-8b6c086380cf1398.js +1 -0
  130. package/.next/static/css/{e4b714970415f2eb.css → 2a4b7072e47636f1.css} +1 -1
  131. package/.next/static/css/{8a3f440e0ff9cd8e.css → 3d41485722b4e3f5.css} +1 -1
  132. package/.next/static/css/{2841bab51b99dd53.css → 92960607d6088082.css} +1 -1
  133. package/.next/static/css/d26cb0a54378b3d9.css +1 -0
  134. package/.next/static/css/f93cf36b16950027.css +1 -0
  135. package/.next/static/pRYrZEXsDEpvxbtvEobPH/_buildManifest.js +1 -0
  136. package/.next/trace +135 -132
  137. package/.turbo/turbo-build.log +36 -33
  138. package/.turbo/turbo-test.log +5 -5
  139. package/@generated/gql.ts +8 -0
  140. package/@generated/graphql.ts +83 -0
  141. package/@generated/persisted-documents.json +1 -0
  142. package/@generated/schema.graphql +51 -0
  143. package/CHANGELOG.md +6 -0
  144. package/cms/faststore/content-types.json +238 -1
  145. package/cms/faststore/sections.json +34 -33
  146. package/cypress/integration/plp.test.js +2 -2
  147. package/index.ts +9 -0
  148. package/package.json +5 -5
  149. package/src/components/cms/GlobalSections.tsx +1 -0
  150. package/src/components/cms/RenderSections.tsx +8 -4
  151. package/src/components/cms/global/Components.ts +8 -0
  152. package/src/components/navigation/Navbar/Navbar.tsx +5 -6
  153. package/src/components/navigation/NavbarLinks/NavbarLinks.tsx +23 -4
  154. package/src/components/region/RegionBar/RegionBar.tsx +48 -12
  155. package/src/components/region/RegionFilterButton/RegionFilterButton.tsx +57 -0
  156. package/src/components/region/RegionFilterButton/index.ts +1 -0
  157. package/src/components/region/RegionModal/RegionModal.tsx +26 -16
  158. package/src/components/region/RegionModal/useRegion.ts +12 -11
  159. package/src/components/region/RegionPopover/RegionPopover.tsx +37 -22
  160. package/src/components/region/RegionSlider/RegionSlider.tsx +407 -0
  161. package/src/components/region/RegionSlider/index.ts +1 -0
  162. package/src/components/region/RegionSlider/section.module.scss +72 -0
  163. package/src/components/search/Filter/FilterDeliveryMethodFacet.tsx +68 -0
  164. package/src/components/search/Filter/FilterDesktop.tsx +148 -90
  165. package/src/components/search/Filter/FilterSlider.tsx +193 -104
  166. package/src/components/search/Filter/section.module.scss +2 -0
  167. package/src/components/sections/ProductGallery/section.module.scss +2 -0
  168. package/src/components/sections/RegionBar/DefaultComponents.ts +1 -0
  169. package/src/components/sections/RegionBar/RegionBar.tsx +2 -1
  170. package/src/components/templates/LandingPage/LandingPage.tsx +6 -3
  171. package/src/components/templates/ProductListingPage/ProductListing.tsx +4 -1
  172. package/src/components/templates/ProductListingPage/ProductListingPage.tsx +3 -0
  173. package/src/components/templates/SearchPage/SearchPage.tsx +3 -0
  174. package/src/components/templates/SearchPage/SearchWrapper.tsx +15 -7
  175. package/src/components/ui/PickupPoints/PickupPointCard.tsx +39 -0
  176. package/src/components/ui/PickupPoints/PickupPointCards.tsx +102 -0
  177. package/src/components/ui/PickupPoints/index.ts +5 -0
  178. package/src/components/ui/ProductGallery/ProductGallery.tsx +6 -9
  179. package/src/experimental/index.ts +6 -0
  180. package/src/pages/404.tsx +15 -6
  181. package/src/pages/500.tsx +15 -6
  182. package/src/pages/[...slug].tsx +14 -5
  183. package/src/pages/[slug]/p.tsx +6 -3
  184. package/src/pages/_app.tsx +15 -10
  185. package/src/pages/account/403.tsx +30 -27
  186. package/src/pages/account/404.tsx +27 -20
  187. package/src/pages/account/orders/[id].tsx +22 -19
  188. package/src/pages/account/orders/index.tsx +25 -22
  189. package/src/pages/account/profile.tsx +27 -23
  190. package/src/pages/account/security.tsx +28 -23
  191. package/src/pages/account/user-details.tsx +20 -17
  192. package/src/pages/checkout.tsx +11 -8
  193. package/src/pages/index.tsx +7 -4
  194. package/src/pages/login.tsx +16 -7
  195. package/src/pages/s.tsx +5 -2
  196. package/src/sdk/deliveryPromise/index.ts +22 -0
  197. package/src/sdk/deliveryPromise/provider.tsx +119 -0
  198. package/src/sdk/deliveryPromise/queries.ts +80 -0
  199. package/src/sdk/deliveryPromise/reducer.ts +137 -0
  200. package/src/sdk/deliveryPromise/useDeliveryPromise.ts +419 -0
  201. package/src/sdk/overrides/PageProvider.tsx +9 -4
  202. package/src/sdk/product/useLocalizedVariables.ts +20 -6
  203. package/src/sdk/search/useFilter.ts +12 -1
  204. package/src/typings/overrides.ts +2 -1
  205. package/src/utils/globalSettings.ts +74 -0
  206. package/test/server/index.test.ts +1 -0
  207. package/.next/server/chunks/6272.js +0 -1
  208. package/.next/static/auvpWeN2p6A4M2zTGhXzk/_buildManifest.js +0 -1
  209. package/.next/static/chunks/2284.1b43aea18c23c79e.js +0 -1
  210. package/.next/static/chunks/3155.ea52e06317dab557.js +0 -1
  211. package/.next/static/chunks/3166-50d81179a0f5a894.js +0 -1
  212. package/.next/static/chunks/3399.13a97fefb512c902.js +0 -1
  213. package/.next/static/chunks/3465.af28497e8069330f.js +0 -1
  214. package/.next/static/chunks/353.52612dbf516cbaee.js +0 -1
  215. package/.next/static/chunks/4803.de5b14237d616808.js +0 -1
  216. package/.next/static/chunks/6355.c0b326c539dbaa90.js +0 -1
  217. package/.next/static/chunks/6700.4e9426fe8b826dab.js +0 -1
  218. package/.next/static/chunks/7191-2a7b8ddbd07128b6.js +0 -1
  219. package/.next/static/chunks/7498-5246b607527180dd.js +0 -1
  220. package/.next/static/chunks/83.b87d797323ff2034.js +0 -1
  221. package/.next/static/chunks/9173-ae6b6ebdc42876f1.js +0 -1
  222. package/.next/static/chunks/9540.69781e999f27cc05.js +0 -1
  223. package/.next/static/chunks/BannerNewsletter.7c592f132e7048e5.js +0 -1
  224. package/.next/static/chunks/CartSidebar.a00083c44c87c268.js +0 -1
  225. package/.next/static/chunks/Newsletter.1004055f09f76d3c.js +0 -1
  226. package/.next/static/chunks/ProductShelf.d51ba3e6a1b4a57d.js +0 -1
  227. package/.next/static/chunks/ProductTiles.77284431e2b8c898.js +0 -1
  228. package/.next/static/chunks/RegionModal.f61aa62e0a09182a.js +0 -1
  229. package/.next/static/chunks/Toast.6116bc845cd67f49.js +0 -1
  230. package/.next/static/chunks/UIBannerText.6cc5c00d4ba9b64e.js +0 -1
  231. package/.next/static/chunks/UISKUMatrixSidebar.782c55a97889e84a.js +0 -1
  232. package/.next/static/chunks/UIToast.494d0b0ce2c6106a.js +0 -1
  233. package/.next/static/chunks/pages/[...slug]-d0f23d907ec6fbe3.js +0 -1
  234. package/.next/static/chunks/pages/[slug]/p-a255e4a7352455df.js +0 -1
  235. package/.next/static/chunks/pages/_app-1885a948b243078c.js +0 -1
  236. package/.next/static/chunks/pages/account/orders/[id]-b9feb0c860ff1cec.js +0 -1
  237. package/.next/static/chunks/pages/account/orders-1d8409a8b4b0e581.js +0 -1
  238. package/.next/static/chunks/pages/account/profile-5a919fa02b76a422.js +0 -1
  239. package/.next/static/chunks/pages/account/security-b5ab3d1ecbbea9d9.js +0 -1
  240. package/.next/static/chunks/pages/checkout-3a4983b22625c4e3.js +0 -1
  241. package/.next/static/chunks/pages/index-6e68be53d1fef20e.js +0 -1
  242. package/.next/static/chunks/pages/login-de3dd10c6b35159a.js +0 -1
  243. package/.next/static/chunks/pages/s-9b0f606f120d66b0.js +0 -1
  244. package/.next/static/chunks/webpack-f661e0efeacf6028.js +0 -1
  245. package/.next/static/css/202a74b80e6ce63f.css +0 -1
  246. /package/.next/static/{auvpWeN2p6A4M2zTGhXzk → pRYrZEXsDEpvxbtvEobPH}/_ssgManifest.js +0 -0
@@ -1,16 +1,17 @@
1
- import { useCallback, useState } from 'react'
1
+ import { useState } from 'react'
2
2
 
3
3
  import type { Session } from '@faststore/sdk'
4
- import { sessionStore, validateSession } from 'src/sdk/session'
5
- import { getProductCount } from 'src/sdk/product'
6
4
  import { deliveryPromise } from 'discovery.config'
5
+ import { getProductCount } from 'src/sdk/product'
6
+ import { sessionStore, validateSession } from 'src/sdk/session'
7
7
 
8
8
  type SetRegionProps = {
9
9
  session: Session
10
10
  postalCode: string | undefined
11
- onSuccess?: () => void
11
+ onSuccess?: (validatedSession?: Session) => void
12
12
  errorMessage: string
13
13
  noProductsAvailableErrorMessage?: string
14
+ simulation?: boolean
14
15
  }
15
16
 
16
17
  type UseRegionValues = {
@@ -30,6 +31,7 @@ export default function useRegion(): UseRegionValues {
30
31
  session,
31
32
  onSuccess,
32
33
  noProductsAvailableErrorMessage,
34
+ simulation = false,
33
35
  }: SetRegionProps) => {
34
36
  if (typeof postalCode !== 'string') {
35
37
  return
@@ -50,19 +52,18 @@ export default function useRegion(): UseRegionValues {
50
52
  // Check product availability for specific postal code
51
53
  const productCount = await getProductCount()
52
54
  if (productCount === 0) {
53
- const errorFallback = `There are no products available for ${postalCode}.`
54
- const noProductsAvailableError =
55
- noProductsAvailableErrorMessage?.replace(/%s/g, () => postalCode)
56
-
57
- setRegionError(noProductsAvailableError ?? errorFallback)
55
+ setRegionError(
56
+ noProductsAvailableErrorMessage?.replace(/%s/g, postalCode) ??
57
+ `There are no products available for ${postalCode}.`
58
+ )
58
59
  setLoading(false)
59
60
  return
60
61
  }
61
62
  }
62
63
 
63
- sessionStore.set(validatedSession ?? newSession)
64
+ !simulation && sessionStore.set(validatedSession ?? newSession)
64
65
  setRegionError('')
65
- onSuccess?.() // Execute the post-validation action (close modal, etc.)
66
+ onSuccess?.(simulation ? validatedSession : undefined) // Execute the post-validation action (close modal, etc.)
66
67
  } catch (error) {
67
68
  setRegionError(errorMessage)
68
69
  } finally {
@@ -10,8 +10,11 @@ import { useRef, useState } from 'react'
10
10
 
11
11
  import useRegion from '../RegionModal/useRegion'
12
12
 
13
+ import { useDeliveryPromise } from 'src/sdk/deliveryPromise'
13
14
  import { sessionStore, useSession } from 'src/sdk/session'
15
+ import { getGlobalSettings } from 'src/utils/globalSettings'
14
16
  import { textToTitleCase } from 'src/utils/utilities'
17
+
15
18
  import styles from './section.module.scss'
16
19
 
17
20
  interface RegionPopoverProps {
@@ -41,31 +44,42 @@ interface RegionPopoverProps {
41
44
  placement?: UIPopoverProps['placement']
42
45
  }
43
46
 
44
- function RegionPopover({
45
- title = 'Set your location',
46
- closeButtonAriaLabel,
47
- inputField: {
48
- label: inputFieldLabel,
49
- errorMessage: inputFieldErrorMessage,
50
- noProductsAvailableErrorMessage: inputFieldNoProductsAvailableErrorMessage,
51
- buttonActionText: inputButtonActionText,
52
- },
53
- idkPostalCodeLink: {
54
- text: idkPostalCodeLinkText,
55
- to: idkPostalCodeLinkTo,
56
- icon: { icon: idkPostalCodeLinkIcon, alt: idkPostalCodeLinkIconAlt },
57
- },
58
- textBeforeLocation = 'Your current location is:',
59
- textAfterLocation = 'Use the field below to change it.',
60
- description = 'Offers and availability vary by location.',
61
- triggerRef,
62
- offsetTop = 6,
63
- offsetLeft,
64
- placement = 'bottom-start',
65
- }: RegionPopoverProps) {
47
+ function RegionPopover(regionPopoverProps: RegionPopoverProps) {
48
+ const {
49
+ title = 'Set your location',
50
+ closeButtonAriaLabel,
51
+ textBeforeLocation = 'Your current location is:',
52
+ textAfterLocation = 'Use the field below to change it.',
53
+ description = 'Offers and availability vary by location.',
54
+ triggerRef,
55
+ offsetTop = 6,
56
+ offsetLeft,
57
+ placement = 'bottom-start',
58
+ ...otherRegionPopoverProps
59
+ } = regionPopoverProps
60
+ const cmsData = getGlobalSettings(otherRegionPopoverProps)
61
+ const {
62
+ inputField: {
63
+ label: inputFieldLabel = '',
64
+ errorMessage: inputFieldErrorMessage = '',
65
+ noProductsAvailableErrorMessage:
66
+ inputFieldNoProductsAvailableErrorMessage = '',
67
+ buttonActionText: inputButtonActionText = '',
68
+ } = {},
69
+ idkPostalCodeLink: {
70
+ text: idkPostalCodeLinkText = '',
71
+ to: idkPostalCodeLinkTo = '',
72
+ icon: {
73
+ icon: idkPostalCodeLinkIcon = '',
74
+ alt: idkPostalCodeLinkIconAlt = '',
75
+ } = {},
76
+ } = {},
77
+ } = cmsData?.regionalization ?? {}
78
+
66
79
  const inputRef = useRef<HTMLInputElement>(null)
67
80
  const { isValidating, ...session } = useSession()
68
81
  const { popover: displayPopover, closePopover } = useUI()
82
+ const { onPostalCodeChange } = useDeliveryPromise()
69
83
  const { city, postalCode } = sessionStore.read()
70
84
  const location = city ? `${textToTitleCase(city)}, ${postalCode}` : postalCode
71
85
 
@@ -81,6 +95,7 @@ function RegionPopover({
81
95
  await setRegion({
82
96
  session,
83
97
  onSuccess: () => {
98
+ onPostalCodeChange()
84
99
  setInput('')
85
100
  closePopover()
86
101
  },
@@ -0,0 +1,407 @@
1
+ import { toggleFacets, useSearch, type Session } from '@faststore/sdk'
2
+ import {
3
+ useUI,
4
+ type FilterSliderProps as UIFilterSliderProps,
5
+ type IconProps as UIIconProps,
6
+ type InputFieldProps as UIInputFieldProps,
7
+ } from '@faststore/ui'
8
+ import dynamic from 'next/dynamic'
9
+ import type { ChangeEvent, MouseEvent } from 'react'
10
+ import { useEffect, useMemo, useRef, useState } from 'react'
11
+ import useRegion from 'src/components/region/RegionModal/useRegion'
12
+ import { PickupPointCards } from 'src/components/ui/PickupPoints'
13
+ import {
14
+ PICKUP_IN_POINT_FACET_VALUE,
15
+ PICKUP_POINT_FACET_KEY,
16
+ SHIPPING_FACET_KEY,
17
+ useDeliveryPromise,
18
+ } from 'src/sdk/deliveryPromise'
19
+ import { sessionStore, useSession } from 'src/sdk/session'
20
+ import { getGlobalSettings } from 'src/utils/globalSettings'
21
+ import styles from './section.module.scss'
22
+
23
+ const UIFilterSlider = dynamic<UIFilterSliderProps>(
24
+ () =>
25
+ /* webpackChunkName: "UIFilterSlider" */
26
+ import('@faststore/ui').then((mod) => mod.FilterSlider),
27
+ { ssr: false }
28
+ )
29
+ const UIInputField = dynamic<UIInputFieldProps>(() =>
30
+ /* webpackChunkName: "UIInputField" */
31
+ import('@faststore/ui').then((mod) => mod.InputField)
32
+ )
33
+ const UIIcon = dynamic<UIIconProps>(() =>
34
+ /* webpackChunkName: "UIIcon" */
35
+ import('@faststore/ui').then((mod) => mod.Icon)
36
+ )
37
+ const UILink = dynamic(() =>
38
+ /* webpackChunkName: "UILink" */
39
+ import('@faststore/ui').then((mod) => mod.Link)
40
+ )
41
+
42
+ function RegionSlider() {
43
+ const inputRef = useRef<HTMLInputElement>(null)
44
+ const {
45
+ regionSlider: { type: regionSliderType, isOpen },
46
+ closeRegionSlider,
47
+ } = useUI()
48
+ const { isValidating, ...session } = useSession()
49
+ const { state: searchState, setState: setSearchState } = useSearch()
50
+ const {
51
+ loading: loadingRegion,
52
+ setRegion,
53
+ regionError,
54
+ setRegionError,
55
+ } = useRegion()
56
+ const {
57
+ pickupPoints: statePickupPoints,
58
+ onPostalCodeChange,
59
+ changeGlobalPickupPoint,
60
+ selectedPickupPointFacet,
61
+ pickupPointsSimulation,
62
+ clearPickupPointsSimulation,
63
+ globalPickupPoint,
64
+ fetchingPickupPoints,
65
+ changePickupPoint,
66
+ } = useDeliveryPromise({ selectedFilterFacets: searchState.selectedFacets })
67
+
68
+ const isChangingPickupPoint = useMemo(
69
+ () =>
70
+ ['changePickupPoint', 'globalChangePickupPoint'].includes(
71
+ regionSliderType
72
+ ),
73
+ [regionSliderType]
74
+ )
75
+ const pickupPoints = useMemo(
76
+ () =>
77
+ pickupPointsSimulation?.geoCoordinates
78
+ ? pickupPointsSimulation.pickupPoints
79
+ : statePickupPoints,
80
+ [statePickupPoints, pickupPointsSimulation]
81
+ )
82
+
83
+ const [dataLoading, setDataLoading] = useState(
84
+ loadingRegion || fetchingPickupPoints
85
+ )
86
+ const [input, setInput] = useState<string>(session.postalCode ?? '')
87
+ const [appliedInput, setAppliedInput] = useState<string>(
88
+ session.postalCode ?? ''
89
+ )
90
+ const [pickupPointOption, setPickupPointOption] = useState<string | null>(
91
+ regionSliderType === 'globalChangePickupPoint'
92
+ ? (globalPickupPoint?.id ?? null)
93
+ : (selectedPickupPointFacet ?? null)
94
+ )
95
+ const [validatedSession, setValidatedSession] = useState<Session>(undefined)
96
+
97
+ useEffect(() => inputRef.current?.focus(), [])
98
+
99
+ useEffect(() => setDataLoading(fetchingPickupPoints), [fetchingPickupPoints])
100
+
101
+ // Sets default state values based on the `regionSliderType` and when the postal code changes
102
+ useEffect(() => {
103
+ if (regionSliderType === 'none') return
104
+
105
+ setInput(session.postalCode)
106
+ setAppliedInput(session.postalCode)
107
+ setPickupPointOption(
108
+ regionSliderType === 'globalChangePickupPoint'
109
+ ? (globalPickupPoint?.id ?? null)
110
+ : (selectedPickupPointFacet ?? null)
111
+ )
112
+ setDataLoading(false)
113
+ }, [session.postalCode, regionSliderType])
114
+
115
+ const cmsData = getGlobalSettings()
116
+ const inputField = cmsData?.regionalization?.inputField
117
+ const idkPostalCodeLink = cmsData?.regionalization?.idkPostalCodeLink
118
+
119
+ const handleSubmit = async () => {
120
+ if (isValidating) {
121
+ return
122
+ }
123
+
124
+ setDataLoading(true)
125
+ setAppliedInput(input)
126
+
127
+ await setRegion({
128
+ session,
129
+ simulation: isChangingPickupPoint,
130
+ onSuccess: (newSession) => {
131
+ // Pickup points simulation
132
+ if (newSession) {
133
+ setValidatedSession(newSession)
134
+ onPostalCodeChange({
135
+ simulatePickupPoints: true,
136
+ validatedSession: newSession,
137
+ })
138
+ } else {
139
+ onPostalCodeChange()
140
+ }
141
+
142
+ if (!isChangingPickupPoint) {
143
+ setInput('')
144
+ closeRegionSlider()
145
+ }
146
+ },
147
+ postalCode: input,
148
+ errorMessage: inputField?.errorMessage,
149
+ noProductsAvailableErrorMessage:
150
+ inputField?.noProductsAvailableErrorMessage,
151
+ })
152
+ }
153
+
154
+ const handlePickupPointOnChange = (
155
+ e: ChangeEvent<HTMLInputElement> & MouseEvent<HTMLInputElement>
156
+ ) => {
157
+ if (pickupPointOption === e.currentTarget.value) {
158
+ return setPickupPointOption(null)
159
+ }
160
+
161
+ setPickupPointOption(e.currentTarget.value)
162
+ }
163
+
164
+ const handlePickupPointUpdate = async () => {
165
+ if (validatedSession && isChangingPickupPoint) {
166
+ sessionStore.set(validatedSession)
167
+ }
168
+
169
+ // If shipping is not 'pickup-in-point', we need to toggle it
170
+ const facetsToToggle = []
171
+ const shippingFacet = searchState.selectedFacets.find(
172
+ (facet) => facet.key === SHIPPING_FACET_KEY
173
+ )
174
+
175
+ // Add/update the pickupPoint facet
176
+ if (pickupPointOption) {
177
+ facetsToToggle.push({
178
+ key: PICKUP_POINT_FACET_KEY,
179
+ value: pickupPointOption,
180
+ })
181
+
182
+ if (regionSliderType === 'globalChangePickupPoint') {
183
+ const pickupPointFacet = pickupPoints.find(
184
+ (pickupPoint) => pickupPoint.id === pickupPointOption
185
+ )
186
+
187
+ changeGlobalPickupPoint(pickupPointFacet)
188
+ }
189
+ }
190
+
191
+ if (!shippingFacet || shippingFacet.value !== PICKUP_IN_POINT_FACET_VALUE) {
192
+ facetsToToggle.push({
193
+ key: SHIPPING_FACET_KEY,
194
+ value: PICKUP_IN_POINT_FACET_VALUE,
195
+ })
196
+ }
197
+
198
+ isChangingPickupPoint && onPostalCodeChange()
199
+
200
+ if (facetsToToggle.length > 0) {
201
+ setSearchState({
202
+ selectedFacets: toggleFacets(
203
+ searchState.selectedFacets,
204
+ facetsToToggle,
205
+ true
206
+ ),
207
+ page: 0,
208
+ })
209
+ }
210
+ }
211
+
212
+ // The `shouldClearPickupPointsSimulation` param prevent triggering store update more than once.
213
+ const onDismissSlider = async ({
214
+ shouldClearPickupPointsSimulation = true,
215
+ }: { shouldClearPickupPointsSimulation?: boolean } = {}) => {
216
+ setDataLoading(true)
217
+ setInput(session.postalCode)
218
+ setAppliedInput(session.postalCode)
219
+ setValidatedSession(undefined)
220
+ setPickupPointOption(null)
221
+ shouldClearPickupPointsSimulation && clearPickupPointsSimulation()
222
+ }
223
+
224
+ const clearFilter = async () => {
225
+ const selectedPickupInPointFacets = searchState.selectedFacets.filter(
226
+ ({ key, value }) =>
227
+ value === PICKUP_IN_POINT_FACET_VALUE || key === PICKUP_POINT_FACET_KEY
228
+ )
229
+
230
+ if (selectedPickupInPointFacets.length === 0) {
231
+ if (regionSliderType === 'globalChangePickupPoint') {
232
+ changeGlobalPickupPoint(null)
233
+ await onDismissSlider()
234
+ return closeRegionSlider()
235
+ }
236
+
237
+ return onDismissSlider()
238
+ }
239
+
240
+ if (isChangingPickupPoint) {
241
+ changePickupPoint(null)
242
+ regionSliderType === 'globalChangePickupPoint' &&
243
+ changeGlobalPickupPoint(null)
244
+ }
245
+
246
+ setSearchState({
247
+ selectedFacets: toggleFacets(
248
+ searchState.selectedFacets,
249
+ selectedPickupInPointFacets,
250
+ true
251
+ ),
252
+ page: 0,
253
+ })
254
+
255
+ await onDismissSlider()
256
+ closeRegionSlider()
257
+ }
258
+
259
+ const idkPostalCodeLinkProps = useMemo(
260
+ () => ({
261
+ href: idkPostalCodeLink?.to,
262
+ children: (
263
+ <>
264
+ {idkPostalCodeLink?.text}
265
+ {!!idkPostalCodeLink?.icon?.icon && (
266
+ <UIIcon
267
+ name={idkPostalCodeLink?.icon?.icon}
268
+ aria-label={idkPostalCodeLink?.icon?.alt}
269
+ width={20}
270
+ height={20}
271
+ />
272
+ )}
273
+ </>
274
+ ),
275
+ }),
276
+ [idkPostalCodeLink]
277
+ )
278
+
279
+ if (!isOpen) return null
280
+
281
+ const isSameSelectedPickupPoint =
282
+ regionSliderType === 'changePickupPoint' &&
283
+ selectedPickupPointFacet &&
284
+ pickupPointOption === selectedPickupPointFacet
285
+ const isSameGlobalPickupPoint =
286
+ regionSliderType === 'globalChangePickupPoint' &&
287
+ globalPickupPoint &&
288
+ pickupPointOption === globalPickupPoint.id
289
+ const shouldDisableUpdateButton =
290
+ dataLoading ||
291
+ input === '' ||
292
+ input !== appliedInput ||
293
+ pickupPoints?.length === 0 ||
294
+ !pickupPointOption ||
295
+ isSameSelectedPickupPoint ||
296
+ isSameGlobalPickupPoint ||
297
+ regionError !== ''
298
+ const shouldDisableClearButton =
299
+ !dataLoading && !pickupPointOption && !selectedPickupPointFacet
300
+
301
+ return (
302
+ <UIFilterSlider
303
+ data-fs-filter-region-slider
304
+ testId="fs-region-slider"
305
+ overlayProps={{
306
+ className: `section ${styles.section} section-filter-slider`,
307
+ }}
308
+ title={
309
+ regionSliderType !== 'none'
310
+ ? cmsData?.deliveryPromise?.regionSlider?.title?.[regionSliderType]
311
+ : ''
312
+ }
313
+ size="partial"
314
+ direction="rightSide"
315
+ onClose={() => onDismissSlider()}
316
+ onDismiss={() => onDismissSlider()}
317
+ footer={isChangingPickupPoint ? true : false}
318
+ applyBtnProps={
319
+ isChangingPickupPoint
320
+ ? {
321
+ variant: 'primary',
322
+ children:
323
+ cmsData?.deliveryPromise?.regionSlider
324
+ ?.pickupPointChangeApplyButtonLabel,
325
+ disabled: shouldDisableUpdateButton,
326
+ onClick: async () => {
327
+ await handlePickupPointUpdate()
328
+
329
+ // Clear local state when leaving the component after setting a pickup point.
330
+ // Pickup points simulation data are reset by the `onPostalCodeChange` callback.
331
+ await onDismissSlider({
332
+ shouldClearPickupPointsSimulation: false,
333
+ })
334
+ },
335
+ }
336
+ : undefined
337
+ }
338
+ clearBtnProps={
339
+ isChangingPickupPoint
340
+ ? {
341
+ variant: 'secondary',
342
+ disabled: shouldDisableClearButton,
343
+ onClick: () => clearFilter(),
344
+ children:
345
+ cmsData?.deliveryPromise?.regionSlider
346
+ ?.pickupPointClearFilterButtonLabel ?? 'Clear filter',
347
+ }
348
+ : undefined
349
+ }
350
+ >
351
+ <div data-fs-filter-region-slider-content>
352
+ <span data-fs-filter-region-slider-description>
353
+ {cmsData?.deliveryPromise?.deliveryMethods?.description}
354
+ </span>
355
+ <UIInputField
356
+ id="region-slider-input-field"
357
+ inputRef={inputRef}
358
+ label={inputField?.label}
359
+ actionable
360
+ value={input}
361
+ buttonActionText={dataLoading ? '...' : inputField?.buttonActionText}
362
+ onInput={(e) => {
363
+ setInput(e.currentTarget.value)
364
+ regionError !== '' && setRegionError('')
365
+ }}
366
+ onSubmit={() => handleSubmit()}
367
+ onClear={() => {
368
+ setInput('')
369
+ setRegionError('')
370
+ }}
371
+ error={regionError}
372
+ />
373
+ {idkPostalCodeLink?.to && (
374
+ <UILink data-fs-filter-delivery-link {...idkPostalCodeLinkProps} />
375
+ )}
376
+
377
+ {isChangingPickupPoint &&
378
+ input !== '' &&
379
+ input === appliedInput &&
380
+ !dataLoading && (
381
+ <PickupPointCards
382
+ pickupPoints={regionError ? [] : pickupPoints}
383
+ selectedOption={pickupPointOption}
384
+ onChange={handlePickupPointOnChange}
385
+ noPickupPointsAvailableMessage={
386
+ pickupPoints?.length === 0
387
+ ? cmsData.deliveryPromise?.regionSlider
388
+ ?.noPickupPointsAvailableInLocation
389
+ : undefined
390
+ }
391
+ errorMessage={{
392
+ title: regionError,
393
+ description:
394
+ cmsData?.regionalization?.inputField?.errorMessageHelper,
395
+ }}
396
+ choosePickupPointAriaLabel={
397
+ cmsData?.deliveryPromise?.regionSlider
398
+ ?.choosePickupPointAriaLabel
399
+ }
400
+ />
401
+ )}
402
+ </div>
403
+ </UIFilterSlider>
404
+ )
405
+ }
406
+
407
+ export default RegionSlider
@@ -0,0 +1 @@
1
+ export { default } from './RegionSlider'
@@ -0,0 +1,72 @@
1
+ .section {
2
+ @import "@faststore/ui/src/components/atoms/Button/styles.scss";
3
+ @import "@faststore/ui/src/components/atoms/Icon/styles.scss";
4
+ @import "@faststore/ui/src/components/atoms/Input/styles.scss";
5
+ @import "@faststore/ui/src/components/atoms/Link/styles.scss";
6
+ @import "@faststore/ui/src/components/molecules/InputField/styles.scss";
7
+ @import "@faststore/ui/src/components/molecules/Modal/styles.scss";
8
+ @import "@faststore/ui/src/components/molecules/PickupPointCard/styles.scss";
9
+ @import "@faststore/ui/src/components/organisms/EmptyState/styles.scss";
10
+ @import "@faststore/ui/src/components/organisms/FilterSlider/styles.scss";
11
+ @import "@faststore/ui/src/components/organisms/PickupPointCards/styles.scss";
12
+ @import "@faststore/ui/src/components/organisms/SlideOver/styles.scss";
13
+
14
+ [data-fs-filter-region-slider] {
15
+ [data-fs-filter-slider-title] {
16
+ font-size: var(--fs-text-size-3);
17
+ font-weight: var(--fs-text-weight-semibold);
18
+ }
19
+
20
+ [data-fs-filter-slider-footer] {
21
+ [data-fs-filter-slider-footer-button-apply] {
22
+ width: 100%;
23
+ }
24
+
25
+ [data-fs-filter-slider-footer-button-clear] {
26
+ width: 100%;
27
+ }
28
+ }
29
+
30
+ [data-fs-slide-over-header] {
31
+ @include media(">=notebook") {
32
+ padding-right: var(--fs-spacing-6);
33
+ padding-left: var(--fs-spacing-6);
34
+ }
35
+ }
36
+
37
+ [data-fs-filter-region-slider-content] {
38
+ height: 100%;
39
+ padding: 0 var(--fs-spacing-3);
40
+
41
+ @include media(">=notebook") {
42
+ padding: 0 var(--fs-spacing-6);
43
+ }
44
+
45
+ [data-fs-empty-state] {
46
+ height: calc(90% - var(--fs-filter-slider-footer-height));
47
+ padding: 0;
48
+ color: var(--fs-empty-state-title-color);
49
+ }
50
+ }
51
+
52
+ [data-fs-filter-region-slider-description] {
53
+ display: flex;
54
+ padding: var(--fs-spacing-4) 0;
55
+ font-size: var(--fs-text-size-2);
56
+ line-height: 1.25;
57
+ }
58
+
59
+ // Duplicate from data-fs-region-modal-link
60
+ [data-fs-filter-delivery-link] {
61
+ display: flex;
62
+ flex-direction: row;
63
+ column-gap: var(--fs-filter-link-column-gap);
64
+ align-content: flex-start;
65
+ align-items: center;
66
+ justify-content: flex-start;
67
+ padding: var(--fs-filter-link-padding);
68
+ margin-top: var(--fs-spacing-2);
69
+ color: var(--fs-filter-link-color);
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,68 @@
1
+ import { regionSliderTypes, Button as UIButton, useUI } from '@faststore/ui'
2
+ import { sessionStore } from 'src/sdk/session'
3
+ import type { GlobalCmsData } from 'src/utils/globalSettings'
4
+ import { textToTitleCase } from 'src/utils/utilities'
5
+
6
+ interface FacetValue {
7
+ value: string
8
+ label: string
9
+ selected: boolean
10
+ quantity: number
11
+ }
12
+
13
+ interface FilterDeliveryMethodFacetProps {
14
+ item: FacetValue
15
+ deliveryMethods: GlobalCmsData['deliveryPromise']['deliveryMethods']
16
+ }
17
+
18
+ export default function FilterDeliveryMethodFacet({
19
+ item,
20
+ deliveryMethods,
21
+ }: FilterDeliveryMethodFacetProps) {
22
+ const { city, postalCode } = sessionStore.read()
23
+ const { openRegionSlider } = useUI()
24
+
25
+ const location = city ? `${textToTitleCase(city)}, ${postalCode}` : postalCode
26
+ const mapDeliveryMethodLabel: Record<string, string> = {
27
+ delivery: deliveryMethods?.delivery ?? 'Shipping to',
28
+ 'pickup-in-point': deliveryMethods?.pickupInPoint ?? 'Pickup at',
29
+ 'pickup-nearby': deliveryMethods?.pickupNearby ?? 'Pickup Nearby',
30
+ 'pickup-all': deliveryMethods?.pickupAll?.label ?? 'Pickup Anywhere',
31
+ }
32
+
33
+ if (item.value === 'delivery') {
34
+ return (
35
+ <>
36
+ {mapDeliveryMethodLabel[item.value]}
37
+ <UIButton
38
+ data-fs-filter-list-item-button
39
+ size="small"
40
+ onClick={() => {
41
+ openRegionSlider(regionSliderTypes.changeLocation)
42
+ }}
43
+ >
44
+ {location}
45
+ </UIButton>
46
+ </>
47
+ )
48
+ }
49
+
50
+ if (item.value === 'pickup-in-point') {
51
+ return (
52
+ <>
53
+ {mapDeliveryMethodLabel[item.value]}
54
+ <UIButton
55
+ data-fs-filter-list-item-button
56
+ size="small"
57
+ onClick={() => {
58
+ openRegionSlider(regionSliderTypes.changePickupPoint)
59
+ }}
60
+ >
61
+ {item.label}
62
+ </UIButton>
63
+ </>
64
+ )
65
+ }
66
+
67
+ return <>{mapDeliveryMethodLabel[item.value] ?? item.label}</>
68
+ }