@faststore/core 4.4.1-dev.0 → 4.5.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 (89) hide show
  1. package/.turbo/turbo-generate.log +4 -4
  2. package/.turbo/turbo-test.log +137 -51
  3. package/@generated/cached-operations.json +4 -0
  4. package/@generated/gql.ts +30 -0
  5. package/@generated/graphql.ts +141 -0
  6. package/@generated/persisted-documents.json +5 -0
  7. package/@generated/schema.graphql +100 -0
  8. package/CHANGELOG.md +26 -0
  9. package/cms/faststore/components/cms_component__recommendationshelf.jsonc +86 -0
  10. package/cms/faststore/schema.json +89 -0
  11. package/discovery.config.default.js +3 -0
  12. package/discovery.config.js +10 -1
  13. package/discovery.config.local.example.js +62 -0
  14. package/package.json +6 -6
  15. package/public/icons.svg +4 -0
  16. package/src/Layout.tsx +6 -0
  17. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/ContractSwitcher.tsx +376 -0
  18. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/index.ts +2 -0
  19. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +52 -23
  20. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerBody.tsx +16 -6
  21. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerFooter.tsx +55 -0
  22. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader.tsx +45 -5
  23. package/src/components/account/Drawer/OrganizationDrawer/index.ts +5 -0
  24. package/src/components/account/Drawer/OrganizationDrawer/section.module.scss +452 -54
  25. package/src/components/account/Layout/Layout.tsx +14 -5
  26. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotes.tsx +170 -0
  27. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountListQuotesTable.tsx +194 -0
  28. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountQuoteStatusBadge.tsx +15 -0
  29. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/MyAccountQuotesFilterSlider.tsx +162 -0
  30. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/section.module.scss +49 -0
  31. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesSelectedTags/MyAccountQuotesSelectedTags.tsx +125 -0
  32. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector.tsx +107 -0
  33. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/styles.module.scss +144 -0
  34. package/src/components/account/quotes/MyAccountListQuotes/index.ts +2 -0
  35. package/src/components/account/quotes/MyAccountListQuotes/quoteFilters.ts +89 -0
  36. package/src/components/account/quotes/MyAccountListQuotes/styles.module.scss +174 -0
  37. package/src/components/cms/global/Components.ts +10 -0
  38. package/src/components/sections/ProductGallery/ProductGallery.tsx +1 -1
  39. package/src/components/sections/RecommendationShelf/RecommendationShelf.tsx +223 -0
  40. package/src/components/sections/RecommendationShelf/RecommendationShelf.types.ts +111 -0
  41. package/src/components/sections/RecommendationShelf/index.ts +1 -0
  42. package/src/components/sections/RecommendationShelf/section.module.scss +49 -0
  43. package/src/components/sections/RecommendationShelf/useRecommendationUserId.ts +49 -0
  44. package/src/components/sections/RecommendationShelf/useRecommendations.ts +55 -0
  45. package/src/components/sections/RecommendationShelf/vrn.ts +55 -0
  46. package/src/customizations/src/myAccount/extensions/quotes/after.tsx +5 -0
  47. package/src/customizations/src/myAccount/extensions/quotes/before.tsx +5 -0
  48. package/src/pages/[slug]/p.tsx +45 -0
  49. package/src/pages/pvt/account/quotes/index.tsx +264 -0
  50. package/src/sdk/account/changeContractToken.ts +119 -0
  51. package/src/sdk/account/clearPersistedSessionState.ts +26 -0
  52. package/src/sdk/account/getMyAccountRoutes.ts +7 -0
  53. package/src/sdk/account/refreshToken.ts +21 -8
  54. package/src/sdk/account/useAvailableContracts.ts +49 -0
  55. package/src/sdk/account/useSwitchContract.ts +60 -0
  56. package/src/sdk/analytics/hooks/useStartRecommendationSession.ts +96 -0
  57. package/src/sdk/analytics/utils.ts +9 -0
  58. package/src/utils/quoteStatus.ts +27 -0
  59. package/src/utils/retry.ts +69 -0
  60. package/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx +235 -0
  61. package/test/components/RecommendationShelf/useRecommendationUserId.test.ts +69 -0
  62. package/test/components/RecommendationShelf/useRecommendations.test.ts +52 -0
  63. package/test/components/RecommendationShelf/vrn.test.ts +56 -0
  64. package/test/components/account/ContractSwitcher.browser.test.tsx +236 -0
  65. package/test/components/account/Layout.test.tsx +62 -0
  66. package/test/components/account/OrganizationDrawer.browser.test.tsx +197 -0
  67. package/test/components/account/OrganizationDrawerBody.test.tsx +52 -0
  68. package/test/components/account/OrganizationDrawerFooter.browser.test.tsx +54 -0
  69. package/test/components/account/OrganizationDrawerHeader.browser.test.tsx +55 -0
  70. package/test/components/account/doLogout.browser.test.ts +102 -0
  71. package/test/components/quotes/MyAccountListQuotes.test.tsx +232 -0
  72. package/test/components/quotes/MyAccountListQuotesTable.test.tsx +240 -0
  73. package/test/components/quotes/MyAccountQuoteStatusBadge.test.tsx +28 -0
  74. package/test/components/quotes/MyAccountQuotesFilterSlider.test.tsx +152 -0
  75. package/test/components/quotes/MyAccountQuotesSelectedTags.test.tsx +122 -0
  76. package/test/components/quotes/MyAccountQuotesStatusSelector.test.tsx +85 -0
  77. package/test/components/quotes/quoteFilters.test.ts +185 -0
  78. package/test/sdk/account/changeContractToken.browser.test.ts +157 -0
  79. package/test/sdk/account/clearPersistedSessionState.browser.test.ts +41 -0
  80. package/test/sdk/account/clearPersistedSessionState.test.ts +20 -0
  81. package/test/sdk/account/getMyAccountRoutes.test.ts +90 -0
  82. package/test/sdk/account/refreshToken.browser.test.ts +34 -0
  83. package/test/sdk/account/refreshToken.test.ts +87 -0
  84. package/test/sdk/account/useAvailableContracts.browser.test.ts +124 -0
  85. package/test/sdk/account/useSwitchContract.browser.test.ts +111 -0
  86. package/test/sdk/analytics/useStartRecommendationSession.test.ts +69 -0
  87. package/test/sdk/analytics/utils.test.ts +23 -0
  88. package/test/server/index.test.ts +5 -0
  89. package/test/utils/retry.test.ts +59 -0
