@faststore/core 4.9.0-dev.3 → 4.9.0-dev.5

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 (39) hide show
  1. package/.turbo/turbo-generate.log +3 -3
  2. package/.turbo/turbo-test.log +132 -138
  3. package/CHANGELOG.md +12 -0
  4. package/cms/faststore/my-account/components/cms_component__accountlistquotes.jsonc +240 -0
  5. package/cms/faststore/my-account/components/cms_component__accountnavigation.jsonc +76 -0
  6. package/cms/faststore/my-account/pages/cms_content_type__myaccountquotes.jsonc +18 -0
  7. package/package.json +6 -6
  8. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/ContractSwitcher.tsx +129 -26
  9. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader.tsx +7 -1
  10. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotes.tsx +42 -11
  11. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountListQuotesTable.tsx +45 -15
  12. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountQuoteStatusBadge.tsx +11 -3
  13. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/MyAccountQuotesFilterSlider.tsx +23 -0
  14. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesSelectedTags/MyAccountQuotesSelectedTags.tsx +35 -11
  15. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector.tsx +27 -10
  16. package/src/components/account/quotes/MyAccountListQuotes/index.ts +2 -0
  17. package/src/components/account/quotes/MyAccountListQuotes/quoteFilters.ts +16 -6
  18. package/src/components/account/quotes/MyAccountListQuotes/quotesLabels.ts +163 -0
  19. package/src/components/cms/account/Components.ts +2 -0
  20. package/src/components/sections/Account/AccountListQuotes.tsx +141 -0
  21. package/src/pages/pvt/account/quotes/index.tsx +59 -40
  22. package/src/pages/{s.tsx → s/index.tsx} +11 -0
  23. package/src/sdk/account/accountPageContext.ts +17 -0
  24. package/src/sdk/account/getMyAccountRoutes.ts +17 -0
  25. package/src/sdk/account/resolveNonBlankLabels.ts +36 -0
  26. package/src/server/cms/myAccountDefaultSections.ts +2 -0
  27. package/src/utils/quoteStatus.ts +81 -0
  28. package/test/components/account/ContractSwitcher.browser.test.tsx +163 -0
  29. package/test/components/account/OrganizationDrawer.browser.test.tsx +4 -0
  30. package/test/components/account/OrganizationDrawerHeader.browser.test.tsx +44 -1
  31. package/test/components/quotes/MyAccountListQuotes.test.tsx +36 -2
  32. package/test/components/quotes/MyAccountListQuotesTable.test.tsx +68 -0
  33. package/test/components/quotes/MyAccountQuoteStatusBadge.test.tsx +25 -0
  34. package/test/components/quotes/MyAccountQuotesFilterSlider.test.tsx +52 -11
  35. package/test/components/quotes/MyAccountQuotesSelectedTags.test.tsx +32 -0
  36. package/test/components/quotes/MyAccountQuotesStatusSelector.test.tsx +100 -0
  37. package/test/components/quotes/quoteFilters.test.ts +34 -0
  38. package/test/components/sections/Account/AccountListQuotes.browser.test.tsx +122 -0
  39. package/test/server/cms/myAccountDefaultSections.test.ts +21 -0
