@faststore/core 4.8.1-dev.0 → 4.8.1-dev.2

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 +3 -3
  2. package/.turbo/turbo-test.log +146 -105
  3. package/@generated/gql.ts +3 -3
  4. package/@generated/graphql.ts +4 -2
  5. package/@generated/persisted-documents.json +1 -1
  6. package/@generated/schema.graphql +4 -0
  7. package/CHANGELOG.md +16 -0
  8. package/cms/faststore/my-account/components/cms_component__accountnavigation.jsonc +10 -0
  9. package/cms/faststore/my-account/components/cms_component__accountorderdelivery.jsonc +80 -0
  10. package/cms/faststore/my-account/components/cms_component__accountorderdetails.jsonc +25 -0
  11. package/cms/faststore/my-account/components/cms_component__accountorderslist.jsonc +10 -0
  12. package/cms/faststore/my-account/components/cms_component__accountorderstatus.jsonc +90 -0
  13. package/cms/faststore/my-account/components/cms_component__accountordersummary.jsonc +25 -0
  14. package/package.json +6 -6
  15. package/src/components/account/Drawer/OrganizationDrawer/ContractSwitcher/ContractSwitcher.tsx +4 -6
  16. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +18 -3
  17. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerFooter.tsx +13 -2
  18. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerHeader.tsx +1 -0
  19. package/src/components/account/Drawer/OrganizationDrawer/section.module.scss +7 -0
  20. package/src/components/account/Drawer/OrganizationSignInButton/OrganizationSignInButton.tsx +7 -9
  21. package/src/components/account/Menu/Menu.tsx +24 -7
  22. package/src/components/account/orders/ListOrders/FilterSlider/FilterFacetPendingApproval/FilterFacetPendingApproval.tsx +8 -3
  23. package/src/components/account/orders/ListOrders/FilterSlider/FilterSlider.tsx +1 -0
  24. package/src/components/account/orders/ListOrders/ListOrders.tsx +2 -2
  25. package/src/components/account/orders/ListOrders/listOrdersLabels.ts +4 -0
  26. package/src/components/account/orders/OrderDetails/BuyingPolicyAlert/BuyingPolicyAlert.tsx +45 -9
  27. package/src/components/account/orders/OrderDetails/DeliveryCard/DeliveryCard.tsx +14 -3
  28. package/src/components/account/orders/OrderDetails/DeliveryOptionAccordion/DeliveryOptionAccordion.tsx +4 -1
  29. package/src/components/account/orders/OrderDetails/DeliveryOptionAccordion/DeliveryOptionAccordionDeliveryInfo.tsx +14 -7
  30. package/src/components/account/orders/OrderDetails/DeliveryOptionAccordion/DeliveryOptionAccordionProducts.tsx +8 -5
  31. package/src/components/account/orders/OrderDetails/OrderDetailsHeader.tsx +5 -0
  32. package/src/components/account/orders/OrderDetails/StatusCard/StatusCard.tsx +26 -89
  33. package/src/components/account/orders/OrderDetails/StatusCard/statusCardLocalization.ts +114 -0
  34. package/src/components/account/orders/OrderDetails/SummaryCard/SummaryCard.tsx +2 -1
  35. package/src/components/account/orders/OrderDetails/SummaryCard/getOrderTotalLabel.ts +19 -0
  36. package/src/components/account/orders/OrderDetails/getDeliveryOptionLabel.ts +135 -0
  37. package/src/components/account/orders/OrderDetails/orderDetailsLabels.ts +73 -0
  38. package/src/components/navigation/Navbar/Navbar.tsx +17 -19
  39. package/src/components/navigation/Navbar/NavbarAccountArea.tsx +57 -0
  40. package/src/components/navigation/NavbarSlider/NavbarSlider.tsx +16 -18
  41. package/src/components/sections/Account/AccountOrderDelivery.tsx +3 -1
  42. package/src/components/sections/Account/AccountOrderDetails.tsx +16 -0
  43. package/src/components/sections/Account/AccountOrderStatus.tsx +27 -3
  44. package/src/components/sections/Account/AccountOrdersList.tsx +4 -0
  45. package/src/pages/api/fs/switch-contract.ts +194 -0
  46. package/src/pages/api/graphql.ts +108 -69
  47. package/src/sdk/account/accountPageContext.ts +14 -3
  48. package/src/sdk/account/changeContractToken.ts +19 -63
  49. package/src/sdk/account/clearPersistedSessionState.ts +13 -7
  50. package/src/sdk/account/getMyAccountRoutes.ts +2 -0
  51. package/src/sdk/account/useAvailableContracts.ts +1 -0
  52. package/src/sdk/account/useSwitchContract.ts +11 -3
  53. package/src/sdk/overrides/PageProvider.tsx +1 -1
  54. package/src/sdk/session/index.ts +2 -1
  55. package/src/sdk/session/isSignInAreaResolved.ts +12 -0
  56. package/test/components/account/ContractSwitcher.browser.test.tsx +42 -0
  57. package/test/components/account/Menu.browser.test.tsx +123 -0
  58. package/test/components/account/OrganizationDrawer.browser.test.tsx +148 -0
  59. package/test/components/account/OrganizationDrawerFooter.browser.test.tsx +95 -1
  60. package/test/components/account/OrganizationDrawerHeader.browser.test.tsx +2 -0
  61. package/test/components/account/OrganizationSignInButton.browser.test.tsx +49 -0
  62. package/test/components/account/orders/BuyingPolicyAlert.browser.test.tsx +342 -0
  63. package/test/components/account/orders/DeliveryCard.browser.test.tsx +36 -0
  64. package/test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx +76 -0
  65. package/test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx +41 -0
  66. package/test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx +35 -0
  67. package/test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx +25 -0
  68. package/test/components/account/orders/StatusCard.browser.test.tsx +37 -0
  69. package/test/components/account/orders/StatusCard.test.ts +101 -0
  70. package/test/components/account/orders/SummaryCard.browser.test.tsx +59 -0
  71. package/test/components/account/orders/getDeliveryOptionLabel.test.ts +129 -0
  72. package/test/components/account/orders/getOrderTotalLabel.test.ts +35 -0
  73. package/test/components/account/orders/listOrdersLabels.test.ts +23 -0
  74. package/test/components/account/orders/orderDetailsLabels.test.ts +13 -0
  75. package/test/components/navigation/Navbar.browser.test.tsx +183 -0
  76. package/test/components/navigation/NavbarAccountArea.browser.test.tsx +115 -0
  77. package/test/components/navigation/NavbarSlider.browser.test.tsx +143 -0
  78. package/test/components/sections/Account/AccountOrderDelivery.browser.test.tsx +72 -0
  79. package/test/components/sections/Account/AccountOrderDetails.browser.test.tsx +151 -0
  80. package/test/components/sections/Account/AccountOrderStatus.browser.test.tsx +59 -0
  81. package/test/components/sections/Account/AccountOrdersList.browser.test.tsx +67 -0
  82. package/test/pages/api/graphql.test.ts +27 -1
  83. package/test/pages/api/switch-contract.test.ts +385 -0
  84. package/test/sdk/account/changeContractToken.browser.test.ts +23 -71
  85. package/test/sdk/account/clearPersistedSessionState.browser.test.ts +24 -0
  86. package/test/sdk/account/getMyAccountRoutes.test.ts +11 -1
  87. package/test/sdk/account/useSwitchContract.browser.test.ts +21 -0
  88. package/test/sdk/session/isSignInAreaResolved.test.ts +22 -0
  89. package/test/utils/userOrderStatus.test.ts +22 -0