@@ -1,4 +1,5 @@
1
1
  import { SlideOver, useFadeEffect } from '@faststore/ui'
2
+ import { useEffect, useState } from 'react'
2
3
 
3
4
  import { getStoreURL } from 'src/sdk/localization/useLocalizationConfig'
4
5
  import { useSession } from 'src/sdk/session'
@@ -9,12 +10,15 @@ import {
9
10
  getVtexCookieNames,
10
11
  } from 'src/utils/clearCookies'
11
12
  import storeConfig from '../../../../../discovery.config'
12
- import { ProfileSummary } from '../ProfileSummary/ProfileSummary'
13
+ import { ContractSwitcher } from './ContractSwitcher'
13
14
  import { OrganizationDrawerBody } from './OrganizationDrawerBody'
15
+ import { OrganizationDrawerFooter } from './OrganizationDrawerFooter'
14
16
  import { OrganizationDrawerHeader } from './OrganizationDrawerHeader'
15
17
  import { setReloadAfterLogoutReturn } from './useReloadAfterLogoutReturn'
16
18
  import styles from './section.module.scss'
17
19
 
20
+ type OrganizationDrawerView = 'menu' | 'switch'
21
+
18
22
  type OrganizationDrawerProps = {
19
23
  isOpen: boolean
20
24
  closeDrawer: () => void
@@ -125,7 +129,9 @@ export const doLogout = async (_event?: unknown) => {
125
129
  } finally {
126
130
  setReloadAfterLogoutReturn()
127
131
  window.location.assign(
128
- `${storeConfig.secureSubdomain}/api/vtexid/pub/logout?scope=${storeConfig.api.storeId}&returnUrl=${getStoreURL()}`
132
+ `${storeConfig.secureSubdomain}/api/vtexid/pub/logout?scope=${
133
+ storeConfig.api.storeId
134
+ }&returnUrl=${getStoreURL()}`
129
135
  )
130
136
  }
131
137
  }
