From 614228cba7fee3b4b83f5d56f8ac38e4486a53a5 Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Wed, 6 Oct 2021 13:52:25 -0500 Subject: [PATCH] Onboarding V2 Secure Your Wallet view (#12208) * secure-your-wallet onboarding view --- app/_locales/en/messages.json | 24 ++- app/images/warning-icon.png | Bin 0 -> 1757 bytes .../step-progress-bar/step-progress-bar.js | 2 +- ui/components/ui/box/box.js | 4 + ui/components/ui/box/box.scss | 6 + .../ui/check-box/check-box.component.js | 12 +- ui/components/ui/popover/popover.component.js | 57 +++--- ui/css/design-system/attributes.scss | 6 + ui/helpers/constants/design-system.js | 7 + ui/pages/onboarding-flow/index.scss | 1 + ui/pages/onboarding-flow/onboarding-flow.js | 9 +- .../secure-your-wallet/index.scss | 47 +++++ .../secure-your-wallet/secure-your-wallet.js | 167 ++++++++++++++++++ .../secure-your-wallet.stories.js | 15 ++ .../secure-your-wallet.test.js | 59 +++++++ .../skip-srp-backup-popover.js | 79 +++++++++ ui/pages/routes/routes.component.js | 2 +- 17 files changed, 463 insertions(+), 34 deletions(-) create mode 100644 app/images/warning-icon.png create mode 100644 ui/pages/onboarding-flow/secure-your-wallet/index.scss create mode 100644 ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.js create mode 100644 ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.stories.js create mode 100644 ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.test.js create mode 100644 ui/pages/onboarding-flow/secure-your-wallet/skip-srp-backup-popover.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index afe8ad1ef..887ab9e42 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1042,6 +1042,9 @@ "getStarted": { "message": "Get Started" }, + "goBack": { + "message": "Go Back" + }, "goerli": { "message": "Goerli Test Network" }, @@ -1900,6 +1903,12 @@ "seedPhraseEnterMissingWords": { "message": "Confirm Secret Recovery Phrase" }, + "seedPhraseIntroNotRecommendedButtonCopy": { + "message": "Remind me later (not recommended)" + }, + "seedPhraseIntroRecommendedButtonCopy": { + "message": "Secure my wallet (recommended)" + }, "seedPhraseIntroSidebarBulletFour": { "message": "Write down and store in multiple secret places." }, @@ -1913,13 +1922,13 @@ "message": "Store in a bank vault." }, "seedPhraseIntroSidebarCopyOne": { - "message": "Your Secret Recovery Phrase is the “master key” to your wallet and funds." + "message": "Your Secret Recovery Phrase is a 12-word phrase that is the “master key” to your wallet and your funds" }, "seedPhraseIntroSidebarCopyThree": { - "message": "If someone asks for your Secret Recovery Phrase, they are most likely trying to scam you." + "message": "If someone asks for your recovery phrase they are likely trying to scam you and steal your wallet funds" }, "seedPhraseIntroSidebarCopyTwo": { - "message": "Never, ever share your Secret Recovery Phrase, even with MetaMask!" + "message": "Never, ever share your Secret Recovery Phrase, not even with MetaMask!" }, "seedPhraseIntroSidebarTitleOne": { "message": "What is a Secret Recovery Phrase?" @@ -2071,6 +2080,15 @@ "signed": { "message": "Signed" }, + "skip": { + "message": "Skip" + }, + "skipAccountSecurity": { + "message": "Skip Account Security?" + }, + "skipAccountSecurityDetails": { + "message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets." + }, "slow": { "message": "Slow" }, diff --git a/app/images/warning-icon.png b/app/images/warning-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..06b4be3327b4deeccec396a254491a4a17bd154d GIT binary patch literal 1757 zcmV<31|s>1P)&{$Mf_ z6N$+Fj7%{B#5q_tqS<5+!HhX&nK&5Ry7hE7I{lun{c10#TYGxG(LPDnY;Di^o<8sQ z-oAIs{$(H&F+Zu%^o@R+?)!sg_+x;49uLjk8V-A3=88hfe5jBVhn>`l^<*t8A$4Op znT=&;5Ckdjue~(-%_&YU>*3XuRa`{b#r|i-u2RN>uCUMCyFJ1m^O~2YuKrH512<{* zuR&6uEGOsIO3K;!ESZ6%2!hz?$rc)IZsDYyHC3c;uHb|WU5}CVUh1aF^Bv^7JH`}U zPp;Qq;c6R&A%cRK`@6H`*j`Bw?A~FRR4hQd)~z`3;e87QMu4@tSZp3XbS%nL!w9;+KS$Y*l|?1S=>Q}i zwoMyY!rv11_aH&8Uej?dce(;=cb`5JrecsFOcP!y9n}+nsoGg87ph{Aptt9WboG#C z$wtoZ=V**oIzmNG5T4VrJWr~6Jo8{FmTZzxG4qktq;>cDf9E%E?_}m#MKUoSAU4r@ zh9}K3fx;Uq0-T`M(@S<%iOYh!^(@V?at9Cf0eAt;b&yB)fiD{gLF67z6u3_A^EDvI6#R3Ln}?B#0*jppR13Jq;Oiey zvcR{GhtU%(tYeOG!jZUwV<_v|64Ea9kP*mo?@-Yw5La#NbQs{BZO?^H8u_X@X|qTb zz?tRM?&OE1JkUc*VW#O0Y#HCkSnwp66NH)OzgQA3M@o zMNwemEMFi<5Gv;G+OZ_POIg6$Bgam{l5L@)%!ZIGK)spY`l6;V!U#gel@0H47oAR~ zinU?_!&M(WG!#`XIi9X0=l1HfGF7bAxvg4vhDNs@EMRs#d;LaQkmO?}KVJ@WETPaP z2vc2k@MF^2I(Ze4s=(T?#-gK*q&W2lgJM?nuw{JEeX=zrNn%Y{8`ikc7)gACKyU72 z=WdlLb2tjK6v`zv2qYj#+Me3LN#5^KuGwq-w5qWou2HVInohV-@s~%*qN+6X?k7_E zn2Q0i`EPdb44WF4asmmDQ{u1`XZhKWmj#=;G9gq$0_sOw!OG2MFG!P4u#Y6^( zqJoz}RP<*wMl6S=kokM+VtUGjkkm-Cn5*%!6Do@;#xkOo(wSo(+GwFr^YzC z!sG@{c%Zc2zZIY-i?$^?d*}^W*OZzLN literal 0 HcmV?d00001 diff --git a/ui/components/app/step-progress-bar/step-progress-bar.js b/ui/components/app/step-progress-bar/step-progress-bar.js index 72e80319c..a23ecc3c4 100644 --- a/ui/components/app/step-progress-bar/step-progress-bar.js +++ b/ui/components/app/step-progress-bar/step-progress-bar.js @@ -14,7 +14,7 @@ const stages = { export default function StepProgressBar({ stage = 'PASSWORD_CREATE' }) { const t = useI18nContext(); return ( - +
  • { +const CheckBox = ({ + className, + disabled, + id, + onClick, + checked, + title, + dataTestId, +}) => { if (typeof checked === 'boolean') { // eslint-disable-next-line no-param-reassign checked = checked ? CHECKBOX_STATE.CHECKED : CHECKBOX_STATE.UNCHECKED; @@ -43,6 +51,7 @@ const CheckBox = ({ className, disabled, id, onClick, checked, title }) => { readOnly ref={ref} title={title} + data-testid={dataTestId} type="checkbox" /> ); @@ -56,6 +65,7 @@ CheckBox.propTypes = { checked: PropTypes.oneOf([...Object.keys(CHECKBOX_STATE), true, false]) .isRequired, title: PropTypes.string, + dataTestId: PropTypes.string, }; CheckBox.defaultProps = { diff --git a/ui/components/ui/popover/popover.component.js b/ui/components/ui/popover/popover.component.js index c4bae4ffc..637f07528 100644 --- a/ui/components/ui/popover/popover.component.js +++ b/ui/components/ui/popover/popover.component.js @@ -20,6 +20,7 @@ const Popover = ({ centerTitle, }) => { const t = useI18nContext(); + const showHeader = title || onBack || subtitle || onClose; return (
    {CustomBackground ? ( @@ -32,36 +33,38 @@ const Popover = ({ ref={popoverRef} > {showArrow ?
    : null} -
    -
    -

    - {onBack ? ( + {showHeader && ( +
    +
    +

    + {onBack ? ( +

    + {onClose ? (

    - {onClose ? ( -
    + {subtitle ? ( +

    {subtitle}

    ) : null} -
    - {subtitle ? ( -

    {subtitle}

    - ) : null} - + + )} {children ? (
    {children} @@ -78,7 +81,7 @@ const Popover = ({ }; Popover.propTypes = { - title: PropTypes.string.isRequired, + title: PropTypes.string, subtitle: PropTypes.string, children: PropTypes.node, footer: PropTypes.node, diff --git a/ui/css/design-system/attributes.scss b/ui/css/design-system/attributes.scss index d2836769b..7373c7c3b 100644 --- a/ui/css/design-system/attributes.scss +++ b/ui/css/design-system/attributes.scss @@ -13,6 +13,12 @@ $justify-content: space-between, space-evenly; +$flex-direction: + row, + row-reverse, + column, + column-reverse; + $fractions: ( 1\/2: 50%, 1\/3: 33.333333%, diff --git a/ui/helpers/constants/design-system.js b/ui/helpers/constants/design-system.js index 7edcbc0b2..2f3b4bdd9 100644 --- a/ui/helpers/constants/design-system.js +++ b/ui/helpers/constants/design-system.js @@ -88,6 +88,13 @@ export const JUSTIFY_CONTENT = { SPACE_EVENLY: 'space-evenly', }; +export const FLEX_DIRECTION = { + ROW: 'row', + ROW_REVERSE: 'row-reverse', + COLUMN: 'column', + COLUMN_REVERSE: 'column-reverse', +}; + export const DISPLAY = { BLOCK: 'block', FLEX: 'flex', diff --git a/ui/pages/onboarding-flow/index.scss b/ui/pages/onboarding-flow/index.scss index 8e3431e05..8d5a5cd0c 100644 --- a/ui/pages/onboarding-flow/index.scss +++ b/ui/pages/onboarding-flow/index.scss @@ -1,6 +1,7 @@ @import 'recovery-phrase/index'; @import 'new-account/index'; @import 'onboarding-app-header/index'; +@import 'secure-your-wallet/index'; .onboarding-flow { width: 100%; diff --git a/ui/pages/onboarding-flow/onboarding-flow.js b/ui/pages/onboarding-flow/onboarding-flow.js index 7bb908383..82c1b00e6 100644 --- a/ui/pages/onboarding-flow/onboarding-flow.js +++ b/ui/pages/onboarding-flow/onboarding-flow.js @@ -8,6 +8,7 @@ import { ONBOARDING_CONFIRM_SRP_ROUTE, ONBOARDING_UNLOCK_ROUTE, DEFAULT_ROUTE, + ONBOARDING_SECURE_YOUR_WALLET_ROUTE, } from '../../helpers/constants/routes'; import { getCompletedOnboarding, @@ -23,6 +24,7 @@ import { getFirstTimeFlowTypeRoute } from '../../selectors'; import OnboardingFlowSwitch from './onboarding-flow-switch/onboarding-flow-switch'; import NewAccount from './new-account/new-account'; import ReviewRecoveryPhrase from './recovery-phrase/review-recovery-phrase'; +import SecureYourWallet from './secure-your-wallet/secure-your-wallet'; import ConfirmRecoveryPhrase from './recovery-phrase/confirm-recovery-phrase'; export default function OnboardingFlow() { @@ -39,7 +41,7 @@ export default function OnboardingFlow() { // For ONBOARDING_V2 dev purposes, // Remove when ONBOARDING_V2 dev complete if (process.env.ONBOARDING_V2) { - history.push(ONBOARDING_REVIEW_SRP_ROUTE); + history.push(ONBOARDING_SECURE_YOUR_WALLET_ROUTE); return; } @@ -87,6 +89,11 @@ export default function OnboardingFlow() { /> )} /> + } diff --git a/ui/pages/onboarding-flow/secure-your-wallet/index.scss b/ui/pages/onboarding-flow/secure-your-wallet/index.scss new file mode 100644 index 000000000..ef2d0b314 --- /dev/null +++ b/ui/pages/onboarding-flow/secure-your-wallet/index.scss @@ -0,0 +1,47 @@ +.secure-your-wallet { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + max-width: 1000px; + max-height: 1300px; + + &__details { + max-width: 550px; + } + + &__actions { + button { + margin: 16px; + } + } + + &__list { + list-style: disc inside; + + li { + font-size: 18px; + } + } + + &__highlighted { + background-color: $primary-2; + padding: 12px; + border-radius: 10px; + } +} + +.skip-srp-backup-popover { + width: 365px; + + &__checkbox { + margin: 8px 12px 0 0; + } + + &__footer { + button { + width: 140px; + margin: 0 10px; + } + } +} diff --git a/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.js b/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.js new file mode 100644 index 000000000..f7c941260 --- /dev/null +++ b/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.js @@ -0,0 +1,167 @@ +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import Box from '../../../components/ui/box'; +import Button from '../../../components/ui/button'; +import Typography from '../../../components/ui/typography'; +import { + TEXT_ALIGN, + TYPOGRAPHY, + JUSTIFY_CONTENT, + FONT_WEIGHT, +} from '../../../helpers/constants/design-system'; +import ProgressBar from '../../../components/app/step-progress-bar'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { ONBOARDING_REVIEW_SRP_ROUTE } from '../../../helpers/constants/routes'; +import { getCurrentLocale } from '../../../ducks/metamask/metamask'; +import SkipSRPBackup from './skip-srp-backup-popover'; + +export default function SecureYourWallet() { + const history = useHistory(); + const t = useI18nContext(); + const currentLocale = useSelector(getCurrentLocale); + const [showSkipSRPBackupPopover, setShowSkipSRPBackupPopover] = useState( + false, + ); + + const handleClickRecommended = () => { + history.push(ONBOARDING_REVIEW_SRP_ROUTE); + }; + + const handleClickNotRecommended = () => { + setShowSkipSRPBackupPopover(true); + }; + + const subtitles = { + en: 'English', + es: 'Spanish', + hi: 'Hindi', + id: 'Indonesian', + ja: 'Japanese', + ko: 'Korean', + pt: 'Portuguese', + ru: 'Russian', + tl: 'Tagalog', + vi: 'Vietnamese', + }; + + const defaultLang = subtitles[currentLocale] ? currentLocale : 'en'; + return ( +
    + {showSkipSRPBackupPopover && ( + setShowSkipSRPBackupPopover(false)} /> + )} + + + + {t('seedPhraseIntroTitle')} + + + + + {t('seedPhraseIntroTitleCopy')} + + + + + + + + + + + + {t('seedPhraseIntroSidebarTitleOne')} + + + {t('seedPhraseIntroSidebarCopyOne')} + + + + + {t('seedPhraseIntroSidebarTitleTwo')} + +
      +
    • {t('seedPhraseIntroSidebarBulletOne')}
    • +
    • {t('seedPhraseIntroSidebarBulletTwo')}
    • +
    • {t('seedPhraseIntroSidebarBulletThree')}
    • +
    • {t('seedPhraseIntroSidebarBulletFour')}
    • +
    +
    + + + {t('seedPhraseIntroSidebarTitleThree')} + + + {t('seedPhraseIntroSidebarCopyTwo')} + + + + + {t('seedPhraseIntroSidebarCopyThree')} + + +
    + ); +} diff --git a/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.stories.js b/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.stories.js new file mode 100644 index 000000000..6951b001c --- /dev/null +++ b/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.stories.js @@ -0,0 +1,15 @@ +import React from 'react'; +import SecureYourWallet from './secure-your-wallet'; + +export default { + title: 'Onboarding - Secure Your Wallet', + id: __filename, +}; + +export const Base = () => { + return ( +
    + +
    + ); +}; diff --git a/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.test.js b/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.test.js new file mode 100644 index 000000000..15e5db2c0 --- /dev/null +++ b/ui/pages/onboarding-flow/secure-your-wallet/secure-your-wallet.test.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import configureMockStore from 'redux-mock-store'; +import reactRouterDom from 'react-router-dom'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { ONBOARDING_COMPLETION_ROUTE } from '../../../helpers/constants/routes'; +import SecureYourWallet from './secure-your-wallet'; + +describe('Secure Your Wallet Onboarding View', () => { + const useHistoryOriginal = reactRouterDom.useHistory; + const pushMock = jest.fn(); + beforeAll(() => { + jest + .spyOn(reactRouterDom, 'useHistory') + .mockImplementation() + .mockReturnValue({ push: pushMock }); + }); + + afterAll(() => { + reactRouterDom.useHistory = useHistoryOriginal; + }); + + const mockStore = { + metamask: { + provider: { + type: 'test', + }, + }, + }; + + const store = configureMockStore()(mockStore); + it('should show a popover asking the user if they want to skip account security if they click "Remind me later"', () => { + const { queryAllByText, getByText } = renderWithProvider( + , + store, + ); + const remindMeLaterButton = getByText('Remind me later (not recommended)'); + expect(queryAllByText('Skip Account Security?')).toHaveLength(0); + fireEvent.click(remindMeLaterButton); + expect(queryAllByText('Skip Account Security?')).toHaveLength(1); + }); + + it('should not be able to click "skip" until "Skip Account Security" terms are agreed to', () => { + const { getByText, getByTestId } = renderWithProvider( + , + store, + ); + const remindMeLaterButton = getByText('Remind me later (not recommended)'); + fireEvent.click(remindMeLaterButton); + const skipButton = getByText('Skip'); + fireEvent.click(skipButton); + expect(pushMock).toHaveBeenCalledTimes(0); + const checkbox = getByTestId('skip-srp-backup-popover-checkbox'); + fireEvent.click(checkbox); + fireEvent.click(skipButton); + expect(pushMock).toHaveBeenCalledTimes(1); + expect(pushMock).toHaveBeenCalledWith(ONBOARDING_COMPLETION_ROUTE); + }); +}); diff --git a/ui/pages/onboarding-flow/secure-your-wallet/skip-srp-backup-popover.js b/ui/pages/onboarding-flow/secure-your-wallet/skip-srp-backup-popover.js new file mode 100644 index 000000000..00ceb4558 --- /dev/null +++ b/ui/pages/onboarding-flow/secure-your-wallet/skip-srp-backup-popover.js @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router-dom'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import Button from '../../../components/ui/button'; +import Popover from '../../../components/ui/popover'; +import Box from '../../../components/ui/box'; +import Typography from '../../../components/ui/typography'; +import { + ALIGN_ITEMS, + FLEX_DIRECTION, + FONT_WEIGHT, + JUSTIFY_CONTENT, + TYPOGRAPHY, +} from '../../../helpers/constants/design-system'; +import Checkbox from '../../../components/ui/check-box'; +import { ONBOARDING_COMPLETION_ROUTE } from '../../../helpers/constants/routes'; + +export default function SkipSRPBackup({ handleClose }) { + const [checked, setChecked] = useState(false); + const t = useI18nContext(); + const history = useHistory(); + return ( + + + + + } + > + + + + {t('skipAccountSecurity')} + + + { + setChecked(!checked); + }} + checked={checked} + dataTestId="skip-srp-backup-popover-checkbox" + /> + + {t('skipAccountSecurityDetails')} + + + + + ); +} + +SkipSRPBackup.propTypes = { + handleClose: PropTypes.func, +}; diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index a6008025e..23515aa89 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -40,7 +40,6 @@ import { CONFIRM_TRANSACTION_ROUTE, CONNECT_ROUTE, DEFAULT_ROUTE, - INITIALIZE_ROUTE, INITIALIZE_UNLOCK_ROUTE, LOCK_ROUTE, MOBILE_SYNC_ROUTE, @@ -54,6 +53,7 @@ import { BUILD_QUOTE_ROUTE, CONFIRMATION_V_NEXT_ROUTE, CONFIRM_IMPORT_TOKEN_ROUTE, + INITIALIZE_ROUTE, ONBOARDING_ROUTE, } from '../../helpers/constants/routes';