From 9355fb21c70948443372992c07ae8174e829bd7d Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Thu, 30 Sep 2021 16:34:11 -0500 Subject: [PATCH] Establish onboarding-flow wrapper/router base and feature flag env variable (#12247) * establish onboarding-flow wrapper/router base and feature flag env variable * small cleanup * addressing feedback --- development/build/scripts.js | 2 + ui/ducks/metamask/metamask.js | 15 +++ ui/helpers/constants/routes.js | 28 +++++ .../authenticated/authenticated.component.js | 22 +++- .../initialized/initialized.component.js | 10 +- ui/pages/onboarding-flow/index.scss | 18 +++ .../onboarding-flow-switch.js | 41 +++++++ ui/pages/onboarding-flow/onboarding-flow.js | 109 ++++++++++++++++++ ui/pages/pages.scss | 1 + ui/pages/routes/routes.component.js | 8 +- ui/selectors/first-time-flow.js | 10 +- 11 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 ui/pages/onboarding-flow/index.scss create mode 100644 ui/pages/onboarding-flow/onboarding-flow-switch/onboarding-flow-switch.js create mode 100644 ui/pages/onboarding-flow/onboarding-flow.js diff --git a/development/build/scripts.js b/development/build/scripts.js index da7e73e26..618485ad7 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -27,6 +27,7 @@ const bifyModuleGroups = require('bify-module-groups'); const metamaskrc = require('rc')('metamask', { INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID, + ONBOARDING_V2: process.env.ONBOARDING_V2, SEGMENT_HOST: process.env.SEGMENT_HOST, SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY, SEGMENT_LEGACY_WRITE_KEY: process.env.SEGMENT_LEGACY_WRITE_KEY, @@ -612,6 +613,7 @@ function getEnvironmentVariables({ buildType, devMode, testing }) { ? process.env.SEGMENT_PROD_LEGACY_WRITE_KEY : metamaskrc.SEGMENT_LEGACY_WRITE_KEY, SWAPS_USE_DEV_APIS: process.env.SWAPS_USE_DEV_APIS === '1', + ONBOARDING_V2: metamaskrc.ONBOARDING_V2 === '1', }; } diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index b5867a1ff..e1314d5c2 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -325,3 +325,18 @@ export function getIsGasEstimatesLoading(state) { return isGasEstimatesLoading; } + +export function getCompletedOnboarding(state) { + return state.metamask.completedOnboarding; +} +export function getIsInitialized(state) { + return state.metamask.isInitialized; +} + +export function getIsUnlocked(state) { + return state.metamask.isUnlocked; +} + +export function getSeedPhraseBackedUp(state) { + return state.metamask.seedPhraseBackedUp; +} diff --git a/ui/helpers/constants/routes.js b/ui/helpers/constants/routes.js index 800a4badb..1ae2ef494 100644 --- a/ui/helpers/constants/routes.js +++ b/ui/helpers/constants/routes.js @@ -52,6 +52,21 @@ const INITIALIZE_END_OF_FLOW_ROUTE = '/initialize/end-of-flow'; const INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE = '/initialize/seed-phrase/confirm'; const INITIALIZE_METAMETRICS_OPT_IN_ROUTE = '/initialize/metametrics-opt-in'; +const ONBOARDING_ROUTE = '/onboarding'; +const ONBOARDING_REVIEW_SRP_ROUTE = '/onboarding/review-srp'; +const ONBOARDING_CONFIRM_SRP_ROUTE = '/onboarding/confirm-srp'; +const ONBOARDING_CREATE_PASSWORD_ROUTE = '/onboarding/create-password'; +const ONBOARDING_COMPLETION_ROUTE = '/onboarding/completion'; +const ONBOARDING_UNLOCK_ROUTE = '/onboarding/unlock'; +const ONBOARDING_GET_STARTED_ROUTE = '/onboarding/get-started'; +const ONBOARDING_HELP_US_IMPROVE_ROUTE = '/onboarding/help-us-improve'; +const ONBOARDING_IMPORT_WITH_SRP_ROUTE = + '/onboarding/create-password/import-with-sre'; +const ONBOARDING_IMPORT_MOBILE_ROUTE = '/onboarding/create-password'; +const ONBOARDING_SECURE_YOUR_WALLET_ROUTE = '/onboarding/secure-your-wallet'; +const ONBOARDING_PRIVACY_SETTINGS_ROUTE = '/onboarding/privacy-settings'; +const ONBOARDING_PIN_EXTENSION_ROUTE = '/onboarding/pin-extension'; + const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'; const CONFIRM_SEND_ETHER_PATH = '/send-ether'; const CONFIRM_SEND_TOKEN_PATH = '/send-token'; @@ -199,4 +214,17 @@ export { AWAITING_SIGNATURES_ROUTE, SWAPS_ERROR_ROUTE, SWAPS_MAINTENANCE_ROUTE, + ONBOARDING_ROUTE, + ONBOARDING_GET_STARTED_ROUTE, + ONBOARDING_HELP_US_IMPROVE_ROUTE, + ONBOARDING_CREATE_PASSWORD_ROUTE, + ONBOARDING_IMPORT_WITH_SRP_ROUTE, + ONBOARDING_IMPORT_MOBILE_ROUTE, + ONBOARDING_SECURE_YOUR_WALLET_ROUTE, + ONBOARDING_REVIEW_SRP_ROUTE, + ONBOARDING_CONFIRM_SRP_ROUTE, + ONBOARDING_PRIVACY_SETTINGS_ROUTE, + ONBOARDING_COMPLETION_ROUTE, + ONBOARDING_UNLOCK_ROUTE, + ONBOARDING_PIN_EXTENSION_ROUTE, }; diff --git a/ui/helpers/higher-order-components/authenticated/authenticated.component.js b/ui/helpers/higher-order-components/authenticated/authenticated.component.js index 2feebfdb0..1fe151047 100644 --- a/ui/helpers/higher-order-components/authenticated/authenticated.component.js +++ b/ui/helpers/higher-order-components/authenticated/authenticated.component.js @@ -1,16 +1,32 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Redirect, Route } from 'react-router-dom'; -import { UNLOCK_ROUTE, INITIALIZE_ROUTE } from '../../constants/routes'; +import { + UNLOCK_ROUTE, + INITIALIZE_ROUTE, + ONBOARDING_ROUTE, +} from '../../constants/routes'; export default function Authenticated(props) { const { isUnlocked, completedOnboarding } = props; - switch (true) { + // For ONBOARDING_V2 dev purposes, + // Remove when ONBOARDING_V2 dev complete + case process.env.ONBOARDING_V2 === true: + return ; + case isUnlocked && completedOnboarding: return ; case !completedOnboarding: - return ; + return ( + + ); default: return ; } diff --git a/ui/helpers/higher-order-components/initialized/initialized.component.js b/ui/helpers/higher-order-components/initialized/initialized.component.js index a953403fd..5773d605a 100644 --- a/ui/helpers/higher-order-components/initialized/initialized.component.js +++ b/ui/helpers/higher-order-components/initialized/initialized.component.js @@ -1,13 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Redirect, Route } from 'react-router-dom'; -import { INITIALIZE_ROUTE } from '../../constants/routes'; +import { INITIALIZE_ROUTE, ONBOARDING_ROUTE } from '../../constants/routes'; export default function Initialized(props) { return props.completedOnboarding ? ( ) : ( - + ); } diff --git a/ui/pages/onboarding-flow/index.scss b/ui/pages/onboarding-flow/index.scss new file mode 100644 index 000000000..aebae2fbd --- /dev/null +++ b/ui/pages/onboarding-flow/index.scss @@ -0,0 +1,18 @@ +@import 'recovery-phrase/index'; +@import 'new-account/index'; + +.onboarding-flow { + width: 100%; + background-color: $white; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + &__wrapper { + margin-top: 40px; + padding: 32px; + border: 1px solid $Grey-100; + border-radius: 20px; + } +} diff --git a/ui/pages/onboarding-flow/onboarding-flow-switch/onboarding-flow-switch.js b/ui/pages/onboarding-flow/onboarding-flow-switch/onboarding-flow-switch.js new file mode 100644 index 000000000..3512f6f2f --- /dev/null +++ b/ui/pages/onboarding-flow/onboarding-flow-switch/onboarding-flow-switch.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import { + DEFAULT_ROUTE, + ONBOARDING_COMPLETION_ROUTE, + ONBOARDING_GET_STARTED_ROUTE, + ONBOARDING_UNLOCK_ROUTE, + LOCK_ROUTE, +} from '../../../helpers/constants/routes'; +import { + getCompletedOnboarding, + getIsInitialized, + getIsUnlocked, + getSeedPhraseBackedUp, +} from '../../../ducks/metamask/metamask'; + +export default function OnboardingFlowSwitch() { + const completedOnboarding = useSelector(getCompletedOnboarding); + const isInitialized = useSelector(getIsInitialized); + const seedPhraseBackedUp = useSelector(getSeedPhraseBackedUp); + const isUnlocked = useSelector(getIsUnlocked); + + if (completedOnboarding) { + return ; + } + + if (seedPhraseBackedUp !== null) { + return ; + } + + if (isUnlocked) { + return ; + } + + if (!isInitialized) { + return ; + } + + return ; +} diff --git a/ui/pages/onboarding-flow/onboarding-flow.js b/ui/pages/onboarding-flow/onboarding-flow.js new file mode 100644 index 000000000..fed13cbad --- /dev/null +++ b/ui/pages/onboarding-flow/onboarding-flow.js @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from 'react'; +import { Switch, Route, useHistory } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import Unlock from '../unlock-page'; +import { + ONBOARDING_CREATE_PASSWORD_ROUTE, + ONBOARDING_REVIEW_SRP_ROUTE, + ONBOARDING_CONFIRM_SRP_ROUTE, + ONBOARDING_UNLOCK_ROUTE, + DEFAULT_ROUTE, +} from '../../helpers/constants/routes'; +import { + getCompletedOnboarding, + getIsInitialized, + getIsUnlocked, + getSeedPhraseBackedUp, +} from '../../ducks/metamask/metamask'; +import { + createNewVaultAndGetSeedPhrase, + unlockAndGetSeedPhrase, +} from '../../store/actions'; +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 ConfirmRecoveryPhrase from './recovery-phrase/confirm-recovery-phrase'; + +export default function OnboardingFlow() { + const [seedPhrase, setSeedPhrase] = useState(''); + const dispatch = useDispatch(); + const history = useHistory(); + const isInitialized = useSelector(getIsInitialized); + const isUnlocked = useSelector(getIsUnlocked); + const completedOnboarding = useSelector(getCompletedOnboarding); + const seedPhraseBackedUp = useSelector(getSeedPhraseBackedUp); + const nextRoute = useSelector(getFirstTimeFlowTypeRoute); + + useEffect(() => { + // For ONBOARDING_V2 dev purposes, + // Remove when ONBOARDING_V2 dev complete + if (process.env.ONBOARDING_V2) { + history.push(ONBOARDING_CREATE_PASSWORD_ROUTE); + return; + } + + if (completedOnboarding && seedPhraseBackedUp) { + history.push(DEFAULT_ROUTE); + return; + } + + if (isInitialized && !isUnlocked) { + history.push(ONBOARDING_UNLOCK_ROUTE); + } + }, [ + history, + completedOnboarding, + isInitialized, + isUnlocked, + seedPhraseBackedUp, + ]); + + const handleCreateNewAccount = async (password) => { + const newSeedPhrase = await dispatch( + createNewVaultAndGetSeedPhrase(password), + ); + setSeedPhrase(newSeedPhrase); + }; + + const handleUnlock = async (password) => { + const retreivedSeedPhrase = await dispatch( + unlockAndGetSeedPhrase(password), + ); + setSeedPhrase(retreivedSeedPhrase); + history.push(nextRoute); + }; + + return ( +
+
+ + ( + + )} + /> + } + /> + } + /> + ( + + )} + /> + + +
+
+ ); +} diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss index 5cbc7932e..300bdcf4a 100644 --- a/ui/pages/pages.scss +++ b/ui/pages/pages.scss @@ -19,3 +19,4 @@ @import 'settings/index'; @import 'swaps/index'; @import 'unlock-page/index'; +@import 'onboarding-flow/index'; diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 4b631c302..4c37ca5bb 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -53,6 +53,7 @@ import { BUILD_QUOTE_ROUTE, CONFIRMATION_V_NEXT_ROUTE, CONFIRM_IMPORT_TOKEN_ROUTE, + ONBOARDING_ROUTE, } from '../../helpers/constants/routes'; import { @@ -62,6 +63,7 @@ import { import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { isBeta } from '../../helpers/utils/build-types'; import ConfirmationPage from '../confirmation'; +import OnboardingFlow from '../onboarding-flow/onboarding-flow'; export default class Routes extends Component { static propTypes = { @@ -114,9 +116,11 @@ export default class Routes extends Component { renderRoutes() { const { autoLockTimeLimit, setLastActiveTime } = this.props; - const routes = ( + {process.env.ONBOARDING_V2 && ( + + )} @@ -225,7 +229,7 @@ export default class Routes extends Component { const isInitializing = Boolean( matchPath(location.pathname, { - path: INITIALIZE_ROUTE, + path: process.env.ONBOARDING_V2 ? ONBOARDING_ROUTE : INITIALIZE_ROUTE, exact: false, }), ); diff --git a/ui/selectors/first-time-flow.js b/ui/selectors/first-time-flow.js index b56bdc861..bbebfbd3a 100644 --- a/ui/selectors/first-time-flow.js +++ b/ui/selectors/first-time-flow.js @@ -2,6 +2,8 @@ import { INITIALIZE_CREATE_PASSWORD_ROUTE, INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, DEFAULT_ROUTE, + ONBOARDING_CREATE_PASSWORD_ROUTE, + ONBOARDING_IMPORT_WITH_SRP_ROUTE, } from '../helpers/constants/routes'; export function getFirstTimeFlowTypeRoute(state) { @@ -9,9 +11,13 @@ export function getFirstTimeFlowTypeRoute(state) { let nextRoute; if (firstTimeFlowType === 'create') { - nextRoute = INITIALIZE_CREATE_PASSWORD_ROUTE; + nextRoute = process.env.ONBOARDING_V2 + ? ONBOARDING_CREATE_PASSWORD_ROUTE + : INITIALIZE_CREATE_PASSWORD_ROUTE; } else if (firstTimeFlowType === 'import') { - nextRoute = INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE; + nextRoute = process.env.ONBOARDING_V2 + ? ONBOARDING_IMPORT_WITH_SRP_ROUTE + : INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE; } else { nextRoute = DEFAULT_ROUTE; }