diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 554cbc1a6..45728a415 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -322,6 +322,9 @@
"confirmSecretBackupPhrase": {
"message": "Confirm your Secret Backup Phrase"
},
+ "confirmSeedPhrase": {
+ "message": "Confirm Seed Phrase"
+ },
"confirmed": {
"message": "Confirmed"
},
@@ -1140,6 +1143,10 @@
"makeAnotherSwap": {
"message": "Create a new swap"
},
+ "makeSureNoOneWatching": {
+ "message": "Make sure no one is watching your screen",
+ "description": "Warning to users to be care while creating and saving their new seed phrase"
+ },
"max": {
"message": "Max"
},
@@ -1696,12 +1703,21 @@
"secretPhrase": {
"message": "Enter your secret phrase here to restore your vault."
},
+ "secureWallet": {
+ "message": "Secure Wallet"
+ },
"securityAndPrivacy": {
"message": "Security & Privacy"
},
"securitySettingsDescription": {
"message": "Privacy settings and wallet Secret Recovery Phrase"
},
+ "seedPhraseConfirm": {
+ "message": "Confirm Secret Recovery Phrase"
+ },
+ "seedPhraseEnterMissingWords": {
+ "message": "Confirm Secret Recovery Phrase"
+ },
"seedPhraseIntroSidebarBulletFour": {
"message": "Write down and store in multiple secret places."
},
@@ -1747,6 +1763,12 @@
"seedPhraseReq": {
"message": "Secret Recovery Phrases contain 12, 15, 18, 21, or 24 words"
},
+ "seedPhraseWriteDownDetails": {
+ "message": "Write down this 12-word Secret Recovery Phrase and save it in a place that you trust and only you can access."
+ },
+ "seedPhraseWriteDownHeader": {
+ "message": "Write down your Secret Recovery Phrase"
+ },
"selectAHigherGasFee": {
"message": "Select a higher gas fee to accelerate the processing of your transaction.*"
},
diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss
index b579de073..c868491ac 100644
--- a/ui/components/app/app-components.scss
+++ b/ui/components/app/app-components.scss
@@ -28,6 +28,7 @@
@import 'permissions-connect-footer/index';
@import 'permissions-connect-header/index';
@import 'recovery-phrase-reminder/index';
+@import 'step-progress-bar/index.scss';
@import 'selected-account/index';
@import 'sidebars/index';
@import 'signature-request/index';
diff --git a/ui/components/app/step-progress-bar/index.js b/ui/components/app/step-progress-bar/index.js
new file mode 100644
index 000000000..8a02cf42f
--- /dev/null
+++ b/ui/components/app/step-progress-bar/index.js
@@ -0,0 +1 @@
+export { default } from './step-progress-bar';
diff --git a/ui/components/app/step-progress-bar/index.scss b/ui/components/app/step-progress-bar/index.scss
new file mode 100644
index 000000000..b2a35bef1
--- /dev/null
+++ b/ui/components/app/step-progress-bar/index.scss
@@ -0,0 +1,66 @@
+.progressbar {
+ counter-reset: step;
+ display: flex;
+ justify-content: space-evenly;
+}
+
+.progressbar li {
+ list-style-type: none;
+ width: 25%;
+ float: left;
+ font-size: 12px;
+ position: relative;
+ text-align: center;
+ text-transform: uppercase;
+ color: #7d7d7d;
+ z-index: 2;
+}
+
+.progressbar li::before {
+ width: 30px;
+ height: 30px;
+ content: counter(step);
+ counter-increment: step;
+ line-height: 30px;
+ border: 2px solid #d6d9dc;
+ display: block;
+ text-align: center;
+ margin: 0 auto 10px auto;
+ border-radius: 50%;
+ background-color: white;
+ z-index: -1;
+}
+
+.progressbar li::after {
+ width: 100%;
+ height: 2px;
+ content: '';
+ position: absolute;
+ background-color: #d6d9dc;
+ top: 15px;
+ right: 62%;
+ z-index: -1;
+}
+
+.progressbar li:first-child::after {
+ content: none;
+}
+
+.progressbar li.active {
+ color: $primary-blue;
+}
+
+.progressbar li.active::before {
+ border-color: $primary-blue;
+ z-index: 1;
+}
+
+.progressbar li.active + li::after {
+ background-color: $primary-blue;
+ z-index: -1;
+}
+
+.progressbar li.complete::before {
+ background-color: $primary-blue;
+ color: $ui-white;
+}
diff --git a/ui/components/app/step-progress-bar/step-progress-bar.js b/ui/components/app/step-progress-bar/step-progress-bar.js
new file mode 100644
index 000000000..72e80319c
--- /dev/null
+++ b/ui/components/app/step-progress-bar/step-progress-bar.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import classnames from 'classnames';
+import PropTypes from 'prop-types';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import Box from '../../ui/box';
+
+const stages = {
+ PASSWORD_CREATE: 1,
+ SEED_PHRASE_VIDEO: 2,
+ SEED_PHRASE_REVIEW: 3,
+ SEED_PHRASE_CONFIRM: 4,
+ ONBOARDING_COMPLETE: 5,
+};
+export default function StepProgressBar({ stage = 'PASSWORD_CREATE' }) {
+ const t = useI18nContext();
+ return (
+
+
+ - = 1,
+ complete: stages[stage] >= 1,
+ })}
+ >
+ {t('createPassword')}
+
+ - = 2,
+ complete: stages[stage] >= 3,
+ })}
+ >
+ {t('secureWallet')}
+
+ - = 4,
+ complete: stages[stage] >= 5,
+ })}
+ >
+ {t('confirmSeedPhrase')}
+
+
+
+ );
+}
+
+StepProgressBar.propTypes = {
+ stage: PropTypes.string,
+};
diff --git a/ui/components/ui/chip/chip.scss b/ui/components/ui/chip/chip.scss
index 51a176138..02c4df6a5 100644
--- a/ui/components/ui/chip/chip.scss
+++ b/ui/components/ui/chip/chip.scss
@@ -42,6 +42,8 @@
border: none;
background: transparent;
text-align: center;
+ width: 100%;
+ font-size: design-system.$font-size-h5;
&:focus {
text-align: left;
diff --git a/ui/pages/onboarding-flow/recovery-phrase/confirm-recovery-phrase.js b/ui/pages/onboarding-flow/recovery-phrase/confirm-recovery-phrase.js
new file mode 100644
index 000000000..52d3926e1
--- /dev/null
+++ b/ui/pages/onboarding-flow/recovery-phrase/confirm-recovery-phrase.js
@@ -0,0 +1,100 @@
+import React, { useState, useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
+import { debounce } from 'lodash';
+import PropTypes from 'prop-types';
+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 { INITIALIZE_END_OF_FLOW_ROUTE } from '../../../helpers/constants/routes';
+import ProgressBar from '../../../components/app/step-progress-bar';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import RecoveryPhraseChips from './recovery-phrase-chips';
+
+export default function ConfirmRecoveryPhrase({ seedPhrase = '' }) {
+ const history = useHistory();
+ const t = useI18nContext();
+ const splitSeedPhrase = seedPhrase.split(' ');
+ const indicesToCheck = [2, 3, 7];
+ const [matching, setMatching] = useState(false);
+
+ // Removes seed phrase words from chips corresponding to the
+ // indicesToCheck so that user has to complete the phrase and confirm
+ // they have saved it.
+ const initializePhraseElements = () => {
+ const phraseElements = { ...splitSeedPhrase };
+ indicesToCheck.forEach((i) => {
+ phraseElements[i] = '';
+ });
+ return phraseElements;
+ };
+ const [phraseElements, setPhraseElements] = useState(
+ initializePhraseElements(),
+ );
+
+ const validate = useMemo(
+ () =>
+ debounce((elements) => {
+ setMatching(Object.values(elements).join(' ') === seedPhrase);
+ }, 500),
+ [setMatching, seedPhrase],
+ );
+
+ const handleSetPhraseElements = (values) => {
+ setPhraseElements(values);
+ validate(values);
+ };
+
+ return (
+
+
+
+
+ {t('seedPhraseConfirm')}
+
+
+
+
+ {t('seedPhraseEnterMissingWords')}
+
+
+
+
+
+
+
+ );
+}
+
+ConfirmRecoveryPhrase.propTypes = {
+ seedPhrase: PropTypes.string,
+};
diff --git a/ui/pages/onboarding-flow/recovery-phrase/index.scss b/ui/pages/onboarding-flow/recovery-phrase/index.scss
new file mode 100644
index 000000000..7c37d0059
--- /dev/null
+++ b/ui/pages/onboarding-flow/recovery-phrase/index.scss
@@ -0,0 +1,110 @@
+.recovery-phrase {
+ &__tips {
+ flex-direction: column;
+
+ ul {
+ list-style: disc;
+ margin-left: 20px;
+ }
+ }
+
+ &__chips {
+ display: grid;
+ grid-template-columns: 160px 160px 160px;
+ justify-items: center;
+ align-items: center;
+ row-gap: 16px;
+
+ &--hidden {
+ filter: blur(5px);
+ }
+ }
+
+ &__secret {
+ position: relative;
+ }
+
+ &__secret-blocker {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(0, 0, 0, 0.6);
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 0 18px;
+ border-radius: 4px;
+ color: $ui-white;
+
+ &--text {
+ margin-top: 32px;
+ }
+ }
+
+ &__chip-item {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ text-align: center;
+
+ &__number {
+ font-size: $font-size-h5;
+ }
+ }
+
+ &__footer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ &--button {
+ width: 50%;
+ padding: 20px;
+ }
+
+ &--copy {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ &--button {
+ background-color: transparent;
+ border: none;
+ display: flex;
+ justify-content: space-evenly;
+ width: 40%;
+ color: $primary-blue;
+ cursor: pointer;
+ margin-bottom: 24px;
+
+ &:active {
+ color: $ui-black;
+ background-color: transparent;
+ border: none;
+ transform: scale(0.97);
+ }
+ }
+ }
+ }
+
+ &__chip {
+ justify-content: center;
+ border-radius: 13px;
+ height: 32px;
+ width: 120px;
+
+ &--with-input {
+ width: 120px;
+ box-shadow: 0 3px 4px -3px $Grey-800;
+ border-width: 2px;
+ border-radius: 13px;
+ height: 32px;
+ }
+ }
+}
diff --git a/ui/pages/onboarding-flow/recovery-phrase/recovery-phrase-chips.js b/ui/pages/onboarding-flow/recovery-phrase/recovery-phrase-chips.js
new file mode 100644
index 000000000..c46193833
--- /dev/null
+++ b/ui/pages/onboarding-flow/recovery-phrase/recovery-phrase-chips.js
@@ -0,0 +1,101 @@
+import React from 'react';
+import classnames from 'classnames';
+import PropTypes from 'prop-types';
+import Chip from '../../../components/ui/chip';
+import Box from '../../../components/ui/box';
+import Typography from '../../../components/ui/typography';
+import { ChipWithInput } from '../../../components/ui/chip/chip-with-input';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import {
+ TYPOGRAPHY,
+ COLORS,
+ BORDER_STYLE,
+ SIZES,
+ DISPLAY,
+} from '../../../helpers/constants/design-system';
+
+export default function RecoveryPhraseChips({
+ seedPhrase,
+ seedPhraseRevealed,
+ confirmPhase,
+ setInputValue,
+ inputValue,
+ indicesToCheck,
+}) {
+ const t = useI18nContext();
+ const hideSeedPhrase = seedPhraseRevealed === false;
+ return (
+
+
+ {seedPhrase.map((word, index) => {
+ if (
+ confirmPhase &&
+ indicesToCheck &&
+ indicesToCheck.includes(index)
+ ) {
+ return (
+
+
+ {`${index + 1}.`}
+
+
{
+ setInputValue({ ...inputValue, [index]: value });
+ }}
+ />
+
+ );
+ }
+ return (
+
+
+ {`${index + 1}.`}
+
+
+ {word}
+
+
+ );
+ })}
+
+
+ {hideSeedPhrase && (
+
+
+
+ {t('makeSureNoOneWatching')}
+
+
+ )}
+
+ );
+}
+
+RecoveryPhraseChips.propTypes = {
+ seedPhrase: PropTypes.array,
+ seedPhraseRevealed: PropTypes.bool,
+ confirmPhase: PropTypes.bool,
+ setInputValue: PropTypes.func,
+ inputValue: PropTypes.string,
+ indicesToCheck: PropTypes.array,
+};
diff --git a/ui/pages/onboarding-flow/recovery-phrase/review-recovery-phrase.js b/ui/pages/onboarding-flow/recovery-phrase/review-recovery-phrase.js
new file mode 100644
index 000000000..78f8437dc
--- /dev/null
+++ b/ui/pages/onboarding-flow/recovery-phrase/review-recovery-phrase.js
@@ -0,0 +1,124 @@
+import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import PropTypes from 'prop-types';
+import Box from '../../../components/ui/box';
+import Button from '../../../components/ui/button';
+import Typography from '../../../components/ui/typography';
+import Copy from '../../../components/ui/icon/copy-icon.component';
+import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE } from '../../../helpers/constants/routes';
+import {
+ TEXT_ALIGN,
+ TYPOGRAPHY,
+ JUSTIFY_CONTENT,
+ FONT_WEIGHT,
+} from '../../../helpers/constants/design-system';
+import ProgressBar from '../../../components/app/step-progress-bar';
+import RecoveryPhraseChips from './recovery-phrase-chips';
+
+export default function RecoveryPhrase({ seedPhrase }) {
+ const history = useHistory();
+ const t = useI18nContext();
+ const [copied, handleCopy] = useCopyToClipboard();
+ const [seedPhraseRevealed, setSeedPhraseRevealed] = useState(false);
+ return (
+
+
+
+
+ {t('seedPhraseWriteDownHeader')}
+
+
+
+
+ {t('seedPhraseWriteDownDetails')}
+
+
+
+
+ {t('tips')}:
+
+
+ -
+
+ {t('seedPhraseIntroSidebarBulletFour')}
+
+
+ -
+
+ {t('seedPhraseIntroSidebarBulletTwo')}
+
+
+ -
+
+ {t('seedPhraseIntroSidebarBulletThree')}
+
+
+ -
+
+ {t('seedPhraseIntroSidebarBulletFour')}
+
+
+
+
+
+
+ {seedPhraseRevealed ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+RecoveryPhrase.propTypes = {
+ seedPhrase: PropTypes.string,
+};