Skip to content

Commit

Permalink
[UPM-924]/evgeniy/update content for passkeys (binary-com#14715)
Browse files Browse the repository at this point in the history
* chore: [UPM-924]/evgeniy/update content for passkeys

* refactor: test code

* chore: trigger build
  • Loading branch information
yauheni-deriv committed Apr 22, 2024
1 parent 9853d5c commit 711e872
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 85 deletions.
Expand Up @@ -201,6 +201,11 @@ describe('Passkeys', () => {
</RenderWrapper>
);

expect(screen.getByText('Just a reminder')).toBeInTheDocument();
expect(screen.getByText('Enable screen lock on your device.')).toBeInTheDocument();
expect(screen.getByText('Enable bluetooth.')).toBeInTheDocument();
expect(screen.getByText('Sign in to your Google or iCloud account.')).toBeInTheDocument();

const continue_button = screen.getByRole('button', { name: /continue/i });
userEvent.click(continue_button);
expect(mockCreatePasskey).toBeCalledTimes(1);
Expand Down
Expand Up @@ -7,36 +7,34 @@ describe('DescriptionContainer', () => {
const description_data = [
{
question: 'What are passkeys?',
description:
'Passkeys are a security measure that lets you log in the same way you unlock your device: with a fingerprint, a face scan, or a screen lock PIN.',
descriptions: [
'Secure alternative to passwords.',
'Unlock your account like your phone - with biometrics, face scan or PIN.',
],
},
{
question: 'Why passkeys?',
description:
'Passkeys are an added layer of security that protects your account against unauthorised access and phishing attacks.',
descriptions: ['Extra security layer.', 'Shields against unauthorised access and phishing.'],
},
{
question: 'How to create a passkey?',
description:
'Go to ‘Account Settings’ on Deriv to set up your passkey. Each device can only save one passkey; however, iOS users may still see the "Create passkey" button due to iOS’s ability to save passkeys on other devices.',
descriptions: ['Go to ‘Account Settings’ on Deriv.', 'You can create one passkey per device.'],
},
{
question: 'Where are passkeys saved?',
description:
'Passkeys are saved in your Google password manager for Android devices and in iCloud keychain on iOS devices to help you sign in on other devices.',
descriptions: ['Android: Google password manager.', 'iOS: iCloud keychain.'],
},
{
question: 'What happens if my Deriv account email is changed?',
description:
'Even if you change your email address, you can still continue to log in to your Deriv account with the same passkey.',
descriptions: ['No problem! Your passkey still works.', 'Sign in to Deriv with your existing passkey.'],
},
];

render(<DescriptionContainer />);

description_data.forEach(({ question, description }) => {
description_data.forEach(({ question, descriptions }) => {
expect(screen.getByText(question)).toBeInTheDocument();
expect(screen.getByText(description)).toBeInTheDocument();
descriptions.forEach(description => expect(screen.getByText(description)).toBeInTheDocument());
});
});
});
@@ -1,9 +1,19 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { routes } from '@deriv/shared';
import { getStatusContent, PASSKEY_STATUS_CODES } from '../../passkeys-configs';
import PasskeysStatusContainer from '../passkeys-status-container';

const mockHistoryPush = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
}),
}));