@@ -0,0 +1,163 @@
1
+ import {
2
+ resolveNonBlankLabels,
3
+ splitOnFirstToken,
4
+ } from 'src/sdk/account/resolveNonBlankLabels'
5
+ import type { QuoteStatusCmsLabels } from 'src/utils/quoteStatus'
6
+ import {
7
+ pickQuoteStatusCmsLabels as pickQuoteStatusCmsLabelsFromData,
8
+ quoteStatusMap,
9
+ } from 'src/utils/quoteStatus'
10
+
11
+ export type MyAccountListQuotesSectionLabels = {
12
+ pageTitle?: string
13
+ searchPlaceholder?: string
14
+ openFiltersAriaLabel?: string
15
+ filtersLabel?: string
16
+ clearAllLabel?: string
17
+ viewResultsLabel?: string
18
+ noResultsLabel?: string
19
+ noQuotesLabel?: string
20
+ statusFacetLabel?: string
21
+ createdAtFacetLabel?: string
22
+ expiresAtFacetLabel?: string
23
+ /** Accessible name for a status chip's remove button. Supports a "{status}" placeholder, e.g. "Remove {status}". */
24
+ removeStatusAriaLabel?: string
25
+ fromLabel?: string
26
+ toLabel?: string
27
+ invalidDateRangeLabel?: string
28
+ paginationOfLabel?: string
29
+ previousPageLabel?: string
30
+ nextPageLabel?: string
31
+ createdTagLabel?: string
32
+ expiresTagLabel?: string
33
+ /** Lowercase connector used in an open-ended range, e.g. "{fromLabel} Jan 1". */
34
+ dateRangeFromLabel?: string
35
+ /** Lowercase connector used in an open-ended range, e.g. "{dateRangeToLabel} Jan 31". */
36
+ dateRangeToLabel?: string
37
+ /** Connector used between two dates, e.g. "Jan 1 {dateRangeSeparatorLabel} Jan 31". */
38
+ dateRangeSeparatorLabel?: string
39
+ createdByLabel?: string
40
+ creationDateLabel?: string
41
+ expiresOnLabel?: string
42
+ totalLabel?: string
43
+ hourLabel?: string
44
+ hoursLabel?: string
45
+ dayLabel?: string
46
+ daysLabel?: string
47
+ weekLabel?: string
48
+ weeksLabel?: string
49
+ monthLabel?: string
50
+ monthsLabel?: string
51
+ /** Suffix appended to the relative expiry hint, e.g. "3 hours {leftSuffixLabel}". */
52
+ leftSuffixLabel?: string
53
+ draftStatus?: string
54
+ requestedStatus?: string
55
+ inReviewStatus?: string
56
+ revisedStatus?: string
57
+ approvedStatus?: string
58
+ declinedStatus?: string
59
+ expiredStatus?: string
60
+ convertedStatus?: string
61
+ convertedToOrderStatus?: string
62
+ }
63
+
64
+ export const defaultMyAccountListQuotesLabels: Required<MyAccountListQuotesSectionLabels> =
65
+ {
66
+ pageTitle: 'Quotes',
67
+ searchPlaceholder: 'Search',
68
+ openFiltersAriaLabel: 'Open Filters',
69
+ filtersLabel: 'Filters',
70
+ clearAllLabel: 'Clear All',
71
+ viewResultsLabel: 'View Results',
72
+ noResultsLabel: 'No results found',
73
+ noQuotesLabel: "You don't have any quotes",
74
+ statusFacetLabel: 'Status',
75
+ createdAtFacetLabel: 'Created Date',
76
+ expiresAtFacetLabel: 'Expiry Date',
77
+ removeStatusAriaLabel: 'Remove {status}',
78
+ fromLabel: 'From',
79
+ toLabel: 'To',
80
+ invalidDateRangeLabel: 'Invalid date range',
81
+ paginationOfLabel: 'of',
82
+ previousPageLabel: 'Previous Page',
83
+ nextPageLabel: 'Next Page',
84
+ createdTagLabel: 'Created',
85
+ expiresTagLabel: 'Expires',
86
+ dateRangeFromLabel: 'from',
87
+ dateRangeToLabel: 'to',
88
+ dateRangeSeparatorLabel: 'to',
89
+ createdByLabel: 'Created by',
90
+ creationDateLabel: 'Creation date',
91
+ expiresOnLabel: 'Expires on',
92
+ totalLabel: 'Total',
93
+ hourLabel: 'hour',
94
+ hoursLabel: 'hours',
95
+ dayLabel: 'day',
96
+ daysLabel: 'days',
97
+ weekLabel: 'week',
98
+ weeksLabel: 'weeks',
99
+ monthLabel: 'month',
100
+ monthsLabel: 'months',
101
+ leftSuffixLabel: 'left',
102
+ draftStatus: quoteStatusMap.Draft.label,
103
+ requestedStatus: quoteStatusMap.Requested.label,
104
+ inReviewStatus: quoteStatusMap.InReview.label,
105
+ revisedStatus: quoteStatusMap.Reviewed.label,
106
+ approvedStatus: quoteStatusMap.Approved.label,
107
+ declinedStatus: quoteStatusMap.Declined.label,
108
+ expiredStatus: quoteStatusMap.Expired.label,
109
+ convertedStatus: quoteStatusMap.ConvertedToCart.label,
110
+ convertedToOrderStatus: quoteStatusMap.ConvertedToOrder.label,
111
+ }
112
+
113
+ export function resolveMyAccountListQuotesLabels(
114
+ labels?: MyAccountListQuotesSectionLabels
115
+ ): Required<MyAccountListQuotesSectionLabels> {
116
+ return resolveNonBlankLabels(defaultMyAccountListQuotesLabels, labels)
117
+ }
118
+
119
+ export function pickQuoteStatusCmsLabels(
120
+ labels?: MyAccountListQuotesSectionLabels
121
+ ): QuoteStatusCmsLabels | undefined {
122
+ return pickQuoteStatusCmsLabelsFromData(
123
+ labels as Record<string, unknown> | undefined
124
+ )
125
+ }
126
+
127
+ /** Formats the status chip's remove-button accessible name, substituting the "{status}" placeholder. */
128
+ export function formatRemoveStatusAriaLabel(
129
+ template: string,
130
+ status: string
131
+ ): string {
132
+ const { found, prefix, suffix } = splitOnFirstToken(template, '{status}')
133
+
134
+ if (!found) {
135
+ return template
136
+ }
137
+
138
+ return prefix + status + suffix
139
+ }
140
+
141
+ /**
142
+ * Formats a relative expiry hint (e.g. "3 hours left") using CMS-configurable
143
+ * unit words and suffix. `count` singular/plural selection mirrors the
144
+ * original hardcoded implementation.
145
+ */
146
+ export function formatRelativeExpiryLabel(
147
+ count: number,
148
+ unit: 'hour' | 'day' | 'week' | 'month',
149
+ labels: Required<MyAccountListQuotesSectionLabels>
150
+ ): string {
151
+ const unitLabels: Record<typeof unit, { singular: string; plural: string }> =
152
+ {
153
+ hour: { singular: labels.hourLabel, plural: labels.hoursLabel },
154
+ day: { singular: labels.dayLabel, plural: labels.daysLabel },
155
+ week: { singular: labels.weekLabel, plural: labels.weeksLabel },
156
+ month: { singular: labels.monthLabel, plural: labels.monthsLabel },
157
+ }
158
+
159
+ const unitLabel =
160
+ count === 1 ? unitLabels[unit].singular : unitLabels[unit].plural
161
+
162
+ return `${count} ${unitLabel} ${labels.leftSuffixLabel}`
163
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ComponentType } from 'react'
2
2
 
