diff --git a/packages/pwa-kit-create-app/assets/bootstrap/js/config/default.js.hbs b/packages/pwa-kit-create-app/assets/bootstrap/js/config/default.js.hbs index d76b6c4c88..6bff78edca 100644 --- a/packages/pwa-kit-create-app/assets/bootstrap/js/config/default.js.hbs +++ b/packages/pwa-kit-create-app/assets/bootstrap/js/config/default.js.hbs @@ -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: { diff --git a/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/config/default.js.hbs b/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/config/default.js.hbs index e4c13b3d41..4de9aa3466 100644 --- a/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/config/default.js.hbs +++ b/packages/pwa-kit-create-app/assets/templates/@salesforce/retail-react-app/config/default.js.hbs @@ -34,7 +34,9 @@ module.exports = { commerceOrgId: '', siteId: '', enableConversationContext: 'false', - conversationContext: [] + conversationContext: [], + enableAgentFromHeader: 'false', + enableAgentFromFloatingButton: 'false' }, // Customize settings for your url url: { diff --git a/packages/template-retail-react-app/app/components/shopper-agent/index.jsx b/packages/template-retail-react-app/app/components/shopper-agent/index.jsx index 8e3ce66d57..2b7591cbac 100644 --- a/packages/template-retail-react-app/app/components/shopper-agent/index.jsx +++ b/packages/template-retail-react-app/app/components/shopper-agent/index.jsx @@ -175,7 +175,8 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => { commerceOrgId, siteId, enableConversationContext = 'false', - conversationContext = [] + conversationContext = [], + enableAgentFromFloatingButton = 'true' } = commerceAgentConfiguration // User session identifier hook @@ -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 diff --git a/packages/template-retail-react-app/app/components/shopper-agent/index.test.js b/packages/template-retail-react-app/app/components/shopper-agent/index.test.js index 030037a5a0..85106bccd0 100644 --- a/packages/template-retail-react-app/app/components/shopper-agent/index.test.js +++ b/packages/template-retail-react-app/app/components/shopper-agent/index.test.js @@ -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() + + 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 ) }) diff --git a/packages/template-retail-react-app/app/hooks/use-miaw.js b/packages/template-retail-react-app/app/hooks/use-miaw.js index 42b8b4629b..6480fc12d8 100644 --- a/packages/template-retail-react-app/app/hooks/use-miaw.js +++ b/packages/template-retail-react-app/app/hooks/use-miaw.js @@ -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' ) => { try { if ( @@ -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, @@ -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, @@ -125,7 +131,8 @@ const useMiaw = ( embeddedServiceDeploymentUrl, scrt2Url, locale, - refreshToken + refreshToken, + enableAgentFromFloatingButton = 'true' ) => { useEffect(() => { if (scriptLoadStatus.loaded && !scriptLoadStatus.error) { @@ -135,7 +142,7 @@ const useMiaw = ( embeddedServiceDeploymentUrl, scrt2Url, locale, - refreshToken + enableAgentFromFloatingButton ) } }, [ @@ -145,7 +152,8 @@ const useMiaw = ( embeddedServiceDeploymentUrl, scrt2Url, locale, - refreshToken + refreshToken, + enableAgentFromFloatingButton ]) } diff --git a/packages/template-retail-react-app/app/hooks/use-miaw.test.js b/packages/template-retail-react-app/app/hooks/use-miaw.test.js index 0aa907ff71..68ed595640 100644 --- a/packages/template-retail-react-app/app/hooks/use-miaw.test.js +++ b/packages/template-retail-react-app/app/hooks/use-miaw.test.js @@ -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', @@ -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'}, diff --git a/packages/template-retail-react-app/app/utils/config-utils.js b/packages/template-retail-react-app/app/utils/config-utils.js index 6c6541ba1e..02865877f0 100644 --- a/packages/template-retail-react-app/app/utils/config-utils.js +++ b/packages/template-retail-react-app/app/utils/config-utils.js @@ -20,7 +20,8 @@ export const getCommerceAgentConfig = () => { siteId: '', enableConversationContext: 'false', conversationContext: [], - enableAgentFromHeader: 'false' + enableAgentFromHeader: 'false', + enableAgentFromFloatingButton: 'false' } return getConfig().app.commerceAgent ?? defaults } diff --git a/packages/template-retail-react-app/app/utils/shopper-agent-utils.js b/packages/template-retail-react-app/app/utils/shopper-agent-utils.js index 726f3d911c..f169119fdb 100644 --- a/packages/template-retail-react-app/app/utils/shopper-agent-utils.js +++ b/packages/template-retail-react-app/app/utils/shopper-agent-utils.js @@ -9,6 +9,10 @@ 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} */ @@ -16,12 +20,16 @@ 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() } } catch (error) { console.error('Shopper Agent: Error launching chat', error) diff --git a/packages/template-retail-react-app/app/utils/shopper-agent-utils.test.js b/packages/template-retail-react-app/app/utils/shopper-agent-utils.test.js index b3c650a60d..aadbe8f5f1 100644 --- a/packages/template-retail-react-app/app/utils/shopper-agent-utils.test.js +++ b/packages/template-retail-react-app/app/utils/shopper-agent-utils.test.js @@ -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] + ) + }) + + 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')