@faststore/core 4.9.0-dev.2 → 4.9.0-dev.4
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.
- package/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +130 -125
- package/CHANGELOG.md +10 -0
- package/cms/faststore/my-account/components/cms_component__accountlistquotes.jsonc +240 -0
- package/cms/faststore/my-account/components/cms_component__accountnavigation.jsonc +76 -0
- package/cms/faststore/my-account/pages/cms_content_type__myaccountquotes.jsonc +18 -0
- package/package.json +6 -6
- package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/ContractSwitcher.tsx +129 -26
- package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader.tsx +7 -1
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotes.tsx +42 -11
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountListQuotesTable.tsx +45 -15
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountQuoteStatusBadge.tsx +11 -3
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/MyAccountQuotesFilterSlider.tsx +23 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesSelectedTags/MyAccountQuotesSelectedTags.tsx +35 -11
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector.tsx +27 -10
- package/src/components/account/quotes/MyAccountListQuotes/index.ts +2 -0
- package/src/components/account/quotes/MyAccountListQuotes/quoteFilters.ts +16 -6
- package/src/components/account/quotes/MyAccountListQuotes/quotesLabels.ts +163 -0
- package/src/components/cms/account/Components.ts +2 -0
- package/src/components/sections/Account/AccountListQuotes.tsx +141 -0
- package/src/pages/pvt/account/quotes/index.tsx +59 -40
- package/src/sdk/account/accountPageContext.ts +17 -0
- package/src/sdk/account/getMyAccountRoutes.ts +17 -0
- package/src/sdk/account/resolveNonBlankLabels.ts +36 -0
- package/src/server/cms/myAccountDefaultSections.ts +2 -0
- package/src/utils/quoteStatus.ts +81 -0
- package/test/components/account/ContractSwitcher.browser.test.tsx +163 -0
- package/test/components/account/OrganizationDrawer.browser.test.tsx +4 -0
- package/test/components/account/OrganizationDrawerHeader.browser.test.tsx +44 -1
- package/test/components/quotes/MyAccountListQuotes.test.tsx +36 -2
- package/test/components/quotes/MyAccountListQuotesTable.test.tsx +68 -0
- package/test/components/quotes/MyAccountQuoteStatusBadge.test.tsx +25 -0
- package/test/components/quotes/MyAccountQuotesFilterSlider.test.tsx +52 -11
- package/test/components/quotes/MyAccountQuotesSelectedTags.test.tsx +32 -0
- package/test/components/quotes/MyAccountQuotesStatusSelector.test.tsx +100 -0
- package/test/components/quotes/quoteFilters.test.ts +34 -0
- package/test/components/sections/Account/AccountListQuotes.browser.test.tsx +122 -0
- 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
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
|
75
|
+
<Layout
|
|
76
|
+
isRepresentative={isRepresentative}
|
|
77
|
+
accountName={accountName}
|
|
78
|
+
navigationLabels={navigationLabels}
|
|
79
|
+
>
|
|
74
80
|
<BeforeSection />
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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,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
|