@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.
Files changed (113) hide show
  1. package/CHANGELOG.md +8 -4
  2. package/app/components/_app/index.jsx +9 -7
  3. package/app/components/_app/index.test.js +2 -2
  4. package/app/components/_app-config/index.jsx +9 -3
  5. package/app/components/drawer-menu/drawer-menu.jsx +3 -1
  6. package/app/components/footer/index.jsx +3 -1
  7. package/app/components/header/index.jsx +3 -1
  8. package/app/components/header/index.test.js +2 -2
  9. package/app/components/island/README.md +1 -1
  10. package/app/components/island/index.jsx +3 -1
  11. package/app/components/island/index.test.js +94 -5
  12. package/app/components/item-variant/item-attributes.jsx +12 -3
  13. package/app/components/multiship/multiship-order-summary.jsx +137 -0
  14. package/app/components/multiship/multiship-order-summary.test.js +121 -0
  15. package/app/components/order-summary/index.jsx +2 -4
  16. package/app/components/pickup-or-delivery/index.jsx +80 -0
  17. package/app/components/pickup-or-delivery/index.test.jsx +182 -0
  18. package/app/components/product-item/index.jsx +26 -16
  19. package/app/components/product-item/index.test.js +29 -2
  20. package/app/components/product-item-list/index.jsx +10 -0
  21. package/app/components/product-item-list/index.test.jsx +14 -0
  22. package/app/components/product-view/index.jsx +9 -6
  23. package/app/components/product-view/index.test.js +25 -21
  24. package/app/components/quantity-picker/index.test.jsx +12 -12
  25. package/app/components/reset-password/index.test.js +1 -1
  26. package/app/components/shared/ui/AlertDescription/index.jsx +8 -0
  27. package/app/components/shared/ui/index.jsx +1 -0
  28. package/app/components/store-display/index.jsx +28 -4
  29. package/app/components/store-display/index.test.js +71 -0
  30. package/app/components/store-locator/form.test.jsx +16 -4
  31. package/app/components/store-locator/list.jsx +9 -4
  32. package/app/components/toggle-card/index.jsx +14 -0
  33. package/app/components/unavailable-product-confirmation-modal/index.jsx +19 -5
  34. package/app/components/unavailable-product-confirmation-modal/index.test.js +122 -1
  35. package/app/constants.js +20 -6
  36. package/app/contexts/store-locator-provider.jsx +7 -1
  37. package/app/contexts/store-locator-provider.test.jsx +36 -1
  38. package/app/hooks/use-address-form.js +155 -0
  39. package/app/hooks/use-address-form.test.js +501 -0
  40. package/app/hooks/use-auth-modal.js +2 -6
  41. package/app/hooks/use-current-basket.js +71 -2
  42. package/app/hooks/use-current-basket.test.js +37 -1
  43. package/app/hooks/use-dnt-notification.js +4 -4
  44. package/app/hooks/use-dnt-notification.test.js +5 -5
  45. package/app/hooks/use-item-shipment-management.js +233 -0
  46. package/app/hooks/use-item-shipment-management.test.js +696 -0
  47. package/app/hooks/use-multiship.js +589 -0
  48. package/app/hooks/use-multiship.test.js +776 -0
  49. package/app/hooks/use-pickup-shipment.js +70 -106
  50. package/app/hooks/use-pickup-shipment.test.js +345 -209
  51. package/app/hooks/use-product-address-assignment.js +280 -0
  52. package/app/hooks/use-product-address-assignment.test.js +414 -0
  53. package/app/hooks/use-product-inventory.js +100 -0
  54. package/app/hooks/use-product-inventory.test.js +254 -0
  55. package/app/hooks/use-shipment-operations.js +168 -0
  56. package/app/hooks/use-shipment-operations.test.js +385 -0
  57. package/app/hooks/use-store-locator.js +24 -2
  58. package/app/hooks/use-store-locator.test.jsx +109 -1
  59. package/app/pages/account/index.test.js +1 -1
  60. package/app/pages/account/profile.test.js +0 -2
  61. package/app/pages/cart/index.jsx +397 -157
  62. package/app/pages/cart/index.test.js +353 -2
  63. package/app/pages/cart/partials/bonus-products-title.jsx +10 -8
  64. package/app/pages/cart/partials/cart-secondary-button-group.test.js +1 -1
  65. package/app/pages/cart/partials/order-type-display.jsx +68 -0
  66. package/app/pages/cart/partials/order-type-display.test.js +241 -0
  67. package/app/pages/checkout/confirmation.jsx +79 -158
  68. package/app/pages/checkout/index.jsx +34 -9
  69. package/app/pages/checkout/index.test.js +245 -118
  70. package/app/pages/checkout/partials/contact-info.jsx +2 -6
  71. package/app/pages/checkout/partials/contact-info.test.js +93 -7
  72. package/app/pages/checkout/partials/payment.jsx +19 -5
  73. package/app/pages/checkout/partials/pickup-address.jsx +340 -70
  74. package/app/pages/checkout/partials/pickup-address.test.js +1075 -82
  75. package/app/pages/checkout/partials/product-shipping-address-card.jsx +382 -0
  76. package/app/pages/checkout/partials/shipment-details.jsx +209 -0
  77. package/app/pages/checkout/partials/shipment-details.test.js +246 -0
  78. package/app/pages/checkout/partials/shipping-address.jsx +156 -68
  79. package/app/pages/checkout/partials/shipping-address.test.js +673 -0
  80. package/app/pages/checkout/partials/shipping-method-options.jsx +180 -0
  81. package/app/pages/checkout/partials/shipping-methods.jsx +403 -0
  82. package/app/pages/checkout/partials/shipping-methods.test.js +472 -0
  83. package/app/pages/checkout/partials/shipping-multi-address.jsx +259 -0
  84. package/app/pages/checkout/partials/shipping-multi-address.test.js +2088 -0
  85. package/app/pages/checkout/partials/shipping-product-cards.jsx +101 -0
  86. package/app/pages/checkout/util/checkout-context.js +25 -18
  87. package/app/pages/login/index.jsx +2 -6
  88. package/app/pages/product-detail/index.jsx +96 -81
  89. package/app/pages/product-detail/index.test.js +103 -19
  90. package/app/pages/product-list/index.jsx +3 -1
  91. package/app/pages/product-list/partials/inventory-filter.jsx +18 -21
  92. package/app/pages/product-list/partials/inventory-filter.test.js +15 -17
  93. package/app/pages/product-list/partials/selected-refinements.jsx +3 -1
  94. package/app/ssr.js +1 -5
  95. package/app/static/translations/compiled/en-GB.json +316 -30
  96. package/app/static/translations/compiled/en-US.json +316 -30
  97. package/app/static/translations/compiled/en-XA.json +673 -75
  98. package/app/utils/address-utils.js +112 -0
  99. package/app/utils/address-utils.test.js +484 -0
  100. package/app/utils/product-utils.js +17 -5
  101. package/app/utils/product-utils.test.js +17 -8
  102. package/app/utils/sfdc-user-agent-utils.js +32 -0
  103. package/app/utils/sfdc-user-agent-utils.test.js +82 -0
  104. package/app/utils/shipment-utils.js +196 -0
  105. package/app/utils/shipment-utils.test.js +458 -0
  106. package/app/utils/test-utils.js +4 -4
  107. package/app/utils/utils.js +6 -1
  108. package/config/default.js +4 -1
  109. package/config/mocks/default.js +3 -1
  110. package/package.json +9 -9
  111. package/translations/en-GB.json +127 -10
  112. package/translations/en-US.json +127 -10
  113. package/app/pages/checkout/partials/shipping-options.jsx +0 -269
