From 8300e323e9bbd5075a4de558b249535128e30426 Mon Sep 17 00:00:00 2001 From: shafin-deriv Date: Fri, 29 Nov 2024 18:57:57 +0800 Subject: [PATCH] chore: fix logout issue and account switcher style issues --- src/app/CoreStoreProvider.tsx | 2 +- .../layout/header/account-switcher.tsx | 353 ++++++++++-------- src/components/layout/header/header.scss | 11 + .../header/mobile-menu/menu-content.tsx | 9 +- .../mobile-menu/use-mobile-menu-config.tsx | 54 +-- .../loader/rectangle-skeleton/index.tsx | 8 + .../rectangle-skeleton.scss | 15 + src/hooks/auth/useOauth2.ts | 17 +- src/stores/client-store.ts | 9 + 9 files changed, 287 insertions(+), 191 deletions(-) create mode 100644 src/components/loader/rectangle-skeleton/index.tsx create mode 100644 src/components/loader/rectangle-skeleton/rectangle-skeleton.scss diff --git a/src/app/CoreStoreProvider.tsx b/src/app/CoreStoreProvider.tsx index 60cb237f..c971b0f1 100644 --- a/src/app/CoreStoreProvider.tsx +++ b/src/app/CoreStoreProvider.tsx @@ -21,7 +21,7 @@ const CoreStoreProvider: React.FC<{ children: React.ReactNode }> = observer(({ c const { currentLang } = useTranslations(); - const { oAuthLogout } = useOauth2({ handleLogout: async () => client.logout() }); + const { oAuthLogout } = useOauth2({ handleLogout: async () => client.logout(), client }); const activeAccount = useMemo( () => accountList?.find(account => account.loginid === activeLoginid), diff --git a/src/components/layout/header/account-switcher.tsx b/src/components/layout/header/account-switcher.tsx index aa634160..a8aff25c 100644 --- a/src/components/layout/header/account-switcher.tsx +++ b/src/components/layout/header/account-switcher.tsx @@ -1,9 +1,10 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import React from 'react'; import classNames from 'classnames'; import clsx from 'clsx'; import { observer } from 'mobx-react-lite'; import { CurrencyIcon } from '@/components/currency/currency-icon'; +import RectangleSkeleton from '@/components/loader/rectangle-skeleton'; import { getDecimalPlaces, standalone_routes } from '@/components/shared'; import Popover from '@/components/shared_ui/popover'; import { api_base } from '@/external/bot-skeleton'; @@ -11,9 +12,10 @@ import useActiveAccount from '@/hooks/api/account/useActiveAccount'; import { useOauth2 } from '@/hooks/auth/useOauth2'; import { useApiBase } from '@/hooks/useApiBase'; import { useStore } from '@/hooks/useStore'; +import { waitForDomElement } from '@/utils/dom-observer'; import { LegacyDerivIcon, LegacyLogout1pxIcon } from '@deriv/quill-icons/Legacy'; import { Localize, localize } from '@deriv-com/translations'; -import { AccountSwitcher as UIAccountSwitcher, Button, Divider, Text } from '@deriv-com/ui'; +import { AccountSwitcher as UIAccountSwitcher, Button, Divider, Text, useDevice } from '@deriv-com/ui'; type TModifiedAccount = ReturnType['accountList'][number] & { balance: string; @@ -76,37 +78,150 @@ const NoEuAccounts = ({ isVirtual, tabs_labels }) => { ); }; -const RenderAccountItems = ({ - isVirtual, - modifiedAccountList, - modifiedCRAccountList, - modifiedMFAccountList, - switchAccount, - activeLoginId, -}: TAccountSwitcherProps) => { - const { client } = useStore(); +const RenderAccountItems = observer( + ({ + isVirtual, + modifiedAccountList, + modifiedCRAccountList, + modifiedMFAccountList, + switchAccount, + activeLoginId, + }: TAccountSwitcherProps) => { + const { client } = useStore(); - const show_manage_button = client?.loginid?.includes('CR') || client?.loginid?.includes('MF'); + const show_manage_button = client?.loginid?.includes('CR') || client?.loginid?.includes('MF'); - const account_switcher_title_non_eu = - modifiedMFAccountList?.length === 0 ? localize('Deriv accounts') : localize('Non-Eu Deriv accounts'); - const account_switcher_title_eu = modifiedMFAccountList - ? localize('Eu Deriv accounts') - : localize('Deriv accounts'); - const { oAuthLogout } = useOauth2({ handleLogout: async () => client.logout() }); + const account_switcher_title_non_eu = + modifiedMFAccountList?.length === 0 ? localize('Deriv accounts') : localize('Non-Eu Deriv accounts'); + const account_switcher_title_eu = modifiedMFAccountList + ? localize('Eu Deriv accounts') + : localize('Deriv accounts'); + const { oAuthLogout } = useOauth2({ handleLogout: async () => client.logout(), client }); + + useEffect(() => { + // Update the max-height from the accordion content set from deriv-com/ui + const parent_container = document.getElementsByClassName('account-switcher-panel')?.[0] as HTMLDivElement; + if (!isVirtual && parent_container) { + parent_container.style.maxHeight = '70vh'; + waitForDomElement('.deriv-accordion__content', parent_container)?.then((accordionElement: unknown) => { + const element = accordionElement as HTMLDivElement; + if (element) { + element.style.maxHeight = '70vh'; + } + }); + } + }, [isVirtual]); + + if (isVirtual) { + return ( + <> + + {modifiedAccountList + ?.filter(account => account.is_virtual) + .map(account => ( + + { + if (!account.is_disabled) switchAccount(account.loginid); + }} + onResetBalance={ + isVirtual && activeLoginId === account.loginid + ? () => { + api_base?.api?.send({ + topup_virtual: 1, + }); + } + : undefined + } + /> + + ))} + + +
+ + {localize(`Looking for CFD accounts? Go to Trader's Hub`)} + + + + {client.is_logging_out ? ( +
+ +
+ ) : ( +
+ + {localize('Log out')} + + +
+ )} +
+
+ + ); + } - if (isVirtual) { return ( <> - - {modifiedAccountList - ?.filter(account => account.is_virtual) - .map(account => ( + {!isVirtual && modifiedCRAccountList?.length > 0 ? ( + + {modifiedCRAccountList.map(account => ( + + { + if (!account.is_disabled) switchAccount(account.loginid); + }} + /> + + ))} + + ) : ( + + )} + {!isVirtual && modifiedMFAccountList?.length > 0 && ( + + {modifiedMFAccountList.map(account => ( { if (!account.is_disabled) switchAccount(account.loginid); }} - onResetBalance={ - isVirtual && - activeLoginId === account.loginid && - Number(account.balance) !== 10000 - ? () => { - api_base?.api?.send({ - topup_virtual: 1, - }); - } - : undefined - } /> ))} - + + )} -
+
{localize(`Looking for CFD accounts? Go to Trader's Hub`)} - - <> -
- + {show_manage_button && ( +
-
+ + + )} + + {client.is_logging_out ? ( +
+ +
+ ) : ( +
{ + await oAuthLogout(); + }} + > + + {localize('Log out')} + + +
+ )} +
+
); } - - return ( - <> - {!isVirtual && modifiedCRAccountList?.length > 0 ? ( - - {modifiedCRAccountList.map(account => ( - - { - if (!account.is_disabled) switchAccount(account.loginid); - }} - /> - - ))} - - ) : ( - - )} - {!isVirtual && modifiedMFAccountList?.length > 0 && ( - - {modifiedMFAccountList.map(account => ( - - { - if (!account.is_disabled) switchAccount(account.loginid); - }} - /> - - ))} - - )} - -
- - {localize(`Looking for CFD accounts? Go to Trader's Hub`)} - - -
- {show_manage_button && ( - - )} - -
{ - await oAuthLogout(); - }} - > - - {localize('Log out')} - - -
-
-
-
- - ); -}; +); const AccountSwitcher = observer(({ activeAccount }: TAccountSwitcher) => { + const { isDesktop } = useDevice(); const { accountList } = useApiBase(); const { ui, run_panel, client } = useStore(); const { account_switcher_disabled_message } = ui; @@ -330,7 +361,7 @@ const AccountSwitcher = observer(({ activeAccount }: TAccountSwitcher) => { tabsLabels={tabs_labels} modalContentStyle={{ content: { - top: '30%', + top: isDesktop ? '30%' : '50%', borderRadius: '10px', }, }} diff --git a/src/components/layout/header/header.scss b/src/components/layout/header/header.scss index 6202247d..8e7814b7 100644 --- a/src/components/layout/header/header.scss +++ b/src/components/layout/header/header.scss @@ -32,6 +32,7 @@ justify-content: space-between; align-items: center; padding: 0 1rem; + background-color: var(--general-main-1); &--hide-manage-button { justify-content: flex-end; @@ -167,6 +168,16 @@ margin-inline-end: 1rem; font-size: var(--text-size-xs); } + + &--loader { + display: flex; + justify-content: flex-end; + padding: 16px; + } + } + + &__tradershub-link { + background: var(--general-main-1); } &__footer { diff --git a/src/components/layout/header/mobile-menu/menu-content.tsx b/src/components/layout/header/mobile-menu/menu-content.tsx index ea0dd41f..9e90c495 100644 --- a/src/components/layout/header/mobile-menu/menu-content.tsx +++ b/src/components/layout/header/mobile-menu/menu-content.tsx @@ -1,12 +1,15 @@ import clsx from 'clsx'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@/hooks/useStore'; import { MenuItem, Text, useDevice } from '@deriv-com/ui'; import PlatformSwitcher from '../platform-switcher'; import useMobileMenuConfig from './use-mobile-menu-config'; -const MenuContent = () => { +const MenuContent = observer(() => { const { isDesktop } = useDevice(); + const { client } = useStore(); const textSize = isDesktop ? 'sm' : 'md'; - const { config } = useMobileMenuConfig(); + const { config } = useMobileMenuConfig(client); return (
@@ -78,6 +81,6 @@ const MenuContent = () => {
); -}; +}); export default MenuContent; diff --git a/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx b/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx index cc299dd6..abec4709 100644 --- a/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx +++ b/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx @@ -1,10 +1,11 @@ import { ComponentProps, ReactNode } from 'react'; import Livechat from '@/components/chat/Livechat'; +import useIsLiveChatWidgetAvailable from '@/components/chat/useIsLiveChatWidgetAvailable'; import { standalone_routes } from '@/components/shared'; import { useOauth2 } from '@/hooks/auth/useOauth2'; import useRemoteConfig from '@/hooks/growthbook/useRemoteConfig'; -import { useStore } from '@/hooks/useStore'; import useThemeSwitcher from '@/hooks/useThemeSwitcher'; +import RootStore from '@/stores/root-store'; import { ACCOUNT_LIMITS, HELP_CENTRE, RESPONSIBLE } from '@/utils/constants'; import { LegacyAccountLimitsIcon, @@ -31,23 +32,24 @@ type TMenuConfig = { RightComponent?: ReactNode; as: 'a' | 'button'; href?: string; - label: string; + label: ReactNode; onClick?: () => void; removeBorderBottom?: boolean; submenu?: TSubmenuSection; target?: ComponentProps<'a'>['target']; }[]; -const useMobileMenuConfig = () => { +const useMobileMenuConfig = (client?: RootStore['client']) => { const { localize } = useTranslations(); const { is_dark_mode_on, toggleTheme } = useThemeSwitcher(); - const { client } = useStore(); - const { oAuthLogout } = useOauth2({ handleLogout: async () => client.logout() }); + const { oAuthLogout } = useOauth2({ handleLogout: async () => client?.logout(), client }); const { data } = useRemoteConfig(true); const { cs_chat_whatsapp } = data; + const { is_livechat_available } = useIsLiveChatWidgetAvailable(); + const menuConfig: TMenuConfig[] = [ [ { @@ -116,27 +118,31 @@ const useMobileMenuConfig = () => { target: '_blank', } : null, - { - as: 'button', - label: localize('Live chat'), - LeftComponent: Livechat, - onClick: () => { - window.enable_freshworks_live_chat - ? window.fcWidget.open() - : window.LiveChatWidget?.call('maximize'); - }, - }, + is_livechat_available + ? { + as: 'button', + label: localize('Live chat'), + LeftComponent: Livechat, + onClick: () => { + window.enable_freshworks_live_chat + ? window.fcWidget.open() + : window.LiveChatWidget?.call('maximize'); + }, + } + : null, ] as TMenuConfig ).filter(Boolean), - [ - { - as: 'button', - label: localize('Log out'), - LeftComponent: LegacyLogout1pxIcon, - onClick: oAuthLogout, - removeBorderBottom: true, - }, - ], + client?.is_logged_in + ? [ + { + as: 'button', + label: localize('Log out'), + LeftComponent: LegacyLogout1pxIcon, + onClick: oAuthLogout, + removeBorderBottom: true, + }, + ] + : [], ]; return { diff --git a/src/components/loader/rectangle-skeleton/index.tsx b/src/components/loader/rectangle-skeleton/index.tsx new file mode 100644 index 00000000..591234b5 --- /dev/null +++ b/src/components/loader/rectangle-skeleton/index.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import './rectangle-skeleton.scss'; + +const RectangleSkeleton: React.FC<{ width: string; height: string }> = ({ width, height }) => { + return
; +}; + +export default RectangleSkeleton; diff --git a/src/components/loader/rectangle-skeleton/rectangle-skeleton.scss b/src/components/loader/rectangle-skeleton/rectangle-skeleton.scss new file mode 100644 index 00000000..45755341 --- /dev/null +++ b/src/components/loader/rectangle-skeleton/rectangle-skeleton.scss @@ -0,0 +1,15 @@ +.skeleton { + background: linear-gradient(90deg, var(--general-main-1) 25%, var(--general-main-2) 50%, var(--general-main-3) 75%) + 0 0 / 200% 100%; + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + + 100% { + background-position: 200% 0; + } +} diff --git a/src/hooks/auth/useOauth2.ts b/src/hooks/auth/useOauth2.ts index 0c7d8429..153f0b85 100644 --- a/src/hooks/auth/useOauth2.ts +++ b/src/hooks/auth/useOauth2.ts @@ -1,3 +1,4 @@ +import RootStore from '@/stores/root-store'; import { TOAuth2EnabledAppList, useIsOAuth2Enabled, useOAuth2 } from '@deriv-com/auth-client'; import useGrowthbookGetFeatureValue from '../growthbook/useGrowthbookGetFeatureValue'; @@ -14,7 +15,13 @@ import useGrowthbookGetFeatureValue from '../growthbook/useGrowthbookGetFeatureV * @param {{ handleLogout?: () => Promise }} [options] - An object with an optional `handleLogout` property. * @returns {{ isOAuth2Enabled: boolean; oAuthLogout: () => Promise }} */ -export const useOauth2 = ({ handleLogout }: { handleLogout?: () => Promise } = {}) => { +export const useOauth2 = ({ + handleLogout, + client, +}: { + handleLogout?: () => Promise; + client?: RootStore['client']; +} = {}) => { const { featureFlagValue: oAuth2EnabledApps, isGBLoaded: OAuth2EnabledAppsInitialised } = useGrowthbookGetFeatureValue({ featureFlag: 'hydra_be', @@ -31,5 +38,11 @@ export const useOauth2 = ({ handleLogout }: { handleLogout?: () => Promise }; const { OAuth2Logout: oAuthLogout } = useOAuth2(oAuthGrowthbookConfig, handleLogout ?? (() => Promise.resolve())); - return { isOAuth2Enabled, oAuthLogout }; + + const logoutHandler = async () => { + client?.setIsLoggingOut(true); + await oAuthLogout(); + }; + + return { isOAuth2Enabled, oAuthLogout: logoutHandler }; }; diff --git a/src/stores/client-store.ts b/src/stores/client-store.ts index 7776bc05..2811576b 100644 --- a/src/stores/client-store.ts +++ b/src/stores/client-store.ts @@ -25,6 +25,7 @@ export default class ClientStore { accounts: Record = {}; is_landing_company_loaded = false; all_accounts_balance: Balance | null = null; + is_logging_out = false; // TODO: fix with self exclusion updateSelfExclusion = () => {}; @@ -43,6 +44,7 @@ export default class ClientStore { loginid: observable, upgradeable_landing_companies: observable, website_status: observable, + is_logging_out: observable, active_accounts: computed, clients_country: computed, is_bot_allowed: computed, @@ -64,6 +66,7 @@ export default class ClientStore { setBalance: action, setCurrency: action, setIsLoggedIn: action, + setIsLoggingOut: action, setLandingCompany: action, setLoginId: action, setWebsiteStatus: action, @@ -274,6 +277,10 @@ export default class ClientStore { this.all_accounts_balance = all_accounts_balance ?? null; }; + setIsLoggingOut = (is_logging_out: boolean) => { + this.is_logging_out = is_logging_out; + }; + logout = () => { // reset all the states this.account_list = []; @@ -298,6 +305,8 @@ export default class ClientStore { setAccountList([]); setAuthData(null); + this.setIsLoggingOut(false); + // disable livechat window.LC_API?.close_chat?.(); window.LiveChatWidget?.call('hide');