@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
@@ -1,6 +1,8 @@
1
1
  import { Button, Icon, Link, SlideOverHeader } from '@faststore/ui'
2
2
  import type { ReactNode } from 'react'
3
3
 
4
+ import { useAccountNavigationLabels } from 'src/sdk/account/accountPageContext'
5
+
4
6
  export type OrganizationDrawerHeaderProps = {
5
7
  onCloseDrawer?: () => void
6
8
  contractImage?: ReactNode
@@ -25,6 +27,10 @@ export const OrganizationDrawerHeader = ({
25
27
  onCloseDrawer,
26
28
  onChangeContract,
27
29
  }: OrganizationDrawerHeaderProps) => {
30
+ const navigationLabels = useAccountNavigationLabels()
31
+ const changeContractLabel =
32
+ navigationLabels?.changeContractLabel?.trim() || 'Change'
33
+
28
34
  return (
29
35
  <>
30
36
  <SlideOverHeader onClose={() => onCloseDrawer?.()} />
@@ -68,7 +74,7 @@ export const OrganizationDrawerHeader = ({
68
74
  variant="secondary"
69
75
  onClick={onChangeContract}
70
76
  >
71
- Change
77
+ {changeContractLabel}
72
78
  </Button>
73
79
  )}
74
80
  </div>
@@ -24,6 +24,11 @@ import {
24
24
  hasActiveFilters,
25
25
  countActiveFilters,
26
26
  } from './quoteFilters'
27
+ import type { MyAccountListQuotesSectionLabels } from './quotesLabels'
28
+ import {
29
+ pickQuoteStatusCmsLabels,
30
+ resolveMyAccountListQuotesLabels,
31
+ } from './quotesLabels'
27
32
  import styles from './styles.module.scss'
28
33
 
29
34
  export type MyAccountListQuotesProps = Readonly<{
@@ -39,6 +44,7 @@ export type MyAccountListQuotesProps = Readonly<{
39
44
  expiresAtTo: string
40
45
  label: string
41
46
  }
47
+ labels?: MyAccountListQuotesSectionLabels
42
48
  }>
43
49
 
44
50
  export default function MyAccountListQuotes({
@@ -46,7 +52,10 @@ export default function MyAccountListQuotes({
46
52
  total,
47
53
  perPage,
48
54
  filters,
55
+ labels: labelsProp,
49
56
  }: MyAccountListQuotesProps) {
57
+ const labels = resolveMyAccountListQuotesLabels(labelsProp)
58
+ const statusCmsLabels = pickQuoteStatusCmsLabels(labelsProp)
50
59
  const { isDesktop } = useScreenResize()
51
60
  const searchInputRef = useRef(null) as MutableRefObject<SearchInputFieldRef>
52
61
 
@@ -68,7 +77,12 @@ export default function MyAccountListQuotes({
68
77
  )
69
78
 
70
79
  const selectedFacets = getSelectedFacets(filters)
71
- const allFacets = getAllFacets(filters)
80
+ const allFacets = getAllFacets(filters, {
81
+ statusFacetLabel: labels.statusFacetLabel,
82
+ createdAtFacetLabel: labels.createdAtFacetLabel,
83
+ expiresAtFacetLabel: labels.expiresAtFacetLabel,
84
+ statusCmsLabels,
85
+ })
72
86
 
73
87
  const filter = useMyAccountFilter({ allFacets, selectedFacets })
74
88
  const { openFilter, filter: displayFilter } = useUI()
@@ -79,13 +93,13 @@ export default function MyAccountListQuotes({
79
93
 
80
94
  return (
81
95
  <div className={styles.page}>
82
- <AccountHeader pageTitle="Quotes" />
96
+ <AccountHeader pageTitle={labels.pageTitle} />
83
97
  <div data-fs-list-quotes-controls>
84
98
  <div data-fs-list-quotes-search-filters>
85
99
  <SearchInputField
86
100
  ref={searchInputRef}
87
101
  data-fs-search-input-field-list-quotes
88
- placeholder="Search"
102
+ placeholder={labels.searchPlaceholder}
89
103
  onBlur={() => {
90
104
  handleSearchChange(searchInputRef.current.inputRef.value)
91
105
  }}
@@ -107,7 +121,7 @@ export default function MyAccountListQuotes({
107
121
  name="FadersHorizontal"
108
122
  width={24}
109
123
  height={24}
110
- aria-label="Open Filters"
124
+ aria-label={labels.openFiltersAriaLabel}
111
125
  />
112
126
  }
113
127
  iconPosition="left"
@@ -119,23 +133,34 @@ export default function MyAccountListQuotes({
119
133
  openFilter()
120
134
  }}
121
135
  >
122
- Filters
136
+ {labels.filtersLabel}
123
137
  {activeFilterCount > 0 && (
124
138
  <span data-fs-filter-count>{activeFilterCount}</span>
125
139
  )}
126
140
  </Button>
127
141
  </div>
128
142
  {isDesktop && total > 0 && (
129
- <Pagination page={filters.page} total={total} perPage={perPage} />
143
+ <Pagination
144
+ page={filters.page}
145
+ total={total}
146
+ perPage={perPage}
147
+ labels={labels}
148
+ />
130
149
  )}
131
150
  </div>
132
151
 
133
152
  {displayFilter && (
134
153
  <MyAccountQuotesFilterSlider
135
154
  {...filter}
136
- title="Filters"
137
- clearButtonLabel="Clear All"
138
- applyButtonLabel="View Results"
155
+ title={labels.filtersLabel}
156
+ clearButtonLabel={labels.clearAllLabel}
157
+ applyButtonLabel={labels.viewResultsLabel}
158
+ statusLabel={labels.statusFacetLabel}
159
+ removeStatusAriaLabel={labels.removeStatusAriaLabel}
160
+ fromLabel={labels.fromLabel}
161
+ toLabel={labels.toLabel}
162
+ invalidDateRangeLabel={labels.invalidDateRangeLabel}
163
+ statusCmsLabels={statusCmsLabels}
139
164
  testId="my-account-quotes-filter-slider"
140
165
  />
141
166
  )}
@@ -150,7 +175,7 @@ export default function MyAccountListQuotes({
150
175
  weight="thin"
151
176
  />
152
177
  }
153
- title={hasFilters ? 'No results found' : "You don't have any quotes"}
178
+ title={hasFilters ? labels.noResultsLabel : labels.noQuotesLabel}
154
179
  bkgColor="light"
155
180
  />
156
181
  ) : (
@@ -159,11 +184,17 @@ export default function MyAccountListQuotes({
159
184
  total={total}
160
185
  perPage={perPage}
161
186
  filters={filters}
187
+ labels={labels}
162
188
  />
163
189
  )}
164
190
 
165
191
  {!isDesktop && total > 0 && (
166
- <Pagination page={filters.page} total={total} perPage={perPage} />
192
+ <Pagination
193
+ page={filters.page}
194
+ total={total}
195
+ perPage={perPage}
196
+ labels={labels}
197
+ />
167
198
  )}
168
199
  </div>
169
200
  )
@@ -4,6 +4,12 @@ import MyAccountQuoteStatusBadge from './MyAccountQuoteStatusBadge'
4
4
  import { useSession } from 'src/sdk/session'
5
5
  import useScreenResize from 'src/sdk/ui/useScreenResize'
6
6
  import type { ServerListQuotesQueryQuery } from '@generated/graphql'
7
+ import type { MyAccountListQuotesSectionLabels } from '../quotesLabels'
8
+ import {
9
+ formatRelativeExpiryLabel,
10
+ pickQuoteStatusCmsLabels,
11
+ resolveMyAccountListQuotesLabels,
12
+ } from '../quotesLabels'
7
13
 
8
14
  function formatDateTime(isoString: string, locale: string) {
9
15
  if (!isoString) return ''
@@ -29,7 +35,10 @@ function formatDateShort(isoString: string, locale: string) {
29
35
  })
30
36
  }
31
37
 
32
- function getRelativeExpiry(isoString: string): string | null {
38
+ function getRelativeExpiry(
39
+ isoString: string,
40
+ labels: Required<MyAccountListQuotesSectionLabels>
41
+ ): string | null {
33
42
  const diffMs = new Date(isoString).getTime() - Date.now()
34
43
  if (diffMs <= 0) return null
35
44
 
@@ -38,10 +47,10 @@ function getRelativeExpiry(isoString: string): string | null {
38
47
  const weeks = Math.floor(days / 7)
39
48
  const months = Math.floor(days / 30)
40
49
 
41
- if (hours < 24) return `${hours} ${hours === 1 ? 'hour' : 'hours'} left`
42
- if (days < 14) return `${days} ${days === 1 ? 'day' : 'days'} left`
43
- if (weeks < 8) return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} left`
44
- return `${months} ${months === 1 ? 'month' : 'months'} left`
50
+ if (hours < 24) return formatRelativeExpiryLabel(hours, 'hour', labels)
51
+ if (days < 14) return formatRelativeExpiryLabel(days, 'day', labels)
52
+ if (weeks < 8) return formatRelativeExpiryLabel(weeks, 'week', labels)
53
+ return formatRelativeExpiryLabel(months, 'month', labels)
45
54
  }
46
55
 
47
56
  function formatAmount(amount: number, locale: string, currencyCode: string) {
@@ -65,17 +74,21 @@ type MyAccountListQuotesTableProps = Readonly<{
65
74
  expiresAtTo: string
66
75
  label: string
67
76
  }
77
+ labels?: MyAccountListQuotesSectionLabels
68
78
  }>
69
79
 
70
80
  export function Pagination({
71
81
  page,
72
82
  total,
73
83
  perPage,
84
+ labels: labelsProp,
74
85
  }: Readonly<{
75
86
  page: number
76
87
  total: number
77
88
  perPage: number
89
+ labels?: MyAccountListQuotesSectionLabels
78
90
  }>) {
91
+ const labels = resolveMyAccountListQuotesLabels(labelsProp)
79
92
  const totalPages = Math.ceil(total / perPage)
80
93
  const firstIndexLabel = page === 1 ? 1 : (page - 1) * perPage + 1
81
94
  const lastIndexLabel = Math.min(firstIndexLabel + perPage - 1, total)
@@ -95,14 +108,14 @@ export function Pagination({
95
108
 
96
109
  return (
97
110
  <div data-fs-list-orders-table-pagination>
98
- <p>{`${firstIndexLabel} — ${lastIndexLabel} of ${total}`}</p>
111
+ <p>{`${firstIndexLabel} — ${lastIndexLabel} ${labels.paginationOfLabel} ${total}`}</p>
99
112
  <IconButton
100
113
  size="small"
101
114
  variant="tertiary"
102
115
  disabled={page === 1}
103
116
  onClick={() => handlePageChange(page - 1)}
104
117
  icon={<Icon name="CaretLeft" />}
105
- aria-label="Previous Page"
118
+ aria-label={labels.previousPageLabel}
106
119
  />
107
120
  <IconButton
108
121
  size="small"
@@ -110,7 +123,7 @@ export function Pagination({
110
123
  disabled={page === totalPages}
111
124
  onClick={() => handlePageChange(page + 1)}
112
125
  icon={<Icon name="CaretRight" />}
113
- aria-label="Next Page"
126
+ aria-label={labels.nextPageLabel}
114
127
  />
115
128
  </div>
116
129
  )
@@ -121,7 +134,10 @@ export default function MyAccountListQuotesTable({
121
134
  total,
122
135
  perPage,
123
136
  filters,
137
+ labels: labelsProp,
124
138
  }: MyAccountListQuotesTableProps) {
139
+ const labels = resolveMyAccountListQuotesLabels(labelsProp)
140
+ const statusCmsLabels = pickQuoteStatusCmsLabels(labelsProp)
125
141
  const { isDesktop } = useScreenResize()
126
142
  const { locale, currency } = useSession()
127
143
  const currencyCode = currency.code
@@ -130,7 +146,7 @@ export default function MyAccountListQuotesTable({
130
146
  <>
131
147
  <div data-fs-quotes-list>
132
148
  {listQuotes.list.map((item) => {
133
- const relativeExpiry = getRelativeExpiry(item.expiresAt)
149
+ const relativeExpiry = getRelativeExpiry(item.expiresAt, labels)
134
150
  const formattedAmount = formatAmount(
135
151
  item.amount,
136
152
  locale,
@@ -150,20 +166,26 @@ export default function MyAccountListQuotesTable({
150
166
  <div data-fs-quotes-list-col-dates>
151
167
  {item.createdBy && (
152
168
  <div data-fs-quotes-list-date-group>
153
- <span data-fs-quotes-list-date-label>Created by</span>
169
+ <span data-fs-quotes-list-date-label>
170
+ {labels.createdByLabel}
171
+ </span>
154
172
  <span data-fs-quotes-list-date-value>
155
173
  {item.createdBy}
156
174
  </span>
157
175
  </div>
158
176
  )}
159
177
  <div data-fs-quotes-list-date-group>
160
- <span data-fs-quotes-list-date-label>Creation date</span>
178
+ <span data-fs-quotes-list-date-label>
179
+ {labels.creationDateLabel}
180
+ </span>
161
181
  <span data-fs-quotes-list-date-value>
162
182
  {formatDateTime(item.createdAt, locale)}
163
183
  </span>
164
184
  </div>
165
185
  <div data-fs-quotes-list-date-group>
166
- <span data-fs-quotes-list-date-label>Expires on</span>
186
+ <span data-fs-quotes-list-date-label>
187
+ {labels.expiresOnLabel}
188
+ </span>
167
189
  <span data-fs-quotes-list-date-value>
168
190
  {formatDateShort(item.expiresAt, locale)}
169
191
  {relativeExpiry && (
@@ -179,16 +201,24 @@ export default function MyAccountListQuotesTable({
179
201
 
180
202
  {/* Right: badge + total */}
181
203
  <div data-fs-quotes-list-col-status>
182
- <MyAccountQuoteStatusBadge status={item.status} />
204
+ <MyAccountQuoteStatusBadge
205
+ status={item.status}
206
+ statusCmsLabels={statusCmsLabels}
207
+ />
183
208
  <p data-fs-quotes-list-total>
184
- Total <strong>{formattedAmount}</strong>
209
+ {labels.totalLabel} <strong>{formattedAmount}</strong>
185
210
  </p>
186
211
  </div>
187
212
  </div>
188
213
  )
189
214
  })}
190
215
  </div>
191
- <Pagination page={filters.page} total={total} perPage={perPage} />
216
+ <Pagination
217
+ page={filters.page}
218
+ total={total}
219
+ perPage={perPage}
220
+ labels={labels}
221
+ />
192
222
  </>
193
223
  )
194
224
  }
@@ -1,7 +1,15 @@
1
- import { quoteStatusMap, type QuoteStatusKey } from 'src/utils/quoteStatus'
1
+ import type { QuoteStatusCmsLabels } from 'src/utils/quoteStatus'
2
+ import { getLocalizedQuoteStatusMap } from 'src/utils/quoteStatus'
2
3
 
3
- function MyAccountQuoteStatusBadge({ status }: Readonly<{ status: string }>) {
4
- const entry = quoteStatusMap[status as QuoteStatusKey]
4
+ function MyAccountQuoteStatusBadge({
5
+ status,
6
+ statusCmsLabels,
7
+ }: Readonly<{
8
+ status: string
9
+ statusCmsLabels?: QuoteStatusCmsLabels
10
+ }>) {
11
+ const statusMap = getLocalizedQuoteStatusMap(statusCmsLabels)
12
+ const entry = statusMap[status as keyof typeof statusMap]
5
13
  return (
6
14
  <span
7
15
  data-fs-my-account-badge
@@ -6,6 +6,7 @@ import type {
6
6
  MyAccountFilter_FacetsFragment,
7
7
  useMyAccountFilter,
8
8
  } from 'src/sdk/search/useMyAccountFilter'
9
+ import type { QuoteStatusCmsLabels } from 'src/utils/quoteStatus'
9
10
  import MyAccountQuotesStatusSelector from '../MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector'
10
11
  import styles from './section.module.scss'
11
12
 
@@ -15,6 +16,13 @@ export interface MyAccountQuotesFilterSliderProps {
15
16
  title?: string
16
17
  clearButtonLabel?: string
17
18
  applyButtonLabel?: string
19
+ statusLabel?: string
20
+ /** Supports a "{status}" placeholder, e.g. "Remove {status}". */
21
+ removeStatusAriaLabel?: string
22
+ fromLabel?: string
23
+ toLabel?: string
24
+ invalidDateRangeLabel?: string
25
+ statusCmsLabels?: QuoteStatusCmsLabels
18
26
  }
19
27
 
20
28
  function MyAccountQuotesFilterSlider({
@@ -26,6 +34,12 @@ function MyAccountQuotesFilterSlider({
26
34
  title,
27
35
  clearButtonLabel,
28
36
  applyButtonLabel,
37
+ statusLabel,
38
+ removeStatusAriaLabel,
39
+ fromLabel,
40
+ toLabel,
41
+ invalidDateRangeLabel,
42
+ statusCmsLabels,
29
43
  }: MyAccountQuotesFilterSliderProps & ReturnType<typeof useMyAccountFilter>) {
30
44
  const createdDateRangeRef = useRef<{
31
45
  clear: () => void
@@ -115,6 +129,9 @@ function MyAccountQuotesFilterSlider({
115
129
  {statusFacet && (
116
130
  <MyAccountQuotesStatusSelector
117
131
  value={selectedStatusValues}
132
+ statusLabel={statusLabel}
133
+ removeStatusAriaLabel={removeStatusAriaLabel}
134
+ statusCmsLabels={statusCmsLabels}
118
135
  onChange={(newSelected) => {
119
136
  const added = newSelected.filter(
120
137
  (v) => !selectedStatusValues.includes(v)
@@ -143,6 +160,9 @@ function MyAccountQuotesFilterSlider({
143
160
  from={facet.from}
144
161
  to={facet.to}
145
162
  setDisabled={setDisabled}
163
+ fromLabel={fromLabel}
164
+ toLabel={toLabel}
165
+ invalidDateRangeLabel={invalidDateRangeLabel}
146
166
  />
147
167
  )}
148
168
  {facet.key === 'expiresAt' && (
@@ -151,6 +171,9 @@ function MyAccountQuotesFilterSlider({
151
171
  from={facet.from}
152
172
  to={facet.to}
153
173
  setDisabled={setDisabled}
174
+ fromLabel={fromLabel}
175
+ toLabel={toLabel}
176
+ invalidDateRangeLabel={invalidDateRangeLabel}
154
177
  />
155
178
  )}
156
179
  </div>
@@ -1,6 +1,14 @@
1
1
  import { Button } from '@faststore/ui'
2
2
  import { useSession } from 'src/sdk/session'
3
- import { quoteStatusMap, type QuoteStatusKey } from 'src/utils/quoteStatus'
3
+ import {
4
+ getLocalizedQuoteStatusMap,
5
+ type QuoteStatusCmsLabels,
6
+ } from 'src/utils/quoteStatus'
7
+ import type { MyAccountListQuotesSectionLabels } from '../quotesLabels'
8
+ import {
9
+ pickQuoteStatusCmsLabels,
10
+ resolveMyAccountListQuotesLabels,
11
+ } from '../quotesLabels'
4
12
  import { formatFilterDate } from '../quoteFilters'
5
13
 
6
14
  type MyAccountQuotesSelectedTagsProps = Readonly<{
@@ -21,34 +29,42 @@ type MyAccountQuotesSelectedTagsProps = Readonly<{
21
29
  | 'expiresAtTo',
22
30
  value: string
23
31
  ) => void
32
+ labels?: MyAccountListQuotesSectionLabels
24
33
  }>
25
34
 
26
35
  function formatDateRangeLabel(
27
36
  from: string | undefined,
28
37
  to: string | undefined,
29
- locale: string
38
+ locale: string,
39
+ labels: Required<MyAccountListQuotesSectionLabels>
30
40
  ) {
31
41
  if (from && to) {
32
- return `${formatFilterDate(from, locale)} to ${formatFilterDate(to, locale)}`
42
+ return `${formatFilterDate(from, locale)} ${labels.dateRangeSeparatorLabel} ${formatFilterDate(to, locale)}`
33
43
  }
34
44
  if (from) {
35
- return `from ${formatFilterDate(from, locale)}`
45
+ return `${labels.dateRangeFromLabel} ${formatFilterDate(from, locale)}`
36
46
  }
37
- return `to ${formatFilterDate(to!, locale)}`
47
+ return `${labels.dateRangeToLabel} ${formatFilterDate(to!, locale)}`
38
48
  }
39
49
 
40
50
  function Tags({
41
51
  filters,
42
52
  onRemoveFilter,
43
- }: Pick<MyAccountQuotesSelectedTagsProps, 'filters' | 'onRemoveFilter'>) {
53
+ labels,
54
+ statusCmsLabels,
55
+ }: Pick<MyAccountQuotesSelectedTagsProps, 'filters' | 'onRemoveFilter'> & {
56
+ labels: Required<MyAccountListQuotesSectionLabels>
57
+ statusCmsLabels?: QuoteStatusCmsLabels
58
+ }) {
44
59
  const { locale } = useSession()
45
60
  const { createdAtFrom, createdAtTo, expiresAtFrom, expiresAtTo, status } =
46
61
  filters
62
+ const statusMap = getLocalizedQuoteStatusMap(statusCmsLabels)
47
63
 
48
64
  const createdTag = (createdAtFrom || createdAtTo) && (
49
65
  <div key="created-date-range" data-fs-list-orders-selected-tag>
50
66
  <span data-fs-list-orders-selected-tag-label>
51
- {`Created: ${formatDateRangeLabel(createdAtFrom, createdAtTo, locale)}`}
67
+ {`${labels.createdTagLabel}: ${formatDateRangeLabel(createdAtFrom, createdAtTo, locale, labels)}`}
52
68
  </span>
53
69
  <button
54
70
  data-fs-list-orders-selected-tag-clear
@@ -62,7 +78,7 @@ function Tags({
62
78
  const expiresTag = (expiresAtFrom || expiresAtTo) && (
63
79
  <div key="expires-date-range" data-fs-list-orders-selected-tag>
64
80
  <span data-fs-list-orders-selected-tag-label>
65
- {`Expires: ${formatDateRangeLabel(expiresAtFrom, expiresAtTo, locale)}`}
81
+ {`${labels.expiresTagLabel}: ${formatDateRangeLabel(expiresAtFrom, expiresAtTo, locale, labels)}`}
66
82
  </span>
67
83
  <button
68
84
  data-fs-list-orders-selected-tag-clear
@@ -75,7 +91,7 @@ function Tags({
75
91
 
76
92
  const statusTags = (status || []).map((value) => (
77
93
  <div key={`status-${value}`} data-fs-list-orders-selected-tag>
78
- <span>{quoteStatusMap[value as QuoteStatusKey]?.label ?? value}</span>
94
+ <span>{statusMap[value as keyof typeof statusMap]?.label ?? value}</span>
79
95
  <button
80
96
  data-fs-list-orders-selected-tag-clear
81
97
  onClick={() => onRemoveFilter('status', value)}
@@ -98,7 +114,10 @@ function MyAccountQuotesSelectedTags({
98
114
  filters,
99
115
  onClearAll,
100
116
  onRemoveFilter,
117
+ labels: labelsProp,
101
118
  }: MyAccountQuotesSelectedTagsProps) {
119
+ const labels = resolveMyAccountListQuotesLabels(labelsProp)
120
+ const statusCmsLabels = pickQuoteStatusCmsLabels(labelsProp)
102
121
  const hasFilters = Object.entries(filters).some(([, values]) =>
103
122
  Array.isArray(values) ? values.length > 0 : Boolean(values)
104
123
  )
@@ -107,14 +126,19 @@ function MyAccountQuotesSelectedTags({
107
126
  <>
108
127
  {hasFilters && (
109
128
  <div data-fs-list-orders-selected-tags>
110
- <Tags filters={filters} onRemoveFilter={onRemoveFilter} />
129
+ <Tags
130
+ filters={filters}
131
+ onRemoveFilter={onRemoveFilter}
132
+ labels={labels}
133
+ statusCmsLabels={statusCmsLabels}
134
+ />
111
135
  <Button
112
136
  variant="tertiary"
113
137
  size="small"
114
138
  data-fs-list-orders-selected-tags-clear-all-button
115
139
  onClick={onClearAll}
116
140
  >
117
- Clear All
141
+ {labels.clearAllLabel}
118
142
  </Button>
119
143
  </div>
120
144
  )}
@@ -1,23 +1,37 @@
1
- import { useEffect, useId, useRef, useState } from 'react'
1
+ import { useEffect, useId, useMemo, useRef, useState } from 'react'
2
2
 
3
- import { quoteStatusMap } from 'src/utils/quoteStatus'
3
+ import {
4
+ defaultMyAccountListQuotesLabels,
5
+ formatRemoveStatusAriaLabel,
6
+ } from '../quotesLabels'
7
+ import type { QuoteStatusCmsLabels } from 'src/utils/quoteStatus'
8
+ import { getLocalizedQuoteStatusMap } from 'src/utils/quoteStatus'
4
9
  import styles from './styles.module.scss'
5
10
 
6
11
  type MyAccountQuotesStatusSelectorProps = Readonly<{
7
12
  value: string[]
8
13
  onChange: (selected: string[]) => void
14
+ statusLabel?: string
15
+ /** Supports a "{status}" placeholder, e.g. "Remove {status}". */
16
+ removeStatusAriaLabel?: string
17
+ statusCmsLabels?: QuoteStatusCmsLabels
9
18
  }>
10
19
 
11
- const statusEntries = Object.entries(quoteStatusMap).map(([key, entry]) => ({
12
- key,
13
- label: entry.label,
14
- variant: entry.variant,
15
- }))
16
-
17
20
  export default function MyAccountQuotesStatusSelector({
18
21
  value,
19
22
  onChange,
23
+ statusLabel = defaultMyAccountListQuotesLabels.statusFacetLabel,
24
+ removeStatusAriaLabel = defaultMyAccountListQuotesLabels.removeStatusAriaLabel,
25
+ statusCmsLabels,
20
26
  }: MyAccountQuotesStatusSelectorProps) {
27
+ const statusEntries = useMemo(() => {
28
+ const statusMap = getLocalizedQuoteStatusMap(statusCmsLabels)
29
+ return Object.entries(statusMap).map(([key, entry]) => ({
30
+ key,
31
+ label: entry.label,
32
+ variant: entry.variant,
33
+ }))
34
+ }, [statusCmsLabels])
21
35
  const [open, setOpen] = useState(false)
22
36
  const ref = useRef<HTMLDivElement>(null)
23
37
  const labelId = useId()
@@ -47,7 +61,7 @@ export default function MyAccountQuotesStatusSelector({
47
61
  return (
48
62
  <div className={styles.wrapper} ref={ref} data-fs-quotes-status-selector>
49
63
  <span id={labelId} className={styles.label}>
50
- Status
64
+ {statusLabel}
51
65
  </span>
52
66
  <div data-fs-quotes-status-input-wrapper>
53
67
  <div className={styles.input} data-open={open || undefined}>
@@ -64,7 +78,10 @@ export default function MyAccountQuotesStatusSelector({
64
78
  type="button"
65
79
  className={styles.chipRemove}
66
80
  onClick={(e) => remove(key, e)}
67
- aria-label={`Remove ${entry?.label ?? key}`}
81
+ aria-label={formatRemoveStatusAriaLabel(
82
+ removeStatusAriaLabel,
83
+ entry?.label ?? key
84
+ )}
68
85
  >
69
86
  &times;
70
87
  </button>
@@ -1,2 +1,4 @@
1
1
  export { default as MyAccountListQuotes } from './MyAccountListQuotes'
2
2
  export type { MyAccountListQuotesProps } from './MyAccountListQuotes'
3
+ export { defaultMyAccountListQuotesLabels } from './quotesLabels'
4
+ export type { MyAccountListQuotesSectionLabels } from './quotesLabels'
@@ -1,4 +1,5 @@
1
- import { quoteStatusMap } from 'src/utils/quoteStatus'
1
+ import { getLocalizedQuoteStatusMap } from 'src/utils/quoteStatus'
2
+ import type { QuoteStatusCmsLabels } from 'src/utils/quoteStatus'
2
3
 
3
4
  export type QuoteFilters = {
4
5
  page: number
@@ -20,13 +21,22 @@ export function getSelectedFacets(filters: QuoteFilters): QuoteSelectedFacet[] {
20
21
  return facets
21
22
  }
22
23
 
23
- export function getAllFacets(filters: QuoteFilters) {
24
+ export type QuoteFacetLabels = {
25
+ statusFacetLabel?: string
26
+ createdAtFacetLabel?: string
27
+ expiresAtFacetLabel?: string
28
+ statusCmsLabels?: QuoteStatusCmsLabels
29
+ }
30
+
31
+ export function getAllFacets(filters: QuoteFilters, labels?: QuoteFacetLabels) {
32
+ const statusMap = getLocalizedQuoteStatusMap(labels?.statusCmsLabels)
33
+
24
34
  return [
25
35
  {
26
36
  __typename: 'StoreFacetBoolean' as const,
27
37
  key: 'status',
28
- label: 'Status',
29
- values: Object.entries(quoteStatusMap).map(([key, { label }]) => ({
38
+ label: labels?.statusFacetLabel ?? 'Status',
39
+ values: Object.entries(statusMap).map(([key, { label }]) => ({
30
40
  label,
31
41
  quantity: 0,
32
42
  selected: false,
@@ -36,14 +46,14 @@ export function getAllFacets(filters: QuoteFilters) {
36
46
  {
37
47
  __typename: 'StoreFacetRange' as const,
38
48
  key: 'createdAt',
39
- label: 'Created Date',
49
+ label: labels?.createdAtFacetLabel ?? 'Created Date',
40
50
  from: filters.createdAtFrom,
41
51
  to: filters.createdAtTo,
42
52
  },
43
53
  {
44
54
  __typename: 'StoreFacetRange' as const,
45
55
  key: 'expiresAt',
46
- label: 'Expiry Date',
56
+ label: labels?.expiresAtFacetLabel ?? 'Expiry Date',
47
57
  from: filters.expiresAtFrom,
48
58
  to: filters.expiresAtTo,
49
59
  },