From fba17d77de9e60de0e02e90dc6dbcbbf7454158a Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 23 Jan 2019 07:25:34 -0800 Subject: Refactor first time flow, remove seed phrase from state (#5994) * Refactor and fix styling for first time flow. Remove seed phrase from persisted metamask state * Fix linting and tests * Fix translations, initialization notice routing * Fix drizzle tests * Fix e2e tests * Fix integration tests * Fix styling * Fix migration naming from 030 to 031 * Open extension in browser when user has not completed onboarding --- app/_locales/en/messages.json | 54 ++ app/scripts/controllers/preferences.js | 10 + app/scripts/metamask-controller.js | 1 + app/scripts/migrations/031.js | 31 + app/scripts/migrations/index.js | 1 + app/scripts/ui.js | 11 +- development/states/confirm-sig-requests.json | 1 + development/states/currency-localization.json | 1 + development/states/send-edit.json | 1 + development/states/send-new-ui.json | 1 + development/states/tx-list-items.json | 1 + mascara/src/app/first-time/breadcrumbs.js | 26 - mascara/src/app/first-time/buy-ether-screen.js | 200 ----- mascara/src/app/first-time/confirm-seed-screen.js | 162 ---- .../src/app/first-time/create-password-screen.js | 221 ----- .../src/app/first-time/import-account-screen.js | 208 ----- .../app/first-time/import-seed-phrase-screen.js | 192 ----- mascara/src/app/first-time/index.css | 925 --------------------- mascara/src/app/first-time/index.js | 99 --- mascara/src/app/first-time/loading-screen.js | 17 - mascara/src/app/first-time/notice-screen.js | 135 --- mascara/src/app/first-time/seed-screen.js | 176 ---- mascara/src/app/first-time/spinner.js | 70 -- mascara/src/app/first-time/unique-image-screen.js | 50 -- old-ui/app/app.js | 9 +- old-ui/css.js | 1 - test/e2e/beta/drizzle.spec.js | 35 +- test/e2e/beta/from-import-beta-ui.spec.js | 17 +- test/e2e/beta/metamask-beta-responsive-ui.spec.js | 36 +- test/e2e/beta/metamask-beta-ui.spec.js | 35 +- test/e2e/func.js | 2 +- test/unit/migrations/031-test.js | 56 ++ test/unit/ui/app/actions.spec.js | 6 +- ui/app/actions.js | 92 ++ ui/app/app.js | 199 +++-- .../components/app-header/app-header.component.js | 85 +- .../components/app-header/app-header.container.js | 2 - .../breadcrumbs/breadcrumbs.component.js | 29 + ui/app/components/breadcrumbs/index.js | 1 + ui/app/components/breadcrumbs/index.scss | 15 + .../tests/breadcrumbs.component.test.js | 22 + ui/app/components/button/button.component.js | 2 + ui/app/components/index.scss | 2 + ui/app/components/lock-icon/index.js | 1 + ui/app/components/lock-icon/lock-icon.component.js | 32 + ui/app/components/modals/modal.js | 3 +- ui/app/components/page-container/index.scss | 6 + ui/app/components/pages/authenticated.js | 34 - .../create-password/create-password.component.js | 61 ++ .../create-password/create-password.container.js | 12 + .../import-with-seed-phrase.component.js | 214 +++++ .../import-with-seed-phrase/index.js | 1 + .../pages/first-time-flow/create-password/index.js | 1 + .../create-password/new-account/index.js | 1 + .../new-account/new-account.component.js | 178 ++++ .../create-password/unique-image/index.js | 1 + .../unique-image/unique-image.component.js | 53 ++ .../unique-image/unique-image.container.js | 12 + .../first-time-flow-switch.component.js | 57 ++ .../first-time-flow-switch.container.js | 20 + .../first-time-flow-switch/index.js | 1 + .../first-time-flow/first-time-flow.component.js | 145 ++++ .../first-time-flow/first-time-flow.container.js | 30 + ui/app/components/pages/first-time-flow/index.js | 1 + ui/app/components/pages/first-time-flow/index.scss | 99 +++ .../pages/first-time-flow/notices/index.js | 1 + .../first-time-flow/notices/notices.component.js | 124 +++ .../first-time-flow/notices/notices.container.js | 27 + .../confirm-seed-phrase.component.js | 161 ++++ .../confirm-seed-phrase.container.js | 12 + .../confirm-seed-phrase.state.js | 41 + .../seed-phrase/confirm-seed-phrase/index.js | 1 + .../seed-phrase/confirm-seed-phrase/index.scss | 44 + .../pages/first-time-flow/seed-phrase/index.js | 1 + .../pages/first-time-flow/seed-phrase/index.scss | 36 + .../seed-phrase/reveal-seed-phrase/index.js | 1 + .../seed-phrase/reveal-seed-phrase/index.scss | 53 ++ .../reveal-seed-phrase.component.js | 139 ++++ .../seed-phrase/seed-phrase.component.js | 59 ++ .../seed-phrase/seed-phrase.container.js | 12 + .../pages/first-time-flow/welcome/index.js | 1 + .../pages/first-time-flow/welcome/index.scss | 43 + .../first-time-flow/welcome/welcome.component.js | 65 ++ .../first-time-flow/welcome/welcome.container.js | 25 + ui/app/components/pages/home/home.component.js | 4 +- ui/app/components/pages/index.scss | 4 + ui/app/components/pages/initialized.js | 25 - ui/app/components/pages/keychains/index.scss | 197 +++++ ui/app/components/pages/keychains/restore-vault.js | 6 +- ui/app/components/pages/lock/index.js | 1 + ui/app/components/pages/lock/lock.component.js | 26 + ui/app/components/pages/lock/lock.container.js | 24 + ui/app/components/pages/metamask-route.js | 28 - ui/app/components/pages/unlock-page/index.scss | 1 - .../pages/unlock-page/unlock-page.component.js | 43 +- .../pages/unlock-page/unlock-page.container.js | 39 +- .../components/transaction-view-balance/index.scss | 1 + ui/app/css/index.scss | 2 - ui/app/css/itcss/base/index.scss | 7 - ui/app/css/itcss/components/buttons.scss | 12 + ui/app/css/itcss/components/index.scss | 2 - ui/app/css/itcss/components/loading-overlay.scss | 2 +- ui/app/css/itcss/components/newui-sections.scss | 17 +- ui/app/css/itcss/components/welcome-screen.scss | 60 -- .../authenticated/authenticated.component.js | 22 + .../authenticated/authenticated.container.js | 12 + .../higher-order-components/authenticated/index.js | 1 + .../higher-order-components/initialized/index.js | 1 + .../initialized/initialized.component.js | 14 + .../initialized/initialized.container.js | 12 + ui/app/reducers/metamask.js | 7 + ui/app/routes.js | 21 +- ui/app/welcome-screen.js | 83 -- ui/css.js | 1 - 114 files changed, 2722 insertions(+), 3002 deletions(-) create mode 100644 app/scripts/migrations/031.js delete mode 100644 mascara/src/app/first-time/breadcrumbs.js delete mode 100644 mascara/src/app/first-time/buy-ether-screen.js delete mode 100644 mascara/src/app/first-time/confirm-seed-screen.js delete mode 100644 mascara/src/app/first-time/create-password-screen.js delete mode 100644 mascara/src/app/first-time/import-account-screen.js delete mode 100644 mascara/src/app/first-time/import-seed-phrase-screen.js delete mode 100644 mascara/src/app/first-time/index.css delete mode 100644 mascara/src/app/first-time/index.js delete mode 100644 mascara/src/app/first-time/loading-screen.js delete mode 100644 mascara/src/app/first-time/notice-screen.js delete mode 100644 mascara/src/app/first-time/seed-screen.js delete mode 100644 mascara/src/app/first-time/spinner.js delete mode 100644 mascara/src/app/first-time/unique-image-screen.js create mode 100644 test/unit/migrations/031-test.js create mode 100644 ui/app/components/breadcrumbs/breadcrumbs.component.js create mode 100644 ui/app/components/breadcrumbs/index.js create mode 100644 ui/app/components/breadcrumbs/index.scss create mode 100644 ui/app/components/breadcrumbs/tests/breadcrumbs.component.test.js create mode 100644 ui/app/components/lock-icon/index.js create mode 100644 ui/app/components/lock-icon/lock-icon.component.js delete mode 100644 ui/app/components/pages/authenticated.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/create-password.component.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/create-password.container.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/index.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/index.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/new-account/index.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/unique-image/index.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js create mode 100644 ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.container.js create mode 100644 ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js create mode 100644 ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js create mode 100644 ui/app/components/pages/first-time-flow/first-time-flow-switch/index.js create mode 100644 ui/app/components/pages/first-time-flow/first-time-flow.component.js create mode 100644 ui/app/components/pages/first-time-flow/first-time-flow.container.js create mode 100644 ui/app/components/pages/first-time-flow/index.js create mode 100644 ui/app/components/pages/first-time-flow/index.scss create mode 100644 ui/app/components/pages/first-time-flow/notices/index.js create mode 100644 ui/app/components/pages/first-time-flow/notices/notices.component.js create mode 100644 ui/app/components/pages/first-time-flow/notices/notices.container.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/index.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/index.scss create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js create mode 100644 ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js create mode 100644 ui/app/components/pages/first-time-flow/welcome/index.js create mode 100644 ui/app/components/pages/first-time-flow/welcome/index.scss create mode 100644 ui/app/components/pages/first-time-flow/welcome/welcome.component.js create mode 100644 ui/app/components/pages/first-time-flow/welcome/welcome.container.js delete mode 100644 ui/app/components/pages/initialized.js create mode 100644 ui/app/components/pages/keychains/index.scss create mode 100644 ui/app/components/pages/lock/index.js create mode 100644 ui/app/components/pages/lock/lock.component.js create mode 100644 ui/app/components/pages/lock/lock.container.js delete mode 100644 ui/app/components/pages/metamask-route.js delete mode 100644 ui/app/css/itcss/base/index.scss delete mode 100644 ui/app/css/itcss/components/welcome-screen.scss create mode 100644 ui/app/higher-order-components/authenticated/authenticated.component.js create mode 100644 ui/app/higher-order-components/authenticated/authenticated.container.js create mode 100644 ui/app/higher-order-components/authenticated/index.js create mode 100644 ui/app/higher-order-components/initialized/index.js create mode 100644 ui/app/higher-order-components/initialized/initialized.component.js create mode 100644 ui/app/higher-order-components/initialized/initialized.container.js delete mode 100644 ui/app/welcome-screen.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index e849517a8..7810c6eb4 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -206,6 +206,9 @@ "clickToAdd": { "message": "Click on $1 to add them to your account" }, + "clickToRevealSeed": { + "message": "Click here to reveal secret words" + }, "close": { "message": "Close" }, @@ -227,6 +230,9 @@ "confirmPassword": { "message": "Confirm Password" }, + "confirmSecretBackupPhrase": { + "message": "Confirm your Secret Backup Phrase" + }, "confirmTransaction": { "message": "Confirm Transaction" }, @@ -314,6 +320,9 @@ "createDen": { "message": "Create" }, + "createPassword": { + "message": "Create Password" + }, "crypto": { "message": "Crypto", "description": "Exchange type (cryptocurrencies)" @@ -403,6 +412,9 @@ "downloadGoogleChrome": { "message": "Download Google Chrome" }, + "downloadSecretBackup": { + "message": "Download this Secret Backup Phrase and keep it stored safely on an external encrypted hard drive or storage medium." + }, "downloadStateLogs": { "message": "Download State Logs" }, @@ -611,6 +623,9 @@ "importAccountMsg": { "message": " Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts " }, + "importAccountSeedPhrase": { + "message": "Import an Account with Seed Phrase" + }, "importAnAccount": { "message": "Import an account" }, @@ -624,6 +639,9 @@ "importUsingSeed": { "message": "Import using account seed phrase" }, + "importWithSeedPhrase": { + "message": "Import with seed phrase" + }, "info": { "message": "Info" }, @@ -731,6 +749,9 @@ "mainnet": { "message": "Main Ethereum Network" }, + "memorizePhrase": { + "message": "Memorize this phrase." + }, "menu": { "message": "Menu" }, @@ -1096,12 +1117,24 @@ "searchResults": { "message": "Search Results" }, + "secretBackupPhrase": { + "message": "Secret Backup Phrase" + }, + "secretBackupPhraseDescription": { + "message": "Your secret backup phrase makes it easy to back up and restore your account." + }, + "secretBackupPhraseWarning": { + "message": "WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever." + }, "secretPhrase": { "message": "Enter your secret twelve word phrase here to restore your vault." }, "secondsShorthand": { "message": "Sec" }, + "seedPhrasePlaceholder": { + "message": "Separate each word with a single space" + }, "seedPhraseReq": { "message": "Seed phrases are 12 words long" }, @@ -1111,6 +1144,9 @@ "selectCurrency": { "message": "Select Currency" }, + "selectEachPhrase": { + "message": "Please select each phrase in order to make sure it is correct." + }, "selectLocale": { "message": "Select Locale" }, @@ -1258,6 +1294,9 @@ "step3HardwareWalletMsg": { "message": "Use your hardware account like you would with any Ethereum account. Log in to dApps, send Eth, buy and store ERC20 tokens and Non-Fungible tokens like CryptoKitties." }, + "storePhrase": { + "message": "Store this phrase in a password manager like 1Password." + }, "submit": { "message": "Submit" }, @@ -1279,6 +1318,9 @@ "testFaucet": { "message": "Test Faucet" }, + "tips": { + "message": "Tips" + }, "to": { "message": "To" }, @@ -1477,6 +1519,9 @@ "whatsThis": { "message": "What's this?" }, + "writePhrase": { + "message": "Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations." + }, "yesLetsTry": { "message": "Yes, let's try" }, @@ -1492,6 +1537,15 @@ "yourPrivateSeedPhrase": { "message": "Your private seed phrase" }, + "yourUniqueAccountImage": { + "message": "Your unique account image" + }, + "yourUniqueAccountImageDescription1": { + "message": "This image was programmatically generated for you by your new account number." + }, + "yourUniqueAccountImageDescription2": { + "message": "You’ll see this image everytime you need to confirm a transaction." + }, "zeroGasPriceOnSpeedUpError": { "message":"Zero gas price on speed up" } diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index fa162c21f..e82a69da2 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -46,6 +46,7 @@ class PreferencesController { preferences: { useNativeCurrencyAsPrimaryCurrency: true, }, + completedOnboarding: false, }, opts.initState) this.diagnostics = opts.diagnostics @@ -516,6 +517,15 @@ class PreferencesController { return this.store.getState().preferences } + /** + * Sets the completedOnboarding state to true, indicating that the user has completed the + * onboarding process. + */ + completeOnboarding () { + this.store.updateState({ completedOnboarding: true }) + return Promise.resolve(true) + } + // // PRIVATE METHODS // diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ea57582a0..4189bdd10 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -425,6 +425,7 @@ module.exports = class MetamaskController extends EventEmitter { setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), setPreference: nodeify(preferencesController.setPreference, preferencesController), + completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), // BlacklistController diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js new file mode 100644 index 000000000..98d182828 --- /dev/null +++ b/app/scripts/migrations/031.js @@ -0,0 +1,31 @@ +// next version number +const version = 31 +const clone = require('clone') + + /* + * The purpose of this migration is to properly set the completedOnboarding flag baesd on the state + * of the KeyringController. + */ +module.exports = { + version, + + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + return versionedData + }, +} + + function transformState (state) { + const { KeyringController, PreferencesController } = state + + if (KeyringController && PreferencesController) { + const { vault } = KeyringController + PreferencesController.completedOnboarding = Boolean(vault) + } + + return state +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 99cca94b8..eb1b51685 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -41,4 +41,5 @@ module.exports = [ require('./028'), require('./029'), require('./030'), + require('./031'), ] diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 682a4aaac..e4b9b7b9c 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -5,7 +5,7 @@ const {getShouldUseNewUi} = require('../../ui/app/selectors') const startPopup = require('./popup-core') const PortStream = require('extension-port-stream') const { getEnvironmentType } = require('./lib/util') -const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums') +const { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN } = require('./lib/enums') const extension = require('extensionizer') const ExtensionPlatform = require('./platforms/extension') const NotificationManager = require('./lib/notification-manager') @@ -49,7 +49,14 @@ async function start () { if (err) return displayCriticalError(err) const state = store.getState() - let betaUIState = Boolean(state.featureFlags && state.featureFlags.betaUI) + const { metamask: { completedOnboarding, featureFlags } = {} } = state + + if (!completedOnboarding && windowType !== ENVIRONMENT_TYPE_FULLSCREEN) { + global.platform.openExtensionInBrowser() + return + } + + let betaUIState = Boolean(featureFlags && featureFlags.betaUI) const useBetaCss = getShouldUseNewUi(state) let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss() diff --git a/development/states/confirm-sig-requests.json b/development/states/confirm-sig-requests.json index f261ce67f..f41327f16 100644 --- a/development/states/confirm-sig-requests.json +++ b/development/states/confirm-sig-requests.json @@ -1,5 +1,6 @@ { "metamask": { + "completedOnboarding": true, "isInitialized": true, "isUnlocked": true, "featureFlags": {"betaUI": true}, diff --git a/development/states/currency-localization.json b/development/states/currency-localization.json index 43acf7fda..b36d7d2d2 100644 --- a/development/states/currency-localization.json +++ b/development/states/currency-localization.json @@ -1,5 +1,6 @@ { "metamask": { + "completedOnboarding": true, "isInitialized": true, "isUnlocked": true, "featureFlags": {"betaUI": true}, diff --git a/development/states/send-edit.json b/development/states/send-edit.json index 09a380730..5470f4396 100644 --- a/development/states/send-edit.json +++ b/development/states/send-edit.json @@ -1,5 +1,6 @@ { "metamask": { + "completedOnboarding": true, "isInitialized": true, "isUnlocked": true, "featureFlags": {"betaUI": true}, diff --git a/development/states/send-new-ui.json b/development/states/send-new-ui.json index 60a4af228..0a457da0e 100644 --- a/development/states/send-new-ui.json +++ b/development/states/send-new-ui.json @@ -1,5 +1,6 @@ { "metamask": { + "completedOnboarding": true, "isInitialized": true, "isUnlocked": true, "featureFlags": {"betaUI": true}, diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json index a3fcd9184..d41fa664a 100644 --- a/development/states/tx-list-items.json +++ b/development/states/tx-list-items.json @@ -1,5 +1,6 @@ { "metamask": { + "completedOnboarding": true, "isInitialized": true, "isUnlocked": true, "featureFlags": {"betaUI": true}, diff --git a/mascara/src/app/first-time/breadcrumbs.js b/mascara/src/app/first-time/breadcrumbs.js deleted file mode 100644 index d86e10d48..000000000 --- a/mascara/src/app/first-time/breadcrumbs.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -export default class Breadcrumbs extends Component { - - static propTypes = { - total: PropTypes.number, - currentIndex: PropTypes.number, - }; - - render () { - const {total, currentIndex} = this.props - return ( -
- {Array(total).fill().map((_, i) => ( -
- ))} -
- ) - } - -} diff --git a/mascara/src/app/first-time/buy-ether-screen.js b/mascara/src/app/first-time/buy-ether-screen.js deleted file mode 100644 index e270392e1..000000000 --- a/mascara/src/app/first-time/buy-ether-screen.js +++ /dev/null @@ -1,200 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import {connect} from 'react-redux' -import {qrcode} from 'qrcode-npm' -import copyToClipboard from 'copy-to-clipboard' -import ShapeShiftForm from '../shapeshift-form' -import Identicon from '../../../../ui/app/components/identicon' -import {buyEth, showAccountDetail} from '../../../../ui/app/actions' - -class BuyEtherScreen extends Component { - static OPTION_VALUES = { - COINBASE: 'coinbase', - SHAPESHIFT: 'shapeshift', - QR_CODE: 'qr_code', - }; - - static OPTIONS = [ - { - name: 'Direct Deposit', - value: BuyEtherScreen.OPTION_VALUES.QR_CODE, - }, - { - name: 'Buy with Dollars', - value: BuyEtherScreen.OPTION_VALUES.COINBASE, - }, - { - name: 'Buy with Cryptos', - value: BuyEtherScreen.OPTION_VALUES.SHAPESHIFT, - }, - ]; - - static propTypes = { - address: PropTypes.string, - goToCoinbase: PropTypes.func.isRequired, - showAccountDetail: PropTypes.func.isRequired, - } - - state = { - selectedOption: BuyEtherScreen.OPTION_VALUES.QR_CODE, - justCopied: false, - } - - copyToClipboard = () => { - const { address } = this.props - - this.setState({ justCopied: true }, () => copyToClipboard(address)) - - setTimeout(() => this.setState({ justCopied: false }), 1000) - } - - renderSkip () { - const {showAccountDetail, address} = this.props - - return ( -
showAccountDetail(address)} - > - Do it later -
- ) - } - - renderCoinbaseLogo () { - return ( - - - - - - - - - - - - - - - ) - } - - renderCoinbaseForm () { - const {goToCoinbase, address} = this.props - - return ( -
-
{this.renderCoinbaseLogo()}
-
Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.
- What is Ethereum? -
- -
-
- ) - } - - renderContent () { - const { OPTION_VALUES } = BuyEtherScreen - const { address } = this.props - const { justCopied } = this.state - const qrImage = qrcode(4, 'M') - qrImage.addData(address) - qrImage.make() - - switch (this.state.selectedOption) { - case OPTION_VALUES.COINBASE: - return this.renderCoinbaseForm() - case OPTION_VALUES.SHAPESHIFT: - return ( -
-
-
- Trade any leading blockchain asset for any other. Protection by Design. No Account Needed. -
- -
- ) - case OPTION_VALUES.QR_CODE: - return ( -
-
-
Deposit Ether directly into your account.
-
(This is the account address that MetaMask created for you to recieve funds.)
-
- -
-
- ) - default: - return null - } - } - - render () { - const { OPTIONS } = BuyEtherScreen - const { selectedOption } = this.state - - return ( -
- -
Deposit Ether
-
- MetaMask works best if you have Ether in your account to pay for transaction gas fees and more. To get Ether, choose from one of these methods. -
-
-
-
Deposit Options
- {this.renderSkip()} -
-
-
- {OPTIONS.map(({ name, value }) => ( -
this.setState({ selectedOption: value })} - > -
{name}
- {value === selectedOption && ( - - - - )} -
- ))} -
-
- {this.renderContent()} -
-
-
-
- ) - } -} - -export default connect( - ({ metamask: { selectedAddress } }) => ({ - address: selectedAddress, - }), - dispatch => ({ - goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })), - showAccountDetail: address => dispatch(showAccountDetail(address)), - }) -)(BuyEtherScreen) diff --git a/mascara/src/app/first-time/confirm-seed-screen.js b/mascara/src/app/first-time/confirm-seed-screen.js deleted file mode 100644 index dfbaffe33..000000000 --- a/mascara/src/app/first-time/confirm-seed-screen.js +++ /dev/null @@ -1,162 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import classnames from 'classnames' -import shuffle from 'lodash.shuffle' -import { compose } from 'recompose' -import Identicon from '../../../../ui/app/components/identicon' -import { confirmSeedWords, showModal } from '../../../../ui/app/actions' -import Breadcrumbs from './breadcrumbs' -import LoadingScreen from './loading-screen' -import { DEFAULT_ROUTE, INITIALIZE_BACKUP_PHRASE_ROUTE } from '../../../../ui/app/routes' - -class ConfirmSeedScreen extends Component { - static propTypes = { - isLoading: PropTypes.bool, - address: PropTypes.string, - seedWords: PropTypes.string, - confirmSeedWords: PropTypes.func, - history: PropTypes.object, - openBuyEtherModal: PropTypes.func, - }; - - static defaultProps = { - seedWords: '', - } - - constructor (props) { - super(props) - const { seedWords } = props - this.state = { - selectedSeeds: [], - shuffledSeeds: seedWords && shuffle(seedWords.split(' ')) || [], - } - } - - componentWillMount () { - const { seedWords, history } = this.props - - if (!seedWords) { - history.push(DEFAULT_ROUTE) - } - } - - handleClick () { - const { confirmSeedWords, history, openBuyEtherModal } = this.props - - confirmSeedWords() - .then(() => { - history.push(DEFAULT_ROUTE) - openBuyEtherModal() - }) - } - - render () { - const { seedWords, history } = this.props - const { selectedSeeds, shuffledSeeds } = this.state - const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ') - - return ( -
- { - this.props.isLoading - ? - : ( -
-
-
- { - e.preventDefault() - history.push(INITIALIZE_BACKUP_PHRASE_ROUTE) - }} - href="#" - > - {`< Back`} - - -
-
-
- Confirm your Secret Backup Phrase -
-
- Please select each phrase in order to make sure it is correct. -
-
- {selectedSeeds.map(([_, word], i) => ( - - ))} -
-
- {shuffledSeeds.map((word, i) => { - const isSelected = selectedSeeds - .filter(([index, seed]) => seed === word && index === i) - .length - - return ( - - ) - })} -
- -
-
- -
-
-
- ) - } -
- ) - } -} - -export default compose( - withRouter, - connect( - ({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({ - seedWords, - isLoading, - address: selectedAddress, - }), - dispatch => ({ - confirmSeedWords: () => dispatch(confirmSeedWords()), - openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})), - }) - ) -)(ConfirmSeedScreen) diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js deleted file mode 100644 index 0908787da..000000000 --- a/mascara/src/app/first-time/create-password-screen.js +++ /dev/null @@ -1,221 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' -import { withRouter } from 'react-router-dom' -import { compose } from 'recompose' -import { createNewVaultAndKeychain } from '../../../../ui/app/actions' -import Breadcrumbs from './breadcrumbs' -import EventEmitter from 'events' -import Mascot from '../../../../ui/app/components/mascot' -import classnames from 'classnames' -import { - INITIALIZE_UNIQUE_IMAGE_ROUTE, - INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, - INITIALIZE_NOTICE_ROUTE, -} from '../../../../ui/app/routes' -import TextField from '../../../../ui/app/components/text-field' - -class CreatePasswordScreen extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - isLoading: PropTypes.bool.isRequired, - createAccount: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - isInitialized: PropTypes.bool, - isUnlocked: PropTypes.bool, - isMascara: PropTypes.bool.isRequired, - } - - state = { - password: '', - confirmPassword: '', - passwordError: null, - confirmPasswordError: null, - } - - constructor (props) { - super(props) - this.animationEventEmitter = new EventEmitter() - } - - componentWillMount () { - const { isInitialized, history } = this.props - - if (isInitialized) { - history.push(INITIALIZE_NOTICE_ROUTE) - } - } - - isValid () { - const { password, confirmPassword } = this.state - - if (!password || !confirmPassword) { - return false - } - - if (password.length < 8) { - return false - } - - return password === confirmPassword - } - - createAccount = (event) => { - event.preventDefault() - - if (!this.isValid()) { - return - } - - const { password } = this.state - const { createAccount, history } = this.props - - this.setState({ isLoading: true }) - createAccount(password) - .then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE)) - } - - handlePasswordChange (password) { - const { confirmPassword } = this.state - let confirmPasswordError = null - let passwordError = null - - if (password && password.length < 8) { - passwordError = this.context.t('passwordNotLongEnough') - } - - if (confirmPassword && password !== confirmPassword) { - confirmPasswordError = this.context.t('passwordsDontMatch') - } - - this.setState({ password, passwordError, confirmPasswordError }) - } - - handleConfirmPasswordChange (confirmPassword) { - const { password } = this.state - let confirmPasswordError = null - - if (password !== confirmPassword) { - confirmPasswordError = this.context.t('passwordsDontMatch') - } - - this.setState({ confirmPassword, confirmPasswordError }) - } - - render () { - const { history, isMascara } = this.props - const { passwordError, confirmPasswordError } = this.state - const { t } = this.context - - return ( -
-
- {isMascara &&
- -
- MetaMask is a secure identity vault for Ethereum. -
-
- It allows you to hold ether & tokens, and interact with decentralized applications. -
-
} -
-
- Create Password -
- this.handlePasswordChange(event.target.value)} - error={passwordError} - autoFocus - autoComplete="new-password" - margin="normal" - fullWidth - largeLabel - /> - this.handleConfirmPasswordChange(event.target.value)} - error={confirmPasswordError} - autoComplete="confirm-password" - margin="normal" - fullWidth - largeLabel - /> - - { - e.preventDefault() - history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) - }} - > - Import with seed phrase - - { /* } - { - e.preventDefault() - history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE) - }} - > - Import an account - - { */ } - - -
-
- ) - } -} - -const mapStateToProps = ({ metamask, appState }) => { - const { isInitialized, isUnlocked, isMascara, noActiveNotices } = metamask - const { isLoading } = appState - - return { - isLoading, - isInitialized, - isUnlocked, - isMascara, - noActiveNotices, - } -} - -export default compose( - withRouter, - connect( - mapStateToProps, - dispatch => ({ - createAccount: password => dispatch(createNewVaultAndKeychain(password)), - }) - ) -)(CreatePasswordScreen) diff --git a/mascara/src/app/first-time/import-account-screen.js b/mascara/src/app/first-time/import-account-screen.js deleted file mode 100644 index 555a26386..000000000 --- a/mascara/src/app/first-time/import-account-screen.js +++ /dev/null @@ -1,208 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' -import classnames from 'classnames' -import LoadingScreen from './loading-screen' -import {importNewAccount, hideWarning} from '../../../../ui/app/actions' - -const Input = ({ label, placeholder, onChange, errorMessage, type = 'text' }) => ( -
-
{label}
- -
{errorMessage}
-
-) - -Input.prototype.propTypes = { - label: PropTypes.string.isRequired, - placeholder: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - errorMessage: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, -} - -class ImportAccountScreen extends Component { - static OPTIONS = { - PRIVATE_KEY: 'private_key', - JSON_FILE: 'json_file', - }; - - static propTypes = { - warning: PropTypes.string, - back: PropTypes.func.isRequired, - next: PropTypes.func.isRequired, - importNewAccount: PropTypes.func.isRequired, - hideWarning: PropTypes.func.isRequired, - isLoading: PropTypes.bool.isRequired, - }; - - state = { - selectedOption: ImportAccountScreen.OPTIONS.PRIVATE_KEY, - privateKey: '', - jsonFile: {}, - } - - isValid () { - const { OPTIONS } = ImportAccountScreen - const { privateKey, jsonFile, password } = this.state - - switch (this.state.selectedOption) { - case OPTIONS.JSON_FILE: - return Boolean(jsonFile && password) - case OPTIONS.PRIVATE_KEY: - default: - return Boolean(privateKey) - } - } - - onClick = () => { - const { OPTIONS } = ImportAccountScreen - const { importNewAccount, next } = this.props - const { privateKey, jsonFile, password } = this.state - - switch (this.state.selectedOption) { - case OPTIONS.JSON_FILE: - return importNewAccount('JSON File', [ jsonFile, password ]) - // JS runtime requires caught rejections but failures are handled by Redux - .catch() - .then(next) - case OPTIONS.PRIVATE_KEY: - default: - return importNewAccount('Private Key', [ privateKey ]) - // JS runtime requires caught rejections but failures are handled by Redux - .catch() - .then(next) - } - } - - renderPrivateKey () { - return Input({ - label: 'Add Private Key String', - placeholder: 'Enter private key', - onChange: e => this.setState({ privateKey: e.target.value }), - errorMessage: this.props.warning && 'Something went wrong. Please make sure your private key is correct.', - }) - } - - renderJsonFile () { - const { jsonFile: { name } } = this.state - const { warning } = this.props - - return ( -
-
-
Upload File
-
- this.setState({ jsonFile: e.target.files[0] })} - /> - -
{name}
-
-
- {warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'} -
-
- {Input({ - label: 'Enter Password', - placeholder: 'Enter Password', - type: 'password', - onChange: e => this.setState({ password: e.target.value }), - errorMessage: warning && 'Please make sure your password is correct.', - })} -
- ) - } - - renderContent () { - const { OPTIONS } = ImportAccountScreen - - switch (this.state.selectedOption) { - case OPTIONS.JSON_FILE: - return this.renderJsonFile() - case OPTIONS.PRIVATE_KEY: - default: - return this.renderPrivateKey() - } - } - - render () { - const { OPTIONS } = ImportAccountScreen - const { selectedOption } = this.state - - return this.props.isLoading - ? - : ( -
- { - e.preventDefault() - this.props.back() - }} - href="#" - > - {`< Back`} - -
- Import an Account -
-
- How would you like to import your account? -
- - {this.renderContent()} - - - File import not working? - -
- ) - } -} - -export default connect( - ({ appState: { isLoading, warning } }) => ({ isLoading, warning }), - dispatch => ({ - importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args)), - hideWarning: () => dispatch(hideWarning()), - }) -)(ImportAccountScreen) diff --git a/mascara/src/app/first-time/import-seed-phrase-screen.js b/mascara/src/app/first-time/import-seed-phrase-screen.js deleted file mode 100644 index 764e9ed4c..000000000 --- a/mascara/src/app/first-time/import-seed-phrase-screen.js +++ /dev/null @@ -1,192 +0,0 @@ -import {validateMnemonic} from 'bip39' -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' -import { - createNewVaultAndRestore, - unMarkPasswordForgotten, -} from '../../../../ui/app/actions' -import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes' -import TextField from '../../../../ui/app/components/text-field' - -class ImportSeedPhraseScreen extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - warning: PropTypes.string, - createNewVaultAndRestore: PropTypes.func.isRequired, - leaveImportSeedScreenState: PropTypes.func, - history: PropTypes.object, - isLoading: PropTypes.bool, - }; - - state = { - seedPhrase: '', - password: '', - confirmPassword: '', - seedPhraseError: null, - passwordError: null, - confirmPasswordError: null, - } - - parseSeedPhrase = (seedPhrase) => { - return seedPhrase - .trim() - .match(/\w+/g) - .join(' ') - } - - handleSeedPhraseChange (seedPhrase) { - let seedPhraseError = null - - if (seedPhrase) { - const parsedSeedPhrase = this.parseSeedPhrase(seedPhrase) - if (parsedSeedPhrase.split(' ').length !== 12) { - seedPhraseError = this.context.t('seedPhraseReq') - } else if (!validateMnemonic(parsedSeedPhrase)) { - seedPhraseError = this.context.t('invalidSeedPhrase') - } - } - - this.setState({ seedPhrase, seedPhraseError }) - } - - handlePasswordChange (password) { - const { confirmPassword } = this.state - let confirmPasswordError = null - let passwordError = null - - if (password && password.length < 8) { - passwordError = this.context.t('passwordNotLongEnough') - } - - if (confirmPassword && password !== confirmPassword) { - confirmPasswordError = this.context.t('passwordsDontMatch') - } - - this.setState({ password, passwordError, confirmPasswordError }) - } - - handleConfirmPasswordChange (confirmPassword) { - const { password } = this.state - let confirmPasswordError = null - - if (password !== confirmPassword) { - confirmPasswordError = this.context.t('passwordsDontMatch') - } - - this.setState({ confirmPassword, confirmPasswordError }) - } - - onClick = () => { - const { password, seedPhrase } = this.state - const { - createNewVaultAndRestore, - leaveImportSeedScreenState, - history, - } = this.props - - leaveImportSeedScreenState() - createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase)) - .then(() => history.push(INITIALIZE_NOTICE_ROUTE)) - } - - hasError () { - const { passwordError, confirmPasswordError, seedPhraseError } = this.state - return passwordError || confirmPasswordError || seedPhraseError - } - - render () { - const { - seedPhrase, - password, - confirmPassword, - seedPhraseError, - passwordError, - confirmPasswordError, - } = this.state - const { t } = this.context - const { isLoading } = this.props - const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError() - - return ( -
-
-
- { - e.preventDefault() - this.props.history.goBack() - }} - href="#" - > - {`< Back`} - -
- Import an Account with Seed Phrase -
-
- Enter your secret twelve word phrase here to restore your vault. -
-
- -