Skip to content

Commit 20f86ba

Browse files
rebase develop branch
1 parent b8283b3 commit 20f86ba

File tree

12 files changed

+1436
-1061
lines changed

12 files changed

+1436
-1061
lines changed

packages/pwa-kit-dev/src/ssr/server/public-path.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import {bundleBasePath} from '@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths'
1818

1919
/* global __webpack_public_path__: writable */
20-
// eslint-disable-next-line no-undef
21-
const webpackPublicPath = `${bundleBasePath}/development/`
22-
// eslint-disable-next-line no-undef
23-
__webpack_public_path__ = webpackPublicPath
20+
// This global variable is used by webpack for HMR (Hot Module Replacement)
21+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
22+
__webpack_public_path__ = `${bundleBasePath}/development/`

packages/pwa-kit-runtime/CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
- Only allow requests for `/shopper/auth/` through the SLAS private client proxy. Also stop the proxy from swallowing SLAS errors [#2608](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2608)
44

55
## v3.10.0 (May 22, 2025)
6-
76
- Add referrer policy `strict-origin-when-cross-origin` and CSP relaxations for MIAW scripts [#2416](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2416)
87

98
## v3.9.2 (Mar 08, 2025)

packages/pwa-kit-runtime/package-lock.json

Lines changed: 847 additions & 533 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/template-retail-react-app/app/components/_app/index.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ import {
7979
} from '@salesforce/retail-react-app/app/constants'
8080

8181
import Seo from '@salesforce/retail-react-app/app/components/seo'
82-
import {Helmet} from 'react-helmet'
8382
import ShopperAgent from '@salesforce/retail-react-app/app/components/shopper-agent'
8483
import {getPathWithLocale} from '@salesforce/retail-react-app/app/utils/url'
8584

packages/template-retail-react-app/app/components/quantity-picker/index.test.jsx

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,30 @@ const MINUS = '\u2212' // HTML `−`, not the same as '-' (\u002d)
1919

2020
describe('QuantityPicker', () => {
2121
test('clicking plus increments value', async () => {
22-
const user = userEvent.setup()
2322
renderWithProviders(<MockComponent />)
2423
const input = screen.getByRole('spinbutton')
2524
const button = screen.getByText('+')
26-
await user.click(button)
25+
await userEvent.click(button)
2726
expect(input.value).toBe('6')
2827
})
2928
test('clicking minus decrements value', async () => {
30-
const user = userEvent.setup()
3129
renderWithProviders(<MockComponent />)
3230
const input = screen.getByRole('spinbutton')
3331
const button = screen.getByText(MINUS)
34-
await user.click(button)
32+
await userEvent.click(button)
3533
expect(input.value).toBe('4')
3634
})
3735
test('typing enter/space on plus increments value', async () => {
38-
const user = userEvent.setup()
3936
renderWithProviders(<MockComponent />)
4037
const input = screen.getByRole('spinbutton')
4138
const button = screen.getByText('+')
42-
await user.type(button, '{enter}')
39+
fireEvent.keyDown(button, {key: 'Enter'})
4340
expect(input.value).toBe('6')
44-
await user.type(button, '{space}')
41+
fireEvent.keyDown(button, {key: ' '})
4542
expect(input.value).toBe('7')
4643
})
4744

4845
test('keydown enter/space on plus increments value', async () => {
49-
const user = userEvent.setup()
5046
renderWithProviders(<MockComponent />)
5147
const input = screen.getByRole('spinbutton')
5248
const button = screen.getByText('+')
@@ -57,18 +53,16 @@ describe('QuantityPicker', () => {
5753
})
5854

5955
test('typing space on minus decrements value', async () => {
60-
const user = userEvent.setup()
6156
renderWithProviders(<MockComponent />)
6257
const input = screen.getByRole('spinbutton')
6358
const button = screen.getByText(MINUS)
64-
await user.type(button, '{enter}')
59+
fireEvent.keyDown(button, {key: 'Enter'})
6560
expect(input.value).toBe('4')
66-
await user.type(button, '{space}')
61+
fireEvent.keyDown(button, {key: ' '})
6762
expect(input.value).toBe('3')
6863
})
6964

7065
test('keydown enter/space on minus decrements value', async () => {
71-
const user = userEvent.setup()
7266
renderWithProviders(<MockComponent />)
7367
const input = screen.getByRole('spinbutton')
7468
const button = screen.getByText(MINUS)
@@ -79,18 +73,16 @@ describe('QuantityPicker', () => {
7973
})
8074

8175
test('plus button is tabbable', async () => {
82-
const user = userEvent.setup()
8376
renderWithProviders(<MockComponent />)
8477
const input = screen.getByRole('spinbutton')
85-
await user.type(input, '{tab}')
78+
fireEvent.keyDown(input, {key: 'Tab'})
8679
const button = screen.getByText('+')
8780
expect(button).toHaveFocus()
8881
})
8982
test('minus button is tabbable', async () => {
90-
const user = userEvent.setup()
9183
renderWithProviders(<MockComponent />)
9284
const input = screen.getByRole('spinbutton')
93-
await user.type(input, '{shift>}{tab}') // > modifier in {shift>} means "keep key pressed"
85+
fireEvent.keyDown(input, {key: 'Tab', shiftKey: true})
9486
const button = screen.getByText(MINUS)
9587
expect(button).toHaveFocus()
9688
})

packages/template-retail-react-app/app/components/shopper-agent/index.jsx

Lines changed: 122 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,36 @@
66
*/
77

88
import React, {useEffect} from 'react'
9-
import useScript from '@salesforce/retail-react-app/app/hooks/use-script'
9+
import {Helmet} from 'react-helmet'
1010
import {useUsid} from '@salesforce/commerce-sdk-react'
1111
import PropTypes from 'prop-types'
1212
import {useTheme} from '@salesforce/retail-react-app/app/components/shared/ui'
13+
import {
14+
validateCommerceAgentSettings,
15+
isEnabled
16+
} from '@salesforce/retail-react-app/app/utils/shopper-agent-validation'
1317

1418
const onClient = typeof window !== 'undefined'
1519

16-
// Function to initialize embedded messaging
20+
/**
21+
* Initializes the embedded messaging service with the provided configuration.
22+
* This function sets up the embedded messaging bootstrap with the specified
23+
* parameters and handles any initialization errors gracefully.
24+
*
25+
* @param {string} salesforceOrgId - The Salesforce organization ID
26+
* @param {string} embeddedServiceDeploymentName - The name of the embedded service deployment
27+
* @param {string} embeddedServiceDeploymentUrl - The URL of the embedded service deployment
28+
* @param {string} scrt2Url - The URL for the SCRT2 script
29+
* @param {string} locale - The locale for the embedded messaging service
30+
* @example
31+
* initEmbeddedMessaging(
32+
* '00D1234567890ABC',
33+
* 'MIAW_Guided_Shopper_production',
34+
* 'https://myorg.salesforce.com/ESWMIAWGuidedShopper',
35+
* 'https://myorg.salesforce-scrt.com',
36+
* 'en-US'
37+
* )
38+
*/
1739
const initEmbeddedMessaging = (
1840
salesforceOrgId,
1941
embeddedServiceDeploymentName,
@@ -43,16 +65,35 @@ const initEmbeddedMessaging = (
4365
}
4466
}
4567

68+
/**
69+
* Custom hook to manage the embedded messaging service initialization.
70+
* This hook monitors when the embedded messaging script is available and
71+
* initializes the service with the provided configuration.
72+
*
73+
* @param {string} salesforceOrgId - The Salesforce organization ID
74+
* @param {string} embeddedServiceDeploymentName - The name of the embedded service deployment
75+
* @param {string} embeddedServiceDeploymentUrl - The URL of the embedded service deployment
76+
* @param {string} scrt2Url - The URL for the SCRT2 script
77+
* @param {string} locale - The locale for the embedded messaging service
78+
* @example
79+
* useMiaw(
80+
* '00D1234567890ABC',
81+
* 'MIAW_Guided_Shopper_production',
82+
* 'https://myorg.salesforce.com/ESWMIAWGuidedShopper',
83+
* 'https://myorg.salesforce-scrt.com',
84+
* 'en-US'
85+
* )
86+
*/
4687
function useMiaw(
47-
scriptLoadStatus,
4888
salesforceOrgId,
4989
embeddedServiceDeploymentName,
5090
embeddedServiceDeploymentUrl,
5191
scrt2Url,
5292
locale
5393
) {
5494
useEffect(() => {
55-
if (scriptLoadStatus.loaded && !scriptLoadStatus.error) {
95+
// Check if the embedded messaging script has been loaded and is available
96+
if (onClient && window.embeddedservice_bootstrap) {
5697
initEmbeddedMessaging(
5798
salesforceOrgId,
5899
embeddedServiceDeploymentName,
@@ -62,7 +103,6 @@ function useMiaw(
62103
)
63104
}
64105
}, [
65-
scriptLoadStatus,
66106
salesforceOrgId,
67107
embeddedServiceDeploymentName,
68108
embeddedServiceDeploymentUrl,
@@ -71,31 +111,27 @@ function useMiaw(
71111
])
72112
}
73113

74-
function validateCommerceAgentSettings(commerceAgent) {
75-
const requiredFields = [
76-
'enabled',
77-
'askAgentOnSearch',
78-
'embeddedServiceName',
79-
'embeddedServiceEndpoint',
80-
'scriptSourceUrl',
81-
'scrt2Url',
82-
'salesforceOrgId',
83-
'commerceOrgId',
84-
'siteId'
85-
]
86-
87-
const isValid = requiredFields.every((key) => typeof commerceAgent[key] === 'string')
88-
if (!isValid) {
89-
console.error('Invalid commerce agent settings.')
90-
}
91-
return isValid
92-
}
93-
94-
function isEnabled(enabled) {
95-
return enabled === 'true' && onClient
96-
}
97-
98-
function ShopperAgentWindow({commerceAgentConfiguration, locale, basketId}) {
114+
/**
115+
* ShopperAgentWindow component that handles the embedded messaging service initialization
116+
* and event management. This component is responsible for:
117+
* - Loading the embedded messaging script via Helmet
118+
* - Setting up event listeners for embedded messaging events
119+
* - Managing pre-chat fields and basket context
120+
* - Handling z-index management for the chat widget
121+
*
122+
* @param {Object} props - Component props
123+
* @param {Object} props.commerceAgentConfiguration - Commerce agent configuration object
124+
* @param {string} props.basketId - The basket ID for the embedded messaging script
125+
* @param {string} props.locale - The locale for the embedded messaging script
126+
* @returns {JSX.Element} Helmet component with embedded messaging script
127+
* @example
128+
* <ShopperAgentWindow
129+
* commerceAgentConfiguration={config}
130+
* basketId="4a67cda5b1b9325a29207854c1"
131+
* locale="en-US"
132+
* />
133+
*/
134+
const ShopperAgentWindow = ({commerceAgentConfiguration, locale, basketId}) => {
99135
const theme = useTheme()
100136
const {
101137
embeddedServiceName,
@@ -109,15 +145,23 @@ function ShopperAgentWindow({commerceAgentConfiguration, locale, basketId}) {
109145

110146
const {usid} = useUsid()
111147

148+
/**
149+
* Sets up event listeners for embedded messaging events.
150+
* This effect handles:
151+
* - onEmbeddedMessagingReady: Sets initial pre-chat fields
152+
* - onEmbeddedMessagingWindowMaximized: Manages z-index for chat widget
153+
*/
112154
useEffect(() => {
113155
const handleEmbeddedMessagingReady = () => {
114-
window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
115-
SiteId: siteId,
116-
Locale: locale,
117-
OrganizationId: commerceOrgId,
118-
UsId: usid,
119-
IsCartMgmtSupported: true
120-
})
156+
if (window.embeddedservice_bootstrap?.prechatAPI) {
157+
window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
158+
SiteId: siteId,
159+
Locale: locale,
160+
OrganizationId: commerceOrgId,
161+
UsId: usid,
162+
IsCartMgmtSupported: true
163+
})
164+
}
121165
}
122166

123167
const handleEmbeddedMessagingWindowMaximized = () => {
@@ -146,12 +190,18 @@ function ShopperAgentWindow({commerceAgentConfiguration, locale, basketId}) {
146190
}
147191
}, [commerceAgentConfiguration, usid, theme.zIndices.sticky])
148192

149-
// whenever the basketId changes, update the hidden prechat fields
193+
/**
194+
* Updates basket ID in pre-chat fields when basket changes.
195+
* This effect listens for the embedded messaging button click event
196+
* and updates the basket ID in the pre-chat fields.
197+
*/
150198
useEffect(() => {
151199
const handleEmbeddedMessagingButtonClicked = () => {
152-
window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
153-
BasketId: basketId
154-
})
200+
if (window.embeddedservice_bootstrap?.prechatAPI) {
201+
window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
202+
BasketId: basketId
203+
})
204+
}
155205
}
156206

157207
window.addEventListener(
@@ -168,20 +218,19 @@ function ShopperAgentWindow({commerceAgentConfiguration, locale, basketId}) {
168218
}
169219
}, [basketId])
170220

171-
// Load the embedded messaging script
172-
const scriptLoadStatus = useScript(scriptSourceUrl)
173-
174221
// Initialize the embedded messaging service
175-
useMiaw(
176-
scriptLoadStatus,
177-
salesforceOrgId,
178-
embeddedServiceName,
179-
embeddedServiceEndpoint,
180-
scrt2Url,
181-
locale
182-
)
222+
useMiaw(salesforceOrgId, embeddedServiceName, embeddedServiceEndpoint, scrt2Url, locale)
183223

184-
return null
224+
return (
225+
<Helmet>
226+
<script
227+
id="embedded-messaging-script"
228+
src={scriptSourceUrl}
229+
async
230+
type="text/javascript"
231+
/>
232+
</Helmet>
233+
)
185234
}
186235

187236
ShopperAgentWindow.propTypes = {
@@ -191,12 +240,29 @@ ShopperAgentWindow.propTypes = {
191240
}
192241

193242
/**
194-
* ShopperAgent component that initializes and manages the embedded messaging service
243+
* ShopperAgent component that initializes and manages the embedded messaging service.
244+
* This is the main component that:
245+
* - Validates the commerce agent configuration
246+
* - Checks if the feature is enabled
247+
* - Ensures basket loading is complete
248+
* - Renders the ShopperAgentWindow when all conditions are met
249+
*
250+
* The component follows a conditional rendering pattern where it only renders
251+
* the embedded messaging functionality when all prerequisites are satisfied.
252+
*
195253
* @param {Object} props - Component props
196-
* @param {Object} props.commerceAgentConfiguration - Commerce agent settings
254+
* @param {Object} props.commerceAgentConfiguration - Commerce agent configuration object
197255
* @param {string} props.basketId - The basket ID for the embedded messaging script
198256
* @param {string} props.locale - The locale for the embedded messaging script
199-
* @returns {JSX.Element} The ShopperAgent component
257+
* @param {boolean} props.basketDoneLoading - Whether the basket has finished loading
258+
* @returns {JSX.Element|null} The ShopperAgent component or null if conditions not met
259+
* @example
260+
* <ShopperAgent
261+
* commerceAgentConfiguration={config}
262+
* basketId="4a67cda5b1b9325a29207854c1"
263+
* locale="en-US"
264+
* basketDoneLoading={true}
265+
* />
200266
*/
201267
function ShopperAgent({commerceAgentConfiguration, basketId, locale, basketDoneLoading}) {
202268
const {enabled} = commerceAgentConfiguration

0 commit comments

Comments
 (0)