@faststore/core 3.99.1 → 4.0.0-dev.1

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 (543) hide show
  1. package/.turbo/turbo-generate.log +29 -0
  2. package/.turbo/turbo-test.log +47 -16
  3. package/@generated/cached-operations.json +24 -0
  4. package/@generated/gql.ts +134 -210
  5. package/@generated/graphql.ts +1628 -3037
  6. package/@generated/index.ts +1 -1
  7. package/@generated/persisted-documents.json +1 -1
  8. package/@generated/schema.graphql +1854 -1743
  9. package/CHANGELOG.md +11 -5
  10. package/README.md +2 -1
  11. package/api/index.ts +6 -7
  12. package/cms/faststore/base.jsonc +1 -1
  13. package/cms/faststore/components/cms_component__alert.jsonc +5 -1
  14. package/cms/faststore/components/cms_component__bannernewsletter.jsonc +5 -1
  15. package/cms/faststore/components/cms_component__bannertext.jsonc +5 -1
  16. package/cms/faststore/components/cms_component__breadcrumb.jsonc +16 -4
  17. package/cms/faststore/components/cms_component__cartsidebar.jsonc +21 -1
  18. package/cms/faststore/components/cms_component__children.jsonc +5 -1
  19. package/cms/faststore/components/cms_component__crosssellingshelf.jsonc +5 -1
  20. package/cms/faststore/components/cms_component__emptystate.jsonc +8 -3
  21. package/cms/faststore/components/cms_component__footer.jsonc +9 -3
  22. package/cms/faststore/components/cms_component__hero.jsonc +5 -1
  23. package/cms/faststore/components/cms_component__incentives.jsonc +5 -1
  24. package/cms/faststore/components/cms_component__navbar.jsonc +139 -1
  25. package/cms/faststore/components/cms_component__newsletter.jsonc +5 -1
  26. package/cms/faststore/components/cms_component__productdetails.jsonc +33 -1
  27. package/cms/faststore/components/cms_component__productgallery.jsonc +113 -15
  28. package/cms/faststore/components/cms_component__productshelf.jsonc +5 -1
  29. package/cms/faststore/components/cms_component__producttiles.jsonc +5 -1
  30. package/cms/faststore/components/cms_component__regionbar.jsonc +5 -1
  31. package/cms/faststore/components/cms_component__regionmodal.jsonc +5 -1
  32. package/cms/faststore/components/cms_component__regionpopover.jsonc +5 -1
  33. package/cms/faststore/components/cms_component__scrolltotopbutton.jsonc +29 -0
  34. package/cms/faststore/components/cms_component__search.jsonc +5 -1
  35. package/cms/faststore/content-types.json +115 -0
  36. package/cms/faststore/pages/cms_content_type__globalsections.jsonc +52 -0
  37. package/cms/faststore/pages/cms_content_type__home.jsonc +6 -0
  38. package/cms/faststore/pages/cms_content_type__landingpage.jsonc +8 -1
  39. package/cms/faststore/pages/cms_content_type__pdp.jsonc +6 -0
  40. package/cms/faststore/pages/cms_content_type__plp.jsonc +17 -0
  41. package/cms/faststore/pages/cms_content_type__search.jsonc +17 -0
  42. package/cms/faststore/schema.json +547 -43
  43. package/cms/faststore/sections.json +330 -21
  44. package/discovery.config.default.js +49 -3
  45. package/next-env.d.ts +2 -1
  46. package/next.config.js +48 -26
  47. package/package.json +47 -60
  48. package/public/icons.svg +5 -1
  49. package/scripts/i18n.js +76 -0
  50. package/src/components/account/MyAccountDrawer/OrganizationDrawer/OrganizationDrawer.tsx +2 -1
  51. package/src/components/account/MyAccountDrawer/OrganizationDrawer/section.module.scss +25 -24
  52. package/src/components/account/MyAccountDrawer/ProfileSummary/profile-summary.scss +8 -6
  53. package/src/components/account/MyAccountLayout/styles.scss +16 -12
  54. package/src/components/account/MyAccountMenu/styles.scss +21 -22
  55. package/src/components/account/MyAccountUserDetails/styles.module.scss +4 -2
  56. package/src/components/account/components/MyAccountAccordion/styles.scss +4 -2
  57. package/src/components/account/components/MyAccountHeader/styles.scss +3 -1
  58. package/src/components/account/components/MyAccountTable/styles.scss +6 -4
  59. package/src/components/account/orders/MyAccountListOrders/MyAccountFilterSlider/section.module.scss +21 -19
  60. package/src/components/account/orders/MyAccountListOrders/MyAccountListOrdersTable/MyAccountListOrdersTable.tsx +3 -1
  61. package/src/components/account/orders/MyAccountListOrders/MyAccountListOrdersTable/styles.scss +5 -3
  62. package/src/components/account/orders/MyAccountListOrders/styles.module.scss +25 -22
  63. package/src/components/account/orders/MyAccountOrderDetails/MyAccountBudgetsCard/styles.scss +7 -5
  64. package/src/components/account/orders/MyAccountOrderDetails/MyAccountDeliveryOptionAccordion/styles.scss +7 -5
  65. package/src/components/account/orders/MyAccountOrderDetails/MyAccountMoreInformationCard/styles.scss +3 -1
  66. package/src/components/account/orders/MyAccountOrderDetails/MyAccountOrderActionModal/styles.module.scss +8 -6
  67. package/src/components/account/orders/MyAccountOrderDetails/MyAccountOrderActions/styles.scss +3 -1
  68. package/src/components/account/orders/MyAccountOrderDetails/MyAccountStatusCard/styles.scss +8 -6
  69. package/src/components/account/orders/MyAccountOrderDetails/section.module.scss +36 -43
  70. package/src/components/account/profile/profile.module.scss +3 -1
  71. package/src/components/account/section.module.scss +3 -1
  72. package/src/components/account/security/styles.module.scss +14 -8
  73. package/src/components/cart/CartSidebar/CartSidebar.tsx +10 -1
  74. package/src/components/cart/CartSidebar/section.module.scss +19 -17
  75. package/src/components/cart/EmptyCart/EmptyCart.tsx +12 -4
  76. package/src/components/cms/GlobalSections.tsx +12 -12
  77. package/src/components/cms/RenderSections.tsx +10 -2
  78. package/src/components/cms/plp/Components.ts +9 -0
  79. package/src/components/common/Alert/section.module.scss +10 -5
  80. package/src/components/common/PreviewTag/section.module.scss +48 -40
  81. package/src/components/common/Toast/section.module.scss +4 -2
  82. package/src/components/localization/LocalizationSelector/LocalizationSelector.tsx +320 -0
  83. package/src/components/localization/LocalizationSelector/index.ts +1 -0
  84. package/src/components/localization/LocalizationSelector/section.module.scss +18 -0
  85. package/src/components/localization/index.ts +1 -0
  86. package/src/components/navigation/Navbar/Navbar.tsx +49 -1
  87. package/src/components/navigation/NavbarSlider/NavbarSlider.tsx +19 -1
  88. package/src/components/navigation/NavbarSlider/section.module.scss +12 -10
  89. package/src/components/product/OutOfStock/OutOfStock.tsx +6 -1
  90. package/src/components/product/ProductCard/ProductCard.tsx +1 -1
  91. package/src/components/region/RegionModal/RegionModal.tsx +4 -1
  92. package/src/components/region/RegionModal/section.module.scss +10 -8
  93. package/src/components/region/RegionPopover/RegionPopover.tsx +6 -5
  94. package/src/components/region/RegionPopover/section.module.scss +9 -7
  95. package/src/components/region/RegionSlider/RegionSlider.tsx +5 -2
  96. package/src/components/region/RegionSlider/section.module.scss +16 -13
  97. package/src/components/search/Filter/FilterDeliveryMethodFacet.tsx +5 -5
  98. package/src/components/search/Filter/FilterDesktop.tsx +13 -4
  99. package/src/components/search/Filter/FilterSlider.tsx +18 -6
  100. package/src/components/search/Filter/section.module.scss +21 -19
  101. package/src/components/search/SearchDropdown/SearchDropdown.tsx +9 -7
  102. package/src/components/search/SearchHistory/SearchHistory.tsx +7 -2
  103. package/src/components/search/SearchInput/SearchInput.tsx +21 -8
  104. package/src/components/search/SearchProductItem/SearchProductItem.tsx +10 -3
  105. package/src/components/search/SearchTop/SearchTop.tsx +30 -24
  106. package/src/components/search/Sort/Sort.tsx +17 -19
  107. package/src/components/sections/Alert/Alert.tsx +6 -1
  108. package/src/components/sections/BannerNewsletter/section.module.scss +6 -4
  109. package/src/components/sections/BannerText/section.module.scss +5 -3
  110. package/src/components/sections/Breadcrumb/Breadcrumb.tsx +6 -2
  111. package/src/components/sections/Breadcrumb/section.module.scss +7 -5
  112. package/src/components/sections/EmptyState/section.module.scss +5 -3
  113. package/src/components/sections/Footer/Footer.tsx +1 -5
  114. package/src/components/sections/Footer/section.module.scss +10 -8
  115. package/src/components/sections/Hero/Hero.tsx +3 -1
  116. package/src/components/sections/Hero/section.module.scss +6 -4
  117. package/src/components/sections/Incentives/section.module.scss +5 -3
  118. package/src/components/sections/Navbar/Navbar.tsx +45 -0
  119. package/src/components/sections/Navbar/section.module.scss +34 -33
  120. package/src/components/sections/Newsletter/section.module.scss +10 -8
  121. package/src/components/sections/ProductDetails/ProductDetails.tsx +23 -1
  122. package/src/components/sections/ProductDetails/section.module.scss +32 -30
  123. package/src/components/sections/ProductGallery/EmptyGallery.tsx +13 -16
  124. package/src/components/sections/ProductGallery/section.module.scss +42 -39
  125. package/src/components/sections/ProductShelf/section.module.scss +22 -17
  126. package/src/components/sections/ProductTiles/section.module.scss +15 -13
  127. package/src/components/sections/RegionBar/section.module.scss +5 -3
  128. package/src/components/sections/ScrollToTopButton/ScrollToTopButton.tsx +4 -3
  129. package/src/components/sections/ScrollToTopButton/section.module.scss +4 -2
  130. package/src/components/templates/LandingPage/LandingPage.tsx +22 -16
  131. package/src/components/templates/ProductListingPage/ProductListing.tsx +1 -13
  132. package/src/components/templates/ProductListingPage/ProductListingPage.tsx +8 -3
  133. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +14 -11
  134. package/src/components/ui/Button/ButtonSignIn/ButtonSignIn.tsx +2 -1
  135. package/src/components/ui/Button/ButtonSignIn/ButtonSignInFallback/ButtonSignInFallback.tsx +3 -1
  136. package/src/components/ui/Link/Link.tsx +19 -2
  137. package/src/components/ui/LinkButton/LinkButton.tsx +28 -0
  138. package/src/components/ui/LinkButton/index.ts +2 -0
  139. package/src/components/ui/LocalizationButton/LocalizationButton.tsx +124 -0
  140. package/src/components/ui/LocalizationButton/index.ts +1 -0
  141. package/src/components/ui/PickupPoints/PickupPointCards.tsx +2 -4
  142. package/src/components/ui/ProductComparison/ProductComparisonSidebar.tsx +34 -17
  143. package/src/components/ui/ProductDescription/ProductDescription.tsx +8 -3
  144. package/src/components/ui/ProductDetails/AddToCartLoadingSkeleton.tsx +6 -2
  145. package/src/components/ui/ProductDetails/ProductDetailsSettings.tsx +17 -5
  146. package/src/components/ui/ProductGallery/ProductGallery.tsx +7 -3
  147. package/src/components/ui/ProductGallery/ProductGalleryPage.tsx +1 -3
  148. package/src/components/ui/ShippingSimulation/ShippingSimulation.tsx +6 -1
  149. package/src/customizations/src/styles/custom-mixins.scss +0 -2
  150. package/src/experimental/index.ts +26 -0
  151. package/src/experimental/myAccountServerSideProps.ts +11 -2
  152. package/src/experimental/searchServerSideFunctions/getServerSideProps.ts +13 -7
  153. package/src/experimental/searchServerSideFunctions/getStaticProps.ts +5 -4
  154. package/src/instrumentation.ts +20 -6
  155. package/src/pages/404.tsx +5 -4
  156. package/src/pages/500.tsx +5 -4
  157. package/src/pages/[...slug].tsx +7 -4
  158. package/src/pages/[slug]/p.tsx +26 -8
  159. package/src/pages/_app.tsx +9 -1
  160. package/src/pages/api/fs/logout.ts +3 -2
  161. package/src/pages/api/graphql.ts +7 -23
  162. package/src/pages/api/preview.ts +21 -8
  163. package/src/pages/checkout.tsx +15 -5
  164. package/src/pages/index.tsx +18 -9
  165. package/src/pages/login.tsx +5 -4
  166. package/src/pages/pvt/account/403.tsx +11 -2
  167. package/src/pages/pvt/account/404.tsx +13 -3
  168. package/src/pages/pvt/account/index.tsx +6 -4
  169. package/src/pages/pvt/account/orders/[id].tsx +11 -3
  170. package/src/pages/pvt/account/orders/index.tsx +11 -3
  171. package/src/pages/pvt/account/profile.tsx +11 -2
  172. package/src/pages/pvt/account/security.tsx +11 -2
  173. package/src/pages/pvt/account/user-details.tsx +11 -2
  174. package/src/pages/s.tsx +26 -6
  175. package/src/proxy.ts +154 -0
  176. package/src/sdk/account/refreshToken.ts +4 -4
  177. package/src/sdk/account/useReorder.ts +8 -2
  178. package/src/sdk/analytics/platform/vtex/search.ts +3 -2
  179. package/src/sdk/cart/redirectToCheckout.ts +14 -8
  180. package/src/sdk/cart/useCheckoutButton.ts +7 -1
  181. package/src/sdk/deliveryPromise/useDeliveryPromise.ts +10 -18
  182. package/src/sdk/graphql/request.ts +102 -50
  183. package/src/sdk/localization/bindingSelector.ts +91 -0
  184. package/src/sdk/localization/index.ts +4 -0
  185. package/src/sdk/localization/match-url.ts +64 -0
  186. package/src/sdk/localization/types.ts +31 -0
  187. package/src/sdk/localization/useBindingSelector.ts +183 -0
  188. package/src/sdk/localization/useLocaleValidation.ts +49 -0
  189. package/src/sdk/localization/useLocalizationConfig.tsx +160 -0
  190. package/src/sdk/offer/fetcher.ts +3 -2
  191. package/src/sdk/product/useLocalizedVariables.ts +1 -1
  192. package/src/sdk/product/usePageProductsQuery.ts +2 -2
  193. package/src/sdk/product/useProductGalleryQuery.ts +1 -2
  194. package/src/sdk/product/useProductLink.ts +3 -1
  195. package/src/sdk/search/formatSearchPath.ts +28 -3
  196. package/src/sdk/search/state.ts +22 -13
  197. package/src/sdk/search/useSearchBase.ts +10 -0
  198. package/src/sdk/search/useSearchHistory.ts +41 -7
  199. package/src/sdk/session/index.ts +21 -3
  200. package/src/sdk/shipping/useShippingSimulation.ts +10 -2
  201. package/src/sdk/tsconfig.json +6 -0
  202. package/src/sdk/ui/useLink.ts +48 -0
  203. package/src/server/cms/global.ts +40 -0
  204. package/src/server/cms/index.ts +18 -10
  205. package/src/server/cms/pdp.ts +1 -0
  206. package/src/server/cms/plp.ts +3 -0
  207. package/src/server/content/service.ts +36 -24
  208. package/src/server/content/types.ts +10 -3
  209. package/src/server/index.ts +27 -20
  210. package/src/server/options.ts +27 -2
  211. package/src/styles/global/index.scss +10 -7
  212. package/src/styles/main.scss +3 -3
  213. package/src/typings/locales.ts +41 -0
  214. package/src/utils/fetchProductGallerySSR.ts +3 -1
  215. package/src/utils/getBaseDomain.ts +2 -1
  216. package/src/utils/getRequestHostname.ts +26 -0
  217. package/src/utils/globalSettings.ts +11 -0
  218. package/src/utils/localization/bindingPaths.ts +337 -0
  219. package/src/utils/localization/validateLocaleForHostname.ts +69 -0
  220. package/src/utils/localization/withLocaleValidation.ts +36 -0
  221. package/src/utils/utilities.ts +22 -0
  222. package/test/pages/api/preview.test.ts +93 -0
  223. package/test/sdk/localization/bindingSelector.test.ts +246 -0
  224. package/test/sdk/localization/store-url.browser.test.ts +140 -0
  225. package/test/sdk/localization/store-url.test.ts +96 -0
  226. package/test/sdk/localization/useBindingSelector.test.tsx +219 -0
  227. package/test/sdk/search/useSearchHistory.test.ts +222 -0
  228. package/test/server/cms/index.test.ts +3 -4
  229. package/test/server/content/service.test.ts +98 -0
  230. package/test/server/index.test.ts +10 -22
  231. package/test/utils/cookieCacheBusting.test.ts +32 -24
  232. package/test/utils/getRequestHostname.test.ts +71 -0
  233. package/test/utils/localization/bindingPaths.test.ts +706 -0
  234. package/test/utils/match-url.test.ts +388 -0
  235. package/tsconfig.json +11 -3
  236. package/vitest.config.ts +61 -0
  237. package/.next/BUILD_ID +0 -1
  238. package/.next/build-manifest.json +0 -197
  239. package/.next/cache/.tsbuildinfo +0 -1
  240. package/.next/cache/config.json +0 -7
  241. package/.next/cache/webpack/client-production/0.pack +0 -0
  242. package/.next/cache/webpack/client-production/index.pack +0 -0
  243. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  244. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  245. package/.next/cache/webpack/server-production/0.pack +0 -0
  246. package/.next/cache/webpack/server-production/index.pack +0 -0
  247. package/.next/export-marker.json +0 -1
  248. package/.next/images-manifest.json +0 -1
  249. package/.next/next-minimal-server.js.nft.json +0 -1
  250. package/.next/next-server.js.nft.json +0 -1
  251. package/.next/package.json +0 -1
  252. package/.next/prerender-manifest.js +0 -1
  253. package/.next/prerender-manifest.json +0 -1
  254. package/.next/react-loadable-manifest.json +0 -478
  255. package/.next/required-server-files.json +0 -1
  256. package/.next/routes-manifest.json +0 -1
  257. package/.next/server/chunks/1280.js +0 -1
  258. package/.next/server/chunks/1454.js +0 -1
  259. package/.next/server/chunks/1554.js +0 -1
  260. package/.next/server/chunks/1607.js +0 -1
  261. package/.next/server/chunks/1780.js +0 -1
  262. package/.next/server/chunks/1917.js +0 -1
  263. package/.next/server/chunks/1972.js +0 -1
  264. package/.next/server/chunks/2230.js +0 -1
  265. package/.next/server/chunks/2249.js +0 -1
  266. package/.next/server/chunks/2430.js +0 -1
  267. package/.next/server/chunks/2445.js +0 -12
  268. package/.next/server/chunks/2570.js +0 -1
  269. package/.next/server/chunks/2792.js +0 -1
  270. package/.next/server/chunks/2903.js +0 -1
  271. package/.next/server/chunks/294.js +0 -1
  272. package/.next/server/chunks/2955.js +0 -1
  273. package/.next/server/chunks/3006.js +0 -1
  274. package/.next/server/chunks/3029.js +0 -1
  275. package/.next/server/chunks/3060.js +0 -1
  276. package/.next/server/chunks/3408.js +0 -1
  277. package/.next/server/chunks/3483.js +0 -1
  278. package/.next/server/chunks/3683.js +0 -1
  279. package/.next/server/chunks/3836.js +0 -1
  280. package/.next/server/chunks/3890.js +0 -494
  281. package/.next/server/chunks/3922.js +0 -1
  282. package/.next/server/chunks/3945.js +0 -1
  283. package/.next/server/chunks/3951.js +0 -7
  284. package/.next/server/chunks/416.js +0 -1
  285. package/.next/server/chunks/4194.js +0 -1
  286. package/.next/server/chunks/4365.js +0 -1
  287. package/.next/server/chunks/4451.js +0 -1
  288. package/.next/server/chunks/4803.js +0 -1
  289. package/.next/server/chunks/4913.js +0 -13
  290. package/.next/server/chunks/5402.js +0 -1
  291. package/.next/server/chunks/5723.js +0 -13
  292. package/.next/server/chunks/5796.js +0 -1
  293. package/.next/server/chunks/6393.js +0 -1
  294. package/.next/server/chunks/6457.js +0 -6
  295. package/.next/server/chunks/6698.js +0 -1
  296. package/.next/server/chunks/6804.js +0 -6
  297. package/.next/server/chunks/6886.js +0 -1
  298. package/.next/server/chunks/7098.js +0 -9
  299. package/.next/server/chunks/7169.js +0 -1
  300. package/.next/server/chunks/7228.js +0 -1
  301. package/.next/server/chunks/7275.js +0 -1
  302. package/.next/server/chunks/7371.js +0 -1
  303. package/.next/server/chunks/7692.js +0 -1
  304. package/.next/server/chunks/7799.js +0 -1
  305. package/.next/server/chunks/8068.js +0 -1
  306. package/.next/server/chunks/83.js +0 -1
  307. package/.next/server/chunks/831.js +0 -1
  308. package/.next/server/chunks/8563.js +0 -1
  309. package/.next/server/chunks/8569.js +0 -1
  310. package/.next/server/chunks/8687.js +0 -1
  311. package/.next/server/chunks/870.js +0 -1
  312. package/.next/server/chunks/8737.js +0 -1
  313. package/.next/server/chunks/8741.js +0 -1
  314. package/.next/server/chunks/9088.js +0 -1
  315. package/.next/server/chunks/9117.js +0 -1
  316. package/.next/server/chunks/9237.js +0 -1
  317. package/.next/server/chunks/948.js +0 -3
  318. package/.next/server/chunks/9563.js +0 -3
  319. package/.next/server/chunks/9570.js +0 -1
  320. package/.next/server/chunks/9740.js +0 -1
  321. package/.next/server/chunks/9853.js +0 -1
  322. package/.next/server/chunks/9984.js +0 -1
  323. package/.next/server/chunks/9985.js +0 -1
  324. package/.next/server/chunks/9990.js +0 -1
  325. package/.next/server/chunks/ButtonSignIn.js +0 -1
  326. package/.next/server/chunks/Dropdown.js +0 -1
  327. package/.next/server/chunks/DropdownButton.js +0 -1
  328. package/.next/server/chunks/DropdownItem.js +0 -1
  329. package/.next/server/chunks/DropdownMenu.js +0 -1
  330. package/.next/server/chunks/FilterSkeleton.js +0 -1
  331. package/.next/server/chunks/ScrollToTopButton.js +0 -1
  332. package/.next/server/chunks/UIBannerText.js +0 -1
  333. package/.next/server/chunks/UISKUMatrixSidebar.js +0 -1
  334. package/.next/server/chunks/font-manifest.json +0 -1
  335. package/.next/server/edge-instrumentation.js +0 -2
  336. package/.next/server/edge-instrumentation.js.map +0 -1
  337. package/.next/server/edge-runtime-webpack.js +0 -157
  338. package/.next/server/edge-runtime-webpack.js.map +0 -1
  339. package/.next/server/font-manifest.json +0 -1
  340. package/.next/server/functions-config-manifest.json +0 -1
  341. package/.next/server/instrumentation.js +0 -1
  342. package/.next/server/instrumentation.js.nft.json +0 -1
  343. package/.next/server/middleware-build-manifest.js +0 -1
  344. package/.next/server/middleware-manifest.json +0 -6
  345. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  346. package/.next/server/next-font-manifest.js +0 -1
  347. package/.next/server/next-font-manifest.json +0 -1
  348. package/.next/server/pages/404.js +0 -1
  349. package/.next/server/pages/404.js.nft.json +0 -1
  350. package/.next/server/pages/500.js +0 -1
  351. package/.next/server/pages/500.js.nft.json +0 -1
  352. package/.next/server/pages/[...slug].js +0 -1
  353. package/.next/server/pages/[...slug].js.nft.json +0 -1
  354. package/.next/server/pages/[slug]/p.js +0 -1
  355. package/.next/server/pages/[slug]/p.js.nft.json +0 -1
  356. package/.next/server/pages/_app.js +0 -1
  357. package/.next/server/pages/_app.js.nft.json +0 -1
  358. package/.next/server/pages/_document.js +0 -1
  359. package/.next/server/pages/_document.js.nft.json +0 -1
  360. package/.next/server/pages/_error.js +0 -1
  361. package/.next/server/pages/_error.js.nft.json +0 -1
  362. package/.next/server/pages/api/fs/logout.js +0 -1
  363. package/.next/server/pages/api/fs/logout.js.nft.json +0 -1
  364. package/.next/server/pages/api/graphql.js +0 -3
  365. package/.next/server/pages/api/graphql.js.nft.json +0 -1
  366. package/.next/server/pages/api/health/live.js +0 -1
  367. package/.next/server/pages/api/health/live.js.nft.json +0 -1
  368. package/.next/server/pages/api/health/ready.js +0 -1
  369. package/.next/server/pages/api/health/ready.js.nft.json +0 -1
  370. package/.next/server/pages/api/preview.js +0 -1
  371. package/.next/server/pages/api/preview.js.nft.json +0 -1
  372. package/.next/server/pages/checkout.js +0 -1
  373. package/.next/server/pages/checkout.js.nft.json +0 -1
  374. package/.next/server/pages/en-US/404.html +0 -42
  375. package/.next/server/pages/en-US/404.json +0 -1
  376. package/.next/server/pages/en-US/500.html +0 -42
  377. package/.next/server/pages/en-US/500.json +0 -1
  378. package/.next/server/pages/en-US/checkout.html +0 -42
  379. package/.next/server/pages/en-US/checkout.json +0 -1
  380. package/.next/server/pages/en-US/login.html +0 -42
  381. package/.next/server/pages/en-US/login.json +0 -1
  382. package/.next/server/pages/en-US/s.html +0 -42
  383. package/.next/server/pages/en-US/s.json +0 -1
  384. package/.next/server/pages/en-US.html +0 -42
  385. package/.next/server/pages/en-US.json +0 -1
  386. package/.next/server/pages/index.js +0 -1
  387. package/.next/server/pages/index.js.nft.json +0 -1
  388. package/.next/server/pages/login.js +0 -1
  389. package/.next/server/pages/login.js.nft.json +0 -1
  390. package/.next/server/pages/pvt/account/403.js +0 -1
  391. package/.next/server/pages/pvt/account/403.js.nft.json +0 -1
  392. package/.next/server/pages/pvt/account/404.js +0 -1
  393. package/.next/server/pages/pvt/account/404.js.nft.json +0 -1
  394. package/.next/server/pages/pvt/account/[...unknown].js +0 -1
  395. package/.next/server/pages/pvt/account/[...unknown].js.nft.json +0 -1
  396. package/.next/server/pages/pvt/account/orders/[id].js +0 -1
  397. package/.next/server/pages/pvt/account/orders/[id].js.nft.json +0 -1
  398. package/.next/server/pages/pvt/account/orders.js +0 -1
  399. package/.next/server/pages/pvt/account/orders.js.nft.json +0 -1
  400. package/.next/server/pages/pvt/account/profile.js +0 -1
  401. package/.next/server/pages/pvt/account/profile.js.nft.json +0 -1
  402. package/.next/server/pages/pvt/account/security.js +0 -1
  403. package/.next/server/pages/pvt/account/security.js.nft.json +0 -1
  404. package/.next/server/pages/pvt/account/user-details.js +0 -1
  405. package/.next/server/pages/pvt/account/user-details.js.nft.json +0 -1
  406. package/.next/server/pages/pvt/account.js +0 -1
  407. package/.next/server/pages/pvt/account.js.nft.json +0 -1
  408. package/.next/server/pages/s.js +0 -1
  409. package/.next/server/pages/s.js.nft.json +0 -1
  410. package/.next/server/pages-manifest.json +0 -1
  411. package/.next/server/webpack-api-runtime.js +0 -1
  412. package/.next/server/webpack-runtime.js +0 -1
  413. package/.next/static/NAuY8lqaeBBZf9CMZuYI2/_buildManifest.js +0 -1
  414. package/.next/static/NAuY8lqaeBBZf9CMZuYI2/_ssgManifest.js +0 -1
  415. package/.next/static/chunks/227-f109b24190a008ab.js +0 -8
  416. package/.next/static/chunks/2284.185d834d9829b652.js +0 -1
  417. package/.next/static/chunks/2851.e68ed3c8d27a032a.js +0 -1
  418. package/.next/static/chunks/2927.5a79877943a6bf7c.js +0 -1
  419. package/.next/static/chunks/3155.7bc4c730a79d9ecb.js +0 -1
  420. package/.next/static/chunks/3166-0b30a3887c0c346d.js +0 -1
  421. package/.next/static/chunks/3399.017c5209b74b0d97.js +0 -1
  422. package/.next/static/chunks/3483.ef53d632a763da3f.js +0 -1
  423. package/.next/static/chunks/349.b3b38e1e9f719dda.js +0 -1
  424. package/.next/static/chunks/3802.5e8b0b689eacc0f8.js +0 -1
  425. package/.next/static/chunks/3836.620480018be48233.js +0 -1
  426. package/.next/static/chunks/4436.e7af66f0b654ee36.js +0 -1
  427. package/.next/static/chunks/4803.b1920b6e3a1a6304.js +0 -1
  428. package/.next/static/chunks/5796.4351370494d8b8b6.js +0 -1
  429. package/.next/static/chunks/6393.55f991b1a6bb4fe7.js +0 -1
  430. package/.next/static/chunks/6700.b23221a57514e737.js +0 -1
  431. package/.next/static/chunks/6789.960162355435a81d.js +0 -1
  432. package/.next/static/chunks/7191-befad64ba238a817.js +0 -1
  433. package/.next/static/chunks/7692.8d5bf4560341a2f6.js +0 -1
  434. package/.next/static/chunks/7861.7f9f1a124a43da30.js +0 -6
  435. package/.next/static/chunks/8068.43663fb64762f7b8.js +0 -1
  436. package/.next/static/chunks/83.e0c16f0299b364a5.js +0 -1
  437. package/.next/static/chunks/8325.dabe9aa22eaadeed.js +0 -1
  438. package/.next/static/chunks/8587.53c8ce185ed1a2cb.js +0 -1
  439. package/.next/static/chunks/9173-e89da04079c35360.js +0 -2
  440. package/.next/static/chunks/9237.7555603703f81bf7.js +0 -1
  441. package/.next/static/chunks/9298-1d1ed0a54133cb59.js +0 -28
  442. package/.next/static/chunks/9399.d116a7fd62024783.js +0 -1
  443. package/.next/static/chunks/941.3e2782ab9c490eb0.js +0 -1
  444. package/.next/static/chunks/9701.f9a0bfffba8807dc.js +0 -1
  445. package/.next/static/chunks/9909.787576b171184aa5.js +0 -1
  446. package/.next/static/chunks/9960.7ef77f8a8b046170.js +0 -1
  447. package/.next/static/chunks/9979.aedbc094bdc2c599.js +0 -1
  448. package/.next/static/chunks/BannerNewsletter.00328ad162109908.js +0 -1
  449. package/.next/static/chunks/BannerText.dbbec87cadb8a521.js +0 -1
  450. package/.next/static/chunks/ButtonSignIn.f6e210eaca873e48.js +0 -1
  451. package/.next/static/chunks/CartItem.b34e295377f6f94f.js +0 -1
  452. package/.next/static/chunks/CartSidebar.ac447d1ef6276da1.js +0 -1
  453. package/.next/static/chunks/Dropdown.38b48c749bad6b60.js +0 -1
  454. package/.next/static/chunks/DropdownButton.66fa6b25dddb3a07.js +0 -1
  455. package/.next/static/chunks/DropdownItem.38b8f5a07c7f422a.js +0 -1
  456. package/.next/static/chunks/DropdownMenu.48b11521a24c6efd.js +0 -1
  457. package/.next/static/chunks/EmptyCart.320e2f94a0d5e6b1.js +0 -1
  458. package/.next/static/chunks/FilterSkeleton.7ab99e28f92341c9.js +0 -1
  459. package/.next/static/chunks/Footer.166b02c2f13980c6.js +0 -1
  460. package/.next/static/chunks/Gift.c49ba26da2fbab5e.js +0 -1
  461. package/.next/static/chunks/Newsletter.fc406f6029057022.js +0 -1
  462. package/.next/static/chunks/OrderSummary.5957d69f990d9add.js +0 -1
  463. package/.next/static/chunks/PreviewTag.f4f0c8710a7bcad9.js +0 -1
  464. package/.next/static/chunks/ProductShelf.cc46f5cdeeaeaff6.js +0 -1
  465. package/.next/static/chunks/ProductTiles.9b804e51b0704d60.js +0 -1
  466. package/.next/static/chunks/RegionModal.319ad44250cb12d2.js +0 -1
  467. package/.next/static/chunks/RegionSlider.0e0e93369f4f9c92.js +0 -1
  468. package/.next/static/chunks/ScrollToTopButton.c15431f2e8bb93ef.js +0 -1
  469. package/.next/static/chunks/ShoppingAssistant.ba191eb4dbed67e5.js +0 -1
  470. package/.next/static/chunks/Toast.5f90f0436708be5e.js +0 -1
  471. package/.next/static/chunks/UIBannerText.7315b16af4457059.js +0 -1
  472. package/.next/static/chunks/UISKUMatrixSidebar.b4349b2682bbe183.js +0 -1
  473. package/.next/static/chunks/UIToast.19a8664c01a00d3a.js +0 -1
  474. package/.next/static/chunks/framework-d514426edf885c68.js +0 -33
  475. package/.next/static/chunks/main-ec03882c4375091d.js +0 -1
  476. package/.next/static/chunks/pages/404-e3dfbbac003b02a2.js +0 -1
  477. package/.next/static/chunks/pages/500-c7814e085081d7b8.js +0 -1
  478. package/.next/static/chunks/pages/[...slug]-dc8a395a43b081f1.js +0 -1
  479. package/.next/static/chunks/pages/[slug]/p-644f1132f5a0f736.js +0 -1
  480. package/.next/static/chunks/pages/_app-84aaff8a9d4de60e.js +0 -1
  481. package/.next/static/chunks/pages/_error-2b0148be56a716e9.js +0 -1
  482. package/.next/static/chunks/pages/checkout-2071467ad42dfb99.js +0 -1
  483. package/.next/static/chunks/pages/index-8774d5b4a8744eb2.js +0 -1
  484. package/.next/static/chunks/pages/login-e74747c9f9cb5b49.js +0 -1
  485. package/.next/static/chunks/pages/pvt/account/403-491e6b062487b30f.js +0 -1
  486. package/.next/static/chunks/pages/pvt/account/404-5ad472d9f2ee1980.js +0 -1
  487. package/.next/static/chunks/pages/pvt/account/[...unknown]-f80f645594d2740c.js +0 -1
  488. package/.next/static/chunks/pages/pvt/account/orders/[id]-64e42157c0974c48.js +0 -1
  489. package/.next/static/chunks/pages/pvt/account/orders-faab39f2aff05fae.js +0 -1
  490. package/.next/static/chunks/pages/pvt/account/profile-9672177ee2665bc0.js +0 -1
  491. package/.next/static/chunks/pages/pvt/account/security-03eb1ccab9a30563.js +0 -1
  492. package/.next/static/chunks/pages/pvt/account/user-details-7838e3f79781596c.js +0 -1
  493. package/.next/static/chunks/pages/pvt/account-65fefcc699344bdb.js +0 -1
  494. package/.next/static/chunks/pages/s-162ba789e6611175.js +0 -1
  495. package/.next/static/chunks/polyfills-c67a75d1b6f99dc8.js +0 -1
  496. package/.next/static/chunks/webpack-f621ff3e951d2982.js +0 -1
  497. package/.next/static/css/02259c549b2179f2.css +0 -1
  498. package/.next/static/css/02eaf202d3d11fd1.css +0 -1
  499. package/.next/static/css/033e241c58ba6fbb.css +0 -1
  500. package/.next/static/css/042d012bc1895b5c.css +0 -1
  501. package/.next/static/css/14176a4704ff97b2.css +0 -1
  502. package/.next/static/css/146a8678babc61bf.css +0 -1
  503. package/.next/static/css/16d65422bfa80f51.css +0 -1
  504. package/.next/static/css/1c4983f11b861ea0.css +0 -1
  505. package/.next/static/css/204754bbd8cc4bef.css +0 -1
  506. package/.next/static/css/24d91007fc96be10.css +0 -1
  507. package/.next/static/css/2980acad3f8e1028.css +0 -1
  508. package/.next/static/css/2c19433e38fd7533.css +0 -1
  509. package/.next/static/css/31380ebc6e671486.css +0 -1
  510. package/.next/static/css/42df884711219edb.css +0 -1
  511. package/.next/static/css/4f0597b0bf0ef2e1.css +0 -1
  512. package/.next/static/css/54cedf4b65e5863f.css +0 -1
  513. package/.next/static/css/6580147d7ab8fcca.css +0 -1
  514. package/.next/static/css/6819dddf2b1b5ea7.css +0 -1
  515. package/.next/static/css/7ef7762f412ed665.css +0 -1
  516. package/.next/static/css/82f3cd529b672812.css +0 -1
  517. package/.next/static/css/837662922091162f.css +0 -1
  518. package/.next/static/css/8e6ab59ace21e03b.css +0 -1
  519. package/.next/static/css/a852afa9be31e946.css +0 -1
  520. package/.next/static/css/b684b347c5cc6514.css +0 -1
  521. package/.next/static/css/bcdaef0a83277ef5.css +0 -1
  522. package/.next/static/css/c33615999008d3f9.css +0 -1
  523. package/.next/static/css/c34f6ea9953e2f63.css +0 -1
  524. package/.next/static/css/ea11d74a2848b093.css +0 -1
  525. package/.next/static/css/f16438bcf4508fb3.css +0 -1
  526. package/.next/static/css/f60366555f563deb.css +0 -1
  527. package/.next/trace +0 -143
  528. package/.turbo/turbo-build.log +0 -140
  529. package/jest.config.js +0 -17
  530. package/public/~partytown/debug/partytown-atomics.js +0 -556
  531. package/public/~partytown/debug/partytown-media.js +0 -374
  532. package/public/~partytown/debug/partytown-sandbox-sw.js +0 -543
  533. package/public/~partytown/debug/partytown-sw.js +0 -59
  534. package/public/~partytown/debug/partytown-ww-atomics.js +0 -1789
  535. package/public/~partytown/debug/partytown-ww-sw.js +0 -1781
  536. package/public/~partytown/debug/partytown.js +0 -72
  537. package/public/~partytown/partytown-atomics.js +0 -2
  538. package/public/~partytown/partytown-media.js +0 -2
  539. package/public/~partytown/partytown-sw.js +0 -2
  540. package/public/~partytown/partytown.js +0 -2
  541. package/src/middleware__DISABLED.ts +0 -65
  542. package/src/server/generator/generateGraphQLSchemaFile.ts +0 -3
  543. package/src/server/generator/schema.ts +0 -81