3
3
  import AccountListCards from 'src/components/sections/Account/AccountListCards'
4
+ import AccountListQuotes from 'src/components/sections/Account/AccountListQuotes'
4
5
  import AccountNavigation from 'src/components/sections/Account/AccountNavigation'
5
6
  import AccountOrderBudgets from 'src/components/sections/Account/AccountOrderBudgets'
6
7
  import AccountOrderDelivery from 'src/components/sections/Account/AccountOrderDelivery'
@@ -27,6 +28,7 @@ const ACCOUNT_COMPONENTS: Record<string, ComponentType<any>> = {
27
28
  [getComponentKey(AccountProfile, 'AccountProfile')]: AccountProfile,
28
29
  [getComponentKey(AccountOrdersList, 'AccountOrdersList')]: AccountOrdersList,
29
30
  [getComponentKey(AccountListCards, 'AccountListCards')]: AccountListCards,
31
+ [getComponentKey(AccountListQuotes, 'AccountListQuotes')]: AccountListQuotes,
30
32
  [getComponentKey(AccountUserDetails, 'AccountUserDetails')]:
31
33
  AccountUserDetails,
32
34
  [getComponentKey(AccountSecurity, 'AccountSecurity')]: AccountSecurity,
@@ -0,0 +1,141 @@
1
+ import { MyAccountListQuotes } from 'src/components/account/quotes/MyAccountListQuotes'
2
+ import type { MyAccountListQuotesSectionLabels } from 'src/components/account/quotes/MyAccountListQuotes/quotesLabels'
3
+ import { defaultMyAccountListQuotesLabels } from 'src/components/account/quotes/MyAccountListQuotes/quotesLabels'
4
+ import {
5
+ type AccountQuotesPageData,
6
+ useAccountPageData,
7
+ } from 'src/sdk/account/accountPageContext'
8
+ import Section from '../Section'
9
+
10
+ export type AccountListQuotesProps = MyAccountListQuotesSectionLabels
11
+
12
+ function hasQuotePageData(
13
+ data: AccountQuotesPageData | Record<string, never>
14
+ ): data is AccountQuotesPageData {
15
+ return (
16
+ 'listQuotes' in data &&
17
+ 'total' in data &&
18
+ 'perPage' in data &&
19
+ 'filters' in data
20
+ )
21
+ }
22
+
23
+ const AccountListQuotes = ({
24
+ pageTitle = defaultMyAccountListQuotesLabels.pageTitle,
25
+ searchPlaceholder = defaultMyAccountListQuotesLabels.searchPlaceholder,
26
+ openFiltersAriaLabel = defaultMyAccountListQuotesLabels.openFiltersAriaLabel,
27
+ filtersLabel = defaultMyAccountListQuotesLabels.filtersLabel,
28
+ clearAllLabel = defaultMyAccountListQuotesLabels.clearAllLabel,
29
+ viewResultsLabel = defaultMyAccountListQuotesLabels.viewResultsLabel,
30
+ noResultsLabel = defaultMyAccountListQuotesLabels.noResultsLabel,
31
+ noQuotesLabel = defaultMyAccountListQuotesLabels.noQuotesLabel,
32
+ statusFacetLabel = defaultMyAccountListQuotesLabels.statusFacetLabel,
33
+ createdAtFacetLabel = defaultMyAccountListQuotesLabels.createdAtFacetLabel,
34
+ expiresAtFacetLabel = defaultMyAccountListQuotesLabels.expiresAtFacetLabel,
35
+ removeStatusAriaLabel = defaultMyAccountListQuotesLabels.removeStatusAriaLabel,
36
+ fromLabel = defaultMyAccountListQuotesLabels.fromLabel,
37
+ toLabel = defaultMyAccountListQuotesLabels.toLabel,
38
+ invalidDateRangeLabel = defaultMyAccountListQuotesLabels.invalidDateRangeLabel,
39
+ paginationOfLabel = defaultMyAccountListQuotesLabels.paginationOfLabel,
40
+ previousPageLabel = defaultMyAccountListQuotesLabels.previousPageLabel,
41
+ nextPageLabel = defaultMyAccountListQuotesLabels.nextPageLabel,
42
+ createdTagLabel = defaultMyAccountListQuotesLabels.createdTagLabel,
43
+ expiresTagLabel = defaultMyAccountListQuotesLabels.expiresTagLabel,
44
+ dateRangeFromLabel = defaultMyAccountListQuotesLabels.dateRangeFromLabel,
45
+ dateRangeToLabel = defaultMyAccountListQuotesLabels.dateRangeToLabel,
46
+ dateRangeSeparatorLabel = defaultMyAccountListQuotesLabels.dateRangeSeparatorLabel,
47
+ createdByLabel = defaultMyAccountListQuotesLabels.createdByLabel,
48
+ creationDateLabel = defaultMyAccountListQuotesLabels.creationDateLabel,
49
+ expiresOnLabel = defaultMyAccountListQuotesLabels.expiresOnLabel,
50
+ totalLabel = defaultMyAccountListQuotesLabels.totalLabel,
51
+ hourLabel = defaultMyAccountListQuotesLabels.hourLabel,
52
+ hoursLabel = defaultMyAccountListQuotesLabels.hoursLabel,
53
+ dayLabel = defaultMyAccountListQuotesLabels.dayLabel,
54
+ daysLabel = defaultMyAccountListQuotesLabels.daysLabel,
55
+ weekLabel = defaultMyAccountListQuotesLabels.weekLabel,
56
+ weeksLabel = defaultMyAccountListQuotesLabels.weeksLabel,
57
+ monthLabel = defaultMyAccountListQuotesLabels.monthLabel,
58
+ monthsLabel = defaultMyAccountListQuotesLabels.monthsLabel,
59
+ leftSuffixLabel = defaultMyAccountListQuotesLabels.leftSuffixLabel,
60
+ draftStatus = defaultMyAccountListQuotesLabels.draftStatus,
61
+ requestedStatus = defaultMyAccountListQuotesLabels.requestedStatus,
62
+ inReviewStatus = defaultMyAccountListQuotesLabels.inReviewStatus,
63
+ revisedStatus = defaultMyAccountListQuotesLabels.revisedStatus,
64
+ approvedStatus = defaultMyAccountListQuotesLabels.approvedStatus,
65
+ declinedStatus = defaultMyAccountListQuotesLabels.declinedStatus,
66
+ expiredStatus = defaultMyAccountListQuotesLabels.expiredStatus,
67
+ convertedStatus = defaultMyAccountListQuotesLabels.convertedStatus,
68
+ convertedToOrderStatus = defaultMyAccountListQuotesLabels.convertedToOrderStatus,
69
+ }: AccountListQuotesProps) => {
70
+ const accountPageData = useAccountPageData<
71
+ AccountQuotesPageData | Record<string, never>
72
+ >()
73
+
74
+ if (!hasQuotePageData(accountPageData)) {
75
+ return null
76
+ }
77
+
78
+ const { listQuotes, total, perPage, filters } = accountPageData
79
+
80
+ return (
81
+ <Section className="section-account-list-quotes">
82
+ <MyAccountListQuotes
83
+ listQuotes={listQuotes}
84
+ total={total}
85
+ perPage={perPage}
86
+ filters={filters}
87
+ labels={{
88
+ pageTitle,
89
+ searchPlaceholder,
90
+ openFiltersAriaLabel,
91
+ filtersLabel,
92
+ clearAllLabel,
93
+ viewResultsLabel,
94
+ noResultsLabel,
95
+ noQuotesLabel,
96
+ statusFacetLabel,
97
+ createdAtFacetLabel,
98
+ expiresAtFacetLabel,
99
+ removeStatusAriaLabel,
100
+ fromLabel,
101
+ toLabel,
102
+ invalidDateRangeLabel,
103
+ paginationOfLabel,
104
+ previousPageLabel,
105
+ nextPageLabel,
106
+ createdTagLabel,
107
+ expiresTagLabel,
108
+ dateRangeFromLabel,
109
+ dateRangeToLabel,
110
+ dateRangeSeparatorLabel,
111
+ createdByLabel,
112
+ creationDateLabel,
113
+ expiresOnLabel,
114
+ totalLabel,
115
+ hourLabel,
116
+ hoursLabel,
117
+ dayLabel,
118
+ daysLabel,
119
+ weekLabel,
120
+ weeksLabel,
121
+ monthLabel,
122
+ monthsLabel,
123
+ leftSuffixLabel,
124
+ draftStatus,
125
+ requestedStatus,
126
+ inReviewStatus,
127
+ revisedStatus,
128
+ approvedStatus,
129
+ declinedStatus,
130
+ expiredStatus,
131
+ convertedStatus,
132
+ convertedToOrderStatus,
133
+ }}
134
+ />
135
+ </Section>
136
+ )
137
+ }
138
+
139
+ AccountListQuotes.$componentKey = 'AccountListQuotes'
140
+
141
+ export default AccountListQuotes
@@ -1,9 +1,12 @@
1
- import type { Locator } from '@vtex/client-cms'
1
+ import type { Locator, Section } from '@vtex/client-cms'
2
2
  import type { GetServerSideProps } from 'next'
3
3
  import { NextSeo } from 'next-seo'
4
4
  import type { ComponentType } from 'react'
5
5
  import { Layout } from 'src/components/account'
6
- import RenderSections from 'src/components/cms/RenderSections'
6
+ import RenderSections, {
7
+ RenderSectionsBase,
8
+ } from 'src/components/cms/RenderSections'
9
+ import ACCOUNT_COMPONENTS from 'src/components/cms/account/Components'
7
10
  import GLOBAL_COMPONENTS from 'src/components/cms/global/Components'
8
11
  import CUSTOM_COMPONENTS from 'src/customizations/src/components'
9
12
 
@@ -22,15 +25,18 @@ import { ServerAccountPageQueryDocument } from '@generated/graphql'
22
25
  import AfterSection from 'src/customizations/src/myAccount/extensions/quotes/after'
23
26
  import BeforeSection from 'src/customizations/src/myAccount/extensions/quotes/before'
24
27
  import type { MyAccountProps } from 'src/experimental/myAccountServerSideProps'
28
+ import type { AccountQuotesPageData } from 'src/sdk/account/accountPageContext'
29
+ import type { AccountNavigationLabels } from 'src/sdk/account/getMyAccountRoutes'
30
+ import { getIsRepresentative } from 'src/sdk/account/getIsRepresentative'
31
+ import PageProvider from 'src/sdk/overrides/PageProvider'
25
32
  import { execute } from 'src/server'
26
33
  import { injectGlobalSections } from 'src/server/cms/global'
34
+ import { extractAccountNavigationData } from 'src/server/cms/myAccountDefaultSections'
35
+ import { fetchMyAccountPageContent } from 'src/server/cms/fetchMyAccountPageContent'
27
36
  import { withLocaleValidationSSR } from 'src/utils/localization/withLocaleValidation'
28
37
  import { getMyAccountRedirect } from 'src/utils/myAccountRedirect'
29
38
 
30
39
  import storeConfig from 'discovery.config'
31
- import { MyAccountListQuotes } from 'src/components/account/quotes/MyAccountListQuotes'
32
- import { getIsRepresentative } from 'src/sdk/account/getIsRepresentative'
33
- import PageProvider from 'src/sdk/overrides/PageProvider'
34
40
  import { extractStatusFromError } from 'src/utils/utilities'
35
41
 
36
42
  const COMPONENTS: Record<string, ComponentType<any>> = {
@@ -39,44 +45,43 @@ const COMPONENTS: Record<string, ComponentType<any>> = {
39
45
  }
40
46
 
41
47
  type ListQuotesPageProps = {
42
- listQuotes: ServerListQuotesQueryQuery['listUserQuotes']
43
- total: number
44
- perPage: number
45
- filters: {
46
- page: number
47
- status: string[]
48
- createdAtFrom: string
49
- createdAtTo: string
50
- expiresAtFrom: string
51
- expiresAtTo: string
52
- label: string
53
- }
48
+ pageSections: Section[]
49
+ navigationLabels: AccountNavigationLabels
50
+ accountPageData: AccountQuotesPageData
54
51
  } & MyAccountProps
55
52
 
56
53
  export default function ListQuotesPage({
57
54
  globalSections: globalSectionsProp,
55
+ pageSections,
56
+ navigationLabels,
57
+ accountPageData,
58
58
  accountName,
59
- listQuotes,
60
- total,
61
- perPage,
62
- filters,
63
59
  isRepresentative,
64
60
  }: ListQuotesPageProps) {
65
61
  const { sections: globalSections, settings: globalSettings } =
66
62
  globalSectionsProp ?? {}
67
63
 
68
64
  return (
69
- <PageProvider context={{ globalSettings }}>
65
+ <PageProvider
66
+ context={{
67
+ globalSettings,
68
+ accountPageData,
69
+ navigationLabels,
70
+ }}
71
+ >
70
72
  <RenderSections globalSections={globalSections} components={COMPONENTS}>
71
73
  <NextSeo noindex nofollow />
72
74
 
73
- <Layout isRepresentative={isRepresentative} accountName={accountName}>
75
+ <Layout
76
+ isRepresentative={isRepresentative}
77
+ accountName={accountName}
78
+ navigationLabels={navigationLabels}
79
+ >
74
80
  <BeforeSection />
75
- <MyAccountListQuotes
76
- listQuotes={listQuotes}
77
- filters={filters}
78
- perPage={perPage}
79
- total={total}
81
+ <RenderSectionsBase
82
+ skipLazyLoading
83
+ sections={pageSections}
84
+ components={ACCOUNT_COMPONENTS}
80
85
  />
81
86
  <AfterSection />
82
87
  </Layout>
@@ -113,7 +118,7 @@ const organizationMemberQuery = gql(`
113
118
  `)
114
119
 
115
120
  const getServerSidePropsBase: GetServerSideProps<
116
- MyAccountProps,
121
+ ListQuotesPageProps,
117
122
  Record<string, string>,
118
123
  Locator
119
124
  > = async (context) => {
@@ -157,6 +162,7 @@ const getServerSidePropsBase: GetServerSideProps<
157
162
  const label = (context.query.label as string | undefined) || ''
158
163
 
159
164
  const [
165
+ pageContent,
160
166
  listQuotesResult,
161
167
  accountProfileResult,
162
168
  organizationMemberResult,
@@ -164,6 +170,11 @@ const getServerSidePropsBase: GetServerSideProps<
164
170
  globalSectionsHeader,
165
171
  globalSectionsFooter,
166
172
  ] = await Promise.all([
173
+ fetchMyAccountPageContent(
174
+ 'myAccountQuotes',
175
+ contentContext,
176
+ '/pvt/account/quotes'
177
+ ),
167
178
  execute<ServerListQuotesQueryQueryVariables, ServerListQuotesQueryQuery>(
168
179
  {
169
180
  variables: {
@@ -232,6 +243,10 @@ const getServerSidePropsBase: GetServerSideProps<
232
243
  }
233
244
  }
234
245
 
246
+ const { pageSections, navigationData } = extractAccountNavigationData(
247
+ pageContent.sections
248
+ )
249
+
235
250
  const globalSectionsResult = injectGlobalSections({
236
251
  globalSections,
237
252
  globalSectionsHeader,
@@ -242,18 +257,22 @@ const getServerSidePropsBase: GetServerSideProps<
242
257
  props: {
243
258
  globalSections: globalSectionsResult,
244
259
  accountName: accountProfileResult?.data?.accountProfile?.name ?? '',
245
- listQuotes: listQuotesResult.data.listUserQuotes,
246
- total: listQuotesResult.data.listUserQuotes.paging.total,
247
- perPage: listQuotesResult.data.listUserQuotes.paging.perPage,
248
- filters: {
249
- page: listQuotesResult.data.listUserQuotes.paging.currentPage ?? page,
250
- status,
251
- createdAtFrom,
252
- createdAtTo,
253
- expiresAtFrom,
254
- expiresAtTo,
255
- label,
260
+ navigationLabels: navigationData as AccountNavigationLabels,
261
+ accountPageData: {
262
+ listQuotes: listQuotesResult.data.listUserQuotes,
263
+ total: listQuotesResult.data.listUserQuotes.paging.total,
264
+ perPage: listQuotesResult.data.listUserQuotes.paging.perPage,
265
+ filters: {
266
+ page: listQuotesResult.data.listUserQuotes.paging.currentPage ?? page,
267
+ status,
268
+ createdAtFrom,
269
+ createdAtTo,
270
+ expiresAtFrom,
271
+ expiresAtTo,
272
+ label,
273
+ },
256
274
  },
275
+ pageSections,
257
276
  isRepresentative,
258
277
  },
259
278
  }
@@ -1,3 +1,14 @@
1
+ // Keep this page at `s/index.tsx`; do not rename it back to a bare `s.tsx`.
2
+ // Next.js maps both to the same `/s` route, but Yarn Classic 1.x loses the
3
+ // bare `s.tsx` while extracting @faststore/core on Windows: right after
4
+ // `yarn install` the file is already absent from node_modules, while every
5
+ // sibling page survives, and `next build` then fails type-checking with a
6
+ // misleading "Cannot find module 'src/pages/s'". Extracting the same tarball
7
+ // with Windows' own `tar.exe` keeps the file, so the loss happens inside
8
+ // Yarn's extractor rather than in how we pack or copy. Why Yarn drops this
9
+ // particular entry is not pinned down, but nesting the page under a folder
10
+ // avoids it. Investigation and the before/after Windows CI runs that verified
11
+ // this layout: https://github.com/vtex/faststore/pull/3486
1
12
  import { NextSeo } from 'next-seo'
2
13
  import { useRouter } from 'next/router'
3
14
  import { useEffect, useMemo, useState } from 'react'
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ServerListCardsQueryQuery,
3
3
  ServerListOrdersQueryQuery,
4
+ ServerListQuotesQueryQuery,
4
5
  ServerOrderDetailsQueryQuery,
5
6
  ServerProfileQueryQuery,
6
7
  } from '@generated/graphql'
@@ -29,6 +30,21 @@ export type AccountOrdersListPageData = {
29
30
  }
30
31
  }
31
32
 
33
+ export type AccountQuotesPageData = {
34
+ listQuotes: ServerListQuotesQueryQuery['listUserQuotes']
35
+ total: number
36
+ perPage: number
37
+ filters: {
38
+ page: number
39
+ status: string[]
40
+ createdAtFrom: string
41
+ createdAtTo: string
42
+ expiresAtFrom: string
43
+ expiresAtTo: string
44
+ label: string
45
+ }
46
+ }
47
+
32
48
  export type AccountCardsPageData = {
33
49
  // Both partitioned from the same listCreditCards response by `origin`
34
50
  // (spec my-account-cards-shared-listing-unblock) — one query, no dedup.
@@ -70,6 +86,7 @@ export type AccountPageData =
70
86
  | AccountSecurityPageData
71
87
  | AccountUserDetailsPageData
72
88
  | AccountCardsPageData
89
+ | AccountQuotesPageData
73
90
  | Record<string, never>
74
91
 
75
92
  export interface AccountPageContext extends PageGlobalContext {
@@ -23,6 +23,23 @@ export type AccountNavigationLabels = Partial<{
23
23
  contractLabel: string
24
24
  cardsLabel: string
25
25
  quotesLabel: string
26
+ /** "Change" CTA next to the active contract in the drawer header (opens the switcher). */
27
+ changeContractLabel: string
28
+ contractSwitcherTitleLabel: string
29
+ contractSwitcherBackLabel: string
30
+ contractSwitcherCloseLabel: string
31
+ contractSwitcherCurrentSessionLabel: string
32
+ contractSwitcherSearchPlaceholder: string
33
+ contractSwitcherSearchAriaLabel: string
34
+ contractSwitcherClearSearchLabel: string
35
+ /** Supports a "{count}" placeholder, e.g. "Select one of {count} available contracts:". */
36
+ contractSwitcherAvailableCountLabel: string
37
+ contractSwitcherNoMatchLabel: string
38
+ contractSwitcherEmptyLabel: string
39
+ contractSwitcherLoadErrorLabel: string
40
+ contractSwitcherSwitchErrorLabel: string
41
+ contractSwitcherCancelLabel: string
42
+ contractSwitcherConfirmLabel: string
26
43
  }>
27
44
 
28
45
  interface GetMyAccountRouteParams {
@@ -0,0 +1,36 @@
1
+ /** Keeps `defaults` when an override is missing, empty, or whitespace-only. */
2
+ export function resolveNonBlankLabels<T extends Record<string, string>>(
3
+ defaults: T,
4
+ overrides?: Partial<T>
5
+ ): T {
6
+ const resolved = { ...defaults }
7
+
8
+ if (!overrides) {
9
+ return resolved
10
+ }
11
+
12
+ for (const key of Object.keys(defaults) as (keyof T)[]) {
13
+ const value = overrides[key]
14
+
15
+ if (typeof value === 'string' && value.trim().length > 0) {
16
+ resolved[key] = value
17
+ }
18
+ }
19
+
20
+ return resolved
21
+ }
22
+
23
+ /** Splits on the first `token`. A missing token leaves the whole string as `prefix`. */
24
+ export function splitOnFirstToken(template: string, token: string) {
25
+ const index = template.indexOf(token)
26
+
27
+ if (index === -1) {
28
+ return { found: false as const, prefix: template, suffix: '' }
29
+ }
30
+
31
+ return {
32
+ found: true as const,
33
+ prefix: template.slice(0, index),
34
+ suffix: template.slice(index + token.length),
35
+ }
36
+ }
@@ -6,6 +6,7 @@ export type NativeMyAccountContentType =
6
6
  | 'myAccountSecurity'
7
7
  | 'myAccountUnauthorized'
8
8
  | 'myAccountCards'
9
+ | 'myAccountQuotes'
9
10
 
10
11
  /** Store content-types are opaque strings; native literals keep autocompletion. */
11
12
  export type MyAccountContentType = NativeMyAccountContentType | (string & {})
@@ -20,6 +21,7 @@ const DEFAULT_SECTION_KEYS: Record<NativeMyAccountContentType, string[]> = {
20
21
  myAccountProfile: ['AccountNavigation', 'AccountProfile'],
21
22
  myAccountOrders: ['AccountNavigation', 'AccountOrdersList'],
22
23
  myAccountCards: ['AccountNavigation', 'AccountListCards'],
24
+ myAccountQuotes: ['AccountNavigation', 'AccountListQuotes'],
23
25
  // Mirrors the render order of the `@deprecated OrderDetails.tsx` component,
24
26
  // which is the design reference available in the repo. The delivery option
25
27
  // accordions render inside AccountOrderDelivery, so they sit right after the