@@ -18,6 +18,13 @@ interface BuyingPolicyAlertProps {
18
18
  rejectModalTitle?: string
19
19
  rejectModalMessage?: string
20
20
  rejectModalConfirmText?: string
21
+ /** Supports the `{policy}` placeholder, replaced with the policy name. */
22
+ approveSuccessToast?: string
23
+ approveErrorToast?: string
24
+ /** Supports the `{policy}` placeholder, replaced with the policy name. */
25
+ rejectSuccessToast?: string
26
+ rejectErrorToast?: string
27
+ pendingFurtherApprovalsAlert?: string
21
28
  }
22
29
  }
23
30
 
@@ -28,9 +35,22 @@ const DEFAULT_REJECT_MODAL_TITLE = 'Reject approval request'
28
35
  const DEFAULT_REJECT_MODAL_MESSAGE =
29
36
  "You're about to reject this approval request, triggered by the {policy} policy. Rejecting any approval request will deny the entire order.\n\nThis action is permanent and cannot be undone."
30
37
  const DEFAULT_REJECT_MODAL_CONFIRM_TEXT = 'Reject'
38
+ const DEFAULT_APPROVE_SUCCESS_TOAST = '{policy} policy approved successfully.'
39
+ const DEFAULT_APPROVE_ERROR_TOAST =
40
+ "Policy couldn't be approved due to a technical issue."
41
+ const DEFAULT_REJECT_SUCCESS_TOAST =
42
+ '{policy} policy rejected successfully. Order denied.'
43
+ const DEFAULT_REJECT_ERROR_TOAST =
44
+ "Policy couldn't be rejected due to a technical issue."
45
+ const DEFAULT_PENDING_FURTHER_APPROVALS_ALERT =
46
+ 'Your approval is recorded. This order is still pending further approvals.'
47
+
48
+ function interpolatePolicyName(template: string, policyName: string): string {
49
+ return template.split('{policy}').join(policyName)
50
+ }
31
51
 
