diff --git a/index.html b/index.html index 82a5211a..3227adf1 100644 --- a/index.html +++ b/index.html @@ -17,5 +17,46 @@
+ + + diff --git a/src/app/app-content.jsx b/src/app/app-content.jsx index bb378dbf..3d04e4cb 100644 --- a/src/app/app-content.jsx +++ b/src/app/app-content.jsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import { observer } from 'mobx-react-lite'; import { ToastContainer } from 'react-toastify'; +import useLiveChat from '@/components/chat/useLiveChat'; import { getUrlBase } from '@/components/shared'; import TncStatusUpdateModal from '@/components/tnc-status-update-modal'; import TransactionDetailsModal from '@/components/transaction-details'; @@ -40,6 +41,20 @@ const AppContent = observer(() => { initTrackJS(client.loginid); + const livechat_client_information = { + is_client_store_initialized: client?.is_logged_in ? !!client?.account_settings?.email : !!client, + is_logged_in: client?.is_logged_in, + loginid: client?.loginid, + landing_company_shortcode: client?.landing_company_shortcode, + currency: client?.currency, + residence: client?.residence, + email: client?.account_settings?.email, + first_name: client?.account_settings?.first_name, + last_name: client?.account_settings?.last_name, + }; + + useLiveChat(livechat_client_information); + useEffect(() => { if (connectionStatus === CONNECTION_STATUS.OPENED) { setIsApiInitialized(true); diff --git a/src/components/chat/Livechat.tsx b/src/components/chat/Livechat.tsx new file mode 100644 index 00000000..5fb2360c --- /dev/null +++ b/src/components/chat/Livechat.tsx @@ -0,0 +1,55 @@ +import { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; +import { V2GetActiveToken } from '@/external/bot-skeleton/services/api/appId'; +import { LegacyLiveChatOutlineIcon } from '@deriv/quill-icons/Legacy'; +import { localize } from '@deriv-com/translations'; +import { Tooltip, useDevice } from '@deriv-com/ui'; +import useFreshChat from './useFreshchat'; +import useIsLiveChatWidgetAvailable from './useIsLiveChatWidgetAvailable'; + +const Livechat = observer(() => { + const { isDesktop } = useDevice(); + + const token = V2GetActiveToken() ?? null; + + const { is_livechat_available } = useIsLiveChatWidgetAvailable(); + const { isReady, featureFlagValue, widget } = useFreshChat(token); + + useEffect(() => { + window.enable_freshworks_live_chat = !!featureFlagValue; + }, [featureFlagValue]); + + const isFreshchatEnabledButNotReady = featureFlagValue && !isReady; + const isNeitherChatNorLiveChatAvailable = !is_livechat_available && !featureFlagValue; + + if (isFreshchatEnabledButNotReady || isNeitherChatNorLiveChatAvailable) { + return null; + } + + // Quick fix for making sure livechat won't popup if feature flag is late to enable. + // We will add a refactor after this + setInterval(() => { + if (featureFlagValue) { + window.LiveChatWidget?.call('destroy'); + } + }, 10); + + const liveChatClickHandler = () => { + featureFlagValue ? widget.open() : window.LiveChatWidget?.call('maximize'); + }; + + if (isDesktop) + return ( +
+ + + +
+ ); + + // For mobile sidebar we only need the component to be rendered. + // Design is handled from use-mobile-menu-config.tsx + return ; +}); + +export default Livechat; diff --git a/src/components/chat/useFreshchat.ts b/src/components/chat/useFreshchat.ts new file mode 100644 index 00000000..0b644e2a --- /dev/null +++ b/src/components/chat/useFreshchat.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; +import { useScript } from 'usehooks-ts'; +import useGrowthbookGetFeatureValue from '@/hooks/growthbook/useGrowthbookGetFeatureValue'; + +const useFreshChat = (token: string | null) => { + const scriptStatus = useScript('https://static.deriv.com/scripts/freshchat/v1.0.2.js'); + const [isReady, setIsReady] = useState(false); + const { featureFlagValue, isGBLoaded } = useGrowthbookGetFeatureValue({ + featureFlag: 'enable_freshchat', + }); + + useEffect(() => { + const checkFcWidget = (intervalId: NodeJS.Timeout) => { + if (typeof window !== 'undefined') { + if (window.fcWidget?.isInitialized() == true && !isReady) { + setIsReady(true); + clearInterval(intervalId); + } + } + }; + + const initFreshChat = async () => { + if (scriptStatus === 'ready' && window.FreshChat && window.fcSettings) { + window.FreshChat.initialize({ + token, + hideButton: true, + }); + + const intervalId = setInterval(() => checkFcWidget(intervalId), 500); + + return () => clearInterval(intervalId); + } + }; + + featureFlagValue && isGBLoaded && initFreshChat(); + }, [featureFlagValue, isGBLoaded, isReady, scriptStatus, token]); + + return { + isReady, + widget: window.fcWidget, + featureFlagValue, + }; +}; + +export default useFreshChat; diff --git a/src/components/chat/useIsLiveChatWidgetAvailable.ts b/src/components/chat/useIsLiveChatWidgetAvailable.ts new file mode 100644 index 00000000..0c225596 --- /dev/null +++ b/src/components/chat/useIsLiveChatWidgetAvailable.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react'; + +const useIsLiveChatWidgetAvailable = () => { + const [is_livechat_available, setIsLivechatAvailable] = useState(false); + + useEffect(() => { + window.LiveChatWidget?.on('ready', data => { + if (data.state.availability === 'online') setIsLivechatAvailable(true); + }); + }, []); + + return { + is_livechat_available, + }; +}; + +export default useIsLiveChatWidgetAvailable; diff --git a/src/components/chat/useLiveChat.ts b/src/components/chat/useLiveChat.ts new file mode 100644 index 00000000..6c24b6af --- /dev/null +++ b/src/components/chat/useLiveChat.ts @@ -0,0 +1,92 @@ +import { useEffect } from 'react'; +import Cookies from 'js-cookie'; +import useRemoteConfig from '@/hooks/growthbook/useRemoteConfig'; +import { URLUtils } from '@deriv-com/utils'; + +type TLiveChatClientInformation = { + is_client_store_initialized: boolean; + is_logged_in: boolean; + loginid?: string; + landing_company_shortcode?: string; + currency?: string; + residence?: string; + email?: string; + first_name?: string; + last_name?: string; +}; + +const useLiveChat = (client_information: TLiveChatClientInformation) => { + const { + is_client_store_initialized, + landing_company_shortcode = ' ', + currency = ' ', + email = ' ', + is_logged_in = ' ', + loginid = ' ', + residence = ' ', + last_name = ' ', + first_name = ' ', + } = client_information; + + const url_query_string = window.location.search; + const url_params = new URLSearchParams(url_query_string); + const reset_password = URLUtils.getQueryParameter('action') === 'reset_password'; + const should_disable_livechat = url_params.get('code') && reset_password; + + const { data } = useRemoteConfig(true); + const { cs_chat_livechat } = data; + + useEffect(() => { + if (is_client_store_initialized && cs_chat_livechat) { + window.LiveChatWidget?.init(); + } + }, [is_client_store_initialized, cs_chat_livechat]); + + useEffect(() => { + if (!should_disable_livechat && is_client_store_initialized) { + window.LiveChatWidget?.on('ready', data => { + //hide red widget on responsive + if (data.state.visibility === 'minimized') { + window.LiveChatWidget?.call('hide'); + } + const utm_data = JSON.parse(Cookies.get('utm_data') || '{}'); + const { utm_source, utm_medium, utm_campaign } = utm_data; + + const session_variables = { + is_logged_in: String(is_logged_in), + utm_source: utm_source || ' ', + utm_medium: utm_medium || ' ', + utm_campaign: utm_campaign || ' ', + loginid: is_logged_in ? loginid : ' ', + landing_company_shortcode: is_logged_in ? landing_company_shortcode : ' ', + currency: is_logged_in ? currency : ' ', + residence: is_logged_in ? residence : ' ', + email: is_logged_in ? email : ' ', + }; + + window.LiveChatWidget?.call('set_session_variables', session_variables); + + if (is_logged_in) { + window.LiveChatWidget?.call('set_customer_email', email); + window.LiveChatWidget?.call('set_customer_name', `${first_name} ${last_name}`); + } else { + window.LiveChatWidget?.call('set_customer_email', ' '); + window.LiveChatWidget?.call('set_customer_name', ' '); + } + }); + } + }, [ + email, + should_disable_livechat, + loginid, + is_logged_in, + landing_company_shortcode, + is_client_store_initialized, + currency, + first_name, + last_name, + residence, + ]); +}; + +export default useLiveChat; diff --git a/src/components/layout/footer/Livechat.tsx b/src/components/layout/footer/Livechat.tsx deleted file mode 100644 index 121df2d1..00000000 --- a/src/components/layout/footer/Livechat.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { LegacyLiveChatOutlineIcon } from '@deriv/quill-icons/Legacy'; -import { useTranslations } from '@deriv-com/translations'; -import { Tooltip } from '@deriv-com/ui'; - -const Livechat = () => { - const { localize } = useTranslations(); - // TODO add the logic of this - // TODO add the test cases for this after adding the logics - - return ( - - - - ); -}; - -export default Livechat; diff --git a/src/components/layout/footer/index.tsx b/src/components/layout/footer/index.tsx index 11c7c963..754b3509 100644 --- a/src/components/layout/footer/index.tsx +++ b/src/components/layout/footer/index.tsx @@ -1,7 +1,9 @@ +import useRemoteConfig from '@/hooks/growthbook/useRemoteConfig'; import useModalManager from '@/hooks/useModalManager'; import { LANGUAGES } from '@/utils/languages'; import { useTranslations } from '@deriv-com/translations'; import { DesktopLanguagesModal } from '@deriv-com/ui'; +import Livechat from '../../chat/Livechat'; import AccountLimits from './AccountLimits'; import ChangeTheme from './ChangeTheme'; import Deriv from './Deriv'; @@ -9,7 +11,6 @@ import Endpoint from './Endpoint'; import FullScreen from './FullScreen'; import HelpCentre from './HelpCentre'; import LanguageSettings from './LanguageSettings'; -import Livechat from './Livechat'; import NetworkStatus from './NetworkStatus'; import ResponsibleTrading from './ResponsibleTrading'; import ServerTime from './ServerTime'; @@ -22,6 +23,9 @@ const Footer = () => { const openLanguageSettingModal = () => showModal('DesktopLanguagesModal'); + const { data } = useRemoteConfig(true); + const { cs_chat_whatsapp } = data; + return (