Skip to content

Commit

Permalink
Merge pull request #135 from shafin-deriv/shafin/BOT-2392/chore-chat-app
Browse files Browse the repository at this point in the history
chore: implement live chat and fresh chat
  • Loading branch information
sandeep-deriv authored Nov 22, 2024
2 parents f4246ad + 5742ff7 commit 617319a
Show file tree
Hide file tree
Showing 19 changed files with 526 additions and 112 deletions.
41 changes: 41 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,46 @@
<div id="modal_root" class="modal-root"></div>
<div id="popup_root" class="popup-root"></div>
<div id="root"></div>
<!-- LiveChat script -->
<script type="text/javascript" defer>
window.__lc = window.__lc || {};
window.__lc.license = 12049137;
window.__lc.asyncInit = true;
(function (n, t, c) {
function i(n) {
return e._h ? e._h.apply(null, n) : e._q.push(n);
}
var e = {
_q: [],
_h: null,
_v: '2.0',
on: function () {
i(['on', c.call(arguments)]);
},
once: function () {
i(['once', c.call(arguments)]);
},
off: function () {
i(['off', c.call(arguments)]);
},
get: function () {
if (!e._h) throw new Error('[LiveChatWidget] You can’t use getters before load.');
return i(['get', c.call(arguments)]);
},
call: function () {
i(['call', c.call(arguments)]);
},
init: function () {
var n = t.createElement('script');
(n.async = !0),
(n.type = 'text/javascript'),
(n.src = 'https://cdn.livechatinc.com/tracking.js'),
t.head.appendChild(n);
},
};
!n.__lc.asyncInit && e.init(), (n.LiveChatWidget = n.LiveChatWidget || e);
})(window, document, [].slice);
</script>
<!-- End LiveChat script -->
</body>
</html>
15 changes: 15 additions & 0 deletions src/app/app-content.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down
55 changes: 55 additions & 0 deletions src/components/chat/Livechat.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div onKeyDown={liveChatClickHandler} onClick={liveChatClickHandler}>
<Tooltip as='button' className='app-footer__icon' tooltipContent={localize('Live chat')}>
<LegacyLiveChatOutlineIcon iconSize='xs' />
</Tooltip>
</div>
);

// For mobile sidebar we only need the component to be rendered.
// Design is handled from use-mobile-menu-config.tsx
return <LegacyLiveChatOutlineIcon iconSize='xs' className='mobile-menu__content__items--right-margin' />;
});

export default Livechat;
45 changes: 45 additions & 0 deletions src/components/chat/useFreshchat.ts
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 17 additions & 0 deletions src/components/chat/useIsLiveChatWidgetAvailable.ts
Original file line number Diff line number Diff line change
@@ -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;
92 changes: 92 additions & 0 deletions src/components/chat/useLiveChat.ts
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 0 additions & 17 deletions src/components/layout/footer/Livechat.tsx

This file was deleted.

8 changes: 6 additions & 2 deletions src/components/layout/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
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';
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';
Expand All @@ -22,6 +23,9 @@ const Footer = () => {

const openLanguageSettingModal = () => showModal('DesktopLanguagesModal');

const { data } = useRemoteConfig(true);
const { cs_chat_whatsapp } = data;

return (
<footer className='app-footer'>
<FullScreen />
Expand All @@ -33,7 +37,7 @@ const Footer = () => {
<ResponsibleTrading />
<Deriv />
<Livechat />
<WhatsApp />
{cs_chat_whatsapp && <WhatsApp />}
<div className='app-footer__vertical-line' />
<ServerTime />
<div className='app-footer__vertical-line' />
Expand Down
Loading

0 comments on commit 617319a

Please sign in to comment.