@salesforce/retail-react-app 7.1.0-preview.0 → 8.0.0-dev
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/CHANGELOG.md +8 -4
- package/app/components/_app/index.jsx +9 -7
- package/app/components/_app/index.test.js +2 -2
- package/app/components/_app-config/index.jsx +9 -3
- package/app/components/drawer-menu/drawer-menu.jsx +3 -1
- package/app/components/footer/index.jsx +3 -1
- package/app/components/header/index.jsx +3 -1
- package/app/components/header/index.test.js +2 -2
- package/app/components/island/README.md +1 -1
- package/app/components/island/index.jsx +3 -1
- package/app/components/island/index.test.js +94 -5
- package/app/components/item-variant/item-attributes.jsx +12 -3
- package/app/components/multiship/multiship-order-summary.jsx +137 -0
- package/app/components/multiship/multiship-order-summary.test.js +121 -0
- package/app/components/order-summary/index.jsx +2 -4
- package/app/components/pickup-or-delivery/index.jsx +80 -0
- package/app/components/pickup-or-delivery/index.test.jsx +182 -0
- package/app/components/product-item/index.jsx +26 -16
- package/app/components/product-item/index.test.js +29 -2
- package/app/components/product-item-list/index.jsx +10 -0
- package/app/components/product-item-list/index.test.jsx +14 -0
- package/app/components/product-view/index.jsx +9 -6
- package/app/components/product-view/index.test.js +25 -21
- package/app/components/quantity-picker/index.test.jsx +12 -12
- package/app/components/reset-password/index.test.js +1 -1
- package/app/components/shared/ui/AlertDescription/index.jsx +8 -0
- package/app/components/shared/ui/index.jsx +1 -0
- package/app/components/store-display/index.jsx +28 -4
- package/app/components/store-display/index.test.js +71 -0
- package/app/components/store-locator/form.test.jsx +16 -4
- package/app/components/store-locator/list.jsx +9 -4
- package/app/components/toggle-card/index.jsx +14 -0
- package/app/components/unavailable-product-confirmation-modal/index.jsx +19 -5
- package/app/components/unavailable-product-confirmation-modal/index.test.js +122 -1
- package/app/constants.js +20 -6
- package/app/contexts/store-locator-provider.jsx +7 -1
- package/app/contexts/store-locator-provider.test.jsx +36 -1
- package/app/hooks/use-address-form.js +155 -0
- package/app/hooks/use-address-form.test.js +501 -0
- package/app/hooks/use-auth-modal.js +2 -6
- package/app/hooks/use-current-basket.js +71 -2
- package/app/hooks/use-current-basket.test.js +37 -1
- package/app/hooks/use-dnt-notification.js +4 -4
- package/app/hooks/use-dnt-notification.test.js +5 -5
- package/app/hooks/use-item-shipment-management.js +233 -0
- package/app/hooks/use-item-shipment-management.test.js +696 -0
- package/app/hooks/use-multiship.js +589 -0
- package/app/hooks/use-multiship.test.js +776 -0
- package/app/hooks/use-pickup-shipment.js +70 -106
- package/app/hooks/use-pickup-shipment.test.js +345 -209
- package/app/hooks/use-product-address-assignment.js +280 -0
- package/app/hooks/use-product-address-assignment.test.js +414 -0
- package/app/hooks/use-product-inventory.js +100 -0
- package/app/hooks/use-product-inventory.test.js +254 -0
- package/app/hooks/use-shipment-operations.js +168 -0
- package/app/hooks/use-shipment-operations.test.js +385 -0
- package/app/hooks/use-store-locator.js +24 -2
- package/app/hooks/use-store-locator.test.jsx +109 -1
- package/app/pages/account/index.test.js +1 -1
- package/app/pages/account/profile.test.js +0 -2
- package/app/pages/cart/index.jsx +397 -157
- package/app/pages/cart/index.test.js +353 -2
- package/app/pages/cart/partials/bonus-products-title.jsx +10 -8
- package/app/pages/cart/partials/cart-secondary-button-group.test.js +1 -1
- package/app/pages/cart/partials/order-type-display.jsx +68 -0
- package/app/pages/cart/partials/order-type-display.test.js +241 -0
- package/app/pages/checkout/confirmation.jsx +79 -158
- package/app/pages/checkout/index.jsx +34 -9
- package/app/pages/checkout/index.test.js +245 -118
- package/app/pages/checkout/partials/contact-info.jsx +2 -6
- package/app/pages/checkout/partials/contact-info.test.js +93 -7
- package/app/pages/checkout/partials/payment.jsx +19 -5
- package/app/pages/checkout/partials/pickup-address.jsx +340 -70
- package/app/pages/checkout/partials/pickup-address.test.js +1075 -82
- package/app/pages/checkout/partials/product-shipping-address-card.jsx +382 -0
- package/app/pages/checkout/partials/shipment-details.jsx +209 -0
- package/app/pages/checkout/partials/shipment-details.test.js +246 -0
- package/app/pages/checkout/partials/shipping-address.jsx +156 -68
- package/app/pages/checkout/partials/shipping-address.test.js +673 -0
- package/app/pages/checkout/partials/shipping-method-options.jsx +180 -0
- package/app/pages/checkout/partials/shipping-methods.jsx +403 -0
- package/app/pages/checkout/partials/shipping-methods.test.js +472 -0
- package/app/pages/checkout/partials/shipping-multi-address.jsx +259 -0
- package/app/pages/checkout/partials/shipping-multi-address.test.js +2088 -0
- package/app/pages/checkout/partials/shipping-product-cards.jsx +101 -0
- package/app/pages/checkout/util/checkout-context.js +25 -18
- package/app/pages/login/index.jsx +2 -6
- package/app/pages/product-detail/index.jsx +96 -81
- package/app/pages/product-detail/index.test.js +103 -19
- package/app/pages/product-list/index.jsx +3 -1
- package/app/pages/product-list/partials/inventory-filter.jsx +18 -21
- package/app/pages/product-list/partials/inventory-filter.test.js +15 -17
- package/app/pages/product-list/partials/selected-refinements.jsx +3 -1
- package/app/ssr.js +1 -5
- package/app/static/translations/compiled/en-GB.json +316 -30
- package/app/static/translations/compiled/en-US.json +316 -30
- package/app/static/translations/compiled/en-XA.json +673 -75
- package/app/utils/address-utils.js +112 -0
- package/app/utils/address-utils.test.js +484 -0
- package/app/utils/product-utils.js +17 -5
- package/app/utils/product-utils.test.js +17 -8
- package/app/utils/sfdc-user-agent-utils.js +32 -0
- package/app/utils/sfdc-user-agent-utils.test.js +82 -0
- package/app/utils/shipment-utils.js +196 -0
- package/app/utils/shipment-utils.test.js +458 -0
- package/app/utils/test-utils.js +4 -4
- package/app/utils/utils.js +6 -1
- package/config/default.js +4 -1
- package/config/mocks/default.js +3 -1
- package/package.json +9 -9
- package/translations/en-GB.json +127 -10
- package/translations/en-US.json +127 -10
- package/app/pages/checkout/partials/shipping-options.jsx +0 -269
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import {screen} from '@testing-library/react'
|
|
9
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
10
|
+
import OrderTypeDisplay from '@salesforce/retail-react-app/app/pages/cart/partials/order-type-display'
|
|
11
|
+
|
|
12
|
+
const mockStore = {
|
|
13
|
+
id: 'store-123',
|
|
14
|
+
name: 'Downtown Store',
|
|
15
|
+
address1: '123 Main Street',
|
|
16
|
+
city: 'San Francisco',
|
|
17
|
+
stateCode: 'CA',
|
|
18
|
+
postalCode: '94105',
|
|
19
|
+
phone: '(555) 123-4567',
|
|
20
|
+
c_customerServiceEmail: 'store@example.com',
|
|
21
|
+
storeHours:
|
|
22
|
+
'Monday - Friday: 9:00 AM - 8:00 PM\nSaturday: 10:00 AM - 6:00 PM\nSunday: 12:00 PM - 5:00 PM',
|
|
23
|
+
distance: 2.5,
|
|
24
|
+
distanceUnit: 'miles'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('OrderTypeDisplay', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
jest.clearAllMocks()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('Pickup Order', () => {
|
|
33
|
+
test('renders pickup order display with store information', () => {
|
|
34
|
+
renderWithProviders(
|
|
35
|
+
<OrderTypeDisplay
|
|
36
|
+
isPickupOrder={true}
|
|
37
|
+
store={mockStore}
|
|
38
|
+
itemsInShipment={3}
|
|
39
|
+
totalItemsInCart={5}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// Check pickup message with item counts
|
|
44
|
+
expect(screen.getByText('Pick Up in Store - 3 out of 5 items')).toBeInTheDocument()
|
|
45
|
+
|
|
46
|
+
// Check store information is displayed
|
|
47
|
+
expect(screen.getByText('Downtown Store')).toBeInTheDocument()
|
|
48
|
+
expect(screen.getByText('123 Main Street')).toBeInTheDocument()
|
|
49
|
+
expect(screen.getByText('San Francisco, CA 94105')).toBeInTheDocument()
|
|
50
|
+
expect(screen.getByText('2.5 miles away')).toBeInTheDocument()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('renders pickup order with single item', () => {
|
|
54
|
+
renderWithProviders(
|
|
55
|
+
<OrderTypeDisplay
|
|
56
|
+
isPickupOrder={true}
|
|
57
|
+
store={mockStore}
|
|
58
|
+
itemsInShipment={1}
|
|
59
|
+
totalItemsInCart={1}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
expect(screen.getByText('Pick Up in Store - 1 out of 1 items')).toBeInTheDocument()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('renders pickup order with zero items in shipment', () => {
|
|
67
|
+
renderWithProviders(
|
|
68
|
+
<OrderTypeDisplay
|
|
69
|
+
isPickupOrder={true}
|
|
70
|
+
store={mockStore}
|
|
71
|
+
itemsInShipment={0}
|
|
72
|
+
totalItemsInCart={3}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
expect(screen.getByText('Pick Up in Store - 0 out of 3 items')).toBeInTheDocument()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('renders pickup order without store (null store)', () => {
|
|
80
|
+
renderWithProviders(
|
|
81
|
+
<OrderTypeDisplay
|
|
82
|
+
isPickupOrder={true}
|
|
83
|
+
store={null}
|
|
84
|
+
itemsInShipment={2}
|
|
85
|
+
totalItemsInCart={4}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expect(screen.getByText('Pick Up in Store - 2 out of 4 items')).toBeInTheDocument()
|
|
90
|
+
// Store display should not crash with null store
|
|
91
|
+
expect(screen.queryByText('Downtown Store')).not.toBeInTheDocument()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('renders pickup order without store (undefined store)', () => {
|
|
95
|
+
renderWithProviders(
|
|
96
|
+
<OrderTypeDisplay
|
|
97
|
+
isPickupOrder={true}
|
|
98
|
+
store={undefined}
|
|
99
|
+
itemsInShipment={2}
|
|
100
|
+
totalItemsInCart={4}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(screen.getByText('Pick Up in Store - 2 out of 4 items')).toBeInTheDocument()
|
|
105
|
+
// Store display should not crash with undefined store
|
|
106
|
+
expect(screen.queryByText('Downtown Store')).not.toBeInTheDocument()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('Delivery Order', () => {
|
|
111
|
+
test('renders delivery order display', () => {
|
|
112
|
+
renderWithProviders(
|
|
113
|
+
<OrderTypeDisplay
|
|
114
|
+
isPickupOrder={false}
|
|
115
|
+
store={mockStore}
|
|
116
|
+
itemsInShipment={4}
|
|
117
|
+
totalItemsInCart={7}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// Check delivery message with item counts
|
|
122
|
+
expect(screen.getByText('Delivery - 4 out of 7 items')).toBeInTheDocument()
|
|
123
|
+
|
|
124
|
+
// Store information should not be displayed for delivery
|
|
125
|
+
expect(screen.queryByText('Downtown Store')).not.toBeInTheDocument()
|
|
126
|
+
expect(screen.queryByText('123 Main Street')).not.toBeInTheDocument()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('renders delivery order with single item', () => {
|
|
130
|
+
renderWithProviders(
|
|
131
|
+
<OrderTypeDisplay
|
|
132
|
+
isPickupOrder={false}
|
|
133
|
+
store={mockStore}
|
|
134
|
+
itemsInShipment={1}
|
|
135
|
+
totalItemsInCart={1}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
expect(screen.getByText('Delivery - 1 out of 1 items')).toBeInTheDocument()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('renders delivery order with zero items in shipment', () => {
|
|
143
|
+
renderWithProviders(
|
|
144
|
+
<OrderTypeDisplay
|
|
145
|
+
isPickupOrder={false}
|
|
146
|
+
store={mockStore}
|
|
147
|
+
itemsInShipment={0}
|
|
148
|
+
totalItemsInCart={2}
|
|
149
|
+
/>
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
expect(screen.getByText('Delivery - 0 out of 2 items')).toBeInTheDocument()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('renders delivery order without store prop', () => {
|
|
156
|
+
renderWithProviders(
|
|
157
|
+
<OrderTypeDisplay
|
|
158
|
+
isPickupOrder={false}
|
|
159
|
+
store={null}
|
|
160
|
+
itemsInShipment={3}
|
|
161
|
+
totalItemsInCart={5}
|
|
162
|
+
/>
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
expect(screen.getByText('Delivery - 3 out of 5 items')).toBeInTheDocument()
|
|
166
|
+
// Store information should not be displayed for delivery regardless of store prop
|
|
167
|
+
expect(screen.queryByText('Downtown Store')).not.toBeInTheDocument()
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('Component Structure', () => {
|
|
172
|
+
test('renders with proper card styling', () => {
|
|
173
|
+
const {container} = renderWithProviders(
|
|
174
|
+
<OrderTypeDisplay
|
|
175
|
+
isPickupOrder={false}
|
|
176
|
+
store={mockStore}
|
|
177
|
+
itemsInShipment={2}
|
|
178
|
+
totalItemsInCart={4}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
// Check for Box component with layerStyle and padding
|
|
183
|
+
const cardBox = container.querySelector('[data-testid], div')
|
|
184
|
+
expect(cardBox).toBeInTheDocument()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('renders pickup text message', () => {
|
|
188
|
+
renderWithProviders(
|
|
189
|
+
<OrderTypeDisplay
|
|
190
|
+
isPickupOrder={true}
|
|
191
|
+
store={mockStore}
|
|
192
|
+
itemsInShipment={2}
|
|
193
|
+
totalItemsInCart={4}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
expect(screen.getByText('Pick Up in Store - 2 out of 4 items')).toBeInTheDocument()
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('renders delivery text message', () => {
|
|
201
|
+
renderWithProviders(
|
|
202
|
+
<OrderTypeDisplay
|
|
203
|
+
isPickupOrder={false}
|
|
204
|
+
store={mockStore}
|
|
205
|
+
itemsInShipment={2}
|
|
206
|
+
totalItemsInCart={4}
|
|
207
|
+
/>
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
expect(screen.getByText('Delivery - 2 out of 4 items')).toBeInTheDocument()
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('Large Numbers', () => {
|
|
215
|
+
test('handles large item counts correctly', () => {
|
|
216
|
+
renderWithProviders(
|
|
217
|
+
<OrderTypeDisplay
|
|
218
|
+
isPickupOrder={true}
|
|
219
|
+
store={mockStore}
|
|
220
|
+
itemsInShipment={999}
|
|
221
|
+
totalItemsInCart={1000}
|
|
222
|
+
/>
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
expect(screen.getByText('Pick Up in Store - 999 out of 1000 items')).toBeInTheDocument()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('handles edge case where items in shipment equals total items', () => {
|
|
229
|
+
renderWithProviders(
|
|
230
|
+
<OrderTypeDisplay
|
|
231
|
+
isPickupOrder={false}
|
|
232
|
+
store={mockStore}
|
|
233
|
+
itemsInShipment={5}
|
|
234
|
+
totalItemsInCart={5}
|
|
235
|
+
/>
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
expect(screen.getByText('Delivery - 5 out of 5 items')).toBeInTheDocument()
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
})
|
|
@@ -22,19 +22,19 @@ import {
|
|
|
22
22
|
} from '@salesforce/retail-react-app/app/components/shared/ui'
|
|
23
23
|
import {useForm} from 'react-hook-form'
|
|
24
24
|
import {useParams} from 'react-router-dom'
|
|
25
|
+
import {nanoid} from 'nanoid'
|
|
25
26
|
import {
|
|
26
27
|
useOrder,
|
|
27
28
|
useProducts,
|
|
28
29
|
useAuthHelper,
|
|
29
30
|
AuthHelpers,
|
|
30
|
-
useStores,
|
|
31
31
|
useShopperCustomersMutation
|
|
32
32
|
} from '@salesforce/commerce-sdk-react'
|
|
33
33
|
import {getCreditCardIcon} from '@salesforce/retail-react-app/app/utils/cc-utils'
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
// Components
|
|
35
36
|
import Link from '@salesforce/retail-react-app/app/components/link'
|
|
36
37
|
import AddressDisplay from '@salesforce/retail-react-app/app/components/address-display'
|
|
37
|
-
import StoreDisplay from '@salesforce/retail-react-app/app/components/store-display'
|
|
38
38
|
import PostCheckoutRegistrationFields from '@salesforce/retail-react-app/app/components/forms/post-checkout-registration-fields'
|
|
39
39
|
import PromoPopover from '@salesforce/retail-react-app/app/components/promo-popover'
|
|
40
40
|
import ItemVariantProvider from '@salesforce/retail-react-app/app/components/item-variant'
|
|
@@ -42,13 +42,16 @@ import CartItemVariantImage from '@salesforce/retail-react-app/app/components/it
|
|
|
42
42
|
import CartItemVariantName from '@salesforce/retail-react-app/app/components/item-variant/item-name'
|
|
43
43
|
import CartItemVariantAttributes from '@salesforce/retail-react-app/app/components/item-variant/item-attributes'
|
|
44
44
|
import CartItemVariantPrice from '@salesforce/retail-react-app/app/components/item-variant/item-price'
|
|
45
|
+
import MultiShipOrderSummary from '@salesforce/retail-react-app/app/components/multiship/multiship-order-summary'
|
|
46
|
+
import ShipmentDetails from '@salesforce/retail-react-app/app/pages/checkout/partials/shipment-details'
|
|
47
|
+
|
|
48
|
+
// Hooks
|
|
49
|
+
import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
|
|
45
50
|
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
46
|
-
import {
|
|
47
|
-
API_ERROR_MESSAGE,
|
|
48
|
-
STORE_LOCATOR_IS_ENABLED
|
|
49
|
-
} from '@salesforce/retail-react-app/app/constants'
|
|
50
51
|
import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
// Constants
|
|
54
|
+
import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
|
|
52
55
|
|
|
53
56
|
const onClient = typeof window !== 'undefined'
|
|
54
57
|
|
|
@@ -69,25 +72,13 @@ const CheckoutConfirmation = () => {
|
|
|
69
72
|
const {currency} = useCurrency()
|
|
70
73
|
const itemIds = order?.productItems.map((item) => item.productId)
|
|
71
74
|
const {data: products} = useProducts({parameters: {ids: itemIds?.join(',')}})
|
|
72
|
-
const productItemsMap = products?.data
|
|
75
|
+
const productItemsMap = (products?.data || []).reduce(
|
|
76
|
+
(map, item) => ({...map, [item.id]: item}),
|
|
77
|
+
{}
|
|
78
|
+
)
|
|
73
79
|
const form = useForm()
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
const isPickupOrder = STORE_LOCATOR_IS_ENABLED
|
|
77
|
-
? order?.shipments?.[0]?.shippingMethod?.c_storePickupEnabled === true
|
|
78
|
-
: false
|
|
79
|
-
const storeId = order?.shipments?.[0]?.c_fromStoreId
|
|
80
|
-
const {data: storeData} = useStores(
|
|
81
|
-
{
|
|
82
|
-
parameters: {
|
|
83
|
-
ids: storeId
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
enabled: !!storeId && isPickupOrder && onClient
|
|
88
|
-
}
|
|
89
|
-
)
|
|
90
|
-
const store = storeData?.data?.[0]
|
|
81
|
+
const hasMultipleShipments = order?.shipments && order.shipments.length > 1
|
|
91
82
|
|
|
92
83
|
useEffect(() => {
|
|
93
84
|
form.reset({
|
|
@@ -108,7 +99,8 @@ const CheckoutConfirmation = () => {
|
|
|
108
99
|
const saveShippingAddress = async (customerId) => {
|
|
109
100
|
try {
|
|
110
101
|
const shippingAddress = order.shipments[0].shippingAddress
|
|
111
|
-
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
103
|
+
const {id, ...shippingAddressWithoutId} = shippingAddress
|
|
112
104
|
const bodyShippingAddress = {
|
|
113
105
|
addressId: nanoid(),
|
|
114
106
|
...shippingAddressWithoutId
|
|
@@ -269,90 +261,8 @@ const CheckoutConfirmation = () => {
|
|
|
269
261
|
</Box>
|
|
270
262
|
)}
|
|
271
263
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
<Stack spacing={6}>
|
|
275
|
-
{isPickupOrder ? (
|
|
276
|
-
<>
|
|
277
|
-
<Heading fontSize="lg">
|
|
278
|
-
<FormattedMessage
|
|
279
|
-
defaultMessage="Pickup Details"
|
|
280
|
-
id="checkout_confirmation.heading.pickup_details"
|
|
281
|
-
/>
|
|
282
|
-
</Heading>
|
|
283
|
-
|
|
284
|
-
<Stack spacing={2}>
|
|
285
|
-
<Heading as="h3" fontSize="md">
|
|
286
|
-
<FormattedMessage
|
|
287
|
-
defaultMessage="Pickup Address"
|
|
288
|
-
id="checkout_confirmation.heading.pickup_address"
|
|
289
|
-
/>
|
|
290
|
-
</Heading>
|
|
291
|
-
{store ? (
|
|
292
|
-
<StoreDisplay
|
|
293
|
-
store={store}
|
|
294
|
-
showDistance={false}
|
|
295
|
-
showEmail={true}
|
|
296
|
-
showPhone={true}
|
|
297
|
-
showStoreHours={true}
|
|
298
|
-
/>
|
|
299
|
-
) : (
|
|
300
|
-
<Text>
|
|
301
|
-
<FormattedMessage
|
|
302
|
-
defaultMessage="Store information isn't available"
|
|
303
|
-
id="checkout_confirmation.message.store_info_unavailable"
|
|
304
|
-
/>
|
|
305
|
-
</Text>
|
|
306
|
-
)}
|
|
307
|
-
</Stack>
|
|
308
|
-
</>
|
|
309
|
-
) : (
|
|
310
|
-
<>
|
|
311
|
-
<Heading fontSize="lg">
|
|
312
|
-
<FormattedMessage
|
|
313
|
-
defaultMessage="Delivery Details"
|
|
314
|
-
id="checkout_confirmation.heading.delivery_details"
|
|
315
|
-
/>
|
|
316
|
-
</Heading>
|
|
317
|
-
|
|
318
|
-
<SimpleGrid columns={[1, 1, 2]} spacing={6}>
|
|
319
|
-
<Stack spacing={1}>
|
|
320
|
-
<Heading as="h3" fontSize="sm">
|
|
321
|
-
<FormattedMessage
|
|
322
|
-
defaultMessage="Shipping Address"
|
|
323
|
-
id="checkout_confirmation.heading.shipping_address"
|
|
324
|
-
/>
|
|
325
|
-
</Heading>
|
|
326
|
-
<AddressDisplay
|
|
327
|
-
address={order.shipments[0].shippingAddress}
|
|
328
|
-
/>
|
|
329
|
-
</Stack>
|
|
330
|
-
|
|
331
|
-
<Stack spacing={1}>
|
|
332
|
-
<Heading as="h3" fontSize="sm">
|
|
333
|
-
<FormattedMessage
|
|
334
|
-
defaultMessage="Shipping Method"
|
|
335
|
-
id="checkout_confirmation.heading.shipping_method"
|
|
336
|
-
/>
|
|
337
|
-
</Heading>
|
|
338
|
-
<Box>
|
|
339
|
-
<Text>
|
|
340
|
-
{order.shipments[0].shippingMethod.name}
|
|
341
|
-
</Text>
|
|
342
|
-
<Text>
|
|
343
|
-
{
|
|
344
|
-
order.shipments[0].shippingMethod
|
|
345
|
-
.description
|
|
346
|
-
}
|
|
347
|
-
</Text>
|
|
348
|
-
</Box>
|
|
349
|
-
</Stack>
|
|
350
|
-
</SimpleGrid>
|
|
351
|
-
</>
|
|
352
|
-
)}
|
|
353
|
-
</Stack>
|
|
354
|
-
</Container>
|
|
355
|
-
</Box>
|
|
264
|
+
{/* Shipment Details */}
|
|
265
|
+
<ShipmentDetails shipments={order.shipments} />
|
|
356
266
|
|
|
357
267
|
<Box layerStyle="card" rounded={[0, 0, 'base']} px={[4, 4, 6]} py={[6, 6, 8]}>
|
|
358
268
|
<Container variant="form">
|
|
@@ -380,56 +290,67 @@ const CheckoutConfirmation = () => {
|
|
|
380
290
|
</Text>
|
|
381
291
|
|
|
382
292
|
<Stack spacing={5} align="flex-start">
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
293
|
+
{hasMultipleShipments ? (
|
|
294
|
+
<MultiShipOrderSummary
|
|
295
|
+
order={order}
|
|
296
|
+
productItemsMap={productItemsMap}
|
|
297
|
+
currency={currency}
|
|
298
|
+
/>
|
|
299
|
+
) : (
|
|
300
|
+
<Stack
|
|
301
|
+
spacing={5}
|
|
302
|
+
align="flex-start"
|
|
303
|
+
width="full"
|
|
304
|
+
divider={<Divider />}
|
|
305
|
+
>
|
|
306
|
+
{order.productItems?.map((product, idx) => {
|
|
307
|
+
const productDetail =
|
|
308
|
+
productItemsMap?.[product.productId] || {}
|
|
309
|
+
const variant = {
|
|
310
|
+
...product,
|
|
311
|
+
...productDetail,
|
|
312
|
+
price: product.price
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<ItemVariantProvider
|
|
317
|
+
key={product.productId}
|
|
318
|
+
index={idx}
|
|
319
|
+
variant={variant}
|
|
320
|
+
>
|
|
321
|
+
<Flex
|
|
322
|
+
width="full"
|
|
323
|
+
alignItems="flex-start"
|
|
413
324
|
>
|
|
414
|
-
<
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
325
|
+
<CartItemVariantImage
|
|
326
|
+
width="80px"
|
|
327
|
+
mr={2}
|
|
328
|
+
/>
|
|
329
|
+
<Stack
|
|
330
|
+
spacing={1}
|
|
331
|
+
marginTop="-3px"
|
|
332
|
+
flex={1}
|
|
419
333
|
>
|
|
420
|
-
<
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
334
|
+
<CartItemVariantName />
|
|
335
|
+
<Flex
|
|
336
|
+
width="full"
|
|
337
|
+
justifyContent="space-between"
|
|
338
|
+
alignItems="flex-end"
|
|
339
|
+
>
|
|
340
|
+
<CartItemVariantAttributes
|
|
341
|
+
includeQuantity
|
|
342
|
+
/>
|
|
343
|
+
<CartItemVariantPrice
|
|
344
|
+
currency={currency}
|
|
345
|
+
/>
|
|
346
|
+
</Flex>
|
|
347
|
+
</Stack>
|
|
348
|
+
</Flex>
|
|
349
|
+
</ItemVariantProvider>
|
|
350
|
+
)
|
|
351
|
+
})}
|
|
352
|
+
</Stack>
|
|
353
|
+
)}
|
|
433
354
|
|
|
434
355
|
<Stack w="full" py={4} borderY="1px" borderColor="gray.200">
|
|
435
356
|
<Flex justify="space-between">
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import ContactInfo from '@salesforce/retail-react-app/app/pages/checkout/partials/contact-info'
|
|
25
25
|
import PickupAddress from '@salesforce/retail-react-app/app/pages/checkout/partials/pickup-address'
|
|
26
26
|
import ShippingAddress from '@salesforce/retail-react-app/app/pages/checkout/partials/shipping-address'
|
|
27
|
-
import
|
|
27
|
+
import ShippingMethods from '@salesforce/retail-react-app/app/pages/checkout/partials/shipping-methods'
|
|
28
28
|
import Payment from '@salesforce/retail-react-app/app/pages/checkout/partials/payment'
|
|
29
29
|
import OrderSummary from '@salesforce/retail-react-app/app/components/order-summary'
|
|
30
30
|
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
@@ -34,30 +34,39 @@ import {useShopperOrdersMutation, useShopperBasketsMutation} from '@salesforce/c
|
|
|
34
34
|
import UnavailableProductConfirmationModal from '@salesforce/retail-react-app/app/components/unavailable-product-confirmation-modal'
|
|
35
35
|
import {
|
|
36
36
|
API_ERROR_MESSAGE,
|
|
37
|
-
TOAST_MESSAGE_REMOVED_ITEM_FROM_CART
|
|
38
|
-
STORE_LOCATOR_IS_ENABLED
|
|
37
|
+
TOAST_MESSAGE_REMOVED_ITEM_FROM_CART
|
|
39
38
|
} from '@salesforce/retail-react-app/app/constants'
|
|
40
39
|
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
|
|
41
40
|
import LoadingSpinner from '@salesforce/retail-react-app/app/components/loading-spinner'
|
|
42
41
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
42
|
+
import {useMultiship} from '@salesforce/retail-react-app/app/hooks/use-multiship'
|
|
43
43
|
|
|
44
44
|
const Checkout = () => {
|
|
45
45
|
const {formatMessage} = useIntl()
|
|
46
46
|
const navigate = useNavigation()
|
|
47
47
|
const {step} = useCheckout()
|
|
48
48
|
const [error, setError] = useState()
|
|
49
|
-
const {data: basket} = useCurrentBasket()
|
|
49
|
+
const {data: basket, derivedData} = useCurrentBasket()
|
|
50
50
|
const [isLoading, setIsLoading] = useState(false)
|
|
51
51
|
const {mutateAsync: createOrder} = useShopperOrdersMutation('createOrder')
|
|
52
52
|
const {passwordless = {}, social = {}} = getConfig().app.login || {}
|
|
53
53
|
const idps = social?.idps
|
|
54
54
|
const isSocialEnabled = !!social?.enabled
|
|
55
55
|
const isPasswordlessEnabled = !!passwordless?.enabled
|
|
56
|
+
const {removeEmptyShipments} = useMultiship(basket)
|
|
57
|
+
const multishipEnabled = getConfig()?.app?.multishipEnabled ?? true
|
|
58
|
+
|
|
59
|
+
// cart has both pickup and delivery orders
|
|
60
|
+
const isDeliveryAndPickupOrder =
|
|
61
|
+
multishipEnabled &&
|
|
62
|
+
derivedData?.totalPickupShipments > 0 &&
|
|
63
|
+
derivedData?.totalDeliveryShipments > 0
|
|
64
|
+
|
|
65
|
+
// Check if there are pickup shipments
|
|
66
|
+
const hasPickupShipments = derivedData?.totalPickupShipments > 0
|
|
56
67
|
|
|
57
68
|
// Only enable BOPIS functionality if the feature toggle is on
|
|
58
|
-
const
|
|
59
|
-
? basket?.shipments[0]?.shippingMethod?.c_storePickupEnabled === true
|
|
60
|
-
: false
|
|
69
|
+
const isPickupOrderOnly = !isDeliveryAndPickupOrder && hasPickupShipments
|
|
61
70
|
|
|
62
71
|
useEffect(() => {
|
|
63
72
|
if (error || step === 4) {
|
|
@@ -65,6 +74,14 @@ const Checkout = () => {
|
|
|
65
74
|
}
|
|
66
75
|
}, [error, step])
|
|
67
76
|
|
|
77
|
+
// Remove any empty shipments whenever navigating to the checkout page
|
|
78
|
+
// Using basketId ensures that the basket is in a valid state before removing empty shipments
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (basket?.shipments?.length > 1) {
|
|
81
|
+
removeEmptyShipments(basket)
|
|
82
|
+
}
|
|
83
|
+
}, [basket?.basketId])
|
|
84
|
+
|
|
68
85
|
const submitOrder = async () => {
|
|
69
86
|
setIsLoading(true)
|
|
70
87
|
try {
|
|
@@ -106,8 +123,16 @@ const Checkout = () => {
|
|
|
106
123
|
isPasswordlessEnabled={isPasswordlessEnabled}
|
|
107
124
|
idps={idps}
|
|
108
125
|
/>
|
|
109
|
-
|
|
110
|
-
{
|
|
126
|
+
|
|
127
|
+
{isPickupOrderOnly ? (
|
|
128
|
+
<PickupAddress />
|
|
129
|
+
) : (
|
|
130
|
+
<>
|
|
131
|
+
{hasPickupShipments && <PickupAddress />}
|
|
132
|
+
<ShippingAddress />
|
|
133
|
+
<ShippingMethods />
|
|
134
|
+
</>
|
|
135
|
+
)}
|
|
111
136
|
<Payment />
|
|
112
137
|
|
|
113
138
|
{step === 5 && (
|