diff --git a/packages/help-center/src/components/help-center-zendesk.tsx b/packages/help-center/src/components/help-center-zendesk.tsx
new file mode 100644
index 0000000000000..31aa08cd72962
--- /dev/null
+++ b/packages/help-center/src/components/help-center-zendesk.tsx
@@ -0,0 +1,65 @@
+import { useOdieAssistantContext } from '@automattic/odie-client';
+import { useSmooch } from '@automattic/zendesk-client';
+import { useEffect, useState } from 'react';
+import { useHelpCenterContext } from '../contexts/HelpCenterContext';
+
+export const HelpCenterZendesk = ( {
+ setMessageHandler,
+ messageHandler,
+}: {
+ setMessageHandler: any;
+ messageHandler: any;
+} ) => {
+ const [ whoAreWeTalkingTo, setWhoAreWeTalkingTo ] = useState< 'odie' | 'human' >( 'odie' );
+ const { chat, addMessage, clearChat } = useOdieAssistantContext();
+ const { site } = useHelpCenterContext();
+ const { createConversation, addMessengerListener, sendMessage } = useSmooch();
+
+ const interceptedUserMessageHandler = ( message: any ) => {
+ if ( sendMessage ) {
+ sendMessage( message?.content );
+ }
+ };
+
+ const forwardedZendeskMessageHandler = ( message: any ) => {
+ if ( addMessage ) {
+ addMessage( {
+ content: message?.text,
+ role: 'human',
+ type: 'message',
+ } );
+ }
+ };
+
+ // Listen for the transfer to human event.
+ useEffect( () => {
+ const lastMessage = chat.messages[ chat.messages.length - 1 ];
+
+ if ( lastMessage?.context?.flags?.forward_to_human_support ) {
+ clearChat();
+ setWhoAreWeTalkingTo( 'human' );
+ if ( createConversation ) {
+ createConversation(
+ {
+ messaging_initial_message: 'Passing in from Odie',
+ messaging_source: 'help-center',
+ messaging_site_id: site?.ID as number,
+ },
+ { odieChatId: chat.chat_id as number }
+ );
+ }
+ }
+ }, [ chat.messages ] );
+
+ // Switch to human
+ useEffect( () => {
+ if ( whoAreWeTalkingTo === 'human' && ! messageHandler ) {
+ setMessageHandler( interceptedUserMessageHandler );
+ if ( addMessengerListener ) {
+ addMessengerListener( forwardedZendeskMessageHandler );
+ }
+ }
+ }, [ whoAreWeTalkingTo ] );
+
+ return null;
+};
diff --git a/packages/help-center/src/components/help-center.tsx b/packages/help-center/src/components/help-center.tsx
index 9649de3872f7f..f05e4a91bb567 100644
--- a/packages/help-center/src/components/help-center.tsx
+++ b/packages/help-center/src/components/help-center.tsx
@@ -3,8 +3,8 @@
* External Dependencies
*/
import { initializeAnalytics } from '@automattic/calypso-analytics';
-import { useZendeskMessagingBindings, useLoadZendeskMessaging } from '@automattic/zendesk-client';
-import { useSelect } from '@wordpress/data';
+import { useLoadZendeskMessaging, useSmooch } from '@automattic/zendesk-client';
+import { useSelect, useDispatch } from '@wordpress/data';
import { createPortal, useEffect, useRef } from '@wordpress/element';
/**
* Internal Dependencies
@@ -35,6 +35,7 @@ const HelpCenter: React.FC< Container > = ( {
isMinimized: helpCenterSelect.getIsMinimized(),
};
}, [] );
+ const { setUnreadCount } = useDispatch( HELP_CENTER_STORE );
const { currentUser } = useHelpCenterContext();
@@ -46,14 +47,31 @@ const HelpCenter: React.FC< Container > = ( {
useActionHooks();
- const { hasActiveChats, isEligibleForChat } = useChatStatus();
+ const { isEligibleForChat } = useChatStatus();
const { isMessagingScriptLoaded } = useLoadZendeskMessaging(
'zendesk_support_chat_key',
- ( isHelpCenterShown && isEligibleForChat ) || hasActiveChats,
- isEligibleForChat || hasActiveChats
+ isHelpCenterShown && isEligibleForChat,
+ isEligibleForChat
);
+ const { init, initSmooch, destroy, addUnreadCountListener } = useSmooch();
+ const ref = useRef( null );
- useZendeskMessagingBindings( HELP_CENTER_STORE, hasActiveChats, isMessagingScriptLoaded );
+ useEffect( () => {
+ if ( isMessagingScriptLoaded && ref?.current ) {
+ initSmooch( ref.current );
+ }
+ return () => {
+ destroy();
+ };
+ }, [ isMessagingScriptLoaded, ref?.current ] );
+
+ useEffect( () => {
+ if ( init && addUnreadCountListener ) {
+ addUnreadCountListener( ( unreadCount: number ) => {
+ setUnreadCount( unreadCount );
+ } );
+ }
+ }, [ init, setUnreadCount ] );
const openingCoordinates = useOpeningCoordinates( isHelpCenterShown, isMinimized );
@@ -73,12 +91,15 @@ const HelpCenter: React.FC< Container > = ( {
}, [ portalParent, handleClose ] );
return createPortal(
- ,
+ <>
+
+
+ >,
portalParent
);
};
diff --git a/packages/odie-client/src/assets/human-avatar.tsx b/packages/odie-client/src/assets/human-avatar.tsx
new file mode 100644
index 0000000000000..f1ee193b4da6e
--- /dev/null
+++ b/packages/odie-client/src/assets/human-avatar.tsx
@@ -0,0 +1,28 @@
+export const HumanAvatar: React.FC = () => (
+
+);
diff --git a/packages/odie-client/src/components/message/index.tsx b/packages/odie-client/src/components/message/index.tsx
index 1d498768ce426..74b650061fcbe 100644
--- a/packages/odie-client/src/components/message/index.tsx
+++ b/packages/odie-client/src/components/message/index.tsx
@@ -8,6 +8,7 @@ import { useI18n } from '@wordpress/react-i18n';
import clsx from 'clsx';
import { useRef, useState } from 'react';
import ReactDOM from 'react-dom';
+import { HumanAvatar } from '../../assets/human-avatar';
import MaximizeIcon from '../../assets/maximize-icon.svg';
import MinimizeIcon from '../../assets/minimize-icon.svg';
import WapuuAvatar from '../../assets/wapuu-squared-avatar.svg';
@@ -75,15 +76,18 @@ const ChatMessage = ( props: ChatMessageProps & MessageIndicators ) => {
>
) : (
<>
-
+ { message.role === 'human' && }
+ { message.role === 'bot' && (
+
+ ) }
{ message.type === 'placeholder' ? (
{ showExtraContactOptions && extraContactOptions }
- { ! hasFeedback && ! isUser && (
+ { ! hasFeedback && isBot && (
) }
- { ! isUser && (
+ { isBot && (
<>
{ ! showExtraContactOptions && }
diff --git a/packages/odie-client/src/components/send-message-input/index.tsx b/packages/odie-client/src/components/send-message-input/index.tsx
index b0834aaf49b7d..5fcde7f853c8f 100644
--- a/packages/odie-client/src/components/send-message-input/index.tsx
+++ b/packages/odie-client/src/components/send-message-input/index.tsx
@@ -28,7 +28,8 @@ export const OdieSendMessageButton = ( {
const { _x } = useI18n();
const [ messageString, setMessageString ] = useState< string >( '' );
const divContainerRef = useRef< HTMLDivElement >( null );
- const { initialUserMessage, chat, trackEvent, isLoading } = useOdieAssistantContext();
+ const { initialUserMessage, chat, trackEvent, isLoading, messageHandler } =
+ useOdieAssistantContext();
const { mutateAsync: sendOdieMessage } = useOdieSendMessage();
useEffect( () => {
@@ -46,8 +47,11 @@ export const OdieSendMessageButton = ( {
role: 'user',
type: 'message',
} as Message;
-
- await sendOdieMessage( { message } );
+ if ( messageHandler ) {
+ messageHandler( message );
+ } else {
+ await sendOdieMessage( { message } );
+ }
trackEvent( 'chat_message_action_receive' );
} catch ( e ) {
@@ -56,7 +60,7 @@ export const OdieSendMessageButton = ( {
error: error?.message,
} );
}
- }, [ messageString, sendOdieMessage, trackEvent ] );
+ }, [ messageString, sendOdieMessage, trackEvent, messageHandler ] );
const sendMessageIfNotEmpty = useCallback( async () => {
if ( messageString.trim() === '' ) {
diff --git a/packages/odie-client/src/context/index.tsx b/packages/odie-client/src/context/index.tsx
index f1cb7828512ca..e0d5811f88259 100644
--- a/packages/odie-client/src/context/index.tsx
+++ b/packages/odie-client/src/context/index.tsx
@@ -37,6 +37,7 @@ type OdieAssistantContextInterface = {
extraContactOptions?: ReactNode;
lastNudge: Nudge | null;
lastMessageInView?: boolean;
+ messageHandler?: ( message: Message ) => void;
navigateToContactOptions?: () => void;
navigateToSupportDocs?: ( blogId: string, postId: string, title: string, link: string ) => void;
odieClientId: string;
@@ -72,6 +73,7 @@ const defaultContextInterfaceValues = {
isUserEligible: false,
lastNudge: null,
lastMessageRef: null,
+ messageHandler: noop,
navigateToContactOptions: noop,
navigateToSupportDocs: noop,
odieClientId: '',
@@ -111,6 +113,7 @@ type OdieAssistantProviderProps = {
isLoadingEnvironment?: boolean;
currentUser: CurrentUser;
extraContactOptions?: ReactNode;
+ messageHandler?: ( message: Message ) => void;
logger?: ( message: string, properties: Record< string, unknown > ) => void;
loggerEventNamePrefix?: string;
navigateToContactOptions?: () => void;
@@ -132,6 +135,7 @@ const OdieAssistantProvider: FC< OdieAssistantProviderProps > = ( {
enabled = true,
logger,
loggerEventNamePrefix,
+ messageHandler,
navigateToContactOptions,
navigateToSupportDocs,
selectedSiteId,
@@ -271,6 +275,7 @@ const OdieAssistantProvider: FC< OdieAssistantProviderProps > = ( {
isVisible,
lastNudge,
lastMessageInView,
+ messageHandler,
navigateToContactOptions,
navigateToSupportDocs,
odieClientId,
diff --git a/packages/odie-client/src/types/index.ts b/packages/odie-client/src/types/index.ts
index 6319388fb59c6..05319005744d2 100644
--- a/packages/odie-client/src/types/index.ts
+++ b/packages/odie-client/src/types/index.ts
@@ -71,7 +71,7 @@ export type Nudge = {
context?: Record< string, unknown >;
};
-export type MessageRole = 'user' | 'bot';
+export type MessageRole = 'user' | 'bot' | 'human';
export type MessageType =
| 'message'
diff --git a/packages/zendesk-client/src/types.ts b/packages/zendesk-client/src/types.ts
index adc78db7bd8e0..e44efa8fa1315 100644
--- a/packages/zendesk-client/src/types.ts
+++ b/packages/zendesk-client/src/types.ts
@@ -45,3 +45,17 @@ export type MessagingMetadata = {
};
export type ZendeskAuthType = 'zendesk' | 'messenger';
+
+export type UseSmoochProps = {
+ init: boolean;
+ destroy: () => void;
+ initSmooch: ( ref: HTMLDivElement ) => void;
+ createConversation?: (
+ userfields: UserFields,
+ metadata: Conversation[ 'metadata' ]
+ ) => Promise< void >;
+ getConversation?: ( chatId?: number ) => Promise< Conversation | undefined >;
+ sendMessage?: ( message: string, chatId?: number | null ) => void;
+ addMessengerListener?: ( callback: ( message: Message ) => void ) => void;
+ addUnreadCountListener?: ( callback: ( unreadCount: number ) => void ) => void;
+};
diff --git a/packages/zendesk-client/src/use-smooch.ts b/packages/zendesk-client/src/use-smooch.ts
index 4280fd75c959e..6fdd1d31d0627 100644
--- a/packages/zendesk-client/src/use-smooch.ts
+++ b/packages/zendesk-client/src/use-smooch.ts
@@ -2,7 +2,7 @@ import { recordTracksEvent } from '@automattic/calypso-analytics';
import { useCallback, useState } from 'react';
import Smooch from 'smooch';
import { SMOOCH_INTEGRATION_ID } from './constants';
-import { UserFields } from './types';
+import { UserFields, UseSmoochProps } from './types';
import { useAuthenticateZendeskMessaging } from './use-authenticate-zendesk-messaging';
import { useUpdateZendeskUserFields } from './use-update-zendesk-user-fields';
@@ -50,7 +50,7 @@ const addUnreadCountListener = ( callback: ( unreadCount: number ) => void ) =>
Smooch.on( 'unreadCount', callback );
};
-export const useSmooch = () => {
+export const useSmooch = (): UseSmoochProps => {
const [ init, setInit ] = useState( typeof Smooch.getConversations === 'function' );
const { data: authData } = useAuthenticateZendeskMessaging( true, 'messenger' );
const { isPending: isSubmittingZendeskUserFields, mutateAsync: submitUserFields } =