@salesforce/retail-react-app 8.1.0-preview.4 → 8.1.0
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 +1 -1
- package/app/components/bonus-product-view-modal/index.jsx +131 -12
- package/app/components/bonus-product-view-modal/index.test.js +167 -0
- package/app/components/product-item/index.jsx +6 -2
- package/app/components/product-item/index.test.js +37 -0
- package/app/components/search/index.jsx +38 -0
- package/app/components/search/index.test.js +305 -0
- package/app/components/shopper-agent/index.jsx +32 -7
- package/app/components/shopper-agent/index.test.js +177 -14
- package/app/hooks/use-variation-attributes.js +0 -1
- package/app/hooks/use-variation-attributes.test.js +88 -0
- package/app/pages/cart/index.jsx +224 -47
- package/app/pages/cart/index.test.js +398 -3
- package/app/pages/cart/partials/bonus-products-title.jsx +6 -6
- package/app/pages/cart/partials/bonus-products-title.test.js +11 -37
- package/app/pages/cart/partials/cart-product-list-with-grouped-bonus-products.jsx +4 -6
- package/app/utils/bonus-product/business-logic.js +45 -2
- package/app/utils/bonus-product/business-logic.test.js +1 -0
- package/app/utils/bonus-product/cart.js +148 -0
- package/app/utils/bonus-product/cart.test.js +1485 -0
- package/app/utils/bonus-product/utils.js +5 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
## v8.1.0
|
|
1
|
+
## v8.1.0 (Sep 25, 2025)
|
|
2
2
|
- Updated search UX - prices, images, suggestions new layout [#3271](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3271)
|
|
3
3
|
- Updated the UI for StoreDisplay component which displays pickup in-store information on different pages. [#3248](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3248)
|
|
4
4
|
- Added warning modal for guest users when toggling between multi ship and ship to one address. [#3280](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3280) [#3302](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3302)
|
|
@@ -44,7 +44,6 @@ const BonusProductViewModal = ({
|
|
|
44
44
|
product,
|
|
45
45
|
isOpen,
|
|
46
46
|
onClose,
|
|
47
|
-
bonusDiscountLineItemId,
|
|
48
47
|
promotionId,
|
|
49
48
|
onReturnToSelection,
|
|
50
49
|
...props
|
|
@@ -70,6 +69,22 @@ const BonusProductViewModal = ({
|
|
|
70
69
|
const {data: basket} = useCurrentBasket()
|
|
71
70
|
const navigate = useNavigation()
|
|
72
71
|
|
|
72
|
+
// Extract available bonus product IDs from basket for variant filtering
|
|
73
|
+
const availableBonusProductIds = useMemo(() => {
|
|
74
|
+
if (!basket?.bonusDiscountLineItems || !promotionId) return []
|
|
75
|
+
|
|
76
|
+
return basket.bonusDiscountLineItems
|
|
77
|
+
.filter((item) => item.promotionId === promotionId)
|
|
78
|
+
.flatMap((item) => item.bonusProducts || [])
|
|
79
|
+
.map((bonusProduct) => bonusProduct.productId)
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
}, [basket, promotionId])
|
|
82
|
+
|
|
83
|
+
// Check if we have promotion data to work with
|
|
84
|
+
const hasPromotionData = useMemo(() => {
|
|
85
|
+
return !!promotionId
|
|
86
|
+
}, [promotionId])
|
|
87
|
+
|
|
73
88
|
const intl = useIntl()
|
|
74
89
|
const {formatMessage} = intl
|
|
75
90
|
const showToast = useToast()
|
|
@@ -222,31 +237,135 @@ const BonusProductViewModal = ({
|
|
|
222
237
|
[messages.viewCart, handleViewCart]
|
|
223
238
|
)
|
|
224
239
|
|
|
225
|
-
// Clean product data
|
|
240
|
+
// Clean product data and pre-filter variants based on available bonus products
|
|
226
241
|
const productToRender = useMemo(() => {
|
|
227
242
|
const baseProduct = productViewModalData.product || safeProduct
|
|
228
|
-
|
|
243
|
+
|
|
244
|
+
// Always provide a fallback product for testing scenarios
|
|
245
|
+
if (!baseProduct) {
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// If no promotion data, just return the basic product without filtering
|
|
250
|
+
if (!hasPromotionData) {
|
|
251
|
+
return {
|
|
252
|
+
...baseProduct,
|
|
253
|
+
variationAttributes: baseProduct.variationAttributes,
|
|
254
|
+
variants: baseProduct.variants,
|
|
255
|
+
variationParams: baseProduct.variationParams,
|
|
256
|
+
selectedVariationAttributes: baseProduct.selectedVariationAttributes,
|
|
257
|
+
type: baseProduct.type,
|
|
258
|
+
inventory: {
|
|
259
|
+
...baseProduct.inventory,
|
|
260
|
+
orderable: true,
|
|
261
|
+
stockLevel: 999
|
|
262
|
+
},
|
|
263
|
+
minOrderQuantity: 1,
|
|
264
|
+
stepQuantity: 1,
|
|
265
|
+
orderable: true,
|
|
266
|
+
rating: baseProduct.rating,
|
|
267
|
+
reviewCount: baseProduct.reviewCount
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// If no bonus products are available, still render but with original product
|
|
272
|
+
if (availableBonusProductIds.length === 0) {
|
|
273
|
+
return {
|
|
274
|
+
...baseProduct,
|
|
275
|
+
variationAttributes: baseProduct.variationAttributes,
|
|
276
|
+
variants: baseProduct.variants,
|
|
277
|
+
variationParams: baseProduct.variationParams,
|
|
278
|
+
selectedVariationAttributes: baseProduct.selectedVariationAttributes,
|
|
279
|
+
type: baseProduct.type,
|
|
280
|
+
inventory: {
|
|
281
|
+
...baseProduct.inventory,
|
|
282
|
+
orderable: true,
|
|
283
|
+
stockLevel: 999
|
|
284
|
+
},
|
|
285
|
+
minOrderQuantity: 1,
|
|
286
|
+
stepQuantity: 1,
|
|
287
|
+
orderable: true,
|
|
288
|
+
rating: baseProduct.rating,
|
|
289
|
+
reviewCount: baseProduct.reviewCount
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check if we should filter variants
|
|
294
|
+
// Only treat it as base product ID if it's NOT found in the variants array
|
|
295
|
+
const isBaseProductId = !baseProduct.variants?.some((v) => v.productId === baseProduct.id)
|
|
296
|
+
const hasBaseProductId =
|
|
297
|
+
isBaseProductId && availableBonusProductIds.includes(baseProduct.id)
|
|
298
|
+
const hasVariantIds = availableBonusProductIds.some((id) =>
|
|
299
|
+
baseProduct.variants?.some((v) => v.productId === id)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
let filteredVariants = baseProduct.variants || []
|
|
303
|
+
let filteredVariationAttributes = baseProduct.variationAttributes || []
|
|
304
|
+
|
|
305
|
+
// If we have specific variant IDs (not base product), filter variants and variation attributes
|
|
306
|
+
if (hasVariantIds && !hasBaseProductId) {
|
|
307
|
+
// Filter variants to only include available ones
|
|
308
|
+
filteredVariants =
|
|
309
|
+
baseProduct.variants?.filter((variant) =>
|
|
310
|
+
availableBonusProductIds.includes(variant.productId)
|
|
311
|
+
) || []
|
|
312
|
+
|
|
313
|
+
// Filter variation attribute values to only show available combinations
|
|
314
|
+
filteredVariationAttributes =
|
|
315
|
+
baseProduct.variationAttributes
|
|
316
|
+
?.map((attr) => {
|
|
317
|
+
const availableValues =
|
|
318
|
+
attr.values?.filter((value) => {
|
|
319
|
+
// Check if this value leads to an available variant
|
|
320
|
+
return filteredVariants.some(
|
|
321
|
+
(variant) => variant.variationValues?.[attr.id] === value.value
|
|
322
|
+
)
|
|
323
|
+
}) || []
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
...attr,
|
|
327
|
+
values: availableValues
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
.filter((attr) => attr.values.length > 0) || []
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// KEY FIX: Ensure the correct variant is pre-selected to prevent flash
|
|
334
|
+
// When we have only one available variant, make sure it's the selected one
|
|
335
|
+
let finalProduct = {
|
|
229
336
|
...baseProduct,
|
|
230
|
-
variationAttributes:
|
|
231
|
-
variants:
|
|
337
|
+
variationAttributes: filteredVariationAttributes,
|
|
338
|
+
variants: filteredVariants,
|
|
232
339
|
variationParams: baseProduct.variationParams,
|
|
233
340
|
selectedVariationAttributes: baseProduct.selectedVariationAttributes,
|
|
234
341
|
type: baseProduct.type,
|
|
235
|
-
// Ensure proper inventory and quantity defaults for bonus products
|
|
236
342
|
inventory: {
|
|
237
343
|
...baseProduct.inventory,
|
|
238
344
|
orderable: true,
|
|
239
|
-
stockLevel: 999
|
|
345
|
+
stockLevel: 999
|
|
240
346
|
},
|
|
241
347
|
minOrderQuantity: 1,
|
|
242
348
|
stepQuantity: 1,
|
|
243
|
-
// Ensure the product is orderable
|
|
244
349
|
orderable: true,
|
|
245
|
-
// Add review data for display
|
|
246
350
|
rating: baseProduct.rating,
|
|
247
351
|
reviewCount: baseProduct.reviewCount
|
|
248
352
|
}
|
|
249
|
-
|
|
353
|
+
|
|
354
|
+
// If we filtered to only one variant, ensure it's pre-selected
|
|
355
|
+
if (filteredVariants.length === 1 && filteredVariants[0].variationValues) {
|
|
356
|
+
const selectedVariant = filteredVariants[0]
|
|
357
|
+
finalProduct = {
|
|
358
|
+
...finalProduct,
|
|
359
|
+
// Override the product ID to be the selected variant
|
|
360
|
+
id: selectedVariant.productId,
|
|
361
|
+
// Pre-set the variation values to match the selected variant
|
|
362
|
+
selectedVariant,
|
|
363
|
+
variationValues: selectedVariant.variationValues
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return finalProduct
|
|
368
|
+
}, [productViewModalData.product, safeProduct, hasPromotionData, availableBonusProductIds])
|
|
250
369
|
|
|
251
370
|
// Calculate max order quantity for UI
|
|
252
371
|
const maxOrderQuantity = getRemainingBonusQuantity()
|
|
@@ -308,7 +427,8 @@ const BonusProductViewModal = ({
|
|
|
308
427
|
}
|
|
309
428
|
pb={productViewModalTheme.layout.body.paddingBottom}
|
|
310
429
|
>
|
|
311
|
-
{productViewModalData.isFetching && !productViewModalData.product
|
|
430
|
+
{(productViewModalData.isFetching && !productViewModalData.product) ||
|
|
431
|
+
!productToRender ? (
|
|
312
432
|
<Box p={8} textAlign="center">
|
|
313
433
|
<Text>Loading product details...</Text>
|
|
314
434
|
</Box>
|
|
@@ -348,7 +468,6 @@ BonusProductViewModal.propTypes = {
|
|
|
348
468
|
onClose: PropTypes.func.isRequired,
|
|
349
469
|
product: PropTypes.object,
|
|
350
470
|
isLoading: PropTypes.bool,
|
|
351
|
-
bonusDiscountLineItemId: PropTypes.string, // The 'id' from bonusDiscountLineItems
|
|
352
471
|
promotionId: PropTypes.string, // The promotion ID to filter promotions in PromoCallout
|
|
353
472
|
onReturnToSelection: PropTypes.func // Callback to return to SelectBonusProductModal
|
|
354
473
|
}
|
|
@@ -1019,3 +1019,170 @@ describe('BonusProductViewModal - Quantity Distribution Across Multiple BonusDis
|
|
|
1019
1019
|
})
|
|
1020
1020
|
})
|
|
1021
1021
|
})
|
|
1022
|
+
|
|
1023
|
+
describe('BonusProductViewModal - Variant Filtering Integration Tests', () => {
|
|
1024
|
+
const mockOnClose = jest.fn()
|
|
1025
|
+
|
|
1026
|
+
beforeEach(() => {
|
|
1027
|
+
jest.clearAllMocks()
|
|
1028
|
+
|
|
1029
|
+
// Setup default working mocks that don't conflict with our tests
|
|
1030
|
+
useShopperBasketsMutationHelper.mockReturnValue({
|
|
1031
|
+
addItemToNewOrExistingBasket: jest.fn()
|
|
1032
|
+
})
|
|
1033
|
+
|
|
1034
|
+
useBonusProductCounts.mockReturnValue({
|
|
1035
|
+
finalSelectedBonusItems: 1,
|
|
1036
|
+
finalMaxBonusItems: 3
|
|
1037
|
+
})
|
|
1038
|
+
|
|
1039
|
+
getRemainingAvailableBonusProductsForProduct.mockReturnValue(2)
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
test('renders modal successfully with variant filtering enabled', () => {
|
|
1043
|
+
// Basic integration test: Modal renders without errors with filtering logic
|
|
1044
|
+
const mockBasket = {
|
|
1045
|
+
bonusDiscountLineItems: [
|
|
1046
|
+
{
|
|
1047
|
+
promotionId: 'test-promo',
|
|
1048
|
+
bonusProducts: [
|
|
1049
|
+
{productId: '793775370033M'} // Specific variant
|
|
1050
|
+
]
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
productItems: []
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const mockProduct = {
|
|
1057
|
+
id: '793775370033',
|
|
1058
|
+
name: 'Test Product',
|
|
1059
|
+
variants: [
|
|
1060
|
+
{productId: '793775370033M', variationValues: {color: 'turquoise'}},
|
|
1061
|
+
{productId: '793775370033R', variationValues: {color: 'red'}}
|
|
1062
|
+
],
|
|
1063
|
+
variationAttributes: [
|
|
1064
|
+
{
|
|
1065
|
+
id: 'color',
|
|
1066
|
+
values: [
|
|
1067
|
+
{value: 'turquoise', name: 'Turquoise'},
|
|
1068
|
+
{value: 'red', name: 'Red'}
|
|
1069
|
+
]
|
|
1070
|
+
}
|
|
1071
|
+
]
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
useCurrentBasket.mockReturnValue({data: mockBasket, derivedData: {totalItems: 0}})
|
|
1075
|
+
useProductViewModal.mockReturnValue({product: mockProduct, isFetching: false})
|
|
1076
|
+
|
|
1077
|
+
expect(() => {
|
|
1078
|
+
renderWithProviders(
|
|
1079
|
+
<BonusProductViewModal
|
|
1080
|
+
product={mockProduct}
|
|
1081
|
+
isOpen={true}
|
|
1082
|
+
onClose={mockOnClose}
|
|
1083
|
+
promotionId="test-promo"
|
|
1084
|
+
/>
|
|
1085
|
+
)
|
|
1086
|
+
}).not.toThrow()
|
|
1087
|
+
|
|
1088
|
+
// Verify modal renders
|
|
1089
|
+
expect(screen.getByTestId('bonus-product-view-modal')).toBeInTheDocument()
|
|
1090
|
+
})
|
|
1091
|
+
|
|
1092
|
+
test('handles edge cases without errors', () => {
|
|
1093
|
+
// Edge case test: No bonus products available
|
|
1094
|
+
const mockBasket = {
|
|
1095
|
+
bonusDiscountLineItems: [
|
|
1096
|
+
{
|
|
1097
|
+
promotionId: 'different-promo',
|
|
1098
|
+
bonusProducts: []
|
|
1099
|
+
}
|
|
1100
|
+
],
|
|
1101
|
+
productItems: []
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const mockProduct = {
|
|
1105
|
+
id: '793775370033',
|
|
1106
|
+
name: 'Test Product',
|
|
1107
|
+
variants: [],
|
|
1108
|
+
variationAttributes: []
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
useCurrentBasket.mockReturnValue({data: mockBasket, derivedData: {totalItems: 0}})
|
|
1112
|
+
useProductViewModal.mockReturnValue({product: mockProduct, isFetching: false})
|
|
1113
|
+
|
|
1114
|
+
expect(() => {
|
|
1115
|
+
renderWithProviders(
|
|
1116
|
+
<BonusProductViewModal
|
|
1117
|
+
product={mockProduct}
|
|
1118
|
+
isOpen={true}
|
|
1119
|
+
onClose={mockOnClose}
|
|
1120
|
+
promotionId="test-promo"
|
|
1121
|
+
/>
|
|
1122
|
+
)
|
|
1123
|
+
}).not.toThrow()
|
|
1124
|
+
|
|
1125
|
+
expect(screen.getByTestId('bonus-product-view-modal')).toBeInTheDocument()
|
|
1126
|
+
})
|
|
1127
|
+
|
|
1128
|
+
test('handles missing bonus data gracefully', () => {
|
|
1129
|
+
// Edge case test: No bonusDiscountLineItems
|
|
1130
|
+
const mockBasket = {productItems: []}
|
|
1131
|
+
const mockProduct = {id: '123', name: 'Test'}
|
|
1132
|
+
|
|
1133
|
+
useCurrentBasket.mockReturnValue({data: mockBasket, derivedData: {totalItems: 0}})
|
|
1134
|
+
useProductViewModal.mockReturnValue({product: mockProduct, isFetching: false})
|
|
1135
|
+
|
|
1136
|
+
expect(() => {
|
|
1137
|
+
renderWithProviders(
|
|
1138
|
+
<BonusProductViewModal
|
|
1139
|
+
product={mockProduct}
|
|
1140
|
+
isOpen={true}
|
|
1141
|
+
onClose={mockOnClose}
|
|
1142
|
+
promotionId="test-promo"
|
|
1143
|
+
/>
|
|
1144
|
+
)
|
|
1145
|
+
}).not.toThrow()
|
|
1146
|
+
})
|
|
1147
|
+
|
|
1148
|
+
test('filtering logic processes complex variant scenarios', () => {
|
|
1149
|
+
// Complex scenario test: Mixed base and variant IDs
|
|
1150
|
+
const mockBasket = {
|
|
1151
|
+
bonusDiscountLineItems: [
|
|
1152
|
+
{
|
|
1153
|
+
promotionId: 'test-promo',
|
|
1154
|
+
bonusProducts: [
|
|
1155
|
+
{productId: '793775370033'}, // Base product
|
|
1156
|
+
{productId: '793775370033M'}, // Variant
|
|
1157
|
+
{productId: '793775370033R'} // Another variant
|
|
1158
|
+
]
|
|
1159
|
+
}
|
|
1160
|
+
]
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const mockProduct = {
|
|
1164
|
+
id: '793775370033',
|
|
1165
|
+
variants: [
|
|
1166
|
+
{productId: '793775370033M', variationValues: {color: 'turquoise'}},
|
|
1167
|
+
{productId: '793775370033R', variationValues: {color: 'red'}},
|
|
1168
|
+
{productId: '793775370033B', variationValues: {color: 'blue'}}
|
|
1169
|
+
],
|
|
1170
|
+
variationAttributes: [{id: 'color', values: []}]
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
useCurrentBasket.mockReturnValue({data: mockBasket, derivedData: {totalItems: 0}})
|
|
1174
|
+
useProductViewModal.mockReturnValue({product: mockProduct, isFetching: false})
|
|
1175
|
+
|
|
1176
|
+
// Should handle complex filtering without errors
|
|
1177
|
+
expect(() => {
|
|
1178
|
+
renderWithProviders(
|
|
1179
|
+
<BonusProductViewModal
|
|
1180
|
+
product={mockProduct}
|
|
1181
|
+
isOpen={true}
|
|
1182
|
+
onClose={mockOnClose}
|
|
1183
|
+
promotionId="test-promo"
|
|
1184
|
+
/>
|
|
1185
|
+
)
|
|
1186
|
+
}).not.toThrow()
|
|
1187
|
+
})
|
|
1188
|
+
})
|
|
@@ -74,10 +74,14 @@ const ProductItem = ({
|
|
|
74
74
|
</Box>
|
|
75
75
|
</HideOnDesktop>
|
|
76
76
|
</Stack>
|
|
77
|
-
{deliveryActions &&
|
|
77
|
+
{deliveryActions && !product.bonusProductLineItem && (
|
|
78
|
+
<HideOnMobile>{deliveryActions}</HideOnMobile>
|
|
79
|
+
)}
|
|
78
80
|
</Flex>
|
|
79
81
|
|
|
80
|
-
{deliveryActions &&
|
|
82
|
+
{deliveryActions && !product.bonusProductLineItem && (
|
|
83
|
+
<HideOnDesktop>{deliveryActions}</HideOnDesktop>
|
|
84
|
+
)}
|
|
81
85
|
|
|
82
86
|
<Flex align="flex-end" justify="space-between">
|
|
83
87
|
<Stack spacing={1}>
|
|
@@ -103,4 +103,41 @@ describe('ProductItem Component', () => {
|
|
|
103
103
|
expect(screen.getByText(/Quantity:/i)).toBeInTheDocument()
|
|
104
104
|
expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument()
|
|
105
105
|
})
|
|
106
|
+
|
|
107
|
+
test('does not render delivery actions for bonus products', () => {
|
|
108
|
+
renderWithProviders(
|
|
109
|
+
<MockedComponent
|
|
110
|
+
product={mockBonusProduct}
|
|
111
|
+
deliveryActions={<button>Delivery Action</button>}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
expect(screen.queryByText(/Delivery Action/i)).not.toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('renders delivery actions for regular products but not bonus products', () => {
|
|
119
|
+
// Test regular product first
|
|
120
|
+
const {unmount} = renderWithProviders(
|
|
121
|
+
<MockedComponent
|
|
122
|
+
product={mockProduct}
|
|
123
|
+
deliveryActions={<button>Delivery Action</button>}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
// Regular product should show delivery actions (appears twice - mobile and desktop)
|
|
128
|
+
expect(screen.getAllByText(/Delivery Action/i)).toHaveLength(2)
|
|
129
|
+
|
|
130
|
+
// Cleanup completely
|
|
131
|
+
unmount()
|
|
132
|
+
|
|
133
|
+
// Test bonus product with fresh render
|
|
134
|
+
renderWithProviders(
|
|
135
|
+
<MockedComponent
|
|
136
|
+
product={mockBonusProduct}
|
|
137
|
+
deliveryActions={<button>Delivery Action</button>}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
expect(screen.queryAllByText(/Delivery Action/i)).toHaveLength(0)
|
|
142
|
+
})
|
|
106
143
|
})
|
|
@@ -42,6 +42,12 @@ import {
|
|
|
42
42
|
} from '@salesforce/retail-react-app/app/utils/url'
|
|
43
43
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
44
44
|
import {getCommerceAgentConfig} from '@salesforce/retail-react-app/app/utils/config-utils'
|
|
45
|
+
import {useUsid} from '@salesforce/commerce-sdk-react'
|
|
46
|
+
import {useLocation} from 'react-router-dom'
|
|
47
|
+
import useRefreshToken from '@salesforce/retail-react-app/app/hooks/use-refresh-token'
|
|
48
|
+
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
|
|
49
|
+
import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin'
|
|
50
|
+
import {normalizeLocaleToSalesforce} from '@salesforce/retail-react-app/app/hooks/use-miaw'
|
|
45
51
|
|
|
46
52
|
const onClient = typeof window !== 'undefined'
|
|
47
53
|
|
|
@@ -106,6 +112,15 @@ const formatSuggestions = (searchSuggestions) => {
|
|
|
106
112
|
*/
|
|
107
113
|
const Search = (props) => {
|
|
108
114
|
const config = getConfig()
|
|
115
|
+
|
|
116
|
+
// Add new hooks for chat functionality
|
|
117
|
+
const {locale, siteId, commerceOrgId, buildUrl} = useMultiSite()
|
|
118
|
+
const {usid} = useUsid()
|
|
119
|
+
const refreshToken = useRefreshToken()
|
|
120
|
+
const location = useLocation()
|
|
121
|
+
const appOrigin = useAppOrigin()
|
|
122
|
+
const sfLanguage = normalizeLocaleToSalesforce(locale.id)
|
|
123
|
+
|
|
109
124
|
const askAgentOnSearchEnabled = useMemo(() => {
|
|
110
125
|
const {enabled, askAgentOnSearch} = getCommerceAgentConfig()
|
|
111
126
|
return isAskAgentOnSearchEnabled(enabled, askAgentOnSearch)
|
|
@@ -183,6 +198,26 @@ const Search = (props) => {
|
|
|
183
198
|
setIsOpen(false)
|
|
184
199
|
}
|
|
185
200
|
|
|
201
|
+
// Function to set pre-chat fields only when launching a new chat session
|
|
202
|
+
const setPrechatFieldsForNewSession = () => {
|
|
203
|
+
// Only set pre-chat fields if this is a new chat launch (not already launched)
|
|
204
|
+
if (!miawChatRef.current.newChatLaunched) {
|
|
205
|
+
if (window.embeddedservice_bootstrap?.prechatAPI) {
|
|
206
|
+
window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
|
|
207
|
+
SiteId: siteId,
|
|
208
|
+
Locale: locale.id,
|
|
209
|
+
OrganizationId: commerceOrgId,
|
|
210
|
+
UsId: usid,
|
|
211
|
+
IsCartMgmtSupported: 'true',
|
|
212
|
+
RefreshToken: refreshToken,
|
|
213
|
+
Currency: locale.preferredCurrency,
|
|
214
|
+
Language: sfLanguage,
|
|
215
|
+
DomainUrl: `${appOrigin}${buildUrl(location.pathname)}`
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
186
221
|
useEffect(() => {
|
|
187
222
|
const handleEmbeddedMessageSent = (e) => {
|
|
188
223
|
if (!miawChatRef.current.hasFired && miawChatRef.current.newChatLaunched) {
|
|
@@ -209,6 +244,9 @@ const Search = (props) => {
|
|
|
209
244
|
}
|
|
210
245
|
}, [])
|
|
211
246
|
const launchChat = () => {
|
|
247
|
+
// Set pre-chat fields only for new sessions
|
|
248
|
+
setPrechatFieldsForNewSession()
|
|
249
|
+
|
|
212
250
|
if (window.embeddedservice_bootstrap?.settings) {
|
|
213
251
|
window.embeddedservice_bootstrap.settings.disableStreamingResponses = true
|
|
214
252
|
window.embeddedservice_bootstrap.settings.enableUserInputForConversationWithBot = false
|