Skip to content

Commit e272dc0

Browse files
Merge branch 'develop' into fileOwnership_stretch
2 parents de657a0 + 414cb66 commit e272dc0

File tree

22 files changed

+556
-224
lines changed

22 files changed

+556
-224
lines changed

packages/pwa-kit-create-app/assets/bootstrap/js/config/default.js.hbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ module.exports = {
3434
commerceOrgId: '',
3535
siteId: '',
3636
enableConversationContext: 'false',
37-
conversationContext: []
37+
conversationContext: [],
38+
enableAgentFromHeader: 'false',
39+
enableAgentFromFloatingButton: 'false'
3840
},
3941
// Customize how your 'site' and 'locale' are displayed in the url.
4042
url: {

packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/config/default.js.hbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ module.exports = {
3434
commerceOrgId: '',
3535
siteId: '',
3636
enableConversationContext: 'false',
37-
conversationContext: []
37+
conversationContext: [],
38+
enableAgentFromHeader: 'false',
39+
enableAgentFromFloatingButton: 'false'
3840
},
3941
// Customize settings for your url
4042
url: {

packages/template-mrt-reference-app/app/ssr.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const ENVS_TO_EXPOSE = [
6565
'aws_lambda_log_stream_name',
6666
'aws_region',
6767
'bundle_id',
68+
'mrt_env_base_path',
6869
// These "customer" defined environment variables are set by the Manager
6970
// and expected by the MRT smoke test suite
7071
'customer_*',
@@ -360,6 +361,19 @@ const loggingMiddleware = (req, res, next) => {
360361
return next()
361362
}
362363

364+
const envBasePathMiddleware = (req, res, next) => {
365+
const basePath = process.env.MRT_ENV_BASE_PATH
366+
console.debug(`Base path: Base path: ${basePath}`)
367+
console.debug(`Request path: Request path: ${req.url}`)
368+
if (basePath && (req.path.startsWith(`${basePath}/`) || req.path === basePath)) {
369+
req.url = req.url.slice(basePath.length) || '/'
370+
console.debug(
371+
`Base path: Rewrote ${basePath} -> Original url: ${req.originalUrl} -> New url: ${req.url}`
372+
)
373+
}
374+
return next()
375+
}
376+
363377
const options = {
364378
// The build directory (an absolute path)
365379
buildDir: path.resolve(process.cwd(), 'build'),
@@ -394,7 +408,8 @@ const {handler, app, server} = runtime.createHandler(options, (app) => {
394408

395409
// Add middleware to log request and response headers
396410
app.use(loggingMiddleware)
397-
411+
// Add a middleware to consume the base path from the request path if one is set
412+
app.use(envBasePathMiddleware)
398413
// Configure routes
399414
app.all('/exception', exception)
400415
app.get('/tls', tlsVersionTest)

packages/template-mrt-reference-app/app/ssr.test.js

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ const {
1717
const {mockClient} = require('aws-sdk-client-mock')
1818
const {ServiceException} = require('@smithy/smithy-client')
1919

20+
const pathsToCheck = [
21+
['/', 200, 'application/json; charset=utf-8'],
22+
['/tls', 200, 'application/json; charset=utf-8'],
23+
['/exception', 500, 'text/html; charset=utf-8'],
24+
['/cache', 200, 'application/json; charset=utf-8'],
25+
['/cookie', 200, 'application/json; charset=utf-8'],
26+
['/set-response-headers', 200, 'application/json; charset=utf-8'],
27+
['/isolation', 200, 'application/json; charset=utf-8'],
28+
['/memtest', 200, 'application/json; charset=utf-8'],
29+
['/streaming-large', 200, 'application/json; charset=utf-8']
30+
]
31+
const pathsToCheckWithBasePath = (basePath) => {
32+
return pathsToCheck.map((pathStatusContentType) => [
33+
basePath + pathStatusContentType[0],
34+
pathStatusContentType[1],
35+
pathStatusContentType[2]
36+
])
37+
}
38+
2039
class AccessDenied extends ServiceException {
2140
constructor(options) {
2241
super({...options, name: 'AccessDenied'})
@@ -53,22 +72,39 @@ describe('server', () => {
5372
process.env = originalEnv
5473
jest.restoreAllMocks()
5574
})
56-
test.each([
57-
['/', 200, 'application/json; charset=utf-8'],
58-
['/tls', 200, 'application/json; charset=utf-8'],
59-
['/exception', 500, 'text/html; charset=utf-8'],
60-
['/cache', 200, 'application/json; charset=utf-8'],
61-
['/cookie', 200, 'application/json; charset=utf-8'],
62-
['/multi-cookies', 200, 'application/json; charset=utf-8'],
63-
['/set-response-headers', 200, 'application/json; charset=utf-8'],
64-
['/isolation', 200, 'application/json; charset=utf-8'],
65-
['/memtest', 200, 'application/json; charset=utf-8'],
66-
['/streaming-large', 200, 'application/json; charset=utf-8']
67-
])('Path %p should render correctly', (path, expectedStatus, expectedContentType) => {
68-
return request(app)
69-
.get(path)
70-
.expect(expectedStatus)
71-
.expect('Content-Type', expectedContentType)
75+
test.each(pathsToCheck)(
76+
'Path %p should render correctly',
77+
(path, expectedStatus, expectedContentType) => {
78+
return request(app)
79+
.get(path)
80+
.expect(expectedStatus)
81+
.expect('Content-Type', expectedContentType)
82+
}
83+
)
84+
85+
test.each(pathsToCheckWithBasePath('/test-base-path'))(
86+
'Path %p should render correctly',
87+
(path, expectedStatus, expectedContentType) => {
88+
process.env.MRT_ENV_BASE_PATH = '/test-base-path'
89+
return request(app)
90+
.get(path)
91+
.expect(expectedStatus)
92+
.expect('Content-Type', expectedContentType)
93+
}
94+
)
95+
96+
test('Path /echo should work with base path', async () => {
97+
const basePath = '/test-base-path'
98+
process.env.MRT_ENV_BASE_PATH = basePath
99+
const response = await request(app).get(`${basePath}/echo?x=foo&y=bar`)
100+
expect(response.status).toBe(200)
101+
// preserves query parameters
102+
expect(response.body.query.x).toBe('foo')
103+
expect(response.body.query.y).toBe('bar')
104+
// path is the path after the base path
105+
expect(response.body.path).toBe('/echo')
106+
// base path env var present in response body
107+
expect(response.body.env.MRT_ENV_BASE_PATH).toBe(basePath)
72108
})
73109

74110
test('Path "/cache" has Cache-Control set', () => {

packages/template-retail-react-app/app/components/otp-auth/index.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Button,
1313
Input,
1414
SimpleGrid,
15+
Spinner,
1516
Stack,
1617
Text,
1718
HStack,
@@ -271,6 +272,17 @@ const OtpAuth = ({
271272
))}
272273
</SimpleGrid>
273274

275+
{/* Loading spinner during verification */}
276+
{isVerifying && (
277+
<Spinner
278+
size="sm"
279+
color="blue.500"
280+
role="status"
281+
aria-live="polite"
282+
data-testid="otp-verifying-spinner"
283+
/>
284+
)}
285+
274286
{/* Error message */}
275287
{error && (
276288
<Text fontSize="sm" color="red.500" textAlign="center">
@@ -290,7 +302,7 @@ const OtpAuth = ({
290302
)}
291303

292304
{/* Buttons */}
293-
<HStack spacing={4} width="100%" justifyContent="flex-end">
305+
<HStack spacing={4} width="100%" justifyContent="center">
294306
{!hideCheckoutAsGuestButton && (
295307
<Button
296308
onClick={handleCheckoutAsGuest}

packages/template-retail-react-app/app/components/otp-auth/index.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,41 @@ describe('OtpAuth', () => {
246246
await user.type(otpInputs[7], '8')
247247
expect(otpInputs[7]).toHaveFocus()
248248
})
249+
250+
test('shows spinner while OTP is being verified', async () => {
251+
const deferred = {}
252+
const verifyingPromise = new Promise((resolve) => {
253+
deferred.resolve = resolve
254+
})
255+
const mockVerify = jest.fn().mockReturnValue(verifyingPromise)
256+
257+
const user = userEvent.setup()
258+
renderWithProviders(
259+
<OtpAuth
260+
isOpen={true}
261+
onClose={mockOnClose}
262+
form={mockForm}
263+
handleOtpVerification={mockVerify}
264+
handleSendEmailOtp={mockHandleSendEmailOtp}
265+
/>
266+
)
267+
268+
expect(screen.queryByTestId('otp-verifying-spinner')).not.toBeInTheDocument()
269+
270+
const otpInputs = screen.getAllByRole('textbox')
271+
fireEvent.paste(otpInputs[0], {
272+
clipboardData: {getData: () => '12345678'}
273+
})
274+
275+
await waitFor(() => {
276+
expect(screen.getByTestId('otp-verifying-spinner')).toBeInTheDocument()
277+
})
278+
279+
deferred.resolve({success: true})
280+
await waitFor(() => {
281+
expect(screen.queryByTestId('otp-verifying-spinner')).not.toBeInTheDocument()
282+
})
283+
})
249284
})
250285

251286
describe('Keyboard Navigation', () => {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
175175
commerceOrgId,
176176
siteId,
177177
enableConversationContext = 'false',
178-
conversationContext = []
178+
conversationContext = [],
179+
enableAgentFromFloatingButton = 'true'
179180
} = commerceAgentConfiguration
180181

181182
// User session identifier hook
@@ -352,7 +353,8 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
352353
embeddedServiceEndpoint,
353354
scrt2Url,
354355
locale.id,
355-
refreshToken
356+
refreshToken,
357+
enableAgentFromFloatingButton
356358
)
357359

358360
// This component doesn't render visible UI, only manages the messaging service

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,31 @@ describe('ShopperAgent Component', () => {
509509
'https://test.salesforce.com', // embeddedServiceEndpoint
510510
'https://test.salesforce.com/scrt2.js', // scrt2Url
511511
'en-US', // locale.id
512-
'test-refresh-token' // refreshToken
512+
'test-refresh-token', // refreshToken
513+
'true' // enableAgentFromFloatingButton (default)
514+
)
515+
})
516+
517+
test('should call useMiaw with enableAgentFromFloatingButton false when configured', () => {
518+
const props = {
519+
...defaultProps,
520+
commerceAgentConfiguration: {
521+
...commerceAgentSettings,
522+
enableAgentFromFloatingButton: 'false'
523+
}
524+
}
525+
526+
render(<ShopperAgent {...props} />)
527+
528+
expect(mockedUseMiaw).toHaveBeenCalledWith(
529+
{loaded: true, error: false},
530+
'test-org-id',
531+
'test-service',
532+
'https://test.salesforce.com',
533+
'https://test.salesforce.com/scrt2.js',
534+
'en-US',
535+
'test-refresh-token',
536+
'false' // enableAgentFromFloatingButton
513537
)
514538
})
515539

packages/template-retail-react-app/app/hooks/use-miaw.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ const normalizeLocaleToSalesforce = (locale) => {
7373
* @param {string} embeddedServiceDeploymentUrl - URL of the embedded service deployment
7474
* @param {string} scrt2Url - SCRT2 URL for the embedded messaging service
7575
* @param {string} locale - BCP-47 locale for the embedded messaging service
76+
* @param {string} [enableAgentFromFloatingButton='true'] - When 'false', hides the floating chat button on load
7677
*/
7778
const initEmbeddedMessaging = (
7879
salesforceOrgId,
7980
embeddedServiceDeploymentName,
8081
embeddedServiceDeploymentUrl,
8182
scrt2Url,
82-
locale
83+
locale,
84+
enableAgentFromFloatingButton = 'true'
8385
) => {
8486
try {
8587
if (
@@ -93,6 +95,9 @@ const initEmbeddedMessaging = (
9395
window.embeddedservice_bootstrap.settings.language = salesforceLanguage
9496
window.embeddedservice_bootstrap.settings.disableStreamingResponses = true
9597
window.embeddedservice_bootstrap.settings.enableUserInputForConversationWithBot = false
98+
// Hide floating chat button when enableAgentFromFloatingButton is 'false'
99+
window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad =
100+
enableAgentFromFloatingButton !== 'true'
96101
window.embeddedservice_bootstrap.init(
97102
salesforceOrgId,
98103
embeddedServiceDeploymentName,
@@ -117,6 +122,7 @@ const initEmbeddedMessaging = (
117122
* @param {string} scrt2Url - SCRT2 URL for the embedded messaging service
118123
* @param {string} locale - BCP-47 locale for the embedded messaging service
119124
* @param {string} refreshToken - Refresh token for the embedded messaging service
125+
* @param {string} [enableAgentFromFloatingButton='true'] - When 'false', hides the floating chat button on load
120126
*/
121127
const useMiaw = (
122128
scriptLoadStatus,
@@ -125,7 +131,8 @@ const useMiaw = (
125131
embeddedServiceDeploymentUrl,
126132
scrt2Url,
127133
locale,
128-
refreshToken
134+
refreshToken,
135+
enableAgentFromFloatingButton = 'true'
129136
) => {
130137
useEffect(() => {
131138
if (scriptLoadStatus.loaded && !scriptLoadStatus.error) {
@@ -135,7 +142,7 @@ const useMiaw = (
135142
embeddedServiceDeploymentUrl,
136143
scrt2Url,
137144
locale,
138-
refreshToken
145+
enableAgentFromFloatingButton
139146
)
140147
}
141148
}, [
@@ -145,7 +152,8 @@ const useMiaw = (
145152
embeddedServiceDeploymentUrl,
146153
scrt2Url,
147154
locale,
148-
refreshToken
155+
refreshToken,
156+
enableAgentFromFloatingButton
149157
])
150158
}
151159

packages/template-retail-react-app/app/hooks/use-miaw.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ describe('useMiaw hook', () => {
141141
expect(
142142
window.embeddedservice_bootstrap.settings.enableUserInputForConversationWithBot
143143
).toBe(false)
144+
expect(window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad).toBe(false)
144145
expect(window.embeddedservice_bootstrap.init).toHaveBeenCalledWith(
145146
'test-org-id',
146147
'test-deployment',
@@ -167,6 +168,18 @@ describe('useMiaw hook', () => {
167168
expect(window.embeddedservice_bootstrap.init).not.toHaveBeenCalled()
168169
})
169170

171+
test('should hide floating chat button when enableAgentFromFloatingButton is false', () => {
172+
renderHook(() =>
173+
useMiaw(
174+
mockScriptLoadStatus,
175+
...Object.values(mockParams),
176+
'false' // enableAgentFromFloatingButton
177+
)
178+
)
179+
180+
expect(window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad).toBe(true)
181+
})
182+
170183
test('should handle different locale mappings', () => {
171184
const testCases = [
172185
{locale: 'en-US', expected: 'en_US'},

0 commit comments

Comments
 (0)