@faststore/core 4.10.1-dev.6 → 4.10.1-dev.7

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 (60) hide show
  1. package/.turbo/turbo-generate.log +3 -3
  2. package/.turbo/turbo-test.log +176 -167
  3. package/@generated/graphql.ts +1 -0
  4. package/@generated/schema.graphql +1 -1
  5. package/CHANGELOG.md +29 -0
  6. package/package.json +6 -6
  7. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +3 -3
  8. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerBody.tsx +5 -7
  9. package/src/components/account/Drawer/OrganizationSignInButton/OrganizationSignInButton.tsx +4 -1
  10. package/src/components/account/Layout/Layout.tsx +5 -7
  11. package/src/components/account/Menu/Menu.tsx +1 -1
  12. package/src/components/navigation/Navbar/Navbar.tsx +3 -3
  13. package/src/components/navigation/Navbar/NavbarAccountArea.tsx +5 -5
  14. package/src/components/navigation/NavbarSlider/NavbarSlider.tsx +3 -3
  15. package/src/experimental/myAccountCmsServerSideProps.ts +3 -3
  16. package/src/experimental/myAccountServerSideProps.ts +4 -4
  17. package/src/pages/[slug]/p.tsx +11 -2
  18. package/src/pages/api/fs/logout.ts +18 -10
  19. package/src/pages/api/fs/switch-contract.ts +170 -21
  20. package/src/pages/api/graphql.ts +4 -3
  21. package/src/pages/pvt/account/cards/index.tsx +5 -5
  22. package/src/pages/pvt/account/orders/[id].tsx +5 -5
  23. package/src/pages/pvt/account/orders/index.tsx +5 -5
  24. package/src/pages/pvt/account/profile.tsx +5 -5
  25. package/src/pages/pvt/account/quotes/index.tsx +5 -5
  26. package/src/pages/pvt/account/security.tsx +5 -5
  27. package/src/pages/pvt/account/user-details.tsx +6 -6
  28. package/src/sdk/account/getB2BSessionClaims.ts +1 -1
  29. package/src/sdk/account/getIsB2BBuyer.ts +28 -0
  30. package/src/sdk/account/refreshToken.ts +89 -11
  31. package/src/sdk/account/refreshTokenPaths.ts +25 -0
  32. package/src/sdk/account/useRefreshToken.ts +10 -5
  33. package/src/sdk/product/isProductNotFound.ts +13 -0
  34. package/src/sdk/product/useProductNotFoundRedirect.ts +43 -0
  35. package/src/sdk/product/useProductQuery.ts +29 -0
  36. package/src/sdk/session/index.ts +4 -4
  37. package/src/utils/getCheckoutParentDomain.ts +53 -0
  38. package/src/utils/trustedForwardedHost.ts +18 -0
  39. package/test/components/account/Layout.test.tsx +1 -1
  40. package/test/components/account/Menu.browser.test.tsx +7 -5
  41. package/test/components/account/OrganizationDrawer.browser.test.tsx +13 -13
  42. package/test/components/account/OrganizationDrawerBody.test.tsx +3 -3
  43. package/test/components/account/OrganizationSignInButton.browser.test.tsx +19 -4
  44. package/test/components/navigation/Navbar.browser.test.tsx +3 -3
  45. package/test/components/navigation/NavbarAccountArea.browser.test.tsx +5 -5
  46. package/test/components/navigation/NavbarSlider.browser.test.tsx +3 -3
  47. package/test/experimental/myAccountCmsServerSideProps.test.ts +5 -5
  48. package/test/pages/api/logout.test.ts +106 -0
  49. package/test/pages/api/switch-contract.test.ts +443 -15
  50. package/test/pages/pvt/account/cards/index.test.ts +3 -3
  51. package/test/sdk/account/getIsB2BBuyer.test.ts +52 -0
  52. package/test/sdk/account/refreshToken.browser.test.ts +10 -1
  53. package/test/sdk/account/refreshToken.test.ts +150 -18
  54. package/test/sdk/account/useRefreshToken.browser.test.ts +4 -1
  55. package/test/sdk/product/useProductNotFoundRedirect.browser.test.ts +83 -0
  56. package/test/sdk/product/useProductQuery.browser.test.ts +154 -0
  57. package/test/sdk/session/refreshTokenRoute.browser.test.ts +106 -0
  58. package/test/sdk/session/validateSession.browser.test.ts +4 -1
  59. package/test/utils/trustedForwardedHost.test.ts +21 -0
  60. package/src/sdk/account/getIsRepresentative.ts +0 -20