@@ -137,17 +143,30 @@ export const OrganizationDrawer = ({
137
143
  }: OrganizationDrawerProps) => {
138
144
  const { fade, fadeOut } = useFadeEffect()
139
145
  const { b2b, person } = useSession()
146
+ const [view, setView] = useState<OrganizationDrawerView>('menu')
147
+
148
+ useEffect(() => {
149
+ if (!isOpen) {
150
+ setView('menu')
151
+ }
152
+ }, [isOpen])
140
153
 
141
154
  const contractName =
142
- b2b?.contractName ??
143
- `${(person?.givenName ?? '').trim()} ${(person?.familyName ?? '').trim()}`.trim() ??
144
- ''
155
+ b2b?.contractName?.trim() ||
156
+ b2b?.unitName?.trim() ||
157
+ `${(person?.givenName ?? '').trim()}` ||
158
+ 'Organization'
145
159
 
146
160
  const contractUrl = b2b?.unitId
147
161
  ? `/pvt/organization-account/org-unit/${b2b?.unitId}`
148
162
  : null
149
163
 
150
164
  const isOrganizationManager = b2b?.organizationManager || false
165
+ // The switcher is only meaningful for B2B buyers tied to an Organization Unit.
166
+ // Buyers with a single (or no alternative) contract see the empty state inside
167
+ // the switcher; we don't fetch the contract count eagerly to protect TTFB.
168
+ const canSwitchContract = Boolean(b2b?.unitId)
169
+ const isOrgMember = Boolean(b2b?.unitId)
151
170
 
152
171
  return (
153
172
  <SlideOver
@@ -162,24 +181,34 @@ export const OrganizationDrawer = ({
162
181
  className: `section ${styles.section} section-organization-drawer`,
163
182
  }}
164
183
  >
165
- <OrganizationDrawerHeader
166
- onCloseDrawer={closeDrawer}
167
- contractName={contractName}
168
- contractUrl={contractUrl}
169
- />
170
- <OrganizationDrawerBody isRepresentative={isRepresentative} />
171
- <footer data-fs-organization-drawer-footer-wrapper>
172
- <ProfileSummary
173
- showManageLink={isOrganizationManager}
174
- bordered={true}
175
- onLogoutClick={doLogout}
176
- person={{
177
- name: b2b?.userName ?? '',
178
- email: b2b?.userEmail ?? '',
179
- }}
180
- orgName={b2b?.unitName ?? ''}
181
- />
182
- </footer>
184
+ {view === 'switch' ? (
185
+ <ContractSwitcher onBack={() => setView('menu')} onClose={fadeOut} />
186
+ ) : (
187
+ <div data-fs-organization-drawer-menu>
188
+ <div data-fs-organization-drawer-menu-scroll>
189
+ <OrganizationDrawerHeader
190
+ onCloseDrawer={closeDrawer}
191
+ contractName={contractName}
192
+ contractUrl={contractUrl}
193
+ onChangeContract={
194
+ canSwitchContract ? () => setView('switch') : undefined
195
+ }
196
+ />
197
+ <OrganizationDrawerBody
198
+ isRepresentative={isRepresentative}
199
+ isOrgMember={isOrgMember}
200
+ />
201
+ </div>
202
+ <OrganizationDrawerFooter
203
+ orgName={b2b?.unitName ?? ''}
204
+ userName={b2b?.userName ?? ''}
205
+ userEmail={b2b?.userEmail ?? ''}
206
+ showManageLink={isOrganizationManager}
207
+ manageUrl={contractUrl ?? undefined}
208
+ onLogoutClick={doLogout}
209
+ />
210
+ </div>
211
+ )}
183
212
  </SlideOver>
184
213
  )
185
214
  }
