From 24aba7acf2bd23406c49dd51c883359d1112e130 Mon Sep 17 00:00:00 2001 From: Taysuisin Date: Mon, 5 Feb 2024 12:22:03 +0800 Subject: [PATCH 1/2] chore: create effortless login modal --- .../effortless-login-content.tsx | 39 +++++++++++++++ .../effortless-login-modal-wrapper.tsx | 36 ++++++++++++++ .../effortless-login-modal.scss | 44 +++++++++++++++++ .../effortless-login-modal.tsx | 17 +++++++ .../effortless-login-tips.tsx | 48 +++++++++++++++++++ .../Containers/EffortlessLoginModal/index.ts | 3 ++ .../src/App/Containers/Modals/app-modals.jsx | 9 ++++ packages/core/src/Stores/client-store.js | 1 + packages/hooks/src/index.ts | 1 + .../hooks/src/useShowEffortlessLoginModal.ts | 19 ++++++++ 10 files changed, 217 insertions(+) create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-content.tsx create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal-wrapper.tsx create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.scss create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.tsx create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-tips.tsx create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/index.ts create mode 100644 packages/hooks/src/useShowEffortlessLoginModal.ts diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-content.tsx b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-content.tsx new file mode 100644 index 000000000000..a74fbc059774 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-content.tsx @@ -0,0 +1,39 @@ +import FormBody from '@deriv/account/src/Components/form-body'; +import EffortlessLoginModalWrapper from './effortless-login-modal-wrapper'; +import { Button, Icon, Text } from '@deriv/components'; +import React from 'react'; +import FormFooter from '@deriv/account/src/Components/form-footer'; +import { Localize } from '@deriv/translations'; +import { EffortLessLoginTips } from './effortless-login-tips'; + +const EffortlessLoginContent = () => ( + + + + + Effortless login with passkeys + + + + + + + +); + +export default EffortlessLoginContent; diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal-wrapper.tsx b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal-wrapper.tsx new file mode 100644 index 000000000000..67330444acb7 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal-wrapper.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { useHistory } from 'react-router'; +import ReactDOM from 'react-dom'; +import { Text } from '@deriv/components'; +import { routes } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; + +const EffortlessLoginModalWrapper = ({ children }: React.PropsWithChildren) => { + const portal_element = document.getElementById('modal_root_absolute'); + const history = useHistory(); + + if (!portal_element) return null; + return ReactDOM.createPortal( +
+ { + localStorage.setItem('show_effortless_login_modal', JSON.stringify(false)); + history.push(routes.traders_hub); + }} + > + + + {children} +
, + portal_element + ); +}; + +export default EffortlessLoginModalWrapper; diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.scss b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.scss new file mode 100644 index 000000000000..9db0b28ac926 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.scss @@ -0,0 +1,44 @@ +.effortless-login-modal { + &__overlay { + &-container { + background: var(--general-main-1); + height: 100vh; + width: 100vw; + + .effortless-login-modal__wrapper { + display: block; + margin-top: 2rem; + + .dc-icon { + display: block; + margin-inline: auto; + } + + .effortless-login-modal { + &__title { + margin-top: 2rem; + } + + &__overlay-tip { + padding-block: 1.6rem; + + &:not(:last-child) { + display: flex; + border-bottom: 1px solid var(--border-disabled); + gap: 0.8rem; + } + + .dc-icon { + margin-inline: unset; + } + } + } + } + } + + &-header { + padding: 1.2rem 2.4rem; + border-bottom: 1px solid var(--border-disabled); + } + } +} diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.tsx b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.tsx new file mode 100644 index 000000000000..5bb81df05d73 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-modal.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Modal } from '@deriv/components'; +import './effortless-login-modal.scss'; +import { useShowEffortlessLoginModal } from '@deriv/hooks'; +import EffortlessLoginContent from './effortless-login-content'; + +const EffortlessLoginModal = () => { + const show_effortless_login_modal = useShowEffortlessLoginModal(); + + return ( + + + + ); +}; + +export default EffortlessLoginModal; diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-tips.tsx b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-tips.tsx new file mode 100644 index 000000000000..0eb14188339c --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/effortless-login-tips.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; + +const getEffortLessLoginTips = () => + [ + { + id: 1, + icon: 'IcFingerprintBold', + description: , + }, + { + id: 2, + icon: 'IcMobileDevice', + + description: , + }, + { + id: 3, + icon: 'IcLockBold', + description: , + }, + ] as const; + +export const EffortLessLoginTips = ({ onLearnMoreClick }: { onLearnMoreClick?: () => void }) => { + const tips = getEffortLessLoginTips(); + + //TODO: css for styles here is removed, need new + return ( +
+ {tips.map(({ id, icon, description }) => ( +
+ + + {description} + +
+ ))} + + + ]} + /> + +
+ ); +}; diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/index.ts b/packages/core/src/App/Containers/EffortlessLoginModal/index.ts new file mode 100644 index 000000000000..3f53b29808e5 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/index.ts @@ -0,0 +1,3 @@ +import EffortlessLoginModal from './effortless-login-modal'; + +export default EffortlessLoginModal; diff --git a/packages/core/src/App/Containers/Modals/app-modals.jsx b/packages/core/src/App/Containers/Modals/app-modals.jsx index e36f4b5e1dc5..a26b4d4a42f8 100644 --- a/packages/core/src/App/Containers/Modals/app-modals.jsx +++ b/packages/core/src/App/Containers/Modals/app-modals.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import { ContentFlag, moduleLoader, routes, SessionStore } from '@deriv/shared'; +import { useShowEffortlessLoginModal } from '@deriv/hooks'; import DerivRealAccountRequiredModal from 'App/Components/Elements/Modals/deriv-real-account-required-modal.jsx'; import MT5AccountNeededModal from 'App/Components/Elements/Modals/mt5-account-needed-modal.jsx'; @@ -17,6 +18,7 @@ import ReadyToDepositModal from './ready-to-deposit-modal'; import RiskAcceptTestWarningModal from './risk-accept-test-warning-modal'; import TradingAssessmentExistingUser from './trading-assessment-existing-user.jsx'; import VerificationModal from '../VerificationModal'; +import EffortlessLoginModal from '../EffortlessLoginModal'; const AccountSignupModal = React.lazy(() => moduleLoader(() => import(/* webpackChunkName: "account-signup-modal" */ '../AccountSignupModal')) @@ -101,6 +103,8 @@ const AppModals = observer(() => { const url_params = new URLSearchParams(useLocation().search || temp_session_signup_params); const url_action_param = url_params.get('action'); + const show_effortless_login_modal = useShowEffortlessLoginModal(); + const is_eu_user = [ContentFlag.LOW_RISK_CR_EU, ContentFlag.EU_REAL, ContentFlag.EU_DEMO].includes(content_flag); const should_show_mt5_notification_modal = @@ -175,6 +179,11 @@ const AppModals = observer(() => { } else if (should_show_risk_accept_modal) { ComponentToLoad = ; } + + if (is_logged_in && show_effortless_login_modal) { + ComponentToLoad = ; + } + if (is_ready_to_deposit_modal_visible) { ComponentToLoad = ; } diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index ac4230bc333e..01bf515a8c86 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -2012,6 +2012,7 @@ export default class ClientStore extends BaseStore { localStorage.removeItem('readScamMessage'); localStorage.removeItem('isNewAccount'); LocalStore.set('marked_notifications', JSON.stringify([])); + localStorage.setItem('show_effortless_login_modal', true); localStorage.setItem('active_loginid', this.loginid); localStorage.setItem('active_user_id', this.user_id); localStorage.setItem('client.accounts', JSON.stringify(this.accounts)); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 1e7c31f8afbf..d8ef67dd67bf 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -69,6 +69,7 @@ export { default as usePlatformRealAccounts } from './usePlatformRealAccounts'; export { default as useRealSTPAccount } from './useRealSTPAccount'; export { default as useRegisterPasskey } from './useRegisterPasskey'; export { default as useServiceToken } from './useServiceToken'; +export { default as useShowEffortlessLoginModal } from './useShowEffortlessLoginModal'; export { default as useStatesList } from './useStatesList'; export { default as useStoreLinkedWalletsAccounts } from './useStoreLinkedWalletsAccounts'; export { default as useStoreWalletAccountsList } from './useStoreWalletAccountsList'; diff --git a/packages/hooks/src/useShowEffortlessLoginModal.ts b/packages/hooks/src/useShowEffortlessLoginModal.ts new file mode 100644 index 000000000000..4c2e8b32ce8f --- /dev/null +++ b/packages/hooks/src/useShowEffortlessLoginModal.ts @@ -0,0 +1,19 @@ +import React from 'react'; +import useIsPasskeySupported from './useIsPasskeySupported'; +import useGetPasskeysList from './useGetPasskeysList'; + +const useShowEffortlessLoginModal = () => { + const [show_effortless_modal, setShouldShowEffortlessModal] = React.useState(false); + const { is_passkey_supported } = useIsPasskeySupported(); + const { data: passkey_list } = useGetPasskeysList(); + const storedValue = localStorage.getItem('show_effortless_login_modal'); + const show_effortless_login_modal = storedValue ? JSON.parse(storedValue) : false; + + React.useEffect(() => { + setShouldShowEffortlessModal(is_passkey_supported && !!true && show_effortless_login_modal); + }, [is_passkey_supported, passkey_list, show_effortless_login_modal]); + + return show_effortless_modal; +}; + +export default useShowEffortlessLoginModal; From c053df664564fdf03cc67eec1c566b6fbb09e8b7 Mon Sep 17 00:00:00 2001 From: Taysuisin Date: Tue, 6 Feb 2024 12:01:51 +0800 Subject: [PATCH 2/2] chore: update test case for effortless login modal --- .../effortless-login-content.spec.tsx | 28 ++++++++++++ .../__test__/effortless-login-modal.spec.tsx | 43 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-content.spec.tsx create mode 100644 packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-modal.spec.tsx diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-content.spec.tsx b/packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-content.spec.tsx new file mode 100644 index 000000000000..5cba66eb5a52 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-content.spec.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import EffortlessLoginContent from '../effortless-login-content'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +describe('EffortlessLoginContent', () => { + const modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root_absolute'); + document.body.appendChild(modal_root_el); + + const mock_store = mockStore({}); + + it('should render EffortlessLoginContent', () => { + render( + + + + ); + expect(screen.getByText(/Maybe later/)).toBeInTheDocument(); + expect(screen.getByText(/Effortless login with passkeys/)).toBeInTheDocument(); + expect(screen.getByText(/No need to remember a password/)).toBeInTheDocument(); + expect(screen.getByText(/Sync across devices/)).toBeInTheDocument(); + expect(screen.getByText(/Enhanced security with biometrics or screen lock/)).toBeInTheDocument(); + expect(screen.getByText(/Learn more about passkeys/)).toBeInTheDocument(); + expect(screen.getByText(/here/)).toBeInTheDocument(); + expect(screen.getByText(/Create passkey/)).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-modal.spec.tsx b/packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-modal.spec.tsx new file mode 100644 index 000000000000..88c25b9fd684 --- /dev/null +++ b/packages/core/src/App/Containers/EffortlessLoginModal/__test__/effortless-login-modal.spec.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import EffortlessLoginModal from '../effortless-login-modal'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { useShowEffortlessLoginModal } from '@deriv/hooks'; + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useShowEffortlessLoginModal: jest.fn(() => false), +})); + +jest.mock('../effortless-login-content', () => jest.fn(() =>
Effortless Content
)); + +const mockUseShowEffortlessLoginModal = useShowEffortlessLoginModal as jest.MockedFunction< + typeof useShowEffortlessLoginModal +>; + +describe('EffortlessLoginModal', () => { + const modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); + + const mock_store = mockStore({}); + + it('should not render EffortlessLoginModal', () => { + render( + + + + ); + expect(screen.queryByText('Effortless Content')).not.toBeInTheDocument(); + }); + + it('should render EffortlessLoginModal', () => { + mockUseShowEffortlessLoginModal.mockReturnValue(true); + render( + + + + ); + expect(screen.getByText('Effortless Content')).toBeInTheDocument(); + }); +});