@salesforce/retail-react-app 10.1.0-nightly-20260708084921 → 10.1.0-preview.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 +2 -1
- package/app/components/order-tracking/index.jsx +6 -0
- package/app/components/order-tracking/index.test.js +27 -0
- package/app/components/shopper-agent/index.jsx +207 -43
- package/app/components/shopper-agent/index.test.js +529 -89
- package/app/components/shopper-agent/token-bridge.js +318 -0
- package/app/components/shopper-agent/token-bridge.test.js +759 -0
- package/app/hooks/use-miaw.js +0 -3
- package/app/hooks/use-miaw.test.js +1 -3
- package/app/pages/account/order-detail.jsx +11 -4
- package/app/ssr.js +13 -0
- package/app/static/translations/compiled/en-GB.json +6 -0
- package/app/static/translations/compiled/en-US.json +6 -0
- package/app/static/translations/compiled/en-XA.json +14 -0
- package/app/utils/shopper-agent-utils.js +37 -0
- package/app/utils/shopper-agent-utils.test.js +59 -0
- package/docs/README-ORDER-MANAGEMENT.md +33 -0
- package/package.json +6 -6
- package/translations/en-GB.json +3 -0
- package/translations/en-US.json +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
## v10.1.0-
|
|
1
|
+
## v10.1.0-preview.0
|
|
2
|
+
- [Feature] Enable customer context for Shopper Agent: on conversation start, call Core's Token Bridge via a same-origin PWA Kit proxy that forwards the shopper's SLAS session (access token in the body for non-HttpOnly mode; both tokens read server-side from `cc-at_*` / `cc-nx_*` cookies in HttpOnly mode) and reset the embedded messaging session on guest ↔ registered transitions so the agent never picks up a stale shopper identity.
|
|
2
3
|
- [Bugfix] Add explicit `rel="noopener noreferrer"` to external carrier tracking links so the order page URL doesn't leak as the `Referer` (Chakra `isExternal` alone only emits `noopener`).
|
|
3
4
|
- Bump `vendor.js` bundle-size budget from 397 kB to 398 kB to accommodate the `commerce-sdk-isomorphic` 5.4.0 upgrade.
|
|
4
5
|
- [Feature] Add item-level order returns on the order detail page. Registered shoppers who own an OMS-managed order can return eligible items (OMS-driven via `quantityAvailableToReturn`) through a modal with per-item quantity and reason selection, inline success/error feedback, and a status badge that reflects return progress (including partially-returned multi-unit lines). [#3904](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3904)
|
|
@@ -53,6 +53,12 @@ const OrderTracking = ({
|
|
|
53
53
|
if (!value) return null
|
|
54
54
|
const date = new Date(value)
|
|
55
55
|
if (isNaN(date.getTime())) return null
|
|
56
|
+
// Defensive guard against an epoch-era SOM sentinel: a truthy date string like
|
|
57
|
+
// "1970-01-01T00:00:00Z" parses to a valid Date and would render "1 Jan 1970".
|
|
58
|
+
// (The `!value` guard above already covers null/0/""; JS parses "0" as year 2000,
|
|
59
|
+
// not the epoch, so this specifically catches truthy 1970-era strings.) SOM does
|
|
60
|
+
// not currently send such a sentinel — this is cheap insurance if it ever does.
|
|
61
|
+
if (date.getUTCFullYear() <= 1970) return null
|
|
56
62
|
return formatDate(date, {year: 'numeric', month: 'short', day: 'numeric'})
|
|
57
63
|
}
|
|
58
64
|
|
|
@@ -154,4 +154,31 @@ describe('OrderTracking component', () => {
|
|
|
154
154
|
// The rest of the block still renders.
|
|
155
155
|
expect(screen.getByText('FedEx Ground')).toBeInTheDocument()
|
|
156
156
|
})
|
|
157
|
+
|
|
158
|
+
test('omits the date line for a truthy epoch-era sentinel string (no "1 Jan 1970")', () => {
|
|
159
|
+
// A truthy string like "1970-01-01T00:00:00Z" parses to a *valid* Date (unlike
|
|
160
|
+
// null/"" which the falsy check already handles), so without the epoch-year guard
|
|
161
|
+
// it would render "1 Jan 1970" to the shopper. SOM does not currently send such a
|
|
162
|
+
// sentinel, but the guard is cheap insurance if it ever does.
|
|
163
|
+
renderWithProviders(
|
|
164
|
+
<OrderTracking
|
|
165
|
+
{...baseProps}
|
|
166
|
+
expectedDeliveryDate="1970-01-01T00:00:00Z"
|
|
167
|
+
actualDeliveryDate="1970-01-01T00:00:00Z"
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
expect(screen.queryByText(/Expected delivery/i)).not.toBeInTheDocument()
|
|
171
|
+
expect(screen.queryByText(/Delivered/i)).not.toBeInTheDocument()
|
|
172
|
+
expect(screen.queryByText(/1970/)).not.toBeInTheDocument()
|
|
173
|
+
// The rest of the block still renders.
|
|
174
|
+
expect(screen.getByText('FedEx Ground')).toBeInTheDocument()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('still renders a legitimate recent date (the epoch guard does not over-suppress)', () => {
|
|
178
|
+
const {container} = renderWithProviders(
|
|
179
|
+
<OrderTracking {...baseProps} expectedDeliveryDate="2026-06-12T00:00:00.000Z" />
|
|
180
|
+
)
|
|
181
|
+
expect(screen.getByText(/Expected delivery/i)).toBeInTheDocument()
|
|
182
|
+
expect(container.textContent).toMatch(/Expected delivery:\s*\d{1,2} Jun 2026/)
|
|
183
|
+
})
|
|
157
184
|
})
|
|
@@ -4,21 +4,40 @@
|
|
|
4
4
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
import React, {useEffect, useRef, useMemo} from 'react'
|
|
9
|
+
import {defineMessage, useIntl} from 'react-intl'
|
|
8
10
|
import useScript from '@salesforce/retail-react-app/app/hooks/use-script'
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
useAccessToken,
|
|
13
|
+
useConfig,
|
|
14
|
+
useConfigurations,
|
|
15
|
+
useCustomerType,
|
|
16
|
+
useUsid
|
|
17
|
+
} from '@salesforce/commerce-sdk-react'
|
|
10
18
|
import PropTypes from 'prop-types'
|
|
11
19
|
import {useTheme} from '@salesforce/retail-react-app/app/components/shared/ui'
|
|
12
20
|
import useMiaw, {normalizeLocaleToSalesforce} from '@salesforce/retail-react-app/app/hooks/use-miaw'
|
|
13
21
|
import useCommerceClientMessaging from '@salesforce/retail-react-app/app/hooks/use-commerce-client-messaging'
|
|
14
22
|
import {DEFAULT_COMMERCE_CLIENT_ELEMENT_ID} from '@salesforce/retail-react-app/app/constants'
|
|
15
|
-
import useRefreshToken from '@salesforce/retail-react-app/app/hooks/use-refresh-token'
|
|
16
23
|
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
|
|
17
24
|
import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin'
|
|
18
|
-
import {
|
|
25
|
+
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
|
|
26
|
+
import {
|
|
27
|
+
resetEmbeddedMessagingForCommerceSessionChange,
|
|
28
|
+
validateCommerceClientAgentSettings
|
|
29
|
+
} from '@salesforce/retail-react-app/app/utils/shopper-agent-utils'
|
|
30
|
+
import {callTokenBridge} from '@salesforce/retail-react-app/app/components/shopper-agent/token-bridge'
|
|
19
31
|
|
|
20
32
|
const onClient = typeof window !== 'undefined'
|
|
21
33
|
|
|
34
|
+
const HTTP_OK = 200
|
|
35
|
+
|
|
36
|
+
const SESSION_INIT_ERROR_MESSAGE = defineMessage({
|
|
37
|
+
id: 'shopper_agent.error.session_init_failed',
|
|
38
|
+
defaultMessage: 'Something went wrong. Try again.'
|
|
39
|
+
})
|
|
40
|
+
|
|
22
41
|
/**
|
|
23
42
|
* Validates that a URL is from a trusted Salesforce domain.
|
|
24
43
|
*
|
|
@@ -125,9 +144,11 @@ const isEnabled = (enabled) => {
|
|
|
125
144
|
* Key responsibilities:
|
|
126
145
|
* - Loads the embedded messaging script using useScript hook
|
|
127
146
|
* - Initializes the MIAW service using useMiaw hook
|
|
128
|
-
* - Sets up prechat fields with current locale, currency, and user context
|
|
147
|
+
* - Sets up prechat fields with current locale, currency, and user context on embedded messaging ready
|
|
148
|
+
* - Calls Core's Token Bridge proxy when a conversation starts (`onEmbeddedMessagingConversationStarted`)
|
|
129
149
|
* - Manages event listeners for messaging lifecycle events
|
|
130
150
|
* - Handles z-index management for maximized chat windows
|
|
151
|
+
* - On guest ↔ registered Commerce session transitions, resets MIAW (FAB) so shoppers start a fresh agent session
|
|
131
152
|
* - Cleans up resources on unmount
|
|
132
153
|
*
|
|
133
154
|
* @param {Object} props - Component props
|
|
@@ -151,19 +172,20 @@ const isEnabled = (enabled) => {
|
|
|
151
172
|
* @see {@link useScript} - For script loading functionality
|
|
152
173
|
* @see {@link useMiaw} - For MIAW initialization
|
|
153
174
|
* @see {@link useMultiSite} - For locale and currency information
|
|
154
|
-
* @see {@link useRefreshToken} - For authentication token
|
|
155
175
|
* @see {@link useUsid} - For user session identifier
|
|
156
176
|
*/
|
|
157
177
|
const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
|
|
158
178
|
// Theme hook for z-index management
|
|
159
179
|
const theme = useTheme()
|
|
160
180
|
|
|
181
|
+
const {formatMessage} = useIntl()
|
|
182
|
+
const toast = useToast()
|
|
183
|
+
const toastRef = useRef(toast)
|
|
184
|
+
toastRef.current = toast
|
|
185
|
+
|
|
161
186
|
// Multi-site hook for locale and currency information
|
|
162
187
|
const {locale} = useMultiSite()
|
|
163
188
|
|
|
164
|
-
// Authentication hook for refresh token
|
|
165
|
-
const refreshToken = useRefreshToken()
|
|
166
|
-
|
|
167
189
|
// Normalize locale to Salesforce language format
|
|
168
190
|
const sfLanguage = normalizeLocaleToSalesforce(locale.id)
|
|
169
191
|
|
|
@@ -183,6 +205,60 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
|
|
|
183
205
|
|
|
184
206
|
// User session identifier hook
|
|
185
207
|
const {usid} = useUsid()
|
|
208
|
+
const {customerType} = useCustomerType()
|
|
209
|
+
const {organizationId, siteId: configSiteId} = useConfig()
|
|
210
|
+
|
|
211
|
+
// Fetch my_domain from Shopper Configurations API
|
|
212
|
+
const {data: configurationsData} = useConfigurations({})
|
|
213
|
+
const myDomain = configurationsData?.configurations?.find(
|
|
214
|
+
(config) => config.configurationType === 'globalConfiguration' && config.id === 'my_domain'
|
|
215
|
+
)?.value
|
|
216
|
+
|
|
217
|
+
// SLAS access token — needed to call Core's Token Bridge directly.
|
|
218
|
+
const {getTokenWhenReady} = useAccessToken()
|
|
219
|
+
const getTokenWhenReadyRef = useRef(getTokenWhenReady)
|
|
220
|
+
getTokenWhenReadyRef.current = getTokenWhenReady
|
|
221
|
+
|
|
222
|
+
const prevCommerceCustomerTypeRef = useRef(undefined)
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Reset embedded messaging whenever customerType changes (login, logout, registration).
|
|
226
|
+
* This ensures the chat context is cleared when user authentication state changes.
|
|
227
|
+
*/
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
const prev = prevCommerceCustomerTypeRef.current
|
|
230
|
+
prevCommerceCustomerTypeRef.current = customerType
|
|
231
|
+
|
|
232
|
+
// Skip initial mount
|
|
233
|
+
if (prev === undefined) {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Reset on any customerType change (login, logout, register)
|
|
238
|
+
if (prev !== customerType) {
|
|
239
|
+
resetEmbeddedMessagingForCommerceSessionChange()
|
|
240
|
+
}
|
|
241
|
+
}, [customerType])
|
|
242
|
+
|
|
243
|
+
const formatMessageRef = useRef(formatMessage)
|
|
244
|
+
formatMessageRef.current = formatMessage
|
|
245
|
+
|
|
246
|
+
/** Latest values for embedded messaging handlers (stable window listeners). */
|
|
247
|
+
const embeddedLifecycleRef = useRef({})
|
|
248
|
+
embeddedLifecycleRef.current = {
|
|
249
|
+
siteId,
|
|
250
|
+
localeId: locale.id,
|
|
251
|
+
preferredCurrency: locale.preferredCurrency,
|
|
252
|
+
commerceOrgId,
|
|
253
|
+
usid,
|
|
254
|
+
sfLanguage,
|
|
255
|
+
domainUrl,
|
|
256
|
+
organizationId,
|
|
257
|
+
configSiteId,
|
|
258
|
+
myDomain
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const lastConversationSessionInitRef = useRef(null)
|
|
186
262
|
|
|
187
263
|
/**
|
|
188
264
|
* Retrieves conversation context data based on configuration.
|
|
@@ -284,30 +360,116 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
|
|
|
284
360
|
}
|
|
285
361
|
}, [])
|
|
286
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Register embedded messaging window listeners once. Handlers read latest values from refs so we do not
|
|
365
|
+
* remove/re-add listeners when auth or config updates (which would fight the widget and loop loading).
|
|
366
|
+
*/
|
|
287
367
|
useEffect(() => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
SiteId: siteId,
|
|
296
|
-
Locale:
|
|
297
|
-
OrganizationId: commerceOrgId,
|
|
298
|
-
UsId: usid,
|
|
368
|
+
const applyHiddenPrechatFields = () => {
|
|
369
|
+
const bootstrap = window.embeddedservice_bootstrap
|
|
370
|
+
if (!bootstrap?.prechatAPI?.setHiddenPrechatFields) {
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
const s = embeddedLifecycleRef.current
|
|
374
|
+
bootstrap.prechatAPI.setHiddenPrechatFields({
|
|
375
|
+
SiteId: s.siteId,
|
|
376
|
+
Locale: s.localeId,
|
|
377
|
+
OrganizationId: s.commerceOrgId,
|
|
378
|
+
UsId: s.usid,
|
|
299
379
|
IsCartMgmtSupported: 'true',
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
DomainUrl: domainUrl
|
|
380
|
+
Currency: s.preferredCurrency,
|
|
381
|
+
Language: s.sfLanguage,
|
|
382
|
+
DomainUrl: s.domainUrl
|
|
304
383
|
})
|
|
305
384
|
}
|
|
306
385
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
386
|
+
const handleEmbeddedMessagingReady = () => {
|
|
387
|
+
applyHiddenPrechatFields()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Reset the embedded messaging session and surface a localized error
|
|
391
|
+
// toast. Called from every failure branch of the conversation-started
|
|
392
|
+
// flow so the shopper sees consistent feedback and the next attempt
|
|
393
|
+
// starts from a clean state.
|
|
394
|
+
const failSessionInit = () => {
|
|
395
|
+
resetEmbeddedMessagingForCommerceSessionChange()
|
|
396
|
+
toastRef.current({
|
|
397
|
+
title: formatMessageRef.current(SESSION_INIT_ERROR_MESSAGE),
|
|
398
|
+
status: 'error'
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const handleEmbeddedMessagingConversationStarted = (event) => {
|
|
403
|
+
const {
|
|
404
|
+
organizationId: orgId,
|
|
405
|
+
configSiteId: sid,
|
|
406
|
+
myDomain: domain
|
|
407
|
+
} = embeddedLifecycleRef.current
|
|
408
|
+
|
|
409
|
+
if (!orgId || !sid) return
|
|
410
|
+
|
|
411
|
+
if (!domain) return
|
|
412
|
+
|
|
413
|
+
// Prevents refiring of the event if already call has been done
|
|
414
|
+
const conversationId = event?.detail?.conversationId
|
|
415
|
+
if (!conversationId || typeof conversationId !== 'string' || !conversationId.trim()) {
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
const normalizedConversationId = conversationId.trim()
|
|
419
|
+
if (lastConversationSessionInitRef.current === normalizedConversationId) return
|
|
420
|
+
lastConversationSessionInitRef.current = normalizedConversationId
|
|
421
|
+
|
|
422
|
+
const getAuthLinkKey =
|
|
423
|
+
window.embeddedservice_bootstrap?.userVerificationAPI?.getAuthLinkKey
|
|
424
|
+
if (typeof getAuthLinkKey !== 'function') {
|
|
425
|
+
console.error('Shopper Agent: getAuthLinkKey is not available')
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
getAuthLinkKey()
|
|
430
|
+
.then(async (authLinkKey) => {
|
|
431
|
+
// Direct callout to Core's Token Bridge via the same-origin
|
|
432
|
+
// PWA Kit proxy. Replaces the prior postSessionInit SCAPI call.
|
|
433
|
+
try {
|
|
434
|
+
// Check if HttpOnly mode is enabled by reading the flag directly
|
|
435
|
+
// (same source as CommerceApiProvider's enableHttpOnlySessionCookies)
|
|
436
|
+
const isHttpOnly =
|
|
437
|
+
typeof window !== 'undefined'
|
|
438
|
+
? window.__MRT_ENABLE_HTTPONLY_SESSION_COOKIES__ === 'true'
|
|
439
|
+
: process.env.MRT_ENABLE_HTTPONLY_SESSION_COOKIES === 'true'
|
|
440
|
+
|
|
441
|
+
// In non-HttpOnly mode, fetch the access token from localStorage
|
|
442
|
+
const slasAccessToken = isHttpOnly
|
|
443
|
+
? undefined
|
|
444
|
+
: await getTokenWhenReadyRef.current()
|
|
445
|
+
|
|
446
|
+
const result = await callTokenBridge({
|
|
447
|
+
authLinkKey,
|
|
448
|
+
// Only send access token in non-HttpOnly mode (from localStorage)
|
|
449
|
+
// In HttpOnly mode, server reads from cc-at_{siteId} cookie
|
|
450
|
+
slasAccessToken,
|
|
451
|
+
siteId: sid
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
if (result.status !== HTTP_OK) {
|
|
455
|
+
const errorCode = result.body?.error || `HTTP_${result.status}`
|
|
456
|
+
console.error('Token Bridge failed', {
|
|
457
|
+
status: result.status,
|
|
458
|
+
error: errorCode
|
|
459
|
+
})
|
|
460
|
+
failSessionInit()
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error('Token Bridge threw', error)
|
|
464
|
+
failSessionInit()
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
.catch((error) => {
|
|
468
|
+
console.error('Shopper Agent: getAuthLinkKey failed', error)
|
|
469
|
+
failSessionInit()
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
311
473
|
const handleEmbeddedMessagingWindowMaximized = () => {
|
|
312
474
|
const zIndex = theme.zIndices.sticky + 1
|
|
313
475
|
const embeddedMessagingFrame = document.body.querySelector(
|
|
@@ -318,31 +480,31 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
|
|
|
318
480
|
}
|
|
319
481
|
}
|
|
320
482
|
|
|
321
|
-
// Set up event listeners for messaging lifecycle events
|
|
322
483
|
window.addEventListener('onEmbeddedMessagingReady', handleEmbeddedMessagingReady)
|
|
484
|
+
window.addEventListener(
|
|
485
|
+
'onEmbeddedMessagingConversationStarted',
|
|
486
|
+
handleEmbeddedMessagingConversationStarted
|
|
487
|
+
)
|
|
323
488
|
window.addEventListener(
|
|
324
489
|
'onEmbeddedMessagingWindowMaximized',
|
|
325
490
|
handleEmbeddedMessagingWindowMaximized
|
|
326
491
|
)
|
|
327
492
|
|
|
328
|
-
// Cleanup function to remove event listeners on unmount
|
|
329
493
|
return () => {
|
|
330
494
|
window.removeEventListener('onEmbeddedMessagingReady', handleEmbeddedMessagingReady)
|
|
495
|
+
window.removeEventListener(
|
|
496
|
+
'onEmbeddedMessagingConversationStarted',
|
|
497
|
+
handleEmbeddedMessagingConversationStarted
|
|
498
|
+
)
|
|
331
499
|
window.removeEventListener(
|
|
332
500
|
'onEmbeddedMessagingWindowMaximized',
|
|
333
501
|
handleEmbeddedMessagingWindowMaximized
|
|
334
502
|
)
|
|
335
503
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
commerceOrgId,
|
|
341
|
-
usid,
|
|
342
|
-
theme.zIndices.sticky,
|
|
343
|
-
refreshToken,
|
|
344
|
-
domainUrl
|
|
345
|
-
])
|
|
504
|
+
// All dynamic config/auth values are read from embeddedLifecycleRef inside
|
|
505
|
+
// the handlers, so we only re-register listeners when the z-index token
|
|
506
|
+
// (used directly in the maximize handler's DOM mutation) changes.
|
|
507
|
+
}, [theme.zIndices.sticky])
|
|
346
508
|
|
|
347
509
|
// Load the embedded messaging script asynchronously
|
|
348
510
|
const scriptLoadStatus = useScript(scriptSourceUrl)
|
|
@@ -355,7 +517,6 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
|
|
|
355
517
|
embeddedServiceEndpoint,
|
|
356
518
|
scrt2Url,
|
|
357
519
|
locale.id,
|
|
358
|
-
refreshToken,
|
|
359
520
|
enableAgentFromFloatingButton
|
|
360
521
|
)
|
|
361
522
|
|
|
@@ -579,8 +740,11 @@ const ShopperAgent = ({commerceAgentConfiguration, basketDoneLoading}) => {
|
|
|
579
740
|
// Build the current domain URL
|
|
580
741
|
const domainUrl = `${appOrigin}${buildUrl('')}`
|
|
581
742
|
|
|
582
|
-
//
|
|
583
|
-
|
|
743
|
+
// Fetch configurations to ensure myDomain is resolved before rendering
|
|
744
|
+
const {isLoading: isConfigurationsLoading} = useConfigurations({})
|
|
745
|
+
|
|
746
|
+
// Only render when the agent is enabled (client-side), the basket has loaded, and configurations API has completed.
|
|
747
|
+
if (!isShopperAgentEnabled || !basketDoneLoading || isConfigurationsLoading) {
|
|
584
748
|
return null
|
|
585
749
|
}
|
|
586
750
|
|