@@ -0,0 +1,382 @@
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 PropTypes from 'prop-types'
9
+ import {
10
+ Box,
11
+ Flex,
12
+ VStack,
13
+ HStack,
14
+ Select,
15
+ Button,
16
+ Image,
17
+ Text,
18
+ List,
19
+ ListItem,
20
+ Alert,
21
+ AlertIcon,
22
+ AlertDescription,
23
+ Stack
24
+ } from '@salesforce/retail-react-app/app/components/shared/ui'
25
+ import LoadingSpinner from '@salesforce/retail-react-app/app/components/loading-spinner'
26
+ import {useIntl, defineMessage} from 'react-intl'
27
+ import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
28
+ import {getPriceData} from '@salesforce/retail-react-app/app/utils/product-utils'
29
+ import ItemVariantProvider from '@salesforce/retail-react-app/app/components/item-variant'
30
+ import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price'
31
+ import AddressFields from '@salesforce/retail-react-app/app/components/forms/address-fields'
32
+ import FormActionButtons from '@salesforce/retail-react-app/app/components/forms/form-action-buttons'
33
+
34
+ const MultiShippingItemAttributes = ({variant, includeQuantity = true}) => {
35
+ const {formatMessage} = useIntl()
36
+ const variationAttributes = variant?.variationAttributes || []
37
+ const variationValues = variant?.variationValues || {}
38
+ return (
39
+ <List
40
+ spacing={1.5}
41
+ flex={1}
42
+ aria-label={formatMessage({
43
+ id: 'shipping_multi_address.product_attributes.label',
44
+ defaultMessage: 'Product attributes'
45
+ })}
46
+ >
47
+ {variationAttributes &&
48
+ variationAttributes.length > 0 &&
49
+ variationAttributes.map((attr) => {
50
+ const value = attr.values?.find((v) => v.value === variationValues[attr.id])
51
+ return (
52
+ <ListItem key={attr.id}>
53
+ <Text lineHeight={1} color="gray.700" fontSize="sm">
54
+ {attr.name || attr.id}: {value?.name || value?.value || ''}
55
+ </Text>
56
+ </ListItem>
57
+ )
58
+ })}
59
+ {includeQuantity && (
60
+ <ListItem>
61
+ <Text lineHeight={1} color="gray.700" fontSize="sm">
62
+ {formatMessage({
63
+ id: 'shipping_multi_address.quantity.label',
64
+ defaultMessage: 'Quantity'
65
+ })}
66
+ : {variant.quantity}
67
+ </Text>
68
+ </ListItem>
69
+ )}
70
+ </List>
71
+ )
72
+ }
73
+
74
+ MultiShippingItemAttributes.propTypes = {
75
+ variant: PropTypes.object.isRequired,
76
+ includeQuantity: PropTypes.bool
77
+ }
78
+
79
+ const AddressForm = ({item, form, onSubmit, onCancel}) => {
80
+ const saveButtonLabel = defineMessage({
81
+ defaultMessage: 'Save',
82
+ id: 'shipping_address_form.button.save'
83
+ })
84
+ return (
85
+ <Box position="relative" bg="white" padding={6} width="100%">
86
+ {form.formState.isSubmitting && <LoadingSpinner />}
87
+ <form
88
+ data-testid="address-form"
89
+ onSubmit={form.handleSubmit(async (data) => {
90
+ await onSubmit(data, form, item.itemId)
91
+ })}
92
+ >
93
+ <Stack spacing={6} width="100%">
94
+ {form.formState.errors?.global && (
95
+ <Alert status="error">
96
+ <AlertIcon color="red.600" boxSize={4} />
97
+ <AlertDescription>
98
+ {form.formState.errors.global.message}
99
+ </AlertDescription>
100
+ </Alert>
101
+ )}
102
+ <AddressFields form={form} />
103
+ <FormActionButtons onCancel={onCancel} saveButtonLabel={saveButtonLabel} />
104
+ </Stack>
105
+ </form>
106
+ </Box>
107
+ )
108
+ }
109
+
110
+ AddressForm.propTypes = {
111
+ item: PropTypes.object.isRequired,
112
+ form: PropTypes.object.isRequired,
113
+ onSubmit: PropTypes.func.isRequired,
114
+ onCancel: PropTypes.func.isRequired
115
+ }
116
+
117
+ /**
118
+ * Component for selecting address for a single product item
119
+ */
120
+ const ProductShippingAddressCard = ({
121
+ item,
122
+ variant,
123
+ imageUrl,
124
+ addressKey,
125
+ selectedAddressId,
126
+ availableAddresses,
127
+ isGuestUser,
128
+ customerLoading,
129
+ onAddressSelect,
130
+ onAddNewAddress,
131
+ showAddAddressForm,
132
+ addressForm,
133
+ handleCreateAddress,
134
+ closeForm
135
+ }) => {
136
+ const {formatMessage} = useIntl()
137
+ const {currency} = useCurrency()
138
+
139
+ return (
140
+ <Box
141
+ key={addressKey}
142
+ border="1px solid"
143
+ borderColor="gray.200"
144
+ borderRadius="md"
145
+ p={2}
146
+ data-testid="multi-shipping-card"
147
+ w="100%"
148
+ flex="1"
149
+ >
150
+ <Flex
151
+ direction={{base: 'column', md: 'row'}}
152
+ align="flex-start"
153
+ w="100%"
154
+ h="100%"
155
+ gap={{base: 4, md: 6}}
156
+ >
157
+ <Flex direction="row" align="flex-start" flex={1} minW={0}>
158
+ <HStack align="flex-start" spacing={3} w="100%">
159
+ <Box
160
+ flexShrink={0}
161
+ borderRadius="md"
162
+ bg="gray.100"
163
+ overflow="hidden"
164
+ position="relative"
165
+ maxW={{base: '60px', md: '80px'}}
166
+ w="100%"
167
+ aspectRatio="1"
168
+ >
169
+ <Image
170
+ src={imageUrl}
171
+ alt={formatMessage(
172
+ {
173
+ id: 'shipping_multi_address.image.alt',
174
+ defaultMessage: 'Product image for {productName}'
175
+ },
176
+ {
177
+ productName: item.productName
178
+ }
179
+ )}
180
+ objectFit="cover"
181
+ w="100%"
182
+ h="100%"
183
+ />
184
+ </Box>
185
+ <ItemVariantProvider variant={variant}>
186
+ <VStack
187
+ justify="flex-start"
188
+ minW={0}
189
+ flex={1}
190
+ pt={0}
191
+ align="flex-start"
192
+ >
193
+ <Text
194
+ id={`product-title-${addressKey}`}
195
+ data-testid={`product-title-${addressKey}`}
196
+ fontWeight="medium"
197
+ fontSize={{base: 'sm', md: 'md'}}
198
+ mb={1}
199
+ color="gray.900"
200
+ textAlign="left"
201
+ >
202
+ {item.productName}
203
+ </Text>
204
+ <Box
205
+ id={`product-description-${addressKey}`}
206
+ data-testid={`product-description-${addressKey}`}
207
+ >
208
+ <MultiShippingItemAttributes
209
+ variant={variant}
210
+ includeQuantity
211
+ />
212
+ </Box>
213
+ </VStack>
214
+ </ItemVariantProvider>
215
+ </HStack>
216
+ </Flex>
217
+
218
+ <VStack
219
+ align="flex-start"
220
+ w="100%"
221
+ flex={{base: 'none', md: '1'}}
222
+ minW={{base: '100%', md: '280px'}}
223
+ maxW={{base: '100%', md: '400px'}}
224
+ pt={0}
225
+ spacing={1}
226
+ mt={{base: 4, md: 0}}
227
+ >
228
+ <Text
229
+ id={`delivery-address-label-${addressKey}`}
230
+ data-testid={`delivery-address-label-${addressKey}`}
231
+ fontWeight="medium"
232
+ fontSize="sm"
233
+ mb={1}
234
+ >
235
+ {formatMessage({
236
+ defaultMessage: 'Delivery Address',
237
+ id: 'shipping_address.label.shipping_address'
238
+ })}
239
+ </Text>
240
+
241
+ <Box w="100%" mb={6}>
242
+ <VStack spacing={3} align="stretch">
243
+ {!isGuestUser && customerLoading ? (
244
+ <Box p={4} textAlign="center">
245
+ <Text color="gray.500">
246
+ {formatMessage({
247
+ id: 'shipping_multi_address.loading_addresses',
248
+ defaultMessage: 'Loading addresses...'
249
+ })}
250
+ </Text>
251
+ </Box>
252
+ ) : (
253
+ <Select
254
+ value={selectedAddressId || ''}
255
+ onChange={(e) => {
256
+ const value = e.target.value
257
+ closeForm(addressKey)
258
+ onAddressSelect(addressKey, value)
259
+ }}
260
+ disabled={
261
+ availableAddresses.length === 0 ||
262
+ (!isGuestUser && customerLoading)
263
+ }
264
+ aria-labelledby={`delivery-address-label-${addressKey}`}
265
+ borderColor="gray.300"
266
+ _hover={{borderColor: 'gray.400'}}
267
+ _focus={{
268
+ borderColor: 'blue.500',
269
+ boxShadow: '0 0 0 1px var(--chakra-colors-blue-500)'
270
+ }}
271
+ data-testid={`address-dropdown-${addressKey}`}
272
+ >
273
+ {availableAddresses.length === 0 ? (
274
+ <option value="">
275
+ {formatMessage({
276
+ id: 'shipping_multi_address.no_addresses_available',
277
+ defaultMessage: 'No address available'
278
+ })}
279
+ </option>
280
+ ) : (
281
+ availableAddresses.map((addr) => (
282
+ <option
283
+ key={addr.addressId}
284
+ value={addr.addressId}
285
+ data-testid={`address-option-${addr.addressId}`}
286
+ >
287
+ {addr.firstName} {addr.lastName} - {addr.address1},{' '}
288
+ {formatMessage(
289
+ {
290
+ id: 'shipping_multi_address.format.address_line_2',
291
+ defaultMessage:
292
+ '{city}, {stateCode} {postalCode}'
293
+ },
294
+ {
295
+ city: addr.city,
296
+ stateCode: addr.stateCode || '',
297
+ postalCode: addr.postalCode
298
+ }
299
+ )}
300
+ </option>
301
+ ))
302
+ )}
303
+ </Select>
304
+ )}
305
+ <Button
306
+ variant="link"
307
+ size="sm"
308
+ onClick={() => {
309
+ onAddNewAddress(addressKey)
310
+ }}
311
+ alignSelf="flex-start"
312
+ aria-label={formatMessage(
313
+ {
314
+ id: 'shipping_multi_address.add_new_address.aria_label',
315
+ defaultMessage: 'Add new delivery address for {productName}'
316
+ },
317
+ {
318
+ productName: item.productName
319
+ }
320
+ )}
321
+ >
322
+ {formatMessage({
323
+ defaultMessage: '+ Add New Address',
324
+ id: 'shipping_address.button.add_new_address'
325
+ })}
326
+ </Button>
327
+ </VStack>
328
+ </Box>
329
+
330
+ <Box
331
+ fontWeight="semibold"
332
+ fontSize="md"
333
+ color="gray.900"
334
+ alignSelf="flex-end"
335
+ mt="auto"
336
+ >
337
+ <DisplayPrice
338
+ priceData={getPriceData(variant)}
339
+ currency={currency}
340
+ labelForA11y={variant.productName}
341
+ />
342
+ </Box>
343
+ </VStack>
344
+ </Flex>
345
+
346
+ {/* Add New Address Form - appears inside the product card */}
347
+ {showAddAddressForm[addressKey] && (
348
+ <Box position="relative" mt={4} width="100%">
349
+ <AddressForm
350
+ item={item}
351
+ form={addressForm}
352
+ onSubmit={(addressData, form, itemId) =>
353
+ handleCreateAddress(addressData, itemId)
354
+ }
355
+ onCancel={() => closeForm(addressKey)}
356
+ />
357
+ </Box>
358
+ )}
359
+ </Box>
360
+ )
361
+ }
362
+
363
+ ProductShippingAddressCard.displayName = 'ProductShippingAddressCard'
364
+
365
+ ProductShippingAddressCard.propTypes = {
366
+ item: PropTypes.object.isRequired,
367
+ variant: PropTypes.object.isRequired,
368
+ imageUrl: PropTypes.string.isRequired,
369
+ addressKey: PropTypes.string.isRequired,
370
+ selectedAddressId: PropTypes.string,
371
+ availableAddresses: PropTypes.array.isRequired,
372
+ isGuestUser: PropTypes.bool.isRequired,
373
+ customerLoading: PropTypes.bool.isRequired,
374
+ onAddressSelect: PropTypes.func.isRequired,
375
+ onAddNewAddress: PropTypes.func.isRequired,
376
+ showAddAddressForm: PropTypes.object.isRequired,
377
+ addressForm: PropTypes.object.isRequired,
378
+ handleCreateAddress: PropTypes.func.isRequired,
379
+ closeForm: PropTypes.func.isRequired
380
+ }
381
+
382
+ export default ProductShippingAddressCard
@@ -0,0 +1,209 @@
1
+ /*
2
+ * Copyright (c) 2025, salesforce.com, 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 {FormattedMessage} from 'react-intl'
9
+ import PropTypes from 'prop-types'
10
+ import {
11
+ Box,
12
+ Container,
13
+ Heading,
14
+ SimpleGrid,
15
+ Stack,
16
+ Text,
17
+ Divider
18
+ } from '@salesforce/retail-react-app/app/components/shared/ui'
19
+ import {useStores} from '@salesforce/commerce-sdk-react'
20
+ import AddressDisplay from '@salesforce/retail-react-app/app/components/address-display'
21
+ import StoreDisplay from '@salesforce/retail-react-app/app/components/store-display'
22
+ import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants'
23
+ import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
24
+ import {isPickupShipment} from '@salesforce/retail-react-app/app/utils/shipment-utils'
25
+
26
+ const onClient = typeof window !== 'undefined'
27
+
28
+ const ShipmentDetails = ({shipments = []}) => {
29
+ const storeLocatorEnabled = getConfig()?.app?.storeLocatorEnabled ?? STORE_LOCATOR_IS_ENABLED
30
+ // Get all unique store IDs from pickup shipments
31
+ const storeIds =
32
+ shipments
33
+ ?.filter((shipment) => storeLocatorEnabled && isPickupShipment(shipment))
34
+ .map((shipment) => shipment.c_fromStoreId)
35
+ .filter(Boolean) || []
36
+
37
+ // Fetch store data for all pickup shipments
38
+ const {data: storeData} = useStores(
39
+ {
40
+ parameters: {
41
+ ids: storeIds.join(',')
42
+ }
43
+ },
44
+ {
45
+ enabled: storeIds.length > 0 && onClient
46
+ }
47
+ )
48
+
49
+ if (!shipments || shipments.length === 0) {
50
+ return null
51
+ }
52
+
53
+ // Group shipments by type (pickup vs delivery)
54
+ const pickupShipments = []
55
+ const deliveryShipments = []
56
+
57
+ shipments.forEach((shipment) => {
58
+ const isPickup = storeLocatorEnabled && isPickupShipment(shipment)
59
+
60
+ if (isPickup) {
61
+ pickupShipments.push(shipment)
62
+ } else {
63
+ deliveryShipments.push(shipment)
64
+ }
65
+ })
66
+
67
+ const getStoreData = (storeId) => {
68
+ if (!storeData?.data) return null
69
+ return storeData.data.find((store) => store.id === storeId)
70
+ }
71
+
72
+ return (
73
+ <Stack spacing={4}>
74
+ {/* Pickup Details */}
75
+ {pickupShipments.length > 0 && (
76
+ <Box layerStyle="card" rounded={[0, 0, 'base']} px={[4, 4, 6]} py={[6, 6, 8]}>
77
+ <Container variant="form">
78
+ <Stack spacing={6}>
79
+ <Heading fontSize="lg">
80
+ <FormattedMessage
81
+ defaultMessage="Pickup Details"
82
+ id="checkout_confirmation.heading.pickup_details"
83
+ />
84
+ </Heading>
85
+
86
+ <Stack spacing={4}>
87
+ {pickupShipments.map((shipment, index) => {
88
+ const store = getStoreData(shipment.c_fromStoreId)
89
+
90
+ return (
91
+ <Box key={`pickup-${index}`}>
92
+ {pickupShipments.length > 1 && (
93
+ <Heading as="h3" fontSize="md" mb={3}>
94
+ <FormattedMessage
95
+ defaultMessage="Pickup Location {number}"
96
+ id="checkout_confirmation.heading.pickup_location_number"
97
+ values={{number: index + 1}}
98
+ />
99
+ </Heading>
100
+ )}
101
+
102
+ <Stack spacing={2}>
103
+ <Heading as="h3" fontSize="sm">
104
+ <FormattedMessage
105
+ defaultMessage="Pickup Address"
106
+ id="checkout_confirmation.heading.pickup_address"
107
+ />
108
+ </Heading>
109
+ {store ? (
110
+ <StoreDisplay
111
+ store={store}
112
+ showDistance={false}
113
+ showEmail={true}
114
+ showPhone={true}
115
+ showStoreHours={true}
116
+ />
117
+ ) : (
118
+ <Text>
119
+ <FormattedMessage
120
+ defaultMessage="Store information isn't available"
121
+ id="checkout_confirmation.message.store_info_unavailable"
122
+ />
123
+ </Text>
124
+ )}
125
+ </Stack>
126
+
127
+ {index < pickupShipments.length - 1 && (
128
+ <Divider my={4} />
129
+ )}
130
+ </Box>
131
+ )
132
+ })}
133
+ </Stack>
134
+ </Stack>
135
+ </Container>
136
+ </Box>
137
+ )}
138
+
139
+ {/* Delivery Details */}
140
+ {deliveryShipments.length > 0 && (
141
+ <Box layerStyle="card" rounded={[0, 0, 'base']} px={[4, 4, 6]} py={[6, 6, 8]}>
142
+ <Container variant="form">
143
+ <Stack spacing={6}>
144
+ <Heading fontSize="lg">
145
+ <FormattedMessage
146
+ defaultMessage="Delivery Details"
147
+ id="checkout_confirmation.heading.delivery_details"
148
+ />
149
+ </Heading>
150
+
151
+ <Stack spacing={4}>
152
+ {deliveryShipments.map((shipment, index) => (
153
+ <Box key={`delivery-${index}`}>
154
+ {deliveryShipments.length > 1 && (
155
+ <Heading as="h3" fontSize="md" mb={3}>
156
+ <FormattedMessage
157
+ defaultMessage="Delivery {number}"
158
+ id="checkout_confirmation.heading.delivery_number"
159
+ values={{number: index + 1}}
160
+ />
161
+ </Heading>
162
+ )}
163
+
164
+ <SimpleGrid columns={[1, 1, 2]} spacing={6}>
165
+ <Stack spacing={1}>
166
+ <Heading as="h3" fontSize="sm">
167
+ <FormattedMessage
168
+ defaultMessage="Shipping Address"
169
+ id="checkout_confirmation.heading.shipping_address"
170
+ />
171
+ </Heading>
172
+ <AddressDisplay
173
+ address={shipment.shippingAddress}
174
+ />
175
+ </Stack>
176
+
177
+ <Stack spacing={1}>
178
+ <Heading as="h3" fontSize="sm">
179
+ <FormattedMessage
180
+ defaultMessage="Shipping Method"
181
+ id="checkout_confirmation.heading.shipping_method"
182
+ />
183
+ </Heading>
184
+ <Box>
185
+ <Text>{shipment.shippingMethod.name}</Text>
186
+ <Text>
187
+ {shipment.shippingMethod.description}
188
+ </Text>
189
+ </Box>
190
+ </Stack>
191
+ </SimpleGrid>
192
+
193
+ {index < deliveryShipments.length - 1 && <Divider my={4} />}
194
+ </Box>
195
+ ))}
196
+ </Stack>
197
+ </Stack>
198
+ </Container>
199
+ </Box>
200
+ )}
201
+ </Stack>
202
+ )
203
+ }
204
+
205
+ ShipmentDetails.propTypes = {
206
+ shipments: PropTypes.array
207
+ }
208
+
209
+ export default ShipmentDetails