@@ -0,0 +1,337 @@
1
+ import storeConfig from 'discovery.config'
2
+ import type { LocalesSettings } from 'src/typings/locales'
3
+ import { filterChannel } from 'src/utils/utilities'
4
+
5
+ export type CustomPathInfo = {
6
+ path: string
7
+ locale: string
8
+ hostname?: string
9
+ }
10
+
11
+ export type SubdomainBinding = {
12
+ hostname: string
13
+ locale: string
14
+ }
15
+
16
+ type BindingsByType = {
17
+ customPaths: CustomPathInfo[]
18
+ subdomainBindings: SubdomainBinding[]
19
+ }
20
+
21
+ let bindingsByTypeCache: BindingsByType | null = null
22
+
23
+ /** Returns path only (strip query ? and hash #) for prefix detection and building */
24
+ function getPathOnly(pathOrLink: string): string {
25
+ return pathOrLink.split('?')[0].split('#')[0]
26
+ }
27
+
28
+ /**
29
+ * Checks if an incoming path matches a binding path.
30
+ * Supports exact match and prefix matching for paths with additional segments.
31
+ * @param incomingPath - The path from the URL being checked (e.g., '/europe/it/products')
32
+ * @param bindingPath - The path from the binding configuration (e.g., '/europe/it')
33
+ * @returns true if they match (exact or prefix match)
34
+ * @example
35
+ * matchesBindingPath('/europe/it', '/europe/it') // true (exact match)
36
+ * matchesBindingPath('/europe/it/products', '/europe/it') // true (prefix match)
37
+ * matchesBindingPath('/europe/item', '/europe/it') // false (not a match)
38
+ * matchesBindingPath('/pt-BR/office', '/pt-BR') // true (prefix match)
39
+ * matchesBindingPath('/', '/') // true (root match)
40
+ */
41
+ export function matchesBindingPath(
42
+ incomingPath: string,
43
+ bindingPath: string
44
+ ): boolean {
45
+ // Normalize paths (remove trailing slash, but keep '/' for root)
46
+ const normalizedIncoming =
47
+ incomingPath.replace(/\/$/, '').toLowerCase() || '/'
48
+ const normalizedBinding = bindingPath.replace(/\/$/, '').toLowerCase() || '/'
49
+
50
+ // Root path only matches exact root
51
+ if (normalizedBinding === '/') {
52
+ return normalizedIncoming === '/'
53
+ }
54
+
55
+ // For non-root paths, match exact or prefix
56
+ return (
57
+ normalizedIncoming === normalizedBinding ||
58
+ normalizedIncoming.startsWith(`${normalizedBinding}/`)
59
+ )
60
+ }
61
+
62
+ /**
63
+ * Checks whether a locale code exists in the localization configuration.
64
+ */
65
+ export function isValidLocale(locale: string): boolean {
66
+ if (!storeConfig.localization?.enabled) {
67
+ return false
68
+ }
69
+
70
+ return locale in (storeConfig.localization.locales || {})
71
+ }
72
+
73
+ /**
74
+ * Single pass over all locale bindings, classifying each as either a
75
+ * custom-path binding (has a non-root pathname) or a subdomain binding
76
+ * (root pathname — hostname alone determines the locale).
77
+ * Results are cached after the first call.
78
+ */
79
+ function getBindingsByType(): BindingsByType {
80
+ if (bindingsByTypeCache !== null) {
81
+ return bindingsByTypeCache
82
+ }
83
+
84
+ const customPaths: CustomPathInfo[] = []
85
+ const subdomainBindings: SubdomainBinding[] = []
86
+
87
+ if (!storeConfig.localization?.enabled) {
88
+ bindingsByTypeCache = { customPaths, subdomainBindings }
89
+ return bindingsByTypeCache
90
+ }
91
+
92
+ const locales = (storeConfig.localization.locales ||
93
+ {}) as LocalesSettings['locales']
94
+ const localeCodes = new Set(Object.keys(locales))
95
+
96
+ for (const [localeCode, localeConfig] of Object.entries(locales)) {
97
+ if (!localeConfig?.bindings || !Array.isArray(localeConfig.bindings)) {
98
+ continue
99
+ }
100
+
101
+ for (const binding of localeConfig.bindings) {
102
+ if (!binding.url) {
103
+ continue
104
+ }
105
+
106
+ try {
107
+ const urlObj = new URL(binding.url)
108
+ const pathname = urlObj.pathname.replace(/\/$/, '') || '/'
109
+
110
+ if (pathname === '/') {
111
+ subdomainBindings.push({
112
+ hostname: urlObj.hostname,
113
+ locale: localeCode,
114
+ })
115
+ } else if (!localeCodes.has(pathname.slice(1))) {
116
+ customPaths.push({
117
+ path: pathname,
118
+ locale: localeCode,
119
+ hostname: urlObj.hostname,
120
+ })
121
+ }
122
+ } catch {
123
+ console.warn(
124
+ `[bindingPaths] Skipping invalid binding URL: "${binding.url}"`
125
+ )
126
+ continue
127
+ }
128
+ }
129
+ }
130
+
131
+ customPaths.sort((a, b) => b.path.length - a.path.length)
132
+
133
+ bindingsByTypeCache = { customPaths, subdomainBindings }
134
+ return bindingsByTypeCache
135
+ }
136
+
137
+ /**
138
+ * Returns bindings that use a custom URL path (non-root pathname).
139
+ * Sorted by path length descending (most specific first).
140
+ */
141
+ export function getCustomPathsFromBindings(): CustomPathInfo[] {
142
+ return getBindingsByType().customPaths
143
+ }
144
+
145
+ /**
146
+ * Returns bindings where the hostname alone determines the locale
147
+ * (root pathname '/').
148
+ */
149
+ export function getSubdomainBindings(): SubdomainBinding[] {
150
+ return getBindingsByType().subdomainBindings
151
+ }
152
+
153
+ /**
154
+ * Extracts custom path prefix from current pathname
155
+ * @param pathname - Current pathname (e.g., '/europe/it/apparel')
156
+ * @returns Custom path prefix if found, null otherwise
157
+ */
158
+ export function extractCustomPathPrefix(pathname: string): string | null {
159
+ const pathOnly = getPathOnly(pathname)
160
+ const customPaths = getCustomPathsFromBindings()
161
+
162
+ for (const { path } of customPaths) {
163
+ if (matchesBindingPath(pathOnly, path)) {
164
+ return path
165
+ }
166
+ }
167
+
168
+ return null
169
+ }
170
+
171
+ /**
172
+ * Adds custom path prefix to a link if needed
173
+ * @param link - The link href (e.g., '/apparel' or '/europe/it/apparel')
174
+ * @param currentPathname - Current pathname (e.g., '/europe/it/apparel')
175
+ * @returns Link with prefix if needed (e.g., '/europe/it/apparel')
176
+ */
177
+ export function addCustomPathPrefix(
178
+ link: string,
179
+ currentPathname: string
180
+ ): string {
181
+ if (!storeConfig.localization?.enabled) {
182
+ return link
183
+ }
184
+
185
+ if (!link.startsWith('/')) {
186
+ return link
187
+ }
188
+
189
+ if (extractCustomPathPrefix(link) !== null) {
190
+ return link
191
+ }
192
+
193
+ const prefix = extractCustomPathPrefix(currentPathname)
194
+
195
+ if (!prefix) {
196
+ return link
197
+ }
198
+
199
+ const pathOnly = getPathOnly(link)
200
+ const queryAndHash = link.slice(pathOnly.length)
201
+ return `${prefix}${pathOnly}${queryAndHash}`
202
+ }
203
+
204
+ /**
205
+ * Collects all binding URL paths from the locales config (both locale-code
206
+ * paths like "/pt-BR" and custom paths like "/europe/it").
207
+ * Returns deduplicated paths sorted by length descending (most specific first).
208
+ *
209
+ * Unlike `getCustomPathsFromBindings`, this does NOT filter out locale-code paths.
210
+ *
211
+ * @returns Array of binding path strings sorted longest-first
212
+ */
213
+ export function collectAllBindingPaths(): string[] {
214
+ if (!storeConfig.localization?.enabled) {
215
+ return []
216
+ }
217
+
218
+ const locales = (storeConfig.localization.locales ||
219
+ {}) as LocalesSettings['locales']
220
+ const pathSet = new Set<string>()
221
+
222
+ for (const localeConfig of Object.values(locales)) {
223
+ if (!localeConfig?.bindings || !Array.isArray(localeConfig.bindings)) {
224
+ continue
225
+ }
226
+
227
+ for (const binding of localeConfig.bindings) {
228
+ if (!binding.url) {
229
+ continue
230
+ }
231
+
232
+ try {
233
+ const pathname = new URL(binding.url).pathname.replace(/\/$/, '')
234
+
235
+ if (pathname && pathname !== '/') {
236
+ pathSet.add(pathname)
237
+ }
238
+ } catch {
239
+ console.warn(
240
+ `[bindingPaths] Skipping invalid binding URL: "${binding.url}"`
241
+ )
242
+ }
243
+ }
244
+ }
245
+
246
+ return Array.from(pathSet).sort((a, b) => b.length - a.length)
247
+ }
248
+
249
+ /**
250
+ * Strips the current binding path prefix from `currentPathname`, returning the
251
+ * remaining "page path" (e.g. "/products/item").
252
+ *
253
+ * @param currentPathname - The full browser pathname (e.g., "/pt-BR/products/item")
254
+ * @returns The page path after stripping the matched prefix, or the full pathname if none matches
255
+ */
256
+ export function getPagePath(currentPathname: string): string {
257
+ const pathOnly = getPathOnly(currentPathname)
258
+ const bindingPaths = collectAllBindingPaths()
259
+
260
+ for (const bp of bindingPaths) {
261
+ if (matchesBindingPath(pathOnly, bp)) {
262
+ const normalizedBp = bp.replace(/\/$/, '').toLowerCase()
263
+ const normalizedPath = pathOnly.replace(/\/$/, '').toLowerCase()
264
+
265
+ if (normalizedPath === normalizedBp) {
266
+ return ''
267
+ }
268
+
269
+ const prefixEnd =
270
+ normalizedPath.indexOf(normalizedBp) === 0 ? bp.length : 0
271
+
272
+ return prefixEnd > 0 ? pathOnly.slice(prefixEnd) : pathOnly
273
+ }
274
+ }
275
+
276
+ return pathOnly
277
+ }
278
+
279
+ /**
280
+ * Resolves the channel string for a given locale by looking up its
281
+ * binding's salesChannel. Falls back to the default session channel when the
282
+ * locale is unknown or localization is disabled.
283
+ *
284
+ * The returned string is a JSON channel object (same format used by
285
+ * `storeConfig.session.channel`) with `hasOnlyDefaultSalesChannel` stripped so
286
+ * it can be passed directly as a `channel` facet/locator value.
287
+ */
288
+ export function getChannelForLocale(locale: string | undefined): string {
289
+ const defaultChannel = storeConfig.session.channel
290
+
291
+ if (!locale || !storeConfig.localization?.enabled) {
292
+ return filterChannel(defaultChannel)
293
+ }
294
+
295
+ const localeConfig = (storeConfig.localization.locales ?? {})[locale]
296
+
297
+ if (!localeConfig?.bindings?.length) {
298
+ return filterChannel(defaultChannel)
299
+ }
300
+
301
+ const binding =
302
+ localeConfig.bindings.find((b) => b.isDefault) ?? localeConfig.bindings[0]
303
+
304
+ if (!binding?.salesChannel) {
305
+ return filterChannel(defaultChannel)
306
+ }
307
+
308
+ try {
309
+ const channelObj = JSON.parse(defaultChannel)
310
+ const updatedChannel = { ...channelObj, salesChannel: binding.salesChannel }
311
+ return filterChannel(JSON.stringify(updatedChannel))
312
+ } catch {
313
+ console.warn('[bindingPaths] Failed to parse channel')
314
+ return filterChannel(defaultChannel)
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Builds a redirect URL by combining the target binding URL with the current
320
+ * page path and query string.
321
+ *
322
+ * @param bindingUrl - The new binding's base URL (e.g., "http://localhost:3000/en-US")
323
+ * @param currentUrl - The current page URL path including query string (e.g., "/pt-BR/products/item?color=red")
324
+ * @returns The full redirect URL preserving the page path and query string
325
+ */
326
+ export function buildRedirectUrl(
327
+ bindingUrl: string,
328
+ currentUrl: string
329
+ ): string {
330
+ const pathOnly = getPathOnly(currentUrl)
331
+ const suffix = currentUrl.slice(pathOnly.length)
332
+
333
+ const pagePath = getPagePath(pathOnly)
334
+ const baseUrl = bindingUrl.replace(/\/$/, '')
335
+
336
+ return `${baseUrl}${pagePath}${suffix}`
337
+ }
@@ -0,0 +1,69 @@
1
+ import storeConfig from 'discovery.config.default.js'
2
+ import type { LocalesSettings } from '../../typings/locales'
3
+ import { getRequestHostname } from '../getRequestHostname'
4
+
5
+ function getLocalesSettings(): LocalesSettings {
6
+ return storeConfig.localization as LocalesSettings
7
+ }
8
+
9
+ /**
10
+ * A binding is valid if the hostname matches AND the locale code matches.
11
+ * This works for both:
12
+ * - Path-based bindings (e.g., "https://brandless.fast.store/pt-BR")
13
+ * - Subdomain bindings (e.g., "https://canada.brandless.fast.store" for "fr-CA")
14
+ *
15
+ * @param hostname - The request hostname
16
+ * @param localeCode - The locale code to check
17
+ * @returns true if there's a valid binding for this hostname and locale, false otherwise
18
+ */
19
+ function hasValidBindingForHostname(
20
+ hostname: string,
21
+ localeCode: string
22
+ ): boolean {
23
+ const localesSettings = getLocalesSettings()
24
+ const localeData = localesSettings.locales[localeCode]
25
+
26
+ if (!localeData?.bindings) {
27
+ return false
28
+ }
29
+
30
+ return localeData.bindings.some((binding) => {
31
+ if (!binding.url) return false
32
+
33
+ try {
34
+ const bindingUrl = new URL(binding.url)
35
+
36
+ return bindingUrl.hostname === hostname
37
+ } catch {
38
+ return false
39
+ }
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Validates if a locale has a valid binding for the hostname.
45
+ *
46
+ * @param hostname - The request hostname
47
+ * @param locale - The locale code (e.g., "fr-CA")
48
+ * @returns true if the locale/hostname combination is valid, false otherwise
49
+ */
50
+ export function validateLocaleForHostname(
51
+ hostname: string,
52
+ locale: string
53
+ ): boolean {
54
+ const normalizedHostname = getRequestHostname(hostname)
55
+
56
+ if (!normalizedHostname) {
57
+ return false
58
+ }
59
+
60
+ // For localhost, allow all locales (useful for development)
61
+ if (
62
+ normalizedHostname === 'localhost' ||
63
+ normalizedHostname === '127.0.0.1'
64
+ ) {
65
+ return true
66
+ }
67
+
68
+ return hasValidBindingForHostname(normalizedHostname, locale)
69
+ }
@@ -0,0 +1,36 @@
1
+ import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'
2
+ import { validateLocaleForHostname } from './validateLocaleForHostname'
3
+
4
+ type ServerPropsHandler<P> = (
5
+ context: GetServerSidePropsContext
6
+ ) => Promise<GetServerSidePropsResult<P>>
7
+
8
+ /**
9
+ * Higher Order Function that validates locale binding before executing getServerSideProps
10
+ * @param getServerSidePropsFn - The original getServerSideProps function
11
+ * @returns getServerSideProps function with locale binding validation
12
+ */
13
+ export function withLocaleValidationSSR<P extends Record<string, any>>(
14
+ getServerSidePropsFn: ServerPropsHandler<P>
15
+ ): ServerPropsHandler<P> {
16
+ return async (context: GetServerSidePropsContext) => {
17
+ const { locale, req } = context
18
+
19
+ const hostname = req.headers.host || ''
20
+
21
+ if (!locale) {
22
+ // If there's no locale in context, execute original function
23
+ return getServerSidePropsFn(context)
24
+ }
25
+
26
+ const isValid = validateLocaleForHostname(hostname, locale)
27
+
28
+ if (!isValid) {
29
+ return {
30
+ notFound: true,
31
+ }
32
+ }
33
+
34
+ return getServerSidePropsFn(context)
35
+ }
36
+ }
@@ -109,6 +109,28 @@ export const buildFormData = (
109
109
  export const toArray = <T>(x: T[] | T | undefined) =>
110
110
  Array.isArray(x) ? x : x ? [x] : []
111
111
 
112
+ /**
113
+ * Filters the channel object by removing the hasOnlyDefaultSalesChannel key.
114
+ * This key is used only in the useAuth hook and is only required to send on the ValidateSession mutation,
115
+ * so we remove it from the session's channel object to avoid unnecessary cache invalidations and query executions
116
+ *
117
+ * @param channel - The channel string to filter.
118
+ * @returns The filtered channel string.
119
+ */
120
+ export function filterChannel(channel: string): string {
121
+ if (!channel) {
122
+ return '{}'
123
+ }
124
+ try {
125
+ const { hasOnlyDefaultSalesChannel, ...filteredChannel } =
126
+ JSON.parse(channel)
127
+ return JSON.stringify(filteredChannel)
128
+ } catch {
129
+ console.warn('[filterChannel] Invalid channel JSON:', channel)
130
+ return channel
131
+ }
132
+ }
133
+
112
134
  /**
113
135
  * Formats the file name to a more user-friendly version.
114
136
  * @param fileName - The original file name.
@@ -0,0 +1,93 @@
1
+ import type { NextApiRequest, NextApiResponse } from 'next'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+
4
+ import handler from '../../../src/pages/api/preview'
5
+ import { contentService } from '../../../src/server/content/service'
6
+
7
+ const createResponse = () => {
8
+ const res = {
9
+ clearPreviewData: vi.fn(),
10
+ setPreviewData: vi.fn(),
11
+ redirect: vi.fn(),
12
+ status: vi.fn().mockReturnThis(),
13
+ end: vi.fn(),
14
+ }
15
+
16
+ return res as unknown as NextApiResponse & typeof res
17
+ }
18
+
19
+ describe('/api/preview', () => {
20
+ beforeEach(() => {
21
+ vi.restoreAllMocks()
22
+ })
23
+
24
+ it('stores the requested locale in the preview session data', async () => {
25
+ vi.spyOn(contentService, 'getSingleContent').mockResolvedValue({} as never)
26
+
27
+ const req = {
28
+ query: {
29
+ contentType: 'home',
30
+ documentId: 'entry-1',
31
+ versionId: 'branch-1',
32
+ locale: 'pt-BR',
33
+ },
34
+ } as unknown as NextApiRequest
35
+ const res = createResponse()
36
+
37
+ await handler(req, res)
38
+
39
+ expect(contentService.getSingleContent).toHaveBeenCalledWith({
40
+ contentType: 'home',
41
+ documentId: 'entry-1',
42
+ versionId: 'branch-1',
43
+ previewData: {
44
+ contentType: 'home',
45
+ documentId: 'entry-1',
46
+ versionId: 'branch-1',
47
+ },
48
+ locale: 'pt-BR',
49
+ releaseId: undefined,
50
+ slug: undefined,
51
+ })
52
+ expect(res.setPreviewData).toHaveBeenCalledWith(
53
+ {
54
+ contentType: 'home',
55
+ documentId: 'entry-1',
56
+ versionId: 'branch-1',
57
+ locale: 'pt-BR',
58
+ },
59
+ {
60
+ maxAge: 3600,
61
+ path: '/',
62
+ }
63
+ )
64
+ expect(res.redirect).toHaveBeenCalledWith('/')
65
+ })
66
+
67
+ it('keeps preview session data locator-only when locale is absent', async () => {
68
+ vi.spyOn(contentService, 'getSingleContent').mockResolvedValue({} as never)
69
+
70
+ const req = {
71
+ query: {
72
+ contentType: 'home',
73
+ documentId: 'entry-1',
74
+ versionId: 'branch-1',
75
+ },
76
+ } as unknown as NextApiRequest
77
+ const res = createResponse()
78
+
79
+ await handler(req, res)
80
+
81
+ expect(res.setPreviewData).toHaveBeenCalledWith(
82
+ {
83
+ contentType: 'home',
84
+ documentId: 'entry-1',
85
+ versionId: 'branch-1',
86
+ },
87
+ {
88
+ maxAge: 3600,
89
+ path: '/',
90
+ }
91
+ )
92
+ })
93
+ })