Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ module.exports = {
commerceOrgId: '',
siteId: '',
enableConversationContext: 'false',
conversationContext: []
conversationContext: [],
enableAgentFromHeader: 'false',
enableAgentFromFloatingButton: 'false'
},
// Customize how your 'site' and 'locale' are displayed in the url.
url: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ module.exports = {
commerceOrgId: '',
siteId: '',
enableConversationContext: 'false',
conversationContext: []
conversationContext: [],
enableAgentFromHeader: 'false',
enableAgentFromFloatingButton: 'false'
},
// Customize settings for your url
url: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
commerceOrgId,
siteId,
enableConversationContext = 'false',
conversationContext = []
conversationContext = [],
enableAgentFromFloatingButton = 'true'
} = commerceAgentConfiguration

// User session identifier hook
Expand Down Expand Up @@ -352,7 +353,8 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
embeddedServiceEndpoint,
scrt2Url,
locale.id,
refreshToken
refreshToken,
enableAgentFromFloatingButton
)

// This component doesn't render visible UI, only manages the messaging service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,31 @@ describe('ShopperAgent Component', () => {
'https://test.salesforce.com', // embeddedServiceEndpoint
'https://test.salesforce.com/scrt2.js', // scrt2Url
'en-US', // locale.id
'test-refresh-token' // refreshToken
'test-refresh-token', // refreshToken
'true' // enableAgentFromFloatingButton (default)
)
})

test('should call useMiaw with enableAgentFromFloatingButton false when configured', () => {
const props = {
...defaultProps,
commerceAgentConfiguration: {
...commerceAgentSettings,
enableAgentFromFloatingButton: 'false'
}
}

render(<ShopperAgent {...props} />)

expect(mockedUseMiaw).toHaveBeenCalledWith(
{loaded: true, error: false},
'test-org-id',
'test-service',
'https://test.salesforce.com',
'https://test.salesforce.com/scrt2.js',
'en-US',
'test-refresh-token',
'false' // enableAgentFromFloatingButton
)
})