32
52
  function renderRejectMessage(template: string, policyName: string): ReactNode {
33
- const lines = template.replace('{policy}', policyName).split('\n')
53
+ const lines = interpolatePolicyName(template, policyName).split('\n')
34
54
 
35
55
  return lines.map((line, index) => (
36
56
  <Fragment key={`${index}-${line}`}>
@@ -53,6 +73,17 @@ export default function BuyingPolicyAlert({
53
73
  labels?.rejectModalMessage ?? DEFAULT_REJECT_MODAL_MESSAGE
54
74
  const rejectModalConfirmText =
55
75
  labels?.rejectModalConfirmText ?? DEFAULT_REJECT_MODAL_CONFIRM_TEXT
76
+ const approveSuccessToast =
77
+ labels?.approveSuccessToast ?? DEFAULT_APPROVE_SUCCESS_TOAST
78
+ const approveErrorToast =
79
+ labels?.approveErrorToast ?? DEFAULT_APPROVE_ERROR_TOAST
80
+ const rejectSuccessToast =
81
+ labels?.rejectSuccessToast ?? DEFAULT_REJECT_SUCCESS_TOAST
82
+ const rejectErrorToast =
83
+ labels?.rejectErrorToast ?? DEFAULT_REJECT_ERROR_TOAST
84
+ const pendingFurtherApprovalsAlert =
85
+ labels?.pendingFurtherApprovalsAlert ??
86
+ DEFAULT_PENDING_FURTHER_APPROVALS_ALERT
56
87
  const { pushToast } = useUI()
57
88
  const [isAuthorizationOpen, setIsAuthorizationOpen] = useState<boolean>(false)
58
89
  const { data, error, processOrderAuthorization, loading } =
@@ -72,15 +103,18 @@ export default function BuyingPolicyAlert({
72
103
  // Success toast
73
104
  pushToast({
74
105
  status: 'INFO',
75
- message: `${ruleForAuthorization.rule.name} policy approved successfully.`,
106
+ message: interpolatePolicyName(
107
+ approveSuccessToast,
108
+ ruleForAuthorization.rule.name
109
+ ),
76
110
  icon: <UIIcon width={30} height={30} name="CircleWavyCheck" />,
77
111
  })
78
112
 
79
113
  onAuthorizationComplete?.()
80
- } catch (error) {
114
+ } catch {
81
115
  pushToast({
82
116
  status: 'ERROR',
83
- message: "Policy couldn't be approved due to a technical issue.",
117
+ message: approveErrorToast,
84
118
  icon: <UIIcon width={30} height={30} name="CircleWavyWarning" />,
85
119
  })
86
120
  }
@@ -99,16 +133,19 @@ export default function BuyingPolicyAlert({
99
133
 
100
134
  pushToast({
101
135
  status: 'INFO',
102
- message: `${ruleForAuthorization.rule.name} policy rejected successfully. Order denied.`,
136
+ message: interpolatePolicyName(
137
+ rejectSuccessToast,
138
+ ruleForAuthorization.rule.name
139
+ ),
103
140
  icon: <UIIcon width={30} height={30} name="XCircle" />,
104
141
  })
105
142
 
106
143
  setIsAuthorizationOpen(false)
107
144
  onAuthorizationComplete?.()
108
- } catch (error) {
145
+ } catch {
109
146
  pushToast({
110
147
  status: 'ERROR',
111
- message: "Policy couldn't be rejected due to a technical issue.",
148
+ message: rejectErrorToast,
112
149
  icon: <UIIcon width={30} height={30} name="CircleWavyWarning" />,
113
150
  })
114
151
  }
@@ -159,8 +196,7 @@ export default function BuyingPolicyAlert({
159
196
  data-fs-pending-policies-alert
160
197
  icon={<UIIcon name="Info" width={20} height={20} />}
161
198
  >
162
- Your approval is recorded. This order is still pending further
163
- approvals.
199
+ {pendingFurtherApprovalsAlert}
164
200
  </UIAlert>
165
201
  )}
166
202
 
@@ -1,10 +1,19 @@
1
1
  import type { UserOrderDeliveryOptionsData } from '@generated/graphql'
2
2
  import { camelCaseToTitle } from 'src/utils/utilities'
3
3
  import Card from '../../../components/Card'
4
+ import {
5
+ getDeliveryOptionKey,
6
+ getDeliveryOptionLabel,
7
+ } from '../getDeliveryOptionLabel'
8
+ import {
9
+ type OrderDeliverySectionLabels,
10
+ resolveOrderDeliveryLabels,
11
+ } from '../orderDetailsLabels'
4
12
 
5
13
  interface DeliveryCardProps {
6
14
  title?: string
7
15
  deliveryOptionsData?: UserOrderDeliveryOptionsData
16
+ labels?: OrderDeliverySectionLabels
8
17
  fields?: Array<{
9
18
  name: string
10
19
  value: string
@@ -15,8 +24,10 @@ interface DeliveryCardProps {
15
24
  export default function DeliveryCard({
16
25
  title = 'Delivery',
17
26
  deliveryOptionsData,
27
+ labels: labelsProp,
18
28
  fields,
19
- }: DeliveryCardProps) {
29
+ }: Readonly<DeliveryCardProps>) {
30
+ const labels = resolveOrderDeliveryLabels(labelsProp)
20
31
  const deliveryOptions = deliveryOptionsData?.deliveryOptions ?? []
21
32
  const contact = deliveryOptionsData?.contact
22
33
 
@@ -24,8 +35,8 @@ export default function DeliveryCard({
24
35
  <Card title={title} data-fs-order-delivery-card>
25
36
  <div data-fs-delivery-methods>
26
37
  {deliveryOptions.map((option) => (
27
- <p key={option.friendlyDeliveryOptionName} data-fs-delivery-method>
28
- {option.friendlyDeliveryOptionName}
38
+ <p key={getDeliveryOptionKey(option)} data-fs-delivery-method>
39
+ {getDeliveryOptionLabel(option, labels)}
29
40
  </p>
30
41
  ))}
31
42
  </div>
@@ -20,6 +20,7 @@ import {
20
20
  type OrderDeliverySectionLabels,
21
21
  resolveOrderDeliveryLabels,
22
22
  } from '../orderDetailsLabels'
23
+ import { getDeliveryOptionLabel } from '../getDeliveryOptionLabel'
23
24
 
24
25
  interface DeliveryOptionAccordionProps {
25
26
  deliveryOption: UserOrderDeliveryOption
@@ -50,7 +51,7 @@ function DeliveryOptionAccordion({
50
51
  )
51
52
  const formatPrice = useFormatPrice()
52
53
 
53
- const title = deliveryOption.friendlyDeliveryOptionName
54
+ const title = getDeliveryOptionLabel(deliveryOption, labels)
54
55
 
55
56
  const itemCount = labels.itemCountTemplate.replace(
56
57
  '{count}',
@@ -80,6 +81,7 @@ function DeliveryOptionAccordion({
80
81
  <DeliveryOptionAccordionDeliveryInfo
81
82
  deliveryOption={deliveryOption}
82
83
  contact={contact}
84
+ labels={labels}
83
85
  />
84
86
  <DeliveryOptionAccordionProducts>
85
87
  {deliveryOption.items?.map((item, index) => {
@@ -97,6 +99,7 @@ function DeliveryOptionAccordion({
97
99
  price={formatPrice(item.sellingPrice ?? 0, currencyCode)}
98
100
  tax={formatPrice(tax, currencyCode)}
99
101
  total={formatPrice(item.total ?? 0, currencyCode)}
102
+ labels={labels}
100
103
  />
101
104
  )
102
105
  })}
@@ -3,13 +3,14 @@ import type {
3
3
  UserOrderDeliveryOption,
4
4
  UserOrderDeliveryOptionsContact,
5
5
  } from '@generated/graphql'
6
+ import type { OrderDeliverySectionLabels } from '../orderDetailsLabels'
6
7
 
7
8
  interface InfoContainerProps {
8
9
  title: string
9
10
  children: ReactNode
10
11
  }
11
12
 
12
- function InfoContainer({ title, children }: InfoContainerProps) {
13
+ function InfoContainer({ title, children }: Readonly<InfoContainerProps>) {
13
14
  return (
14
15
  <div data-fs-delivery-option-accordion-info-container>
15
16
  <span data-fs-delivery-option-accordion-info-title>{title}</span>
@@ -21,12 +22,14 @@ function InfoContainer({ title, children }: InfoContainerProps) {
21
22
  interface DeliveryOptionAccordionDeliveryInfoProps {
22
23
  deliveryOption: UserOrderDeliveryOption
23
24
  contact?: UserOrderDeliveryOptionsContact | null
25
+ labels: Required<OrderDeliverySectionLabels>
24
26
  }
25
27
 
26
28
  function DeliveryOptionAccordionDeliveryInfo({
27
29
  deliveryOption,
28
30
  contact,
29
- }: DeliveryOptionAccordionDeliveryInfoProps) {
31
+ labels,
32
+ }: Readonly<DeliveryOptionAccordionDeliveryInfoProps>) {
30
33
  const isPickup = deliveryOption.deliveryChannel === 'pickup-in-point'
31
34
  const address = isPickup
32
35
  ? deliveryOption.pickupStoreInfo?.address
@@ -39,7 +42,7 @@ function DeliveryOptionAccordionDeliveryInfo({
39
42
  return (
40
43
  <div data-fs-delivery-option-accordion-delivery-info>
41
44
  {!isPickup && contact && (
42
- <InfoContainer title="Recipient">
45
+ <InfoContainer title={labels.recipientLabel}>
43
46
  <span data-fs-delivery-option-accordion-info>
44
47
  <strong>{contact.name}</strong>
45
48
  </span>
@@ -52,7 +55,11 @@ function DeliveryOptionAccordionDeliveryInfo({
52
55
  </InfoContainer>
53
56
  )}
54
57
 
55
- <InfoContainer title={isPickup ? 'Store address' : 'Delivery address'}>
58
+ <InfoContainer
59
+ title={
60
+ isPickup ? labels.storeAddressLabel : labels.deliveryAddressLabel
61
+ }
62
+ >
56
63
  <span data-fs-delivery-option-accordion-info>
57
64
  <strong>{address.city}</strong>
58
65
  </span>
@@ -64,15 +71,15 @@ function DeliveryOptionAccordionDeliveryInfo({
64
71
 
65
72
  {/* TODO: Validate this after we check the return from api */}
66
73
  {isPickup && deliveryOption.deliveryWindow && (
67
- <InfoContainer title="Store Hours">
74
+ <InfoContainer title={labels.storeHoursLabel}>
68
75
  <span data-fs-delivery-option-accordion-info>
69
- From:{' '}
76
+ {labels.fromLabel}{' '}
70
77
  {new Date(
71
78
  deliveryOption.deliveryWindow.startDateUtc
72
79
  ).toLocaleString()}
73
80
  </span>
74
81
  <span data-fs-delivery-option-accordion-info>
75
- To:{' '}
82
+ {labels.toLabel}{' '}
76
83
  {new Date(
77
84
  deliveryOption.deliveryWindow.endDateUtc
78
85
  ).toLocaleString()}
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
  import { camelCaseToTitle } from 'src/utils/utilities'
3
+ import type { OrderDeliverySectionLabels } from '../orderDetailsLabels'
3
4
 
4
5
  interface DeliveryOptionAccordionProductProps {
5
6
  image: string
@@ -13,6 +14,7 @@ interface DeliveryOptionAccordionProductProps {
13
14
  price: string
14
15
  tax: string
15
16
  total: string
17
+ labels: Required<OrderDeliverySectionLabels>
16
18
  }
17
19
 
18
20
  function DeliveryOptionAccordionProduct({
@@ -23,7 +25,8 @@ function DeliveryOptionAccordionProduct({
23
25
  price,
24
26
  tax,
25
27
  total,
26
- }: DeliveryOptionAccordionProductProps) {
28
+ labels,
29
+ }: Readonly<DeliveryOptionAccordionProductProps>) {
27
30
  return (
28
31
  <div data-fs-delivery-option-accordion-product>
29
32
  <div data-fs-delivery-option-accordion-product-top>
@@ -43,15 +46,15 @@ function DeliveryOptionAccordionProduct({
43
46
  <div data-fs-delivery-option-accordion-product-bottom>
44
47
  <div data-fs-delivery-option-accordion-product-price>
45
48
  <p>{price}</p>
46
- <span>Each</span>
49
+ <span>{labels.eachLabel}</span>
47
50
  </div>
48
51
  <div data-fs-delivery-option-accordion-product-tax>
49
52
  <p>{tax}</p>
50
- <span>Taxes</span>
53
+ <span>{labels.taxesLabel}</span>
51
54
  </div>
52
55
  <div data-fs-delivery-option-accordion-product-total>
53
56
  <p>{total}</p>
54
- <span>Total</span>
57
+ <span>{labels.totalLabel}</span>
55
58
  </div>
56
59
  </div>
57
60
  </div>
@@ -64,7 +67,7 @@ interface DeliveryOptionAccordionProductsProps {
64
67
 
65
68
  function DeliveryOptionAccordionProducts({
66
69
  children,
67
- }: DeliveryOptionAccordionProductsProps) {
70
+ }: Readonly<DeliveryOptionAccordionProductsProps>) {
68
71
  return <div data-fs-delivery-option-accordion-products>{children}</div>
69
72
  }
70
73
 
@@ -78,6 +78,11 @@ export function OrderDetailsHeader({
78
78
  rejectModalTitle: labels.rejectModalTitle,
79
79
  rejectModalMessage: labels.rejectModalMessage,
80
80
  rejectModalConfirmText: labels.rejectModalConfirmText,
81
+ approveSuccessToast: labels.approveSuccessToast,
82
+ approveErrorToast: labels.approveErrorToast,
83
+ rejectSuccessToast: labels.rejectSuccessToast,
84
+ rejectErrorToast: labels.rejectErrorToast,
85
+ pendingFurtherApprovalsAlert: labels.pendingFurtherApprovalsAlert,
81
86
  }}
82
87
  />
83
88
  )}
@@ -1,21 +1,24 @@
1
1
  import { Icon as UIIcon, Skeleton as UISkeleton } from '@faststore/ui'
2
+ import { useRouter } from 'next/router'
2
3
  import { useRef, type ReactNode } from 'react'
3
4
  import Card from 'src/components/account/components/Card'
4
- import { orderStatusMap, type OrderStatusKey } from 'src/utils/userOrderStatus'
5
+ import {
6
+ orderStatusMap,
7
+ type OrderStatusCmsLabels,
8
+ type OrderStatusKey,
9
+ } from 'src/utils/userOrderStatus'
5
10
  import { useConnectorPositioning } from './useConnectorPositioning'
6
11
  import type { ServerOrderDetailsQueryQuery } from '@generated/graphql'
7
12
  import {
8
13
  type OrderStatusSectionLabels,
9
14
  resolveOrderStatusLabels,
10
15
  } from '../orderDetailsLabels'
11
-
12
- export type StepStatus = 'completed' | 'loading' | 'not-started' | 'failed'
13
- export type StepKey =
14
- | 'order'
15
- | 'approval'
16
- | 'payment'
17
- | 'processing'
18
- | 'shipping'
16
+ import {
17
+ formatDate,
18
+ getDisplayStepLabel,
19
+ type StepKey,
20
+ type StepStatus,
21
+ } from './statusCardLocalization'
19
22
 
20
23
  interface Step {
21
24
  label: ReactNode
@@ -27,63 +30,24 @@ interface StatusCardProps {
27
30
  status: OrderStatusKey
28
31
  creationDate: ServerOrderDetailsQueryQuery['userOrder']['creationDate']
29
32
  labels?: OrderStatusSectionLabels
30
- }
31
-
32
- // Define custom labels for each step based on their status
33
- // This allows us to show different text for the same step depending on its current state
34
- const STEP_LABELS: Record<StepKey, Record<StepStatus, string>> = {
35
- order: {
36
- completed: 'Order Placed',
37
- loading: 'Order Placed',
38
- 'not-started': 'Order Placed',
39
- failed: 'Order Placed',
40
- },
41
- approval: {
42
- completed: 'Approved', // Custom
43
- loading: 'Pending approval', // Use from orderStatusMap
44
- 'not-started': 'Pending approval',
45
- failed: 'Denied', // Custom
46
- },
47
- payment: {
48
- completed: 'Payment Approved', // Use from orderStatusMap
49
- loading: 'Payment Pending', // Use from orderStatusMap
50
- 'not-started': 'Payment authorization', // Custom
51
- failed: 'Payment Denied', // Use from orderStatusMap
52
- },
53
- processing: {
54
- completed: 'Ready for Delivery', // Use from orderStatusMap
55
- loading: 'Handling order', // Custom
56
- 'not-started': 'Handling order', // Custom
57
- failed: 'Canceled', // Use from orderStatusMap
58
- },
59
- shipping: {
60
- completed: 'Invoiced', // Use from orderStatusMap
61
- loading: 'Shipping order', // Custom
62
- 'not-started': 'Ship order', // Custom
63
- failed: 'Canceled', // Use from orderStatusMap
64
- },
33
+ statusLabels?: OrderStatusCmsLabels
65
34
  }
66
35
 
67
36
  // Define the visual progression of order steps from start to finish
68
37
  const VISUAL_STEPS = [
69
38
  {
70
- label: (stepStatus: StepStatus) => STEP_LABELS.order[stepStatus],
71
39
  key: 'order',
72
40
  },
73
41
  {
74
- label: (stepStatus: StepStatus) => STEP_LABELS.approval[stepStatus],
75
42
  key: 'approval',
76
43
  },
77
44
  {
78
- label: (stepStatus: StepStatus) => STEP_LABELS.payment[stepStatus],
79
45
  key: 'payment',
80
46
  },
81
47
  {
82
- label: (stepStatus: StepStatus) => STEP_LABELS.processing[stepStatus],
83
48
  key: 'processing',
84
49
  },
85
50
  {
86
- label: (stepStatus: StepStatus) => STEP_LABELS.shipping[stepStatus],
87
51
  key: 'shipping',
88
52
  },
89
53
  ] as const
@@ -105,24 +69,6 @@ const CANCELED_LABELS = ['Cancellation Requested', 'Canceled']
105
69
  // Define which order status labels indicate a failed state
106
70
  const FAILED_LABELS = ['Payment Denied']
107
71
 
108
- const formatDate = (date: string) => {
109
- const dateObj = new Date(date)
110
-
111
- const dateString = new Intl.DateTimeFormat('en-US', {
112
- month: '2-digit',
113
- day: '2-digit',
114
- year: 'numeric',
115
- }).format(dateObj)
116
-
117
- const timeString = new Intl.DateTimeFormat('en-US', {
118
- hour: 'numeric',
119
- minute: '2-digit',
120
- hour12: true,
121
- }).format(dateObj)
122
-
123
- return { date: dateString, time: timeString }
124
- }
125
-
126
72
  const StepIcon = ({
127
73
  status,
128
74
  isCanceled,
@@ -221,8 +167,10 @@ function StatusCard({
221
167
  status,
222
168
  creationDate,
223
169
  labels: labelsProp,
224
- }: StatusCardProps) {
170
+ statusLabels,
171
+ }: Readonly<StatusCardProps>) {
225
172
  const labels = resolveOrderStatusLabels(labelsProp)
173
+ const { locale = 'en-US' } = useRouter()
226
174
  const containerRef = useRef<HTMLDivElement>(null)
227
175
 
228
176
  useConnectorPositioning(containerRef)
@@ -244,25 +192,14 @@ function StatusCard({
244
192
  isFailed,
245
193
  })
246
194
 
247
- let stepLabel = isCanceled
248
- ? step.key === 'payment'
249
- ? currentStatusLabel
250
- : '—' // prevent hydration mismatch
251
- : step.label(stepStatus)
252
-
253
- if (!isCanceled) {
254
- if (step.key === 'order') {
255
- stepLabel = labels.orderPlacedStep
256
- } else if (step.key === 'payment' && stepStatus === 'loading') {
257
- stepLabel = labels.paymentPendingStep
258
- } else if (step.key === 'processing' && stepStatus === 'loading') {
259
- stepLabel = labels.handlingStep
260
- } else if (step.key === 'shipping' && stepStatus === 'completed') {
261
- stepLabel = labels.invoicedStep
262
- } else if (step.key === 'shipping' && stepStatus === 'loading') {
263
- stepLabel = labels.deliveredStep
264
- }
265
- }
195
+ const stepLabel = getDisplayStepLabel({
196
+ stepKey: step.key,
197
+ stepStatus,
198
+ labels,
199
+ status,
200
+ statusLabels,
201
+ isCanceled,
202
+ })
266
203
 
267
204
  // Add creation date to the order step when it's completed or failed
268
205
  let completedAt: string | undefined
@@ -304,10 +241,10 @@ function StatusCard({
304
241
  {step.completedAt && (
305
242
  <div data-fs-shipping-step-details>
306
243
  <span data-fs-shipping-step-date>
307
- {formatDate(step.completedAt).date}
244
+ {formatDate(step.completedAt, locale).date}
308
245
  </span>
309
246
  <span data-fs-shipping-step-time>
310
- {formatDate(step.completedAt).time}
247
+ {formatDate(step.completedAt, locale).time}
311
248
  </span>
312
249
  </div>
313
250
  )}
@@ -0,0 +1,114 @@
1
+ import {
2
+ getOrderStatusLabel,
3
+ type OrderStatusCmsLabels,
4
+ type OrderStatusKey,
5
+ } from 'src/utils/userOrderStatus'
6
+ import type { OrderStatusSectionLabels } from '../orderDetailsLabels'
7
+
8
+ export type StepStatus = 'completed' | 'loading' | 'not-started' | 'failed'
9
+ export type StepKey =
10
+ | 'order'
11
+ | 'approval'
12
+ | 'payment'
13
+ | 'processing'
14
+ | 'shipping'
15
+
16
+ export function getStepLabel(
17
+ stepKey: StepKey,
18
+ stepStatus: StepStatus,
19
+ labels: Required<OrderStatusSectionLabels>
20
+ ): string {
21
+ const stepLabels: Record<StepKey, Record<StepStatus, string>> = {
22
+ order: {
23
+ completed: labels.orderPlacedStep,
24
+ loading: labels.orderPlacedStep,
25
+ 'not-started': labels.orderPlacedStep,
26
+ failed: labels.orderPlacedStep,
27
+ },
28
+ approval: {
29
+ completed: labels.approvedStep,
30
+ loading: labels.pendingApprovalStep,
31
+ 'not-started': labels.pendingApprovalStep,
32
+ failed: labels.deniedStep,
33
+ },
34
+ payment: {
35
+ completed: labels.paymentApprovedStep,
36
+ loading: labels.paymentPendingStep,
37
+ 'not-started': labels.paymentAuthorizationStep,
38
+ failed: labels.paymentDeniedStep,
39
+ },
40
+ processing: {
41
+ completed: labels.readyForDeliveryStep,
42
+ loading: labels.handlingStep,
43
+ 'not-started': labels.handlingStep,
44
+ failed: labels.canceledStep,
45
+ },
46
+ shipping: {
47
+ completed: labels.invoicedStep,
48
+ loading: labels.deliveredStep,
49
+ 'not-started': labels.shipOrderStep,
50
+ failed: labels.canceledStep,
51
+ },
52
+ }
53
+
54
+ return stepLabels[stepKey][stepStatus]
55
+ }
56
+
57
+ export function getDisplayStepLabel({
58
+ stepKey,
59
+ stepStatus,
60
+ labels,
61
+ status,
62
+ statusLabels,
63
+ isCanceled,
64
+ }: {
65
+ stepKey: StepKey
66
+ stepStatus: StepStatus
67
+ labels: Required<OrderStatusSectionLabels>
68
+ status: OrderStatusKey
69
+ statusLabels?: OrderStatusCmsLabels
70
+ isCanceled: boolean
71
+ }): string {
72
+ if (isCanceled) {
73
+ return stepKey === 'payment'
74
+ ? getOrderStatusLabel({ status, cmsLabels: statusLabels })
75
+ : '—'
76
+ }
77
+
78
+ if (
79
+ stepStatus === 'failed' &&
80
+ (stepKey === 'processing' || stepKey === 'shipping')
81
+ ) {
82
+ return getOrderStatusLabel({
83
+ status: 'canceled',
84
+ cmsLabels: statusLabels,
85
+ statusFallback: labels.canceledStep,
86
+ })
87
+ }
88
+
89
+ if (stepStatus === 'failed' && stepKey === 'payment') {
90
+ return getOrderStatusLabel({
91
+ status,
92
+ cmsLabels: statusLabels,
93
+ statusFallback: labels.paymentDeniedStep,
94
+ })
95
+ }
96
+
97
+ return getStepLabel(stepKey, stepStatus, labels)
98
+ }
99
+
100
+ export function formatDate(date: string, locale: string) {
101
+ const dateObj = new Date(date)
102
+
103
+ return {
104
+ date: new Intl.DateTimeFormat(locale, {
105
+ month: '2-digit',
106
+ day: '2-digit',
107
+ year: 'numeric',
108
+ }).format(dateObj),
109
+ time: new Intl.DateTimeFormat(locale, {
110
+ hour: 'numeric',
111
+ minute: '2-digit',
112
+ }).format(dateObj),
113
+ }
114
+ }
@@ -4,6 +4,7 @@ import {
4
4
  type OrderSummarySectionLabels,
5
5
  resolveOrderSummaryLabels,
6
6
  } from '../orderDetailsLabels'
7
+ import { getOrderTotalLabel } from './getOrderTotalLabel'
7
8
 
8
9
  // Interface for order totals (items, shipping, discounts)
9
10
  // TODO: Use type from API
@@ -83,7 +84,7 @@ function SummaryCard({
83
84
  <Card title={labels.summaryTitle} data-fs-order-summary-card>
84
85
  {displayTotals.map((total) => (
85
86
  <div key={total.id} data-fs-order-summary-item>
86
- <span>{total.name}</span>
87
+ <span>{getOrderTotalLabel(total, labels)}</span>
87
88
  <span>{formatPrice(total.value, currencyCode)}</span>
88
89
  </div>
89
90
  ))}
@@ -0,0 +1,19 @@
1
+ import type { OrderSummarySectionLabels } from '../orderDetailsLabels'
2
+
3
+ const TOTAL_LABEL_KEYS = {
4
+ items: 'itemsTotalLabel',
5
+ discounts: 'discountsTotalLabel',
6
+ shipping: 'shippingTotalLabel',
7
+ tax: 'taxTotalLabel',
8
+ change: 'changeTotalLabel',
9
+ interest: 'interestLabel',
10
+ } as const satisfies Record<string, keyof OrderSummarySectionLabels>
11
+
12
+ export function getOrderTotalLabel(
13
+ total: { id: string; name: string },
14
+ labels: Required<OrderSummarySectionLabels>
15
+ ): string {
16
+ const labelKey = TOTAL_LABEL_KEYS[total.id.toLowerCase()]
17
+
18
+ return labelKey ? labels[labelKey] : total.name
19
+ }