-
Notifications
You must be signed in to change notification settings - Fork 5
Add Salesforce chat widget to book details page #2873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 10 commits
3ecbb69
ef0e6a3
a8f2103
42138c3
84c9785
e525469
6032520
411f534
a813084
57d4412
79be4f7
4a0decb
f839427
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| // Salesforce chat widget styling | ||
| // The chat widget is injected by Salesforce and appears as a fixed overlay | ||
| // Add custom styling overrides here if needed |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| import React from 'react'; | ||
| import useUserContext from '~/contexts/user'; | ||
| import './chat.scss'; | ||
|
|
||
| declare global { | ||
| interface Window { | ||
| embeddedservice_bootstrap?: { | ||
| settings: { | ||
| language: string; | ||
| }; | ||
| init: ( | ||
| orgId: string, | ||
| deploymentName: string, | ||
| baseUrl: string, | ||
| options: {scrt2URL: string} | ||
| ) => void; | ||
| prechatAPI?: { | ||
| setHiddenPrechatFields: (fields: Record<string, string>) => void; | ||
| }; | ||
| }; | ||
| __salesforceChatInitialized?: boolean; | ||
| } | ||
| } | ||
|
|
||
| const SALESFORCE_CONFIG = { | ||
| orgId: '00DU0000000Kwch', | ||
| deploymentName: 'Web_Messaging_Deployment', | ||
| baseUrl: 'https://openstax.my.site.com/ESWWebMessagingDeployme1716235390398', | ||
| scrt2URL: 'https://openstax.my.salesforce-scrt.com', | ||
| bootstrapScript: 'https://openstax.my.site.com/ESWWebMessagingDeployme1716235390398/assets/js/bootstrap.min.js' | ||
| }; | ||
|
|
||
| function initEmbeddedMessaging(): boolean { | ||
| try { | ||
| if (window.embeddedservice_bootstrap) { | ||
| window.embeddedservice_bootstrap.settings.language = 'en_US'; | ||
| window.embeddedservice_bootstrap.init( | ||
| SALESFORCE_CONFIG.orgId, | ||
| SALESFORCE_CONFIG.deploymentName, | ||
| SALESFORCE_CONFIG.baseUrl, | ||
| {scrt2URL: SALESFORCE_CONFIG.scrt2URL} | ||
| ); | ||
| return true; | ||
| } | ||
| return false; | ||
| } catch (err) { | ||
| console.error('Error initializing Salesforce chat:', err); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // eslint-disable-next-line complexity | ||
| export default function Chat() { | ||
| const userContext = useUserContext(); | ||
| const [scriptLoaded, setScriptLoaded] = React.useState(false); | ||
|
|
||
|
RoyEJohnson marked this conversation as resolved.
|
||
| // Derive stable user primitives from userStatus (which is always available) | ||
| // with fallback to userModel when available | ||
| const userStatus = userContext?.userStatus; | ||
| const userModel = userContext?.userModel; | ||
| const uuid = userStatus?.uuid || userModel?.uuid; | ||
| const firstName = userStatus?.firstName || userModel?.first_name; | ||
| const lastName = userStatus?.lastName || userModel?.last_name; | ||
| const email = userStatus?.email || userModel?.email; | ||
| const school = userStatus?.school || userModel?.accountsModel?.school_name; | ||
|
|
||
| // Load Salesforce script once, or short-circuit if already loaded | ||
| React.useEffect(() => { | ||
| let script: HTMLScriptElement | null = null; | ||
|
|
||
|
RoyEJohnson marked this conversation as resolved.
|
||
| // Short-circuit if bootstrap is already available from a previous mount | ||
| if (window.embeddedservice_bootstrap) { | ||
| setScriptLoaded(true); | ||
| } else { | ||
| script = document.createElement('script'); | ||
|
|
||
| script.src = SALESFORCE_CONFIG.bootstrapScript; | ||
| script.type = 'text/javascript'; | ||
| script.async = true; | ||
|
|
||
| script.onload = () => { | ||
| setScriptLoaded(true); | ||
| }; | ||
|
|
||
| script.onerror = () => { | ||
| console.error('Failed to load Salesforce chat script'); | ||
| }; | ||
|
|
||
| document.body.appendChild(script); | ||
| } | ||
|
|
||
| // Always return cleanup function to hide widget on unmount | ||
| return () => { | ||
| if (script && document.body.contains(script)) { | ||
| document.body.removeChild(script); | ||
| } | ||
|
RoyEJohnson marked this conversation as resolved.
|
||
| // Hide the chat widget when component unmounts | ||
| // The Salesforce widget injects elements with these selectors | ||
| const chatElement = document.getElementById('embedded-messaging'); | ||
|
|
||
| if (chatElement) { | ||
| chatElement.style.display = 'none'; | ||
| } | ||
|
RoyEJohnson marked this conversation as resolved.
|
||
| // Note: Don't delete window.embeddedservice_bootstrap or __salesforceChatInitialized | ||
| // to maintain conversation state across component remounts | ||
| }; | ||
|
RoyEJohnson marked this conversation as resolved.
|
||
| }, []); | ||
|
|
||
| // Show chat widget when component mounts (if it was previously hidden) | ||
| React.useEffect(() => { | ||
| if (scriptLoaded && window.__salesforceChatInitialized) { | ||
| const chatElement = document.getElementById('embedded-messaging'); | ||
|
|
||
| if (chatElement) { | ||
| chatElement.style.removeProperty('display'); | ||
| } | ||
| } | ||
| }, [scriptLoaded]); | ||
|
|
||
| // Initialize chat widget once (on first mount or after refresh) | ||
| React.useEffect(() => { | ||
| if (!scriptLoaded || !window.embeddedservice_bootstrap) { | ||
| return; | ||
| } | ||
|
|
||
| // Only initialize if not already initialized (persists across component remounts) | ||
| if (!window.__salesforceChatInitialized) { | ||
| const success = initEmbeddedMessaging(); | ||
|
|
||
| // Only set the flag if initialization succeeded | ||
| if (success) { | ||
| window.__salesforceChatInitialized = true; | ||
| } | ||
| } | ||
|
RoyEJohnson marked this conversation as resolved.
|
||
| }, [scriptLoaded]); | ||
|
|
||
| // Update pre-chat fields whenever user information changes | ||
| // This allows fields to update when a user logs in after chat is initialized | ||
| // eslint-disable-next-line complexity | ||
| React.useEffect(() => { | ||
| if (!scriptLoaded || !window.embeddedservice_bootstrap?.prechatAPI) { | ||
| return; | ||
| } | ||
|
RoyEJohnson marked this conversation as resolved.
|
||
|
|
||
| // Set sProduct for all users (logged in or not) | ||
| const hiddenFields: Record<string, string> = { | ||
| sProduct: 'Website' | ||
| }; | ||
|
|
||
| // Add user information if available | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still doesn't seem to be working with the |
||
| if (uuid) { | ||
| hiddenFields.OpenStax_UUID__c = uuid; // eslint-disable-line camelcase | ||
| } | ||
| if (firstName) { | ||
| hiddenFields.FirstName = firstName; | ||
| } | ||
| if (lastName) { | ||
| hiddenFields.LastName = lastName; | ||
| } | ||
| if (email) { | ||
| hiddenFields.Email = email; | ||
| } | ||
| if (school) { | ||
| hiddenFields.School = school; | ||
| } | ||
|
|
||
| window.embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields(hiddenFields); | ||
| }, [scriptLoaded, uuid, firstName, lastName, email, school]); | ||
|
|
||
| return null; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.