describe('PasskeysStatusContainer', () => {
const createPasskeyMock = jest.fn();
const setPasskeyStatusMock = jest.fn();
Expand Down Expand Up @@ -37,12 +47,19 @@ describe('PasskeysStatusContainer', () => {

if (status === PASSKEY_STATUS_CODES.LEARN_MORE || status === PASSKEY_STATUS_CODES.NO_PASSKEY) {
userEvent.click(primary_button);
userEvent.click(secondary_button);
expect(createPasskeyMock).toHaveBeenCalled();
userEvent.click(secondary_button);
expect(setPasskeyStatusMock).toHaveBeenCalled();
}

if (status === PASSKEY_STATUS_CODES.CREATED || status === PASSKEY_STATUS_CODES.REMOVED) {
if (status === PASSKEY_STATUS_CODES.CREATED) {
userEvent.click(primary_button);
expect(mockHistoryPush).toHaveBeenCalledWith(routes.traders_hub);
userEvent.click(secondary_button);
expect(setPasskeyStatusMock).toHaveBeenCalledWith(PASSKEY_STATUS_CODES.NONE);
}

if (status === PASSKEY_STATUS_CODES.REMOVED) {
userEvent.click(primary_button);
expect(setPasskeyStatusMock).toHaveBeenCalledWith(PASSKEY_STATUS_CODES.NONE);
}
Expand Down
Expand Up @@ -7,50 +7,66 @@ const getPasskeysDescriptions = () =>
{
id: 1,
question: <Localize i18n_default_text='What are passkeys?' />,
description: (
<Localize i18n_default_text='Passkeys are a security measure that lets you log in the same way you unlock your device: with a fingerprint, a face scan, or a screen lock PIN.' />
),
descriptions: [
<Localize i18n_default_text='Secure alternative to passwords.' key='1.1' />,
<Localize
i18n_default_text='Unlock your account like your phone - with biometrics, face scan or PIN.'
key='1.2'
/>,
],
},
{
id: 2,
question: <Localize i18n_default_text='Why passkeys?' />,
description: (
<Localize i18n_default_text='Passkeys are an added layer of security that protects your account against unauthorised access and phishing attacks.' />
),
descriptions: [
<Localize i18n_default_text='Extra security layer.' key='2.1' />,
<Localize i18n_default_text='Shields against unauthorised access and phishing.' key='2.2' />,
],
},
{
id: 3,
question: <Localize i18n_default_text='How to create a passkey?' />,
description: (
<Localize i18n_default_text='Go to ‘Account Settings’ on Deriv to set up your passkey. Each device can only save one passkey; however, iOS users may still see the "Create passkey" button due to iOS’s ability to save passkeys on other devices.' />
),
descriptions: [
<Localize i18n_default_text='Go to ‘Account Settings’ on Deriv.' key='3.1' />,
<Localize i18n_default_text='You can create one passkey per device.' key='3.2' />,
],
},
{
id: 4,
question: <Localize i18n_default_text='Where are passkeys saved?' />,
description: (
<Localize i18n_default_text='Passkeys are saved in your Google password manager for Android devices and in iCloud keychain on iOS devices to help you sign in on other devices.' />
),
descriptions: [
<Localize i18n_default_text='Android: Google password manager.' key='4.1' />,
<Localize i18n_default_text='iOS: iCloud keychain.' key='4.2' />,
],
},
{
id: 5,
question: <Localize i18n_default_text='What happens if my Deriv account email is changed?' />,
description: (
<Localize i18n_default_text='Even if you change your email address, you can still continue to log in to your Deriv account with the same passkey.' />
),
descriptions: [
<Localize i18n_default_text='No problem! Your passkey still works.' key='5.1' />,
<Localize i18n_default_text='Sign in to Deriv with your existing passkey.' key='5.2' />,
],
},
] as const;

export const DescriptionContainer = () => {
const passkeys_descriptions = getPasskeysDescriptions();
return (
<div className='passkeys-status__description-container'>
{passkeys_descriptions.map(({ id, question, description }) => (
<div key={`description-${id}`} className='passkeys-status__description-card'>
{passkeys_descriptions.map(({ id, question, descriptions }) => (
<div key={`description-card-${id}`} className='passkeys-status__description-card'>
<Text weight='bold' size='xs'>
{question}
</Text>
<Text size='xs'>{description}</Text>
<Text as='ul' size='xs'>
{descriptions.map(description => (
<li key={`description-${description.key}`}>
<Text size='xs' line_height='l'>
{description}
</Text>
</li>
))}
</Text>
</div>
))}
</div>
Expand Down
@@ -1,8 +1,10 @@
import React from 'react';
import { Icon } from '@deriv/components';
import { getStatusContent, PASSKEY_STATUS_CODES, TPasskeysStatus } from 'Sections/Security/Passkeys/passkeys-configs';
import { routes } from '@deriv/shared';
import { getStatusContent, PASSKEY_STATUS_CODES, TPasskeysStatus } from '../passkeys-configs';
import PasskeysFooterButtons from './passkeys-footer-buttons';
import PasskeysStatus from './passkeys-status';
import { useHistory } from 'react-router-dom';

type TPasskeysStatusContainer = {
createPasskey: () => void;
Expand All @@ -11,16 +13,21 @@ type TPasskeysStatusContainer = {
};

const PasskeysStatusContainer = ({ createPasskey, passkey_status, setPasskeyStatus }: TPasskeysStatusContainer) => {
const history = useHistory();
const prev_passkey_status = React.useRef<TPasskeysStatus>(PASSKEY_STATUS_CODES.NONE);

if (passkey_status === PASSKEY_STATUS_CODES.NONE) return null;

const onPrimaryButtonClick = () => {
if (passkey_status === PASSKEY_STATUS_CODES.CREATED || passkey_status === PASSKEY_STATUS_CODES.REMOVED) {
if (passkey_status === PASSKEY_STATUS_CODES.REMOVED) {
// set status to 'NONE' means 'continue' button is clicked
setPasskeyStatus(PASSKEY_STATUS_CODES.NONE);
return;
}
if (passkey_status === PASSKEY_STATUS_CODES.CREATED) {
history.push(routes.traders_hub);
return;
}
// if (passkey_status === PASSKEY_STATUS_CODES.RENAMING) {
// // TODO: implement renaming flow & add 'Save changes' action for onPrimaryButtonClick
// return;
Expand All @@ -37,6 +44,11 @@ const PasskeysStatusContainer = ({ createPasskey, passkey_status, setPasskeyStat
setPasskeyStatus(prev_passkey_status.current);
return;
}

if (passkey_status === PASSKEY_STATUS_CODES.CREATED) {
setPasskeyStatus(PASSKEY_STATUS_CODES.NONE);
return;
}
// if (passkey_status === PASSKEY_STATUS_CODES.RENAMING) {
// setPasskeyStatus(PASSKEY_STATUS_CODES.NONE);
// return;
Expand Down
Expand Up @@ -30,13 +30,15 @@ export const TipsBlock = () => {
<Text size='xxs' line_height='l'>
<Localize i18n_default_text='Before using passkey:' />
</Text>
{tips.map(({ id, description }) => (
<li key={`tip-${id}`}>
<Text size='xxs' line_height='l'>
{description}
</Text>
</li>
))}
<Text as='ul' size='xxs'>
{tips.map(({ id, description }) => (
<li key={`tip-${id}`}>
<Text size='xxs' line_height='l'>
{description}
</Text>
</li>
))}
</Text>
</div>
</div>
);
Expand Down
Expand Up @@ -23,6 +23,8 @@ export const getStatusContent = (status: Exclude<TPasskeysStatus, ''>) => {
const learn_more_button_text = <Localize i18n_default_text='Learn more' />;
const create_passkey_button_text = <Localize i18n_default_text='Create passkey' />;
const continue_button_text = <Localize i18n_default_text='Continue' />;
const continue_trading_button_text = <Localize i18n_default_text='Continue trading' />;
const add_more_passkeys_button_text = <Localize i18n_default_text='Add more passkeys' />;

const getPasskeysRemovedDescription = () => {
const os_type = mobileOSDetect();
Expand Down Expand Up @@ -64,12 +66,7 @@ export const getStatusContent = (status: Exclude<TPasskeysStatus, ''>) => {
<TipsBlock />
</React.Fragment>
),
no_passkey: (
<Localize
i18n_default_text='Enhanced security is just a tap away.<0/>Hit <1>Learn more</1> to explore passkeys or <1>Create passkey</1> to get started.'
components={[<br key={0} />, <strong key={1} />]}
/>
),
no_passkey: <Localize i18n_default_text='Enhanced security is just a tap away.' />,
removed: getPasskeysRemovedDescription(),
renaming: '',
verifying: (
Expand All @@ -84,16 +81,16 @@ export const getStatusContent = (status: Exclude<TPasskeysStatus, ''>) => {
renaming: 'IcEditPasskey',
verifying: 'IcVerifyPasskey',
};
const button_texts = {
created: continue_button_text,
const primary_button_texts = {
created: continue_trading_button_text,
learn_more: create_passkey_button_text,
no_passkey: create_passkey_button_text,
removed: continue_button_text,
renaming: <Localize i18n_default_text='Save changes' />,
verifying: <Localize i18n_default_text='Send email' />,
};
const back_button_texts = {
created: undefined,
const secondary_button_texts = {
created: add_more_passkeys_button_text,
learn_more: undefined,
no_passkey: learn_more_button_text,
removed: undefined,
Expand All @@ -105,8 +102,8 @@ export const getStatusContent = (status: Exclude<TPasskeysStatus, ''>) => {
title: titles[status],
description: descriptions[status],
icon: icons[status],
primary_button_text: button_texts[status],
secondary_button_text: back_button_texts[status],
primary_button_text: primary_button_texts[status],
secondary_button_text: secondary_button_texts[status],
};
};

Expand All @@ -117,10 +114,23 @@ type TGetModalContent = {
};

export const getModalContent = ({ error, is_passkey_registration_started }: TGetModalContent) => {
const reminder_tips = [
<Localize i18n_default_text='Enable screen lock on your device.' key='tip_1' />,
<Localize i18n_default_text='Enable bluetooth.' key='tip_2' />,
<Localize i18n_default_text='Sign in to your Google or iCloud account.' key='tip_3' />,
];
if (is_passkey_registration_started) {
return {
description: (
<Localize i18n_default_text='Make sure the screen lock and Bluetooth on your device are active and you are signed in to your Google or iCloud account.' />
<ul>
{reminder_tips.map(tip => (
<li key={tip.key}>
<Text size='xxs' line_height='l'>
{tip}
</Text>
</li>
))}
</ul>
),
button_text: <Localize i18n_default_text='Continue' />,
header: (
Expand Down

0 comments on commit 711e872

Please sign in to comment.