Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suisin/chore: create effortless login modal #40

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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(
<StoreProvider store={mock_store}>
<EffortlessLoginContent />
</StoreProvider>
);
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();
});
});
Original file line number Diff line number Diff line change
@@ -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(() => <div>Effortless Content</div>));

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(
<StoreProvider store={mock_store}>
<EffortlessLoginModal />
</StoreProvider>
);
expect(screen.queryByText('Effortless Content')).not.toBeInTheDocument();
});

it('should render EffortlessLoginModal', () => {
mockUseShowEffortlessLoginModal.mockReturnValue(true);
render(
<StoreProvider store={mock_store}>
<EffortlessLoginModal />
</StoreProvider>
);
expect(screen.getByText('Effortless Content')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -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 = () => (
<EffortlessLoginModalWrapper>
<FormBody scroll_offset='15rem' className='effortless-login-modal__wrapper'>
<Icon icon='IcInfoPasskey' size={96} />
<Text
as='div'
color='general'
weight='bold'
size='s'
align='center'
className='effortless-login-modal__title'
>
Effortless login with passkeys
</Text>
<EffortLessLoginTips />
</FormBody>
<FormFooter className='effortless-login-modal__footer'>
<Button
type='button'
has_effect
primary
// onClick={() => {
// }}
>
<Localize i18n_default_text='Create passkey' />
</Button>
</FormFooter>
</EffortlessLoginModalWrapper>
);

export default EffortlessLoginContent;
Original file line number Diff line number Diff line change
@@ -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(
<div className={'effortless-login-modal__overlay-container'}>
<Text
as='div'
size='xxs'
color='loss-danger'
weight='bold'
line_height='xl'
align='right'
className='effortless-login-modal__overlay-header'
onClick={() => {
localStorage.setItem('show_effortless_login_modal', JSON.stringify(false));
history.push(routes.traders_hub);
}}
>
<Localize i18n_default_text='Maybe later' />
</Text>
{children}
</div>,
portal_element
);
};

export default EffortlessLoginModalWrapper;
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 (
<Modal portalId='modal_root_absolute' is_open={show_effortless_login_modal} has_close_icon={false}>
<EffortlessLoginContent />
</Modal>
);
};

export default EffortlessLoginModal;
Original file line number Diff line number Diff line change
@@ -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: <Localize i18n_default_text='No need to remember a password' />,
},
{
id: 2,
icon: 'IcMobileDevice',

description: <Localize i18n_default_text='Sync across devices' />,
},
{
id: 3,
icon: 'IcLockBold',
description: <Localize i18n_default_text='Enhanced security with biometrics or screen lock ' />,
},
] as const;

export const EffortLessLoginTips = ({ onLearnMoreClick }: { onLearnMoreClick?: () => void }) => {
const tips = getEffortLessLoginTips();

//TODO: css for styles here is removed, need new
return (
<div>
{tips.map(({ id, icon, description }) => (
<div key={`tip-${id}`} className='effortless-login-modal__overlay-tip'>
<Icon icon={icon} size={24} />
<Text size='xs' weight='bold'>
{description}
</Text>
</div>
))}

<Text as='div' size='xs' className='effortless-login-modal__overlay-tip'>
<Localize
i18n_default_text='Learn more about passkeys <0> here</0>.'
components={[<Text key={0} color='loss-danger' size='xs' onClick={onLearnMoreClick} />]}
/>
</Text>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import EffortlessLoginModal from './effortless-login-modal';

export default EffortlessLoginModal;
9 changes: 9 additions & 0 deletions packages/core/src/App/Containers/Modals/app-modals.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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'))
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -175,6 +179,11 @@ const AppModals = observer(() => {
} else if (should_show_risk_accept_modal) {
ComponentToLoad = <RiskAcceptTestWarningModal />;
}

if (is_logged_in && show_effortless_login_modal) {
ComponentToLoad = <EffortlessLoginModal />;
}

if (is_ready_to_deposit_modal_visible) {
ComponentToLoad = <ReadyToDepositModal />;
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/Stores/client-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
19 changes: 19 additions & 0 deletions packages/hooks/src/useShowEffortlessLoginModal.ts
Original file line number Diff line number Diff line change
@@ -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;