@@ -1280,7 +1280,7 @@ type StoreAuthor {
1280
1280
 
1281
1281
  type StoreB2B {
1282
1282
  customerId: String!
1283
- isRepresentative: Boolean
1283
+ isRepresentative: Boolean @deprecated(reason: "Always true when `b2b` is set: a B2B session is identified by `unitId` and `customerId`. Check that `b2b` is not null instead. Scheduled for removal in the next major.")
1284
1284
  unitName: String
1285
1285
  unitId: String
1286
1286
  firstName: String
package/CHANGELOG.md CHANGED
@@ -3,6 +3,35 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.10.1-dev.7](https://github.com/vtex/faststore/compare/v4.10.1-dev.6...v4.10.1-dev.7) (2026-10-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **api:** apply the unit and contract rule to accountProfile and guard non-string claims ([ab97c85](https://github.com/vtex/faststore/commit/ab97c85951e0c4b1a4f0f57a0e9a49560b74a81b))
11
+ - **api:** identify B2B sessions by organizational unit and contract ([36b7dc6](https://github.com/vtex/faststore/commit/36b7dc6eead7c8dd0ae38b15d4020b35ddafe8d2))
12
+ - **core:** address Sonar findings on the PDP 404 redirect ([47ccb81](https://github.com/vtex/faststore/commit/47ccb81b741a48d14a7c8da5361e23b08c15d3ca))
13
+ - **core:** expire parent-domain auth cookies on contract switch ([abe0845](https://github.com/vtex/faststore/commit/abe0845bcb487d1998294aab5ee5fd3a91e269e7))
14
+ - **core:** keep a single refresh route per session, like main ([afd6ce9](https://github.com/vtex/faststore/commit/afd6ce90126830d0c67f9e6bdf9521f95345c9f6))
15
+ - **core:** keep IP hosts host-only and Secure by scheme in switch-contract ([20ab5bf](https://github.com/vtex/faststore/commit/20ab5bf951b4be0dbb5ec09263b1e0c0aa32571e))
16
+ - **core:** keep switch-contract cookies host-only on previews ([940e76e](https://github.com/vtex/faststore/commit/940e76e6751293592cdb8b60d581be42c8c8e8dd))
17
+ - **core:** keep the PDP loading until the B2B 404 redirect ([7dc8008](https://github.com/vtex/faststore/commit/7dc80084c23e85e38d456a7bde0f505f38d11649))
18
+ - **core:** keep the switched contract on a sibling checkout host ([3b69fad](https://github.com/vtex/faststore/commit/3b69fad4594699c5b395ce86dbf19cb1821eba97))
19
+ - **core:** read the connection when x-forwarded-proto is absent ([6d4a7fa](https://github.com/vtex/faststore/commit/6d4a7fa9902d6d486e239001aeac94b51c1d06f6))
20
+ - **core:** redirect to /404 without reusing PDP path as data source ([158e653](https://github.com/vtex/faststore/commit/158e65305048eb3cf4d275324eef00d0c5bcb152))
21
+ - **core:** redirect to the 404 page for any shopper out of the assortment ([9ba660d](https://github.com/vtex/faststore/commit/9ba660d3f513512fd4bc0c27fb1750c6211a9182))
22
+ - **core:** redirect to the 404 page once per not-found product ([17bc72b](https://github.com/vtex/faststore/commit/17bc72b35291b9a847e1550555ecdaeb7805c40c))
23
+ - **core:** refresh the token through the Authenticator route ([8da50ef](https://github.com/vtex/faststore/commit/8da50efb6d9924385c2e2be98cdf0ca9a0564891))
24
+ - **core:** relay vid_rt on contract switch and bound the refreshAfter fallback ([d154bff](https://github.com/vtex/faststore/commit/d154bffc6523b51f391da0db928234223f693e8e))
25
+ - **core:** relay vid_rt on contract switch only with refresh token on ([0111217](https://github.com/vtex/faststore/commit/0111217b01079d4efdbd86bdee7be90a08bac8cf))
26
+ - **core:** retry transient PDP refetch failures, not 404s ([638f8a4](https://github.com/vtex/faststore/commit/638f8a47d878256239b4e1e43987fc6e287dade5))
27
+ - **core:** scope switch-contract auth cookie like VTEX ID ([dcab25f](https://github.com/vtex/faststore/commit/dcab25f861e022ec460606e234a0e8ac8e2ef2f7))
28
+ - **core:** scope the switched auth cookie on {account}.vtex.app ([b878178](https://github.com/vtex/faststore/commit/b87817886efd0632e61b30e6c4edb96975a4c05e))
29
+ - **core:** show 404 on the PDP when a B2B shopper cannot access the product ([45485ad](https://github.com/vtex/faststore/commit/45485adfb31b67e5310144cf8d37a0867b51a19c))
30
+ - **core:** skip retries for every not-found shape on the PDP refetch ([f7e808e](https://github.com/vtex/faststore/commit/f7e808ee4253ad5c966feb02c3b574abe5a742e3))
31
+ - **core:** surface a failed PDP product refetch so the 404 redirect can run ([d6a0923](https://github.com/vtex/faststore/commit/d6a0923125a68308913b122f8c2584e0fb5fcf32))
32
+ - **core:** use the Authenticator refresh route only for B2B sessions ([31d55cd](https://github.com/vtex/faststore/commit/31d55cd293b91646aba6147edfda84c37ba3fd17))
33
+ - **core:** use the B2B refresh route only with a unit and a contract ([bd76720](https://github.com/vtex/faststore/commit/bd76720a84fd4bae3e6fbea2d8b1bf3afdaabe02))
34
+
6
35
  ## [4.10.1-dev.6](https://github.com/vtex/faststore/compare/v4.10.1-dev.5...v4.10.1-dev.6) (2026-10-28)
7
36
 
8
37
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.10.1-dev.6",
3
+ "version": "4.10.1-dev.7",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/api": "4.10.1-dev.6",
75
- "@faststore/lighthouse": "4.10.1-dev.6",
76
- "@faststore/diagnostics": "4.10.1-dev.6",
77
- "@faststore/ui": "4.10.1-dev.6",
78
- "@faststore/sdk": "4.10.1-dev.6"
74
+ "@faststore/api": "4.10.1-dev.7",
75
+ "@faststore/diagnostics": "4.10.1-dev.7",
76
+ "@faststore/sdk": "4.10.1-dev.7",
77
+ "@faststore/ui": "4.10.1-dev.7",
78
+ "@faststore/lighthouse": "4.10.1-dev.7"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -23,7 +23,7 @@ type OrganizationDrawerView = 'menu' | 'switch'
23
23
  type OrganizationDrawerProps = {
24
24
  isOpen: boolean
25
25
  closeDrawer: () => void
26
- isRepresentative: boolean
26
+ isB2BBuyer: boolean
27
27
  /** Open straight into the contract switcher (My Account "Switch" button). */
28
28
  initialView?: OrganizationDrawerView
29
29
  }
@@ -142,7 +142,7 @@ export const doLogout = async (_event?: unknown) => {
142
142
  export const OrganizationDrawer = ({
143
143
  isOpen,
144
144
  closeDrawer,
145
- isRepresentative,
145
+ isB2BBuyer,
146
146
  initialView = 'menu',
147
147
  }: OrganizationDrawerProps) => {
148
148
  const { fade, fadeOut } = useFadeEffect()
@@ -210,7 +210,7 @@ export const OrganizationDrawer = ({
210
210
  }
211
211
  />
212
212
  <OrganizationDrawerBody
213
- isRepresentative={isRepresentative}
213
+ isB2BBuyer={isB2BBuyer}
214
214
  isOrgMember={isOrgMember}
215
215
  />
216
216
  </div>
@@ -9,13 +9,13 @@ import {
9
9
  getMyAccountRoutes,
10
10
  } from 'src/sdk/account/getMyAccountRoutes'
11
11
 
12
- const ROUTES_ONLY_FOR_REPRESENTATIVE = [USER_DETAILS_ROUTE]
12
+ const ROUTES_ONLY_FOR_B2B_BUYERS = new Set<string>([USER_DETAILS_ROUTE])
13
13
 
14
14
  export const OrganizationDrawerBody = ({
15
- isRepresentative,
15
+ isB2BBuyer,
16
16
  isOrgMember,
17
17
  }: {
18
- isRepresentative: boolean
18
+ isB2BBuyer: boolean
19
19
  isOrgMember: boolean
20
20
  }) => {
21
21
  const navigationLabels = useAccountNavigationLabels()
@@ -26,11 +26,9 @@ export const OrganizationDrawerBody = ({
26
26
  })
27
27
 
28
28
  const routes = (
29
- isRepresentative
29
+ isB2BBuyer
30
30
  ? menuItems
31
- : menuItems.filter(
32
- ({ route }) => !ROUTES_ONLY_FOR_REPRESENTATIVE.includes(route)
33
- )
31
+ : menuItems.filter(({ route }) => !ROUTES_ONLY_FOR_B2B_BUYERS.has(route))
34
32
  ).filter(
35
33
  ({ route }) => isOrgMember || !ROUTES_ONLY_FOR_B2B_MEMBERS.includes(route)
36
34
  )
@@ -6,9 +6,12 @@ import { OrganizationDrawer } from '../OrganizationDrawer/OrganizationDrawer'
6
6
 
7
7
  export const OrganizationSignInButton = ({
8
8
  icon: { alt, icon },
9
+ isB2BBuyer,
9
10
  isRepresentative,
10
11
  }: {
11
12
  icon: { alt: string; icon: string }
13
+ isB2BBuyer?: boolean
14
+ /** @deprecated Use `isB2BBuyer`. */
12
15
  isRepresentative?: boolean
13
16
  }) => {
14
17
  const [isOpen, setIsOpen] = useState(false)
@@ -50,7 +53,7 @@ export const OrganizationSignInButton = ({
50
53
  <OrganizationDrawer
51
54
  isOpen={isOpen}
52
55
  closeDrawer={closeDrawer}
53
- isRepresentative={isRepresentative}
56
+ isB2BBuyer={Boolean(isB2BBuyer ?? isRepresentative)}
54
57
  />
55
58
  )}
56
59
  </>
@@ -13,16 +13,16 @@ import styles from '../section.module.scss'
13
13
 
14
14
  export type LayoutProps = {
15
15
  accountName: string
16
- isRepresentative?: boolean
16
+ isB2BBuyer?: boolean
17
17
  navigationLabels?: AccountNavigationLabels
18
18
  }
19
19
 
20
- const ROUTES_ONLY_FOR_REPRESENTATIVE = [USER_DETAILS_ROUTE]
20
+ const ROUTES_ONLY_FOR_B2B_BUYERS = new Set<string>([USER_DETAILS_ROUTE])
21
21
 
22
22
  const Layout = ({
23
23
  children,
24
24
  accountName,
25
- isRepresentative = true,
25
+ isB2BBuyer = true,
26
26
  navigationLabels,
27
27
  }: PropsWithChildren<LayoutProps>) => {
28
28
  const { b2b } = useSession()
@@ -41,11 +41,9 @@ const Layout = ({
41
41
  // page. `useAdHocCard` is a separate, finer gate that only affects the
42
42
  // Personal-tab rendering inside the page itself — it never hides the entry.
43
43
  const routes = (
44
- isRepresentative
44
+ isB2BBuyer
45
45
  ? menuItems
46
- : menuItems.filter(
47
- ({ route }) => !ROUTES_ONLY_FOR_REPRESENTATIVE.includes(route)
48
- )
46
+ : menuItems.filter(({ route }) => !ROUTES_ONLY_FOR_B2B_BUYERS.has(route))
49
47
  ).filter(
50
48
  ({ route }) => isOrgMember || !ROUTES_ONLY_FOR_B2B_MEMBERS.includes(route)
51
49
  )
@@ -79,7 +79,7 @@ const Menu = ({ avatarImageUrl, accountName, items }: MenuProps) => {
79
79
  <OrganizationDrawer
80
80
  isOpen
81
81
  closeDrawer={() => setIsSwitcherOpen(false)}
82
- isRepresentative={Boolean(b2b?.isRepresentative)}
82
+ isB2BBuyer={Boolean(b2b)}
83
83
  initialView="switch"
84
84
  />
85
85
  )}
@@ -132,9 +132,9 @@ function Navbar({
132
132
  const isFaststoreMyAccountEnabled =
133
133
  storeConfig.experimental?.enableFaststoreMyAccount
134
134
 
135
- const isRepresentative = b2b?.isRepresentative
135
+ const isB2BBuyer = Boolean(b2b)
136
136
 
137
- const isOrganizationEnabled = isFaststoreMyAccountEnabled && isRepresentative
137
+ const isOrganizationEnabled = isFaststoreMyAccountEnabled && isB2BBuyer
138
138
  const isLocalizationEnabled =
139
139
  storeConfig.localization?.enabled &&
140
140
  localizationButton?.shouldDisplayLocalizationButton
@@ -240,7 +240,7 @@ function Navbar({
240
240
  <NavbarAccountArea
241
241
  isSignInResolved={isSignInResolved}
242
242
  isOrganizationEnabled={isOrganizationEnabled}
243
- isRepresentative={isRepresentative}
243
+ isB2BBuyer={isB2BBuyer}
244
244
  signInButton={signInButton}
245
245
  ButtonSignIn={ButtonSignIn}
246
246
  skeletonAttr="data-fs-navbar-signin-skeleton"
@@ -10,9 +10,9 @@ type NavbarOverrides = ReturnType<typeof useOverrideComponents<'Navbar'>>
10
10
  export type NavbarAccountAreaProps = {
11
11
  /** Whether the B2B session has resolved enough to know what to render. */
12
12
  isSignInResolved: boolean
13
- /** Whether the representative-only Organization sign-in area is enabled. */
13
+ /** Whether the Organization sign-in area for B2B buyers is enabled. */
14
14
  isOrganizationEnabled: boolean | undefined
15
- isRepresentative?: boolean
15
+ isB2BBuyer?: boolean
16
16
  signInButton: SectionNavbarProps['signInButton']
17
17
  ButtonSignIn: NavbarOverrides['_experimentalButtonSignIn']
18
18
  /** Data attribute toggled on the loading skeleton (Navbar vs NavbarSlider). */
@@ -24,13 +24,13 @@ export type NavbarAccountAreaProps = {
24
24
  /**
25
25
  * Renders the account/sign-in area shared by the desktop Navbar and the
26
26
  * mobile NavbarSlider: a loading skeleton until the B2B session resolves,
27
- * then either the Organization sign-in button (representatives) or the
27
+ * then either the Organization sign-in button (B2B buyers) or the
28
28
  * storefront's own sign-in button.
29
29
  */
30
30
  export const NavbarAccountArea = ({
31
31
  isSignInResolved,
32
32
  isOrganizationEnabled,
33
- isRepresentative,
33
+ isB2BBuyer,
34
34
  signInButton,
35
35
  ButtonSignIn,
36
36
  skeletonAttr,
@@ -48,7 +48,7 @@ export const NavbarAccountArea = ({
48
48
  return (
49
49
  <OrganizationSignInButton
50
50
  icon={signInButton.icon}
51
- isRepresentative={isRepresentative}
51
+ isB2BBuyer={isB2BBuyer}
52
52
  />
53
53
  )
54
54
  }
@@ -54,8 +54,8 @@ function NavbarSlider({
54
54
  const isFaststoreMyAccountEnabled =
55
55
  storeConfig.experimental?.enableFaststoreMyAccount
56
56
 
57
- const isRepresentative = b2b?.isRepresentative
58
- const isOrganizationEnabled = isFaststoreMyAccountEnabled && isRepresentative
57
+ const isB2BBuyer = Boolean(b2b)
58
+ const isOrganizationEnabled = isFaststoreMyAccountEnabled && isB2BBuyer
59
59
 
60
60
  const isLocalizationEnabled =
61
61
  storeConfig.localization?.enabled &&
@@ -92,7 +92,7 @@ function NavbarSlider({
92
92
  <NavbarAccountArea
93
93
  isSignInResolved={isSignInResolved}
94
94
  isOrganizationEnabled={isOrganizationEnabled}
95
- isRepresentative={isRepresentative}
95
+ isB2BBuyer={isB2BBuyer}
96
96
  signInButton={signInButton}
97
97
  ButtonSignIn={ButtonSignIn}
98
98
  skeletonAttr="data-fs-navbar-slider-signin-skeleton"
@@ -7,7 +7,7 @@ import type { Locator } from '@vtex/client-cms'
7
7
  import type { GetServerSideProps } from 'next'
8
8
 
9
9
  import { getGlobalSectionsData } from 'src/components/cms/GlobalSections'
10
- import { getIsRepresentative } from 'src/sdk/account/getIsRepresentative'
10
+ import { getIsB2BBuyer } from 'src/sdk/account/getIsB2BBuyer'
11
11
  import type { AccountNavigationLabels } from 'src/sdk/account/getMyAccountRoutes'
12
12
  import { validateUser } from 'src/sdk/account/validateUser'
13
13
  import { execute } from 'src/server'
@@ -87,7 +87,7 @@ export function myAccountCmsServerSideProps(
87
87
  return { redirect }
88
88
  }
89
89
 
90
- const isRepresentative = getIsRepresentative({
90
+ const isB2BBuyer = getIsB2BBuyer({
91
91
  headers: context.req.headers as Record<string, string>,
92
92
  account: storeConfig.api.storeId,
93
93
  })
@@ -135,7 +135,7 @@ export function myAccountCmsServerSideProps(
135
135
  props: {
136
136
  globalSections: globalSectionsResult,
137
137
  accountName: account.data?.accountProfile?.name ?? '',
138
- isRepresentative,
138
+ isB2BBuyer,
139
139
  pageSections,
140
140
  navigationLabels: navigationData as AccountNavigationLabels,
141
141
  },
@@ -10,7 +10,7 @@ import {
10
10
  type GlobalSectionsData,
11
11
  getGlobalSectionsData,
12
12
  } from 'src/components/cms/GlobalSections'
13
- import { getIsRepresentative } from 'src/sdk/account/getIsRepresentative'
13
+ import { getIsB2BBuyer } from 'src/sdk/account/getIsB2BBuyer'
14
14
  import { execute } from 'src/server'
15
15
 
16
16
  import { validateUser } from 'src/sdk/account/validateUser'
@@ -23,7 +23,7 @@ import storeConfig from '../../discovery.config'
23
23
  export type MyAccountProps = {
24
24
  globalSections: GlobalSectionsData
25
25
  accountName: string
26
- isRepresentative?: boolean
26
+ isB2BBuyer?: boolean
27
27
  }
28
28
 
29
29
  const query = gql(`
@@ -77,7 +77,7 @@ const getServerSidePropsBase: GetServerSideProps<
77
77
  return { redirect }
78
78
  }
79
79
 
80
- const isRepresentative = getIsRepresentative({
80
+ const isB2BBuyer = getIsB2BBuyer({
81
81
  headers: context.req.headers as Record<string, string>,
82
82
  account: storeConfig.api.storeId,
83
83
  })
@@ -115,7 +115,7 @@ const getServerSidePropsBase: GetServerSideProps<
115
115
  props: {
116
116
  globalSections: globalSectionsResult,
117
117
  accountName: account.data?.accountProfile?.name ?? '',
118
- isRepresentative,
118
+ isB2BBuyer,
119
119
  },
120
120
  }
121
121
  }
@@ -44,6 +44,7 @@ import { LocalizedProductProvider } from 'src/sdk/localization/LocalizedProductC
44
44
  import { getStoreURL } from 'src/sdk/localization/useLocalizationConfig'
45
45
  import { getOfferUrl, useOffer } from 'src/sdk/offer'
46
46
  import PageProvider, { type PDPContext } from 'src/sdk/overrides/PageProvider'
47
+ import { useProductNotFoundRedirect } from 'src/sdk/product/useProductNotFoundRedirect'
47
48
  import { useProductQuery } from 'src/sdk/product/useProductQuery'
48
49
  import { injectGlobalSections } from 'src/server/cms/global'
49
50
  import type { PDPContentType } from 'src/server/cms/pdp'
@@ -185,7 +186,7 @@ function Page({
185
186
  )
186
187
  }
187
188
 
188
- const { client, isValidating } = isClientOfferEnabled
189
+ const { client, isValidating, clientError } = isClientOfferEnabled
189
190
  ? (() => {
190
191
  const offer = useOffer({ skuId: product.sku })
191
192
  return {
@@ -195,6 +196,11 @@ function Page({
195
196
  },
196
197
  },
197
198
  isValidating: offer.isValidating,
199
+ // This mode reads the offer straight from Intelligent Search and
200
+ // never runs `ClientProductQuery`, where the B2B contract assortment
201
+ // is checked, so there is no not-found to redirect on. Supporting
202
+ // the redirect here is out of scope for now.
203
+ clientError: undefined,
198
204
  }
199
205
  })()
200
206
  : (() => {
@@ -204,15 +210,18 @@ function Page({
204
210
  return {
205
211
  client: productQuery.data,
206
212
  isValidating: productQuery.isValidating,
213
+ clientError: productQuery.error,
207
214
  }
208
215
  })()
209
216
 
217
+ const isLeavingForNotFound = useProductNotFoundRedirect(clientError)
218
+
210
219
  const { sections: globalSections, settings: globalSettings } =
211
220
  globalSectionsProp ?? {}
212
221
  const context = {
213
222
  data: {
214
223
  ...deepmerge(server, client, { arrayMerge: overwriteMerge }),
215
- isValidating,
224
+ isValidating: isValidating || isLeavingForNotFound,
216
225
  },
217
226
  globalSettings,
218
227
  } as PDPContext
@@ -7,6 +7,8 @@ import {
7
7
  getCookieDomains,
8
8
  getVtexCookieNames,
9
9
  } from 'src/utils/clearCookies'
10
+ import { VID_RT_PATHS } from 'src/sdk/account/refreshTokenPaths'
11
+ import { getCheckoutParentDomain } from 'src/utils/getCheckoutParentDomain'
10
12
  import { getRequestHostname } from 'src/utils/getRequestHostname'
11
13
 
12
14
  const ADDITIONAL_COOKIES = ['CheckoutOrderFormOwnership'] as const
@@ -27,20 +29,26 @@ const handler: NextApiHandler = async (
27
29
  try {
28
30
  const hostname = getRequestHostname(request.headers.host)
29
31
  const cookies = parse(request.headers.cookie ?? '')
30
- const domains = hostname ? getCookieDomains(hostname) : [undefined]
32
+ const checkoutParent = hostname
33
+ ? getCheckoutParentDomain(hostname)
34
+ : undefined
35
+ const domains = [
36
+ ...(hostname ? getCookieDomains(hostname) : [undefined]),
37
+ ...(checkoutParent ? [checkoutParent] : []),
38
+ ]
31
39
  const clearedCookies: string[] = []
32
40
 
33
41
  const vtexCookieNames = getVtexCookieNames(Object.keys(cookies))
34
42
 
35
- // Clear vid_rt cookie with specific path (only if refreshToken is enabled)
36
- if (discoveryConfig.experimental?.refreshToken && cookies.vid_rt) {
37
- for (const domain of domains) {
38
- const clearedCookie = expireCookieServer({
39
- name: 'vid_rt',
40
- path: '/api/vtexid/refreshtoken/webstore',
41
- domain,
42
- })
43
- clearedCookies.push(clearedCookie)
43
+ // vid_rt is scoped to the refresh routes, so the browser never sends it
44
+ // here and its presence can't be checked. Expire it on every path it can be scoped to.
45
+ if (discoveryConfig.experimental?.refreshToken) {
46
+ for (const path of VID_RT_PATHS) {
47
+ for (const domain of domains) {
48
+ clearedCookies.push(
49
+ expireCookieServer({ name: 'vid_rt', path, domain })
50
+ )
51
+ }
44
52
  }
45
53
  }
46
54