Expand Down
16 changes: 12 additions & 4 deletions packages/template-retail-react-app/app/hooks/use-miaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,15 @@ const normalizeLocaleToSalesforce = (locale) => {
* @param {string} embeddedServiceDeploymentUrl - URL of the embedded service deployment
* @param {string} scrt2Url - SCRT2 URL for the embedded messaging service
* @param {string} locale - BCP-47 locale for the embedded messaging service
* @param {string} [enableAgentFromFloatingButton='true'] - When 'false', hides the floating chat button on load
*/
const initEmbeddedMessaging = (
salesforceOrgId,
embeddedServiceDeploymentName,
embeddedServiceDeploymentUrl,
scrt2Url,
locale
locale,
enableAgentFromFloatingButton = 'true'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking, I would encourage to have the init function parse the inputs and convert the string input into a boolean. Feel more maintainable for me and avoid enableAgentFromFloatingButton !== 'true'

) => {
try {
if (
Expand All @@ -93,6 +95,9 @@ const initEmbeddedMessaging = (
window.embeddedservice_bootstrap.settings.language = salesforceLanguage
window.embeddedservice_bootstrap.settings.disableStreamingResponses = true
window.embeddedservice_bootstrap.settings.enableUserInputForConversationWithBot = false
// Hide floating chat button when enableAgentFromFloatingButton is 'false'
window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad =
enableAgentFromFloatingButton !== 'true'
window.embeddedservice_bootstrap.init(
salesforceOrgId,
embeddedServiceDeploymentName,
Expand All @@ -117,6 +122,7 @@ const initEmbeddedMessaging = (
* @param {string} scrt2Url - SCRT2 URL for the embedded messaging service
* @param {string} locale - BCP-47 locale for the embedded messaging service
* @param {string} refreshToken - Refresh token for the embedded messaging service
* @param {string} [enableAgentFromFloatingButton='true'] - When 'false', hides the floating chat button on load
*/
const useMiaw = (
scriptLoadStatus,
Expand All @@ -125,7 +131,8 @@ const useMiaw = (
embeddedServiceDeploymentUrl,
scrt2Url,
locale,
refreshToken
refreshToken,
enableAgentFromFloatingButton = 'true'
Comment on lines +134 to +135
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. This function's argument list has grown quite a lot, I would encourage to have optional argument as an object as the last parameter, that way, it's future proof in case if you ever need to add more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it become breaking changes @kevinxh ? I think the team will need to wait for another major breaking changes for the template to do that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a breaking change if the signature is used outside this repo.

) => {
useEffect(() => {
if (scriptLoadStatus.loaded && !scriptLoadStatus.error) {
Expand All @@ -135,7 +142,7 @@ const useMiaw = (
embeddedServiceDeploymentUrl,
scrt2Url,
locale,
refreshToken
enableAgentFromFloatingButton
)
}
}, [
Expand All @@ -145,7 +152,8 @@ const useMiaw = (
embeddedServiceDeploymentUrl,
scrt2Url,
locale,
refreshToken
refreshToken,
enableAgentFromFloatingButton
])
}

Expand Down
13 changes: 13 additions & 0 deletions packages/template-retail-react-app/app/hooks/use-miaw.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ describe('useMiaw hook', () => {
expect(
window.embeddedservice_bootstrap.settings.enableUserInputForConversationWithBot
).toBe(false)
expect(window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad).toBe(false)
expect(window.embeddedservice_bootstrap.init).toHaveBeenCalledWith(
'test-org-id',
'test-deployment',
Expand All @@ -167,6 +168,18 @@ describe('useMiaw hook', () => {
expect(window.embeddedservice_bootstrap.init).not.toHaveBeenCalled()
})

test('should hide floating chat button when enableAgentFromFloatingButton is false', () => {
renderHook(() =>
useMiaw(
mockScriptLoadStatus,
...Object.values(mockParams),
'false' // enableAgentFromFloatingButton
)
)

expect(window.embeddedservice_bootstrap.settings.hideChatButtonOnLoad).toBe(true)
})

test('should handle different locale mappings', () => {
const testCases = [
{locale: 'en-US', expected: 'en_US'},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export const getCommerceAgentConfig = () => {
siteId: '',
enableConversationContext: 'false',
conversationContext: [],
enableAgentFromHeader: 'false'
enableAgentFromHeader: 'false',
enableAgentFromFloatingButton: 'false'
}
return getConfig().app.commerceAgent ?? defaults
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,27 @@ const onClient = typeof window !== 'undefined'
/**
* Launch the chat using the embedded service bootstrap API
*
* When the floating chat button is hidden (hideChatButtonOnLoad=true), this function
* first shows the chat button via utilAPI.showChatButton() before launching the chat,
* ensuring the chat window opens correctly.
*
* @function launchChat
* @returns {void}
*/
export function launchChat() {
if (!onClient) return

try {
// Launch chat using the embedded service bootstrap API
if (
window.embeddedservice_bootstrap?.utilAPI &&
typeof window.embeddedservice_bootstrap.utilAPI.launchChat === 'function'
) {
window.embeddedservice_bootstrap.utilAPI.launchChat()
const utilAPI = window.embeddedservice_bootstrap?.utilAPI
if (!utilAPI) return
const hideChatButtonOnLoad =
window.embeddedservice_bootstrap?.settings?.hideChatButtonOnLoad === true
if (hideChatButtonOnLoad && typeof utilAPI.showChatButton === 'function') {
utilAPI.showChatButton()
}

if (typeof utilAPI.launchChat === 'function') {
utilAPI.launchChat()
Comment on lines +23 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking, the code might be more readable if validation is extract at the top of the function like the following structure.

function launchChat() {
 // 1. validation first
 if (!onClient) return
 if (!window.embeddedservice_bootstrap) return
if (!xxx) return


// 2. implementation
}

}
} catch (error) {
console.error('Shopper Agent: Error launching chat', error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,54 @@ describe('shopper-agent-utils', () => {
expect(() => launchChat()).not.toThrow()
})

test('should call showChatButton before launchChat when hideChatButtonOnLoad is true', () => {
const mockShowChatButton = jest.fn()
const mockLaunchChat = jest.fn()

global.window = {
embeddedservice_bootstrap: {
settings: {
hideChatButtonOnLoad: true
},
utilAPI: {
showChatButton: mockShowChatButton,
launchChat: mockLaunchChat
}
}
}

launchChat()

expect(mockShowChatButton).toHaveBeenCalledTimes(1)
expect(mockLaunchChat).toHaveBeenCalledTimes(1)
// showChatButton must be called before launchChat
expect(mockShowChatButton.mock.invocationCallOrder[0]).toBeLessThan(
mockLaunchChat.mock.invocationCallOrder[0]
)
Comment on lines +104 to +106
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

})

test('should not call showChatButton when hideChatButtonOnLoad is false', () => {
const mockShowChatButton = jest.fn()
const mockLaunchChat = jest.fn()

global.window = {
embeddedservice_bootstrap: {
settings: {
hideChatButtonOnLoad: false
},
utilAPI: {
showChatButton: mockShowChatButton,
launchChat: mockLaunchChat
}
}
}

launchChat()

expect(mockShowChatButton).not.toHaveBeenCalled()
expect(mockLaunchChat).toHaveBeenCalledTimes(1)
})

test('should handle errors and log error', () => {
const mockLaunchChat = jest.fn(() => {
throw new Error('Launch error')
Expand Down
Loading