@@ -3,6 +3,7 @@ import Link from 'src/components/ui/Link'
3
3
  import menuRoutes from 'src/customizations/src/myAccount/navigation'
4
4
  import { useAccountNavigationLabels } from 'src/sdk/account/accountPageContext'
5
5
  import {
6
+ ROUTES_ONLY_FOR_B2B_MEMBERS,
6
7
  USER_DETAILS_ROUTE,
7
8
  getExtraMyAccountRoutes,
8
9
  getMyAccountRoutes,
@@ -12,7 +13,11 @@ const ROUTES_ONLY_FOR_REPRESENTATIVE = [USER_DETAILS_ROUTE]
12
13
 
13
14
  export const OrganizationDrawerBody = ({
14
15
  isRepresentative,
15
- }: { isRepresentative: boolean }) => {
16
+ isOrgMember,
17
+ }: {
18
+ isRepresentative: boolean
19
+ isOrgMember: boolean
20
+ }) => {
16
21
  const navigationLabels = useAccountNavigationLabels()
17
22
 
18
23
  const menuItems = getMyAccountRoutes({
@@ -20,11 +25,16 @@ export const OrganizationDrawerBody = ({
20
25
  labels: navigationLabels,
21
26
  })
22
27
 
23
- const routes = isRepresentative
24
- ? menuItems
25
- : menuItems.filter(
26
- ({ route }) => !ROUTES_ONLY_FOR_REPRESENTATIVE.includes(route)
27
- )
28
+ const routes = (
29
+ isRepresentative
30
+ ? menuItems
31
+ : menuItems.filter(
32
+ ({ route }) => !ROUTES_ONLY_FOR_REPRESENTATIVE.includes(route)
33
+ )
34
+ ).filter(
35
+ ({ route }) => isOrgMember || !ROUTES_ONLY_FOR_B2B_MEMBERS.includes(route)
36
+ )
37
+
28
38
  return (
29
39
  <div data-fs-organization-drawer-body>
30
40
  <div data-fs-organization-drawer-body-contents>
@@ -0,0 +1,55 @@
1
+ import { Icon, IconButton, Link } from '@faststore/ui'
2
+
3
+ export interface OrganizationDrawerFooterProps {
4
+ orgName: string
5
+ userName: string
6
+ userEmail?: string
7
+ showManageLink?: boolean
8
+ manageUrl?: string
9
+ onLogoutClick?: () => void
10
+ }
11
+
12
+ export const OrganizationDrawerFooter = ({
13
+ orgName,
14
+ userName,
15
+ userEmail,
16
+ showManageLink,
17
+ manageUrl,
18
+ onLogoutClick,
19
+ }: OrganizationDrawerFooterProps) => {
20
+ return (
21
+ <div data-fs-organization-drawer-footer>
22
+ <div data-fs-organization-drawer-footer-org>
23
+ <div data-fs-organization-drawer-footer-org-info>
24
+ <Icon
25
+ data-fs-organization-drawer-footer-org-icon
26
+ name="FolderOpen"
27
+ width={18}
28
+ height={18}
29
+ />
30
+ <span data-fs-organization-drawer-footer-org-name>{orgName}</span>
31
+ </div>
32
+ {showManageLink && manageUrl && (
33
+ <Link href={manageUrl} data-fs-organization-drawer-footer-manage>
34
+ Manage
35
+ </Link>
36
+ )}
37
+ </div>
38
+
39
+ <div data-fs-organization-drawer-footer-user>
40
+ <div data-fs-organization-drawer-footer-user-info>
41
+ <p data-fs-organization-drawer-footer-user-name>{userName}</p>
42
+ {userEmail && (
43
+ <p data-fs-organization-drawer-footer-user-email>{userEmail}</p>
44
+ )}
45
+ </div>
46
+ <IconButton
47
+ data-fs-organization-drawer-footer-logout
48
+ aria-label="Log out"
49
+ icon={<Icon name="SignOut" width={20} height={20} />}
50
+ onClick={onLogoutClick}
51
+ />
52
+ </div>
53
+ </div>
54
+ )
55
+ }
@@ -1,18 +1,29 @@
1
- import { Link, SlideOverHeader } from '@faststore/ui'
1
+ import { Button, Icon, Link, SlideOverHeader } from '@faststore/ui'
2
2
  import type { ReactNode } from 'react'
3
3
 
4
4
  export type OrganizationDrawerHeaderProps = {
5
5
  onCloseDrawer?: () => void
6
6
  contractImage?: ReactNode
7
7
  contractName: string
8
- contractUrl: string
8
+ contractUrl: string | null
9
+ /**
10
+ * Marks the active contract as the Organization Unit's default (star).
11
+ */
12
+ isDefault?: boolean
13
+ /**
14
+ * When provided, renders the "Change" CTA next to the active contract,
15
+ * opening the contract switcher (Figma node 103-5434).
16
+ */
17
+ onChangeContract?: () => void
9
18
  }
10
19
 
11
20
  export const OrganizationDrawerHeader = ({
12
21
  contractUrl,
13
22
  contractName,
14
23
  contractImage,
24
+ isDefault,
15
25
  onCloseDrawer,
26
+ onChangeContract,
16
27
  }: OrganizationDrawerHeaderProps) => {
17
28
  return (
18
29
  <>
@@ -20,6 +31,7 @@ export const OrganizationDrawerHeader = ({
20
31
  <div data-fs-organization-drawer-header>
21
32
  <Link
22
33
  data-fs-organization-drawer-header-contract-link
34
+ aria-label={contractName}
23
35
  {...(contractUrl && { href: contractUrl })}
24
36
  >
25
37
  <div data-fs-organization-drawer-header-contract-image>
@@ -29,10 +41,38 @@ export const OrganizationDrawerHeader = ({
29
41
  </span>
30
42
  )}
31
43
  </div>
32
- <h1 data-fs-organization-drawer-header-contract-name>
33
- {contractName}
34
- </h1>
35
44
  </Link>
45
+ <div data-fs-organization-drawer-header-row>
46
+ <Link
47
+ data-fs-organization-drawer-header-contract-name-link
48
+ {...(contractUrl && { href: contractUrl })}
49
+ >
50
+ <h1 data-fs-organization-drawer-header-contract-name>
51
+ {contractName}
52
+ </h1>
53
+ </Link>
54
+ {(isDefault || onChangeContract) && (
55
+ <div data-fs-organization-drawer-header-actions>
56
+ {isDefault && (
57
+ <Icon
58
+ data-fs-organization-drawer-header-default
59
+ name="Star"
60
+ width={20}
61
+ height={20}
62
+ />
63
+ )}
64
+ {onChangeContract && (
65
+ <Button
66
+ data-fs-organization-drawer-header-change
67
+ variant="secondary"
68
+ onClick={onChangeContract}
69
+ >
70
+ Change
71
+ </Button>
72
+ )}
73
+ </div>
74
+ )}
75
+ </div>
36
76
  </div>
37
77
  </>
38
78
  )
@@ -1,3 +1,8 @@
1
1
  export { OrganizationDrawer } from './OrganizationDrawer'
2
2
  export { OrganizationDrawerBody } from './OrganizationDrawerBody'
3
+ export { OrganizationDrawerFooter } from './OrganizationDrawerFooter'
4
+ export type { OrganizationDrawerFooterProps } from './OrganizationDrawerFooter'
3
5
  export { OrganizationDrawerHeader } from './OrganizationDrawerHeader'
6
+ export type { OrganizationDrawerHeaderProps } from './OrganizationDrawerHeader'
7
+ export { ContractSwitcher } from './ContractSwitcher'
8
+ export type { ContractSwitcherProps } from './ContractSwitcher'