aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@users.noreply.github.com>2019-01-23 23:25:34 +0800
committerWhymarrh Whitby <whymarrh.whitby@gmail.com>2019-01-23 23:25:34 +0800
commitfba17d77de9e60de0e02e90dc6dbcbbf7454158a (patch)
tree0a14f465c25b2b400f5706b55993dcf06d6633a3
parent69fcfa427bdee2ea287e9d9c23963dc1032685cd (diff)
downloadtangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.tar
tangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.tar.gz
tangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.tar.bz2
tangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.tar.lz
tangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.tar.xz
tangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.tar.zst
tangerine-wallet-browser-fba17d77de9e60de0e02e90dc6dbcbbf7454158a.zip
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
-rw-r--r--app/_locales/en/messages.json54
-rw-r--r--app/scripts/controllers/preferences.js10
-rw-r--r--app/scripts/metamask-controller.js1
-rw-r--r--app/scripts/migrations/031.js31
-rw-r--r--app/scripts/migrations/index.js1
-rw-r--r--app/scripts/ui.js11
-rw-r--r--development/states/confirm-sig-requests.json1
-rw-r--r--development/states/currency-localization.json1
-rw-r--r--development/states/send-edit.json1
-rw-r--r--development/states/send-new-ui.json1
-rw-r--r--development/states/tx-list-items.json1
-rw-r--r--mascara/src/app/first-time/breadcrumbs.js26
-rw-r--r--mascara/src/app/first-time/buy-ether-screen.js200
-rw-r--r--mascara/src/app/first-time/confirm-seed-screen.js162
-rw-r--r--mascara/src/app/first-time/create-password-screen.js221
-rw-r--r--mascara/src/app/first-time/import-account-screen.js208
-rw-r--r--mascara/src/app/first-time/import-seed-phrase-screen.js192
-rw-r--r--mascara/src/app/first-time/index.css925
-rw-r--r--mascara/src/app/first-time/index.js99
-rw-r--r--mascara/src/app/first-time/loading-screen.js17
-rw-r--r--mascara/src/app/first-time/notice-screen.js135
-rw-r--r--mascara/src/app/first-time/seed-screen.js176
-rw-r--r--mascara/src/app/first-time/spinner.js70
-rw-r--r--mascara/src/app/first-time/unique-image-screen.js50
-rw-r--r--old-ui/app/app.js9
-rw-r--r--old-ui/css.js1
-rw-r--r--test/e2e/beta/drizzle.spec.js35
-rw-r--r--test/e2e/beta/from-import-beta-ui.spec.js17
-rw-r--r--test/e2e/beta/metamask-beta-responsive-ui.spec.js36
-rw-r--r--test/e2e/beta/metamask-beta-ui.spec.js35
-rw-r--r--test/e2e/func.js2
-rw-r--r--test/unit/migrations/031-test.js56
-rw-r--r--test/unit/ui/app/actions.spec.js6
-rw-r--r--ui/app/actions.js92
-rw-r--r--ui/app/app.js199
-rw-r--r--ui/app/components/app-header/app-header.component.js85
-rw-r--r--ui/app/components/app-header/app-header.container.js2
-rw-r--r--ui/app/components/breadcrumbs/breadcrumbs.component.js29
-rw-r--r--ui/app/components/breadcrumbs/index.js1
-rw-r--r--ui/app/components/breadcrumbs/index.scss15
-rw-r--r--ui/app/components/breadcrumbs/tests/breadcrumbs.component.test.js22
-rw-r--r--ui/app/components/button/button.component.js2
-rw-r--r--ui/app/components/index.scss2
-rw-r--r--ui/app/components/lock-icon/index.js1
-rw-r--r--ui/app/components/lock-icon/lock-icon.component.js32
-rw-r--r--ui/app/components/modals/modal.js3
-rw-r--r--ui/app/components/page-container/index.scss6
-rw-r--r--ui/app/components/pages/authenticated.js34
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/create-password.component.js61
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/create-password.container.js12
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js214
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/new-account/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js178
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/unique-image/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js53
-rw-r--r--ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.container.js12
-rw-r--r--ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js57
-rw-r--r--ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js20
-rw-r--r--ui/app/components/pages/first-time-flow/first-time-flow-switch/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/first-time-flow.component.js145
-rw-r--r--ui/app/components/pages/first-time-flow/first-time-flow.container.js30
-rw-r--r--ui/app/components/pages/first-time-flow/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/index.scss99
-rw-r--r--ui/app/components/pages/first-time-flow/notices/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/notices/notices.component.js124
-rw-r--r--ui/app/components/pages/first-time-flow/notices/notices.container.js27
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js161
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js12
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js41
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss44
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/index.scss36
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss53
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js139
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js59
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js12
-rw-r--r--ui/app/components/pages/first-time-flow/welcome/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/welcome/index.scss43
-rw-r--r--ui/app/components/pages/first-time-flow/welcome/welcome.component.js65
-rw-r--r--ui/app/components/pages/first-time-flow/welcome/welcome.container.js25
-rw-r--r--ui/app/components/pages/home/home.component.js4
-rw-r--r--ui/app/components/pages/index.scss4
-rw-r--r--ui/app/components/pages/initialized.js25
-rw-r--r--ui/app/components/pages/keychains/index.scss197
-rw-r--r--ui/app/components/pages/keychains/restore-vault.js6
-rw-r--r--ui/app/components/pages/lock/index.js1
-rw-r--r--ui/app/components/pages/lock/lock.component.js26
-rw-r--r--ui/app/components/pages/lock/lock.container.js24
-rw-r--r--ui/app/components/pages/metamask-route.js28
-rw-r--r--ui/app/components/pages/unlock-page/index.scss1
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.component.js43
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.container.js39
-rw-r--r--ui/app/components/transaction-view-balance/index.scss1
-rw-r--r--ui/app/css/index.scss2
-rw-r--r--ui/app/css/itcss/base/index.scss7
-rw-r--r--ui/app/css/itcss/components/buttons.scss12
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/loading-overlay.scss2
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss17
-rw-r--r--ui/app/css/itcss/components/welcome-screen.scss60
-rw-r--r--ui/app/higher-order-components/authenticated/authenticated.component.js22
-rw-r--r--ui/app/higher-order-components/authenticated/authenticated.container.js12
-rw-r--r--ui/app/higher-order-components/authenticated/index.js1
-rw-r--r--ui/app/higher-order-components/initialized/index.js1
-rw-r--r--ui/app/higher-order-components/initialized/initialized.component.js14
-rw-r--r--ui/app/higher-order-components/initialized/initialized.container.js12
-rw-r--r--ui/app/reducers/metamask.js7
-rw-r--r--ui/app/routes.js21
-rw-r--r--ui/app/welcome-screen.js83
-rw-r--r--ui/css.js1
114 files changed, 2722 insertions, 3002 deletions
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 (
- <div className="breadcrumbs">
- {Array(total).fill().map((_, i) => (
- <div
- key={i}
- className="breadcrumb"
- style={{backgroundColor: i === currentIndex ? '#D8D8D8' : '#FFFFFF'}}
- />
- ))}
- </div>
- )
- }
-
-}
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 (
- <div
- className="buy-ether__do-it-later"
- onClick={() => showAccountDetail(address)}
- >
- Do it later
- </div>
- )
- }
-
- renderCoinbaseLogo () {
- return (
- <svg width="140px" height="49px" viewBox="0 0 579 126" version="1.1">
- <g id="Page-1" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd">
- <g id="Imported-Layers" fill="#0081C9">
- <path d="M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873" id="Fill-1" />
- <path d="M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z" id="Fill-2" />
- <path d="M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z" id="Fill-3" />
- <path d="M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137" id="Fill-4" />
- <path d="M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z" id="Fill-5" />
- <path d="M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z" id="Fill-6" />
- <path d="M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873" id="Fill-7" />
- <path d="M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z" id="Fill-8" />
- </g>
- </g>
- </svg>
- )
- }
-
- renderCoinbaseForm () {
- const {goToCoinbase, address} = this.props
-
- return (
- <div className="buy-ether__action-content-wrapper">
- <div>{this.renderCoinbaseLogo()}</div>
- <div className="buy-ether__body-text">Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
- <a className="first-time-flow__link buy-ether__faq-link">What is Ethereum?</a>
- <div className="buy-ether__buttons">
- <button
- className="first-time-flow__button"
- onClick={() => goToCoinbase(address)}
- >
- Buy
- </button>
- </div>
- </div>
- )
- }
-
- 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 (
- <div className="buy-ether__action-content-wrapper">
- <div className="shapeshift-logo" />
- <div className="buy-ether__body-text">
- Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
- </div>
- <ShapeShiftForm btnClass="first-time-flow__button" />
- </div>
- )
- case OPTION_VALUES.QR_CODE:
- return (
- <div className="buy-ether__action-content-wrapper">
- <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
- <div className="buy-ether__body-text">Deposit Ether directly into your account.</div>
- <div className="buy-ether__small-body-text">(This is the account address that MetaMask created for you to recieve funds.)</div>
- <div className="buy-ether__buttons">
- <button
- className="first-time-flow__button"
- onClick={this.copyToClipboard}
- disabled={justCopied}
- >
- { justCopied ? 'Copied' : 'Copy' }
- </button>
- </div>
- </div>
- )
- default:
- return null
- }
- }
-
- render () {
- const { OPTIONS } = BuyEtherScreen
- const { selectedOption } = this.state
-
- return (
- <div className="buy-ether">
- <Identicon address={this.props.address} diameter={70} />
- <div className="buy-ether__title">Deposit Ether</div>
- <div className="buy-ether__body-text">
- 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.
- </div>
- <div className="buy-ether__content-wrapper">
- <div className="buy-ether__content-headline-wrapper">
- <div className="buy-ether__content-headline">Deposit Options</div>
- {this.renderSkip()}
- </div>
- <div className="buy-ether__content">
- <div className="buy-ether__side-panel">
- {OPTIONS.map(({ name, value }) => (
- <div
- key={value}
- className={classnames('buy-ether__side-panel-item', {
- 'buy-ether__side-panel-item--selected': value === selectedOption,
- })}
- onClick={() => this.setState({ selectedOption: value })}
- >
- <div className="buy-ether__side-panel-item-name">{name}</div>
- {value === selectedOption && (
- <svg viewBox="0 0 574 1024" id="si-ant-right" width="15px" height="15px">
- <path d="M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z" />
- </svg>
- )}
- </div>
- ))}
- </div>
- <div className="buy-ether__action-content">
- {this.renderContent()}
- </div>
- </div>
- </div>
- </div>
- )
- }
-}
-
-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 (
- <div className="first-time-flow">
- {
- this.props.isLoading
- ? <LoadingScreen loadingMessage="Creating your new account" />
- : (
- <div className="first-view-main-wrapper">
- <div className="first-view-main">
- <div className="backup-phrase">
- <a
- className="backup-phrase__back-button"
- onClick={e => {
- e.preventDefault()
- history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
- }}
- href="#"
- >
- {`< Back`}
- </a>
- <Identicon address={this.props.address} diameter={70} />
- <div className="backup-phrase__content-wrapper">
- <div>
- <div className="backup-phrase__title">
- Confirm your Secret Backup Phrase
- </div>
- <div className="backup-phrase__body-text">
- Please select each phrase in order to make sure it is correct.
- </div>
- <div className="backup-phrase__confirm-secret">
- {selectedSeeds.map(([_, word], i) => (
- <button
- key={i}
- className="backup-phrase__confirm-seed-option"
- >
- {word}
- </button>
- ))}
- </div>
- <div className="backup-phrase__confirm-seed-options">
- {shuffledSeeds.map((word, i) => {
- const isSelected = selectedSeeds
- .filter(([index, seed]) => seed === word && index === i)
- .length
-
- return (
- <button
- key={i}
- className={classnames('backup-phrase__confirm-seed-option', {
- 'backup-phrase__confirm-seed-option--selected': isSelected,
- 'backup-phrase__confirm-seed-option--unselected': !isSelected,
- })}
- onClick={() => {
- if (!isSelected) {
- this.setState({
- selectedSeeds: [...selectedSeeds, [i, word]],
- })
- } else {
- this.setState({
- selectedSeeds: selectedSeeds
- .filter(([index, seed]) => !(seed === word && index === i)),
- })
- }
- }}
- >
- {word}
- </button>
- )
- })}
- </div>
- <button
- className="first-time-flow__button"
- onClick={() => isValid && this.handleClick()}
- disabled={!isValid}
- >
- Confirm
- </button>
- </div>
- </div>
- <Breadcrumbs total={3} currentIndex={1} />
- </div>
- </div>
- </div>
- )
- }
- </div>
- )
- }
-}
-
-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 (
- <div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
- <div className={classnames({
- 'first-view-main': !isMascara,
- 'first-view-main__mascara': isMascara,
- })}>
- {isMascara && <div className="mascara-info first-view-phone-invisible">
- <Mascot
- animationEventEmitter={this.animationEventEmitter}
- width="225"
- height="225"
- />
- <div className="info">
- MetaMask is a secure identity vault for Ethereum.
- </div>
- <div className="info">
- It allows you to hold ether & tokens, and interact with decentralized applications.
- </div>
- </div>}
- <form className="create-password">
- <div className="create-password__title">
- Create Password
- </div>
- <TextField
- id="create-password"
- label={t('newPassword')}
- type="password"
- className="first-time-flow__input"
- value={this.state.password}
- onChange={event => this.handlePasswordChange(event.target.value)}
- error={passwordError}
- autoFocus
- autoComplete="new-password"
- margin="normal"
- fullWidth
- largeLabel
- />
- <TextField
- id="confirm-password"
- label={t('confirmPassword')}
- type="password"
- className="first-time-flow__input"
- value={this.state.confirmPassword}
- onChange={event => this.handleConfirmPasswordChange(event.target.value)}
- error={confirmPasswordError}
- autoComplete="confirm-password"
- margin="normal"
- fullWidth
- largeLabel
- />
- <button
- className="first-time-flow__button"
- disabled={!this.isValid()}
- onClick={this.createAccount}
- >
- Create
- </button>
- <a
- href=""
- className="first-time-flow__link create-password__import-link"
- onClick={e => {
- e.preventDefault()
- history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
- }}
- >
- Import with seed phrase
- </a>
- { /* }
- <a
- href=""
- className="first-time-flow__link create-password__import-link"
- onClick={e => {
- e.preventDefault()
- history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
- }}
- >
- Import an account
- </a>
- { */ }
- <Breadcrumbs total={3} currentIndex={0} />
- </form>
- </div>
- </div>
- )
- }
-}
-
-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' }) => (
- <div className="import-account__input-wrapper">
- <div className="import-account__input-label">{label}</div>
- <input
- type={type}
- placeholder={placeholder}
- className={classnames('first-time-flow__input import-account__input', {
- 'first-time-flow__input--error': errorMessage,
- })}
- onChange={onChange}
- />
- <div className="import-account__input-error-message">{errorMessage}</div>
- </div>
-)
-
-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 (
- <div className="">
- <div className="import-account__input-wrapper">
- <div className="import-account__input-label">Upload File</div>
- <div className="import-account__file-picker-wrapper">
- <input
- type="file"
- id="file"
- className="import-account__file-input"
- onChange={e => this.setState({ jsonFile: e.target.files[0] })}
- />
- <label
- htmlFor="file"
- className={classnames('import-account__file-input-label', {
- 'import-account__file-input-label--error': warning,
- })}
- >
- Choose File
- </label>
- <div className="import-account__file-name">{name}</div>
- </div>
- <div className="import-account__input-error-message">
- {warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'}
- </div>
- </div>
- {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.',
- })}
- </div>
- )
- }
-
- 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
- ? <LoadingScreen loadingMessage="Creating your new account" />
- : (
- <div className="import-account">
- <a
- className="import-account__back-button"
- onClick={e => {
- e.preventDefault()
- this.props.back()
- }}
- href="#"
- >
- {`< Back`}
- </a>
- <div className="import-account__title">
- Import an Account
- </div>
- <div className="import-account__selector-label">
- How would you like to import your account?
- </div>
- <select
- className="import-account__dropdown"
- value={selectedOption}
- onChange={e => {
- this.setState({ selectedOption: e.target.value })
- this.props.hideWarning()
- }}
- >
- <option value={OPTIONS.PRIVATE_KEY}>Private Key</option>
- <option value={OPTIONS.JSON_FILE}>JSON File</option>
- </select>
- {this.renderContent()}
- <button
- className="first-time-flow__button"
- disabled={!this.isValid()}
- onClick={this.onClick}
- >
- Import
- </button>
- <a
- href="https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file"
- className="first-time-flow__link import-account__faq-link"
- rel="noopener noreferrer"
- target="_blank"
- >
- File import not working?
- </a>
- </div>
- )
- }
-}
-
-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 (
- <div className="first-view-main-wrapper">
- <div className="first-view-main">
- <div className="import-account">
- <a
- className="import-account__back-button"
- onClick={e => {
- e.preventDefault()
- this.props.history.goBack()
- }}
- href="#"
- >
- {`< Back`}
- </a>
- <div className="import-account__title">
- Import an Account with Seed Phrase
- </div>
- <div className="import-account__selector-label">
- Enter your secret twelve word phrase here to restore your vault.
- </div>
- <div className="import-account__input-wrapper">
- <label className="import-account__input-label">Wallet Seed</label>
- <textarea
- className="import-account__secret-phrase"
- onChange={e => this.handleSeedPhraseChange(e.target.value)}
- value={this.state.seedPhrase}
- placeholder="Separate each word with a single space"
- />
- </div>
- <span className="error">
- { seedPhraseError }
- </span>
- <TextField
- id="password"
- label={t('newPassword')}
- type="password"
- className="first-time-flow__input"
- value={this.state.password}
- onChange={event => this.handlePasswordChange(event.target.value)}
- error={passwordError}
- autoComplete="new-password"
- margin="normal"
- largeLabel
- />
- <TextField
- id="confirm-password"
- label={t('confirmPassword')}
- type="password"
- className="first-time-flow__input"
- value={this.state.confirmPassword}
- onChange={event => this.handleConfirmPasswordChange(event.target.value)}
- error={confirmPasswordError}
- autoComplete="confirm-password"
- margin="normal"
- largeLabel
- />
- <button
- className="first-time-flow__button"
- onClick={() => !disabled && this.onClick()}
- disabled={disabled}
- >
- Import
- </button>
- </div>
- </div>
- </div>
- )
- }
-}
-
-export default connect(
- ({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
- dispatch => ({
- leaveImportSeedScreenState: () => {
- dispatch(unMarkPasswordForgotten())
- },
- createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
- })
-)(ImportSeedPhraseScreen)
diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
deleted file mode 100644
index f3df240e7..000000000
--- a/mascara/src/app/first-time/index.css
+++ /dev/null
@@ -1,925 +0,0 @@
-@font-face {
- font-family: 'Roboto';
- font-style: normal;
- font-weight: 400;
- src: local('Roboto'), local('Roboto-Regular'), url('/fonts/Roboto/Roboto-Regular.ttf') format('truetype');
- unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
-}
-
-.first-time-flow {
- width: 100vw;
- background-color: #fff;
- overflow: auto;
- display: flex;
- justify-content: center;
- flex: 1 0 auto;
- font-weight: 400;
- font-family: Roboto;
-}
-
-@media screen and (min-height: 601px) {
- .first-time-flow {
- height: 100vh;
- }
-}
-
-.alpha-warning__container {
- display: flex;
- justify-content: center;
- background: #f7861c;
- flex: 0 0 auto;
-}
-
-.alpha-warning,
-.alpha-warning-welcome-screen {
- color: #fff;
- line-height: 2em;
-}
-
-@media screen and (min-width: 576px) {
- .alpha-warning {
- width: 85vw;
- }
-}
-
-@media screen and (min-width: 769px) {
- .alpha-warning {
- width: 80vw;
- }
-}
-
-@media screen and (min-width: 1281px) {
- .alpha-warning {
- width: 62vw;
- }
-}
-
-.alpha-warning-welcome-screen {
- padding-left: 0;
- text-align: center;
-}
-
-.first-view-main-wrapper {
- display: flex;
- width: 100%;
- height: 100%;
- justify-content: center;
- padding: 0 10px;
-}
-
-.first-view-main,
-.first-view-main__mascara {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
-}
-
-.first-view-main__mascara {
- justify-content: space-between;
-}
-
-@media screen and (min-width: 1281px) {
- .first-view-main {
- width: 62vw;
- }
-}
-
-.mascara-info {
- display: flex;
- flex-flow: column;
- margin-top: 70px;
- width: 35vw;
- max-width: 550px;
-}
-
-.mascara-info :first-child {
- align-self: flex-end;
-}
-
-.info {
- font-size: 19px;
-}
-
-.create-password,
-.unique-image,
-.tou,
-.backup-phrase,
-.import-account,
-.buy-ether {
- display: flex;
- flex-flow: column nowrap;
- margin: 60px 0 30px 0;
- position: relative;
-}
-
-.import-account {
- max-width: initial;
-}
-
-@media only screen and (max-width: 575px) {
- .create-password,
- .unique-image,
- .tou,
- .backup-phrase,
- .import-account,
- .buy-ether {
- margin: 24px;
- display: flex;
- flex-flow: column nowrap;
- width: calc(100vw - 80px);
- }
-
- .create-password__title,
- .unique-image__title,
- .tou__title,
- .backup-phrase__title,
- .import-account__title,
- .buy-ether__title,
- .tou__title,
- .backup-phrase__title {
- width: initial !important;
- }
-
- .alpha-warning,
- .alpha-warning-welcome-screen {
- line-height: 1em;
- padding: 8px 12px;
- }
-
- .first-view-main {
- height: 100%;
- flex-direction: column;
- align-items: center;
- justify-content: flex-start;
- margin-top: 12px;
- }
-
- .mascara-info {
- margin-top: 0px;
- width: 100%;
- align-items: center;
- }
-
- .mascara-info .info {
- text-align: center;
- font-size: 16px;
- margin: 0 10px;
- padding-left: 0px;
- }
-
- .mascara-info :first-child {
- align-self: center;
- }
-
- .first-view-phone-invisible {
- display: none;
- }
-
- .first-time-flow__input {
- width: 100%;
- }
-
- .tou__body {
- margin: 0 !important;
- padding: 16px 20px !important;
- height: 30vh !important;
- }
-
- .backup-phrase__content-wrapper {
- flex-flow: column nowrap;
- }
-
- .backup-phrase__body-text {
- width: initial !important;
- }
-
- .backup-phrase__secret {
- width: initial !important;
- padding: 12px !important;
- }
-
- .backup-phrase__secret-words {
- font-size: 16px;
- line-height: 22px;
- }
-
- .backup-phrase__tips {
- margin: 40px 0 !important;
- width: initial !important;
- }
-
- .backup-phrase__confirm-secret,
- .import-account__secret-phrase {
- width: initial !important;
- height: initial !important;
- min-height: 190px;
- }
-
- .backup-phrase__confirm-seed-options {
- width: initial !important;
- }
-}
-
-.tou {
- max-width: 46rem;
-}
-
-.create-password__title,
-.unique-image__title,
-.tou__title,
-.backup-phrase__title,
-.import-account__title,
-.buy-ether__title {
- color: #1B344D;
- font-size: 40px;
- line-height: 51px;
- margin-bottom: 24px;
-}
-
-.import-account__title {
- margin-bottom: 10px;
-}
-
-.tou__title,
-.backup-phrase__title {
- width: 480px;
-}
-
-.create-password__confirm-input {
- margin-top: 16px;
-}
-
-.create-password__import-link {
- margin-bottom: 54px;
-}
-
-.unique-image__title,
-.tou__title,
-.backup-phrase__title,
-.buy-ether__title {
- margin-top: 24px;
-}
-
-.unique-image__body-text,
-.backup-phrase__body-text,
-.buy-ether__body-text {
- color: #1B344D;
- font-size: 16px;
- line-height: 23px;
- font-family: Roboto;
-}
-
-.buy-ether__small-body-text {
- font-family: Roboto;
- height: 14px;
- color: #757575;
- font-size: 12px;
- line-height: 14px;
-}
-
-.unique-image__body-text {
- width: 335px;
-}
-
-@media only screen and (max-width: 575px) {
- .unique-image__body-text {
- width: initial;
- }
-}
-
-.unique-image__body-text +
-.unique-image__body-text,
-.backup-phrase__body-text +
-.backup-phrase__body-text,
-.backup-phrase__tips-text +
-.backup-phrase__tips-text {
- margin-top: 24px;
-}
-
-.tou__body {
- border: 1px solid #979797;
- border-radius: 8px;
- background-color: #FFFFFF;
- margin: 0 142px 0 0;
- height: 200px;
- overflow-y: auto;
- color: #757575;
- font-family: Roboto;
- font-size: 12px;
- line-height: 15px;
- text-align: justify;
- padding: 22px 30px;
-}
-
-.backup-phrase__content-wrapper {
- display: flex;
- flex-flow: row wrap;
- justify-content: space-between;
-}
-
-.backup-phrase__phrase {
- flex-grow: .5;
- min-width: 0;
-}
-
-.backup-phrase__next-button {
- flex-grow: 1;
- width: 100%;
-}
-
-.backup-phrase__body-text {
- width: 450px;
-}
-
-.backup-phrase__tips {
- margin-top: 40px;
- width: 285px;
- flex-grow: .5;
- min-width: 0;
-}
-
-.backup-phrase__tips-text {
- color: #5B5D67;
- font-size: 16px;
- line-height: 23px;
- font-family: Roboto;
- min-width: 0;
-}
-
-.backup-phrase__tips-text--link {
- color: #2f9ae0;
- cursor: pointer;
-}
-
-.backup-phrase__tips-text--link:hover {
- color: #2f9ae0;
-}
-
-.backup-phrase__tips-text--strong {
- font-weight: bold;
-}
-
-@media only screen and (max-width: 768px) {
- .backup-phrase__content-wrapper {
- flex-direction: column;
- }
-
- .backup-phrase__phrase {
- flex: 1 0 auto;
- }
-
- .backup-phrase__tips {
- width: 100%;
- flex: 1 0 auto;
- }
-}
-
-.backup-phrase__secret {
- position: relative;
- display: flex;
- justify-content: center;
- width: 349px;
- border: 1px solid #CDCDCD;
- border-radius: 6px;
- background-color: #FFFFFF;
- padding: 20px 0;
- margin-top: 36px;
-}
-
-.backup-phrase__secret-words {
- width: 310px;
- color: #5B5D67;
- font-family: Roboto;
- font-size: 20px;
- line-height: 26px;
- text-align: center;
-}
-
-.backup-phrase__secret-words--hidden {
- filter: blur(5px);
-}
-
-.backup-phrase__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;
- padding: 13px 0 18px;
-}
-
-.backup-phrase__reveal-button {
- border: 1px solid #979797;
- border-radius: 4px;
- background: none;
- box-shadow: none;
- color: #FFFFFF;
- font-family: Roboto;
- font-size: 12px;
- font-weight: bold;
- line-height: 15px;
- text-align: center;
- text-transform: uppercase;
- margin-top: 10px;
-}
-
-.backup-phrase__back-button,
-.import-account__back-button {
- margin-bottom: 18px;
- color: #22232c;
- font-size: 16px;
- line-height: 21px;
- position: absolute;
- top: -25px;
-}
-
-.backup-phrase__back-button {
- top: -30px;
-}
-
-button.backup-phrase__reveal-button:hover {
- transform: scale(1);
-}
-
-.backup-phrase__confirm-secret,
-.import-account__secret-phrase {
- height: 190px;
- width: 495px;
- border: 1px solid #CDCDCD;
- border-radius: 6px;
- background-color: #FFFFFF;
- margin: 25px 0 36px;
- padding: 17px;
-}
-
-.import-account__secret-phrase {
- font-size: 16px;
- margin: initial;
-}
-
-.import-account__secret-phrase::placeholder {
- color: #9B9B9B;
- font-weight: 200;
-}
-
-.backup-phrase__confirm-seed-options {
- display: flex;
- flex-flow: row wrap;
- width: 465px;
- position: relative;
- left: -7px;
-}
-
-.backup-phrase__confirm-seed-option {
- color: #5B5D67;
- font-family: Roboto;
- font-size: 16px;
- line-height: 21px;
- background-color: #E7E7E7;
- padding: 8px 19px;
- box-shadow: none;
- min-width: 65px;
- margin: 7px;
-}
-
-.backup-phrase__confirm-seed-option--selected {
- background-color: #85D1CC;
- color: #FFFFFF;
-}
-
-button.backup-phrase__confirm-seed-option:hover {
- transform: scale(1);
-}
-
-.import-account__faq-link {
- font-size: 18px;
- line-height: 23px;
- font-family: Roboto;
-}
-
-.import-account__selector-label {
- color: #1B344D;
- font-size: 16px;
-}
-
-.import-account__dropdown {
- width: 325px;
- border: 1px solid #CDCDCD;
- border-radius: 4px;
- background-color: #FFFFFF;
- margin-top: 14px;
- color: #5B5D67;
- font-family: Roboto;
- font-size: 18px;
- line-height: 23px;
- padding: 14px 21px;
- appearance: none;
- -webkit-appearance: none;
- -moz-appearance: none;
- cursor: pointer;
-}
-
-.import-account__description-text {
- color: #757575;
- font-size: 18px;
- line-height: 23px;
- margin-top: 21px;
- font-family: Roboto;
-}
-
-.import-account__input-wrapper {
- display: flex;
- flex-flow: column nowrap;
- margin-top: 30px;
-}
-
-.import-account__input-error-message {
- margin-top: 10px;
- width: 422px;
- color: #FF001F;
- font-size: 16px;
- line-height: 21px;
-}
-
-.import-account__input-label {
- margin-bottom: 9px;
- color: #1B344D;
- font-size: 18px;
- line-height: 23px;
-}
-
-.import-account__input-label__disabled {
- opacity: 0.5;
-}
-
-.import-account__input {
- width: 350px;
-}
-
-@media only screen and (max-width: 575px) {
- .import-account__input {
- width: 100%;
- }
-}
-
-.import-account__file-input {
- display: none;
-}
-
-.import-account__file-input-label {
- height: 53px;
- width: 148px;
- border: 1px solid #1B344D;
- border-radius: 4px;
- color: #1B344D;
- font-family: Roboto;
- font-size: 18px;
- display: flex;
- flex-flow: column nowrap;
- align-items: center;
- justify-content: center;
- cursor: pointer;
-}
-
-.import-account__file-picker-wrapper {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
-}
-
-.import-account__file-name {
- color: #000000;
- font-family: Roboto;
- font-size: 18px;
- line-height: 23px;
- margin-left: 22px;
-}
-
-.buy-ether__content-wrapper {
- display: flex;
- flex-flow: column nowrap;
- margin-top: 31px;
-}
-
-.buy-ether__content-headline-wrapper {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- justify-content: space-between;
-}
-
-.buy-ether__content-headline {
- color: #1B344D;
- font-family: Roboto;
- font-size: 18px;
- line-height: 23px;
-}
-
-.buy-ether__do-it-later {
- color: #1B344D;
- font-size: 16px;
- line-height: 23px;
- cursor: pointer;
-}
-
-.buy-ether__content {
- margin-top: 12px;
- display: flex;
- flex-flow: row nowrap;
-}
-
-.buy-ether__side-panel {
- display: flex;
- flex-flow: column nowrap;
-}
-
-.buy-ether__side-panel-item {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- padding: 20px 0;
- color: #9B9B9B;
- font-family: Roboto;
- font-size: 14px;
- line-height: 18px;
- cursor: pointer;
- min-width: 140px;
-}
-
-
-.buy-ether__side-panel-item {
- border-bottom: 1px solid #CDCDCD;
-}
-
-.buy-ether__side-panel-item--selected {
- position: relative;
- color: #1B344D;
-}
-
-.buy-ether__side-panel-item-name {
- flex: 1 0 auto;
- padding-right: 13px;
-}
-
-.buy-ether__action-content {
- margin-left: 34px;
-}
-
-.buy-ether__buttons {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
-}
-
-.buy-ether__button-separator-text {
- font-size: 20px;
- line-height: 26px;
- font-family: Roboto;
- margin: 35px 0 14px 30px;
- display: flex;
- flex-flow: column nowrap;
- justify-content: center;
-}
-
-.buy-ether__faq-link {
- margin-top: 26px;
- color: #1B344D !important;
- font-size: 14px !important;
- line-height: 18px !important;
- font-family: Roboto;
-}
-
-.buy-ether__action-content-wrapper {
- display: flex;
- flex-flow: column nowrap;
-}
-
-.first-time-flow__input {
- max-width: 350px;
-}
-
-.first-time-flow__button {
- height: 54px;
- width: 198px;
- box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14);
- color: #FFFFFF;
- font-size: 20px;
- font-weight: 500;
- font-family: Roboto;
- line-height: 26px;
- text-align: center;
- text-transform: uppercase;
- margin: 35px 0 14px;
- transition: 200ms ease-in-out;
- background-color: rgba(247, 134, 28, 0.9);
-}
-
-button.first-time-flow__button[disabled] {
- opacity: .6;
-}
-
-button.first-time-flow__button:hover {
- transform: scale(1);
- background-color: rgba(247, 134, 28, 0.9);
-}
-
-.first-time-flow__button--tertiary {
- height: 54px;
- width: 198px;
- box-shadow: none;
- color: #1B344D;
- font-size: 20px;
- line-height: 26px;
- font-family: Roboto;
- text-align: center;
- margin: 35px 0 14px;
- background-color: transparent;
-}
-
-button.first-time-flow__button--tertiary:hover {
- transform: scale(1);
-}
-
-.first-time-flow__link {
- color: #1B344D;
- font-size: 18px;
- line-height: 23px;
-}
-
-.breadcrumbs {
- display: flex;
- flex-flow: row nowrap;
-}
-
-.breadcrumb {
- height: 10px;
- width: 10px;
- border: 1px solid #979797;
- border-radius: 50%;
-}
-
-.breadcrumb + .breadcrumb {
- margin-left: 10px;
-}
-
-.loading-screen {
- width: 100vw;
- height: 100vh;
- display: flex;
- flex-flow: column nowrap;
- align-items: center;
- margin-top: 143px;
-}
-
-.loading-screen .spinner {
- margin-bottom: 25px;
- width: 100px;
- height: 100px;
-}
-
-.loading-screen__message {
- color: #1B344D;
- font-size: 20px;
- line-height: 26px;
- text-align: center;
- font-family: Roboto;
-}
-
-.icon {
- background-repeat: no-repeat;
- background-size: contain;
- background-position: center;
-}
-
-.shapeshift-logo {
- background: url('data:image/png;base64,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');
- width: 161px;
- height: 84px;
- background-size: cover;
- background-repeat: no-repeat;
- background-position: 50%;
-}
-
-.shapeshift-form {
- width: 360px;
- border-radius: 8px;
- background-color: rgba(0, 0, 0, .05);
- padding: 17px 15px;
-}
-
-.shapeshift-form__selectors {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- padding-bottom: 17px;
-}
-
-.shapeshift-form__caret {
- width: 40px;
- height: 40px;
- flex: 0 0 auto;
- width: 120px;
- margin-top: 24px;
-}
-
-.shapeshift-form__selector {
- flex: 1 0 auto;
-}
-
-.shapeshift-form__selector-label,
-.shapeshift-form__deposit-instruction {
- color: #757575;
- color: rgba(0, 0, 0, 0.45);
- font-family: Roboto;
- font-weight: 300;
- line-height: 19px;
- padding-bottom: 6px;
-}
-
-.shapeshift-form__selector-input {
- color: #5B5D67;
- font-size: 16px;
- font-weight: 300;
- line-height: 21px;
- border: 1px solid #D8D8D8;
- background-color: #FFFFFF;
- text-align: center;
- width: 100%;
- height: 45px;
- line-height: 44px;
- font-family: Roboto;
-}
-
-.shapeshift-form__address-input-label {
- color: #757575;
- font-size: 14px;
- font-weight: 500;
- line-height: 18px;
- padding-bottom: 6px;
- font-family: Roboto;
-}
-
-.shapeshift-form__address-input {
- border: 1px solid #D8D8D8;
- background-color: #FFFFFF;
- font-size: 16px;
- font-weight: 300;
- line-height: 21px;
- padding: 15px;
- width: 100%;
-}
-
-.shapeshift-form__address-input-wrapper--error .shapeshift-form__address-input {
- border-color: #FF001F;
-}
-
-.shapeshift-form__address-input-error-message {
- color: #FF001F;
- font-family: Roboto;
- font-size: 12px;
- height: 24px;
- line-height: 18px;
-}
-
-.shapeshift-form__metadata {
- display: flex;
- flex-flow: row wrap;
- color: #9B9B9B;
- font-family: Roboto;
- font-size: 10px;
- line-height: 16px;
-}
-
-.shapeshift-form__metadata-wrapper {
- flex: 1 0 50%;
- display: flex;
- flex-flow: row nowrap;
- overflow: hidden;
- white-space: nowrap;
-}
-
-.shapeshift-form__metadata-wrapper:nth-child(odd) {
- padding-right: 14px;
-}
-
-.shapeshift-form__metadata-label {
- flex: 1 0 60%;
-}
-
-.shapeshift-form__metadata-value {
- flex: 0 0 40%;
- overflow: hidden;
- color: #000;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.shapeshift-form__qr-code {
- display: flex;
- flex-flow: row nowrap;
- justify-content: center;
-}
diff --git a/mascara/src/app/first-time/index.js b/mascara/src/app/first-time/index.js
deleted file mode 100644
index 6e4dc74bb..000000000
--- a/mascara/src/app/first-time/index.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import {connect} from 'react-redux'
-import { withRouter, Switch, Route } from 'react-router-dom'
-import { compose } from 'recompose'
-
-import CreatePasswordScreen from './create-password-screen'
-import UniqueImageScreen from './unique-image-screen'
-import NoticeScreen from './notice-screen'
-import BackupPhraseScreen from './seed-screen'
-import ImportAccountScreen from './import-account-screen'
-import ImportSeedPhraseScreen from './import-seed-phrase-screen'
-import ConfirmSeed from './confirm-seed-screen'
-import {
- INITIALIZE_ROUTE,
- INITIALIZE_IMPORT_ACCOUNT_ROUTE,
- INITIALIZE_UNIQUE_IMAGE_ROUTE,
- INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
- INITIALIZE_NOTICE_ROUTE,
- INITIALIZE_BACKUP_PHRASE_ROUTE,
- INITIALIZE_CONFIRM_SEED_ROUTE,
- INITIALIZE_CREATE_PASSWORD_ROUTE,
-} from '../../../../ui/app/routes'
-import WelcomeScreen from '../../../../ui/app/welcome-screen'
-
-class FirstTimeFlow extends Component {
-
- static propTypes = {
- isInitialized: PropTypes.bool,
- seedWords: PropTypes.string,
- address: PropTypes.string,
- noActiveNotices: PropTypes.bool,
- goToBuyEtherView: PropTypes.func,
- isUnlocked: PropTypes.bool,
- history: PropTypes.object,
- welcomeScreenSeen: PropTypes.bool,
- isPopup: PropTypes.bool,
- };
-
- static defaultProps = {
- isInitialized: false,
- seedWords: '',
- noActiveNotices: false,
- };
-
- render () {
- return (
- <div className="flex-column flex-grow">
- <div className="first-time-flow">
- <Switch>
- <Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
- <Route
- exact
- path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
- component={ImportSeedPhraseScreen}
- />
- <Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
- <Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
- <Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
- <Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
- <Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
- <Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
- </Switch>
- </div>
- </div>
- )
- }
-}
-
-const mapStateToProps = ({ metamask }) => {
- const {
- isInitialized,
- seedWords,
- noActiveNotices,
- selectedAddress,
- forgottenPassword,
- isMascara,
- isUnlocked,
- welcomeScreenSeen,
- isPopup,
- } = metamask
-
- return {
- isMascara,
- isInitialized,
- seedWords,
- noActiveNotices,
- address: selectedAddress,
- forgottenPassword,
- isUnlocked,
- welcomeScreenSeen,
- isPopup,
- }
-}
-
-export default compose(
- withRouter,
- connect(mapStateToProps)
-)(FirstTimeFlow)
diff --git a/mascara/src/app/first-time/loading-screen.js b/mascara/src/app/first-time/loading-screen.js
deleted file mode 100644
index 596f818ca..000000000
--- a/mascara/src/app/first-time/loading-screen.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import Spinner from './spinner'
-
-export default function LoadingScreen ({ className = '', loadingMessage }) {
- return (
- <div className={`${className} loading-screen`}>
- <Spinner color="#1B344D" />
- <div className="loading-screen__message">{loadingMessage}</div>
- </div>
- )
-}
-
-LoadingScreen.propTypes = {
- className: PropTypes.string,
- loadingMessage: PropTypes.string,
-}
diff --git a/mascara/src/app/first-time/notice-screen.js b/mascara/src/app/first-time/notice-screen.js
deleted file mode 100644
index 8cb6f1057..000000000
--- a/mascara/src/app/first-time/notice-screen.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import Markdown from 'react-markdown'
-import { connect } from 'react-redux'
-import { withRouter } from 'react-router-dom'
-import { compose } from 'recompose'
-import debounce from 'lodash.debounce'
-import { markNoticeRead } from '../../../../ui/app/actions'
-import Identicon from '../../../../ui/app/components/identicon'
-import Breadcrumbs from './breadcrumbs'
-import { INITIALIZE_BACKUP_PHRASE_ROUTE } from '../../../../ui/app/routes'
-import LoadingScreen from './loading-screen'
-
-class NoticeScreen extends Component {
- static propTypes = {
- address: PropTypes.string.isRequired,
- nextUnreadNotice: PropTypes.shape({
- title: PropTypes.string,
- date: PropTypes.string,
- body: PropTypes.string,
- }),
- location: PropTypes.shape({
- state: PropTypes.shape({
- next: PropTypes.func.isRequired,
- }),
- }),
- markNoticeRead: PropTypes.func,
- history: PropTypes.object,
- isLoading: PropTypes.bool,
- noActiveNotices: PropTypes.bool,
- };
-
- static defaultProps = {
- nextUnreadNotice: {},
- };
-
- state = {
- atBottom: false,
- }
-
- componentDidMount () {
- if (this.props.noActiveNotices) {
- this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
- }
-
- this.onScroll()
- }
-
- acceptTerms = () => {
- const { markNoticeRead, nextUnreadNotice, history } = this.props
- markNoticeRead(nextUnreadNotice)
- .then(hasActiveNotices => {
- if (!hasActiveNotices) {
- history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
- } else {
- this.setState({ atBottom: false })
- this.onScroll()
- }
- })
- }
-
- onScroll = debounce(() => {
- if (this.state.atBottom) return
-
- const target = document.querySelector('.tou__body')
- const {scrollTop, offsetHeight, scrollHeight} = target
- const atBottom = scrollTop + offsetHeight >= scrollHeight
-
- this.setState({atBottom: atBottom})
- }, 25)
-
- render () {
- const {
- address,
- nextUnreadNotice: { title, body },
- isLoading,
- } = this.props
- const { atBottom } = this.state
-
- return (
- isLoading
- ? <LoadingScreen />
- : (
- <div className="first-time-flow">
- <div className="first-view-main-wrapper">
- <div className="first-view-main">
- <div
- className="tou"
- onScroll={this.onScroll}
- >
- <Identicon address={address} diameter={70} />
- <div className="tou__title">{title}</div>
- <Markdown
- className="tou__body markdown"
- source={body}
- skipHtml
- />
- <button
- className="first-time-flow__button"
- onClick={atBottom && this.acceptTerms}
- disabled={!atBottom}
- >
- Accept
- </button>
- <Breadcrumbs total={3} currentIndex={2} />
- </div>
- </div>
- </div>
- </div>
- )
- )
- }
-}
-
-const mapStateToProps = ({ metamask, appState }) => {
- const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask
- const { isLoading } = appState
-
- return {
- address: selectedAddress,
- nextUnreadNotice,
- noActiveNotices,
- isLoading,
- }
-}
-
-export default compose(
- withRouter,
- connect(
- mapStateToProps,
- dispatch => ({
- markNoticeRead: notice => dispatch(markNoticeRead(notice)),
- })
- )
-)(NoticeScreen)
diff --git a/mascara/src/app/first-time/seed-screen.js b/mascara/src/app/first-time/seed-screen.js
deleted file mode 100644
index 97d5d7930..000000000
--- a/mascara/src/app/first-time/seed-screen.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'react-redux'
-import classnames from 'classnames'
-import { withRouter } from 'react-router-dom'
-import { compose } from 'recompose'
-import Identicon from '../../../../ui/app/components/identicon'
-import {exportAsFile} from '../../../../ui/app/util'
-import Breadcrumbs from './breadcrumbs'
-import LoadingScreen from './loading-screen'
-import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
-
-const LockIcon = props => (
- <svg
- version="1.1"
- id="Capa_1"
- xmlns="http://www.w3.org/2000/svg"
- xmlnsXlink="http://www.w3.org/1999/xlink"
- x="0px"
- y="0px"
- width="401.998px"
- height="401.998px"
- viewBox="0 0 401.998 401.998"
- style={{enableBackground: 'new 0 0 401.998 401.998'}}
- xmlSpace="preserve"
- {...props}
- >
- <g>
- <path
- d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
- C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
- h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
- c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
- C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
- c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
- z"
- />
- </g>
- </svg>
-)
-
-class BackupPhraseScreen extends Component {
- static propTypes = {
- isLoading: PropTypes.bool.isRequired,
- address: PropTypes.string.isRequired,
- seedWords: PropTypes.string,
- history: PropTypes.object,
- };
-
- static defaultProps = {
- seedWords: '',
- }
-
- constructor (props) {
- super(props)
- this.state = {
- isShowingSecret: false,
- }
- }
-
- componentWillMount () {
- const { seedWords, history } = this.props
-
- if (!seedWords) {
- history.push(DEFAULT_ROUTE)
- }
- }
-
- exportSeedWords = () => {
- const { seedWords } = this.props
-
- exportAsFile('MetaMask Secret Backup Phrase', seedWords, 'text/plain')
- }
-
- renderSecretWordsContainer () {
- const { isShowingSecret } = this.state
-
- return (
- <div className="backup-phrase__secret">
- <div className={classnames('backup-phrase__secret-words', {
- 'backup-phrase__secret-words--hidden': !isShowingSecret,
- })}>
- {this.props.seedWords}
- </div>
- {!isShowingSecret && (
- <div
- className="backup-phrase__secret-blocker"
- onClick={() => this.setState({ isShowingSecret: true })}
- >
- <LockIcon width="28px" height="35px" fill="#FFFFFF" />
- <div
- className="backup-phrase__reveal-button"
- >
- Click here to reveal secret words
- </div>
- </div>
- )}
- </div>
- )
- }
-
- renderSecretScreen () {
- const { isShowingSecret } = this.state
- const { history } = this.props
-
- return (
- <div className="backup-phrase__content-wrapper">
- <div className="backup-phrase__phrase">
- <div className="backup-phrase__title">Secret Backup Phrase</div>
- <div className="backup-phrase__body-text">
- Your secret backup phrase makes it easy to back up and restore your account.
- </div>
- <div className="backup-phrase__body-text">
- WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever.
- </div>
- {this.renderSecretWordsContainer()}
- </div>
- <div className="backup-phrase__tips">
- <div className="backup-phrase__tips-text">Tips:</div>
- <div className="backup-phrase__tips-text">
- Store this phrase in a password manager like 1Password.
- </div>
- <div className="backup-phrase__tips-text">
- 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.
- </div>
- <div className="backup-phrase__tips-text">
- Memorize this phrase.
- </div>
- <div className="backup-phrase__tips-text">
- <strong>
- <a className="backup-phrase__tips-text--link backup-phrase__tips-text--strong" onClick={this.exportSeedWords}>
- Download this Secret Backup Phrase
- </a>
- </strong> and keep it stored safely on an external encrypted hard drive or storage medium.
- </div>
- </div>
- <div className="backup-phrase__next-button">
- <button
- className="first-time-flow__button"
- onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
- disabled={!isShowingSecret}
- >
- Next
- </button>
- <Breadcrumbs total={3} currentIndex={1} />
- </div>
- </div>
- )
- }
-
- render () {
- return this.props.isLoading
- ? <LoadingScreen loadingMessage="Creating your new account" />
- : (
- <div className="first-view-main-wrapper">
- <div className="first-view-main">
- <div className="backup-phrase">
- <Identicon address={this.props.address} diameter={70} />
- {this.renderSecretScreen()}
- </div>
- </div>
- </div>
- )
- }
-}
-
-export default compose(
- withRouter,
- connect(
- ({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
- seedWords,
- isLoading,
- address: selectedAddress,
- })
- )
-)(BackupPhraseScreen)
diff --git a/mascara/src/app/first-time/spinner.js b/mascara/src/app/first-time/spinner.js
deleted file mode 100644
index 78dca9a88..000000000
--- a/mascara/src/app/first-time/spinner.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react';
-
-export default function Spinner({ className = '', color = "#000000" }) {
- return (
- <div className={`spinner ${className}`}>
- <svg className="lds-spinner" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style={{background: 'none'}}>
- <g transform="rotate(0 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(30 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(60 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.75s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(90 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(120 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(150 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(180 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(210 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(240 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.25s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(270 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(300 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" />
- </rect>
- </g>
- <g transform="rotate(330 50 50)">
- <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
- <animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" />
- </rect>
- </g>
- </svg>
- </div>
- );
-}
diff --git a/mascara/src/app/first-time/unique-image-screen.js b/mascara/src/app/first-time/unique-image-screen.js
deleted file mode 100644
index 9555e5318..000000000
--- a/mascara/src/app/first-time/unique-image-screen.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { withRouter } from 'react-router-dom'
-import { compose } from 'recompose'
-import {connect} from 'react-redux'
-import Identicon from '../../../../ui/app/components/identicon'
-import Breadcrumbs from './breadcrumbs'
-import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
-
-class UniqueImageScreen extends Component {
- static propTypes = {
- address: PropTypes.string,
- history: PropTypes.object,
- }
-
- render () {
- return (
- <div className="first-view-main-wrapper">
- <div className="first-view-main">
- <div className="unique-image">
- <Identicon address={this.props.address} diameter={70} />
- <div className="unique-image__title">Your unique account image</div>
- <div className="unique-image__body-text">
- This image was programmatically generated for you by your new account number.
- </div>
- <div className="unique-image__body-text">
- You’ll see this image everytime you need to confirm a transaction.
- </div>
- <button
- className="first-time-flow__button"
- onClick={() => this.props.history.push(INITIALIZE_NOTICE_ROUTE)}
- >
- Next
- </button>
- <Breadcrumbs total={3} currentIndex={1} />
- </div>
- </div>
- </div>
- )
- }
-}
-
-export default compose(
- withRouter,
- connect(
- ({ metamask: { selectedAddress } }) => ({
- address: selectedAddress,
- })
- )
-)(UniqueImageScreen)
diff --git a/old-ui/app/app.js b/old-ui/app/app.js
index 2d364ef6f..f694693f5 100644
--- a/old-ui/app/app.js
+++ b/old-ui/app/app.js
@@ -5,8 +5,7 @@ const h = require('react-hyperscript')
const actions = require('../../ui/app/actions')
const log = require('loglevel')
// mascara
-const MascaraFirstTime = require('../../mascara/src/app/first-time').default
-const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
+const FirstTime = require('../../ui/app/components/pages/first-time-flow').default
// init
const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain')
@@ -153,7 +152,7 @@ App.prototype.renderPrimary = function () {
const {isMascara, isOnboarding, providerRequests} = props
if (isMascara && isOnboarding) {
- return h(MascaraFirstTime)
+ return h(FirstTime)
}
// notices
@@ -270,10 +269,6 @@ App.prototype.renderPrimary = function () {
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
- case 'onboardingBuyEth':
- log.debug('rendering onboarding buy ether screen')
- return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
-
case 'qr':
log.debug('rendering show qr screen')
return h(AccountQrScreen, {
diff --git a/old-ui/css.js b/old-ui/css.js
index 21b311c28..043363cd7 100644
--- a/old-ui/css.js
+++ b/old-ui/css.js
@@ -9,7 +9,6 @@ var cssFiles = {
'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'),
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
- 'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'),
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
}
diff --git a/test/e2e/beta/drizzle.spec.js b/test/e2e/beta/drizzle.spec.js
index e669dabcc..309df952c 100644
--- a/test/e2e/beta/drizzle.spec.js
+++ b/test/e2e/beta/drizzle.spec.js
@@ -112,7 +112,7 @@ describe('MetaMask', function () {
await loadExtension(driver, extensionId)
await delay(regularDelayMs)
- const continueBtn = await findElement(driver, By.css('.welcome-screen__button'))
+ const continueBtn = await findElement(driver, By.css('.first-time-flow__button'))
await continueBtn.click()
await delay(regularDelayMs)
})
@@ -120,9 +120,9 @@ describe('MetaMask', function () {
describe('Going through the first time flow', () => {
it('accepts a secure password', async () => {
- const passwordBox = await findElement(driver, By.css('.create-password #create-password'))
- const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password'))
- const button = await findElement(driver, By.css('.create-password button'))
+ const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password'))
+ const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password'))
+ const button = await findElement(driver, By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple')
@@ -131,19 +131,21 @@ describe('MetaMask', function () {
})
it('clicks through the unique image screen', async () => {
- const nextScreen = await findElement(driver, By.css('.unique-image button'))
+ await findElement(driver, By.css('.first-time-flow__unique-image'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the ToS', async () => {
// terms of use
- const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
+ await findElement(driver, By.css('.first-time-flow__markdown'))
+ const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
- const acceptTos = await findElement(driver, By.css('.tou button'))
+ const acceptTos = await findElement(driver, By.css('button.first-time-flow__button'))
driver.wait(until.elementIsEnabled(acceptTos))
await acceptTos.click()
await delay(regularDelayMs)
@@ -151,17 +153,17 @@ describe('MetaMask', function () {
it('clicks through the privacy notice', async () => {
// privacy notice
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
- const noticeElement = await driver.findElement(By.css('.markdown'))
+ const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
@@ -169,24 +171,23 @@ describe('MetaMask', function () {
let seedPhrase
it('reveals the seed phrase', async () => {
- const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
+ const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
- seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
+ seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText()
assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
async function clickWordAndWait (word) {
- const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected'
- const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]`
+ const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000)
await word0.click()
@@ -196,13 +197,13 @@ describe('MetaMask', function () {
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
try {
if (wasReloaded) {
- const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
+ const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
}
diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js
index aa951a243..d4a380584 100644
--- a/test/e2e/beta/from-import-beta-ui.spec.js
+++ b/test/e2e/beta/from-import-beta-ui.spec.js
@@ -95,7 +95,7 @@ describe('Using MetaMask with an existing account', function () {
describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => {
- const welcomeScreenBtn = await findElement(driver, By.css('.welcome-screen__button'))
+ const welcomeScreenBtn = await findElement(driver, By.css('.welcome-page .first-time-flow__button'))
welcomeScreenBtn.click()
await delay(largeDelayMs)
})
@@ -105,7 +105,7 @@ describe('Using MetaMask with an existing account', function () {
await seedPhrase.click()
await delay(regularDelayMs)
- const [seedTextArea] = await findElements(driver, By.css('textarea.import-account__secret-phrase'))
+ const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs)
@@ -121,30 +121,31 @@ describe('Using MetaMask with an existing account', function () {
it('clicks through the ToS', async () => {
// terms of use
- await delay(largeDelayMs)
- const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
+ await findElement(driver, By.css('.first-time-flow__markdown'))
+ const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
- const acceptTos = await findElement(driver, By.css('.tou button'))
+ const acceptTos = await findElement(driver, By.css('button.first-time-flow__button'))
+ driver.wait(until.elementIsEnabled(acceptTos))
await acceptTos.click()
await delay(regularDelayMs)
})
it('clicks through the privacy notice', async () => {
// privacy notice
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
- const noticeElement = await driver.findElement(By.css('.markdown'))
+ const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
diff --git a/test/e2e/beta/metamask-beta-responsive-ui.spec.js b/test/e2e/beta/metamask-beta-responsive-ui.spec.js
index 2ab8d86ce..98f0e02d5 100644
--- a/test/e2e/beta/metamask-beta-responsive-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-responsive-ui.spec.js
@@ -81,15 +81,15 @@ describe('MetaMask', function () {
describe('Going through the first time flow', () => {
it('clicks the continue button on the welcome screen', async () => {
- const welcomeScreenBtn = await findElement(driver, By.css('.welcome-screen__button'))
+ const welcomeScreenBtn = await findElement(driver, By.css('.welcome-page .first-time-flow__button'))
welcomeScreenBtn.click()
await delay(largeDelayMs)
})
it('accepts a secure password', async () => {
- const passwordBox = await findElement(driver, By.css('.create-password #create-password'))
- const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password'))
- const button = await findElement(driver, By.css('.create-password button'))
+ const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password'))
+ const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password'))
+ const button = await findElement(driver, By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple')
@@ -98,19 +98,21 @@ describe('MetaMask', function () {
})
it('clicks through the unique image screen', async () => {
- const nextScreen = await findElement(driver, By.css('.unique-image button'))
+ await findElement(driver, By.css('.first-time-flow__unique-image'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the ToS', async () => {
// terms of use
- const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
+ await findElement(driver, By.css('.first-time-flow__markdown'))
+ const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
- const acceptTos = await findElement(driver, By.css('.tou button'))
+ const acceptTos = await findElement(driver, By.css('button.first-time-flow__button'))
driver.wait(until.elementIsEnabled(acceptTos))
await acceptTos.click()
await delay(regularDelayMs)
@@ -118,17 +120,17 @@ describe('MetaMask', function () {
it('clicks through the privacy notice', async () => {
// privacy notice
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
- const noticeElement = await driver.findElement(By.css('.markdown'))
+ const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
@@ -136,24 +138,23 @@ describe('MetaMask', function () {
let seedPhrase
it('reveals the seed phrase', async () => {
- const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
+ const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
- seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
+ seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText()
assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
async function clickWordAndWait (word) {
- const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected'
- const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]`
+ const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000)
await word0.click()
@@ -163,13 +164,13 @@ describe('MetaMask', function () {
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
try {
if (wasReloaded) {
- const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
+ const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
}
@@ -191,6 +192,7 @@ describe('MetaMask', function () {
const words = seedPhrase.split(' ')
await retypeSeedPhrase(words)
+ await delay(regularDelayMs)
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirm.click()
diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js
index a69b25e77..489b9b9da 100644
--- a/test/e2e/beta/metamask-beta-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-ui.spec.js
@@ -102,15 +102,15 @@ describe('MetaMask', function () {
describe('Going through the first time flow', () => {
it('clicks the continue button on the welcome screen', async () => {
- const welcomeScreenBtn = await findElement(driver, By.css('.welcome-screen__button'))
+ const welcomeScreenBtn = await findElement(driver, By.css('.welcome-page .first-time-flow__button'))
welcomeScreenBtn.click()
await delay(largeDelayMs)
})
it('accepts a secure password', async () => {
- const passwordBox = await findElement(driver, By.css('.create-password #create-password'))
- const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password'))
- const button = await findElement(driver, By.css('.create-password button'))
+ const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password'))
+ const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password'))
+ const button = await findElement(driver, By.css('.first-time-flow__form button'))
await passwordBox.sendKeys('correct horse battery staple')
await passwordBoxConfirm.sendKeys('correct horse battery staple')
@@ -119,19 +119,21 @@ describe('MetaMask', function () {
})
it('clicks through the unique image screen', async () => {
- const nextScreen = await findElement(driver, By.css('.unique-image button'))
+ await findElement(driver, By.css('.first-time-flow__unique-image'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the ToS', async () => {
// terms of use
- const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
+ await findElement(driver, By.css('.first-time-flow__markdown'))
+ const canClickThrough = await driver.findElement(By.css('button.first-time-flow__button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos)
await delay(regularDelayMs)
- const acceptTos = await findElement(driver, By.css('.tou button'))
+ const acceptTos = await findElement(driver, By.css('button.first-time-flow__button'))
driver.wait(until.elementIsEnabled(acceptTos))
await acceptTos.click()
await delay(regularDelayMs)
@@ -139,17 +141,17 @@ describe('MetaMask', function () {
it('clicks through the privacy notice', async () => {
// privacy notice
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
it('clicks through the phishing notice', async () => {
// phishing notice
- const noticeElement = await driver.findElement(By.css('.markdown'))
+ const noticeElement = await driver.findElement(By.css('.first-time-flow__markdown'))
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.tou button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
@@ -157,24 +159,23 @@ describe('MetaMask', function () {
let seedPhrase
it('reveals the seed phrase', async () => {
- const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
+ const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
- seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText()
+ seedPhrase = await driver.findElement(By.css('.reveal-seed-phrase__secret-words')).getText()
assert.equal(seedPhrase.split(' ').length, 12)
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
})
async function clickWordAndWait (word) {
- const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected'
- const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]`
+ const xpath = `//div[contains(@class, 'confirm-seed-phrase__seed-word--shuffled') and not(contains(@class, 'confirm-seed-phrase__seed-word--selected')) and contains(text(), '${word}')]`
const word0 = await findElement(driver, By.xpath(xpath), 10000)
await word0.click()
@@ -184,13 +185,13 @@ describe('MetaMask', function () {
async function retypeSeedPhrase (words, wasReloaded, count = 0) {
try {
if (wasReloaded) {
- const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button')
+ const byRevealButton = By.css('.reveal-seed-phrase__secret-blocker .reveal-seed-phrase__reveal-button')
await driver.wait(until.elementLocated(byRevealButton, 10000))
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000)
await revealSeedPhraseButton.click()
await delay(regularDelayMs)
- const nextScreen = await findElement(driver, By.css('.backup-phrase button'))
+ const nextScreen = await findElement(driver, By.css('button.first-time-flow__button'))
await nextScreen.click()
await delay(regularDelayMs)
}
diff --git a/test/e2e/func.js b/test/e2e/func.js
index 5301d78ae..dfad8466c 100644
--- a/test/e2e/func.js
+++ b/test/e2e/func.js
@@ -85,7 +85,7 @@ function buildFirefoxWebdriver (opts = {}) {
async function getExtensionIdChrome (driver) {
await driver.get('chrome://extensions')
- const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")')
+ const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")')
return extensionId
}
diff --git a/test/unit/migrations/031-test.js b/test/unit/migrations/031-test.js
new file mode 100644
index 000000000..c85fd7af4
--- /dev/null
+++ b/test/unit/migrations/031-test.js
@@ -0,0 +1,56 @@
+const assert = require('assert')
+const migration31 = require('../../../app/scripts/migrations/031')
+
+ describe('migration #31', () => {
+ it('should set completedOnboarding to true if vault exists', done => {
+ const oldStorage = {
+ 'meta': {},
+ 'data': {
+ 'PreferencesController': {
+ 'tokens': [{address: '0xa', symbol: 'A', decimals: 4}, {address: '0xb', symbol: 'B', decimals: 4}],
+ 'identities': {
+ '0x6d14': {},
+ '0x3695': {},
+ },
+ },
+ 'KeyringController': {
+ 'vault': {
+ 'data': 'test0',
+ 'iv': 'test1',
+ 'salt': 'test2',
+ },
+ },
+ },
+ }
+
+ migration31.migrate(oldStorage)
+ .then(newStorage => {
+ assert.equal(newStorage.data.PreferencesController.completedOnboarding, true)
+ done()
+ })
+ .catch(done)
+ })
+
+ it('should set completedOnboarding to false if vault does not exist', done => {
+ const oldStorage = {
+ 'meta': {},
+ 'data': {
+ 'PreferencesController': {
+ 'tokens': [{address: '0xa', symbol: 'A', decimals: 4}, {address: '0xb', symbol: 'B', decimals: 4}],
+ 'identities': {
+ '0x6d14': {},
+ '0x3695': {},
+ },
+ },
+ 'KeyringController': {},
+ },
+ }
+
+ migration31.migrate(oldStorage)
+ .then(newStorage => {
+ assert.equal(newStorage.data.PreferencesController.completedOnboarding, false)
+ done()
+ })
+ .catch(done)
+ })
+})
diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js
index c7ac8b6cf..8d7de8b02 100644
--- a/test/unit/ui/app/actions.spec.js
+++ b/test/unit/ui/app/actions.spec.js
@@ -198,7 +198,7 @@ describe('Actions', () => {
createNewVaultAndRestoreSpy = sinon.spy(background, 'createNewVaultAndRestore')
clearSeedWordCacheSpy = sinon.spy(background, 'clearSeedWordCache')
return store.dispatch(actions.createNewVaultAndRestore())
- .then(() => {
+ .catch(() => {
assert(clearSeedWordCacheSpy.calledOnce)
assert(createNewVaultAndRestoreSpy.calledOnce)
})
@@ -218,7 +218,7 @@ describe('Actions', () => {
})
return store.dispatch(actions.createNewVaultAndRestore())
- .then(() => {
+ .catch(() => {
assert.deepEqual(store.getActions(), expectedActions)
})
})
@@ -240,7 +240,7 @@ describe('Actions', () => {
})
return store.dispatch(actions.createNewVaultAndRestore())
- .then(() => {
+ .catch(() => {
assert.deepEqual(store.getActions(), expectedActions)
})
})
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 7cc88e2b3..29cf4b2f2 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -85,6 +85,8 @@ var actions = {
createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress,
+ createNewVaultAndGetSeedPhrase,
+ unlockAndGetSeedPhrase,
addNewKeyring,
importNewAccount,
addNewAccount,
@@ -312,6 +314,11 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
+ // Onboarding
+ setCompletedOnboarding,
+ completeOnboarding,
+ COMPLETE_ONBOARDING: 'COMPLETE_ONBOARDING',
+
setMouseUserState,
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
@@ -451,6 +458,7 @@ function createNewVaultAndRestore (password, seed) {
.catch(err => {
dispatch(actions.displayWarning(err.message))
dispatch(actions.hideLoadingIndication())
+ return Promise.reject(err)
})
}
}
@@ -485,12 +493,71 @@ function createNewVaultAndKeychain (password) {
}
}
+function createNewVaultAndGetSeedPhrase (password) {
+ return async dispatch => {
+ dispatch(actions.showLoadingIndication())
+
+ try {
+ await createNewVault(password)
+ const seedWords = await verifySeedPhrase()
+ dispatch(actions.hideLoadingIndication())
+ return seedWords
+ } catch (error) {
+ dispatch(actions.hideLoadingIndication())
+ dispatch(actions.displayWarning(error.message))
+ throw new Error(error.message)
+ }
+ }
+}
+
+function unlockAndGetSeedPhrase (password) {
+ return async dispatch => {
+ dispatch(actions.showLoadingIndication())
+
+ try {
+ await submitPassword(password)
+ const seedWords = await verifySeedPhrase()
+ await forceUpdateMetamaskState(dispatch)
+ dispatch(actions.hideLoadingIndication())
+ return seedWords
+ } catch (error) {
+ dispatch(actions.hideLoadingIndication())
+ dispatch(actions.displayWarning(error.message))
+ throw new Error(error.message)
+ }
+ }
+}
+
function revealSeedConfirmation () {
return {
type: this.REVEAL_SEED_CONFIRMATION,
}
}
+function submitPassword (password) {
+ return new Promise((resolve, reject) => {
+ background.submitPassword(password, error => {
+ if (error) {
+ return reject(error)
+ }
+
+ resolve()
+ })
+ })
+}
+
+function createNewVault (password) {
+ return new Promise((resolve, reject) => {
+ background.createNewVaultAndKeychain(password, error => {
+ if (error) {
+ return reject(error)
+ }
+
+ resolve(true)
+ })
+ })
+}
+
function verifyPassword (password) {
return new Promise((resolve, reject) => {
background.submitPassword(password, error => {
@@ -2356,6 +2423,31 @@ function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
}
+function setCompletedOnboarding () {
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.completeOnboarding(err => {
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.completeOnboarding())
+ resolve()
+ })
+ })
+ }
+}
+
+function completeOnboarding () {
+ return {
+ type: actions.COMPLETE_ONBOARDING,
+ }
+}
+
function setNetworkNonce (networkNonce) {
return {
type: actions.SET_NETWORK_NONCE,
diff --git a/ui/app/app.js b/ui/app/app.js
index f320ced0a..28f4860a8 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -1,16 +1,14 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const { Route, Switch, withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const h = require('react-hyperscript')
-const actions = require('./actions')
-const classnames = require('classnames')
-const log = require('loglevel')
-const { getMetaMaskAccounts, getNetworkIdentifier } = require('./selectors')
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
+import { compose } from 'recompose'
+import actions from './actions'
+import log from 'loglevel'
+import { getMetaMaskAccounts, getNetworkIdentifier } from './selectors'
// init
-const InitializeScreen = require('../../mascara/src/app/first-time').default
+import FirstTimeFlow from './components/pages/first-time-flow'
// accounts
const SendTransactionScreen = require('./components/send/send.container')
const ConfirmTransaction = require('./components/pages/confirm-transaction')
@@ -21,8 +19,9 @@ const Sidebar = require('./components/sidebars').default
// other views
import Home from './components/pages/home'
import Settings from './components/pages/settings'
-const Authenticated = require('./components/pages/authenticated')
-const Initialized = require('./components/pages/initialized')
+import Authenticated from './higher-order-components/authenticated'
+import Initialized from './higher-order-components/initialized'
+import Lock from './components/pages/lock'
const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
@@ -49,8 +48,9 @@ import {
} from './selectors/transactions'
// Routes
-const {
+import {
DEFAULT_ROUTE,
+ LOCK_ROUTE,
UNLOCK_ROUTE,
SETTINGS_ROUTE,
REVEAL_SEED_ROUTE,
@@ -62,8 +62,15 @@ const {
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_ROUTE,
+ INITIALIZE_UNLOCK_ROUTE,
NOTICE_ROUTE,
-} = require('./routes')
+} from './routes'
+
+// enums
+import {
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_POPUP,
+} from '../../app/scripts/lib/enums'
class App extends Component {
componentWillMount () {
@@ -75,37 +82,67 @@ class App extends Component {
}
renderRoutes () {
- const exact = true
-
return (
- h(Switch, [
- h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
- h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
- h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
- h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
- h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
- h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
- h(Authenticated, {
- path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`,
- component: ConfirmTransaction,
- }),
- h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
- h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
- h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
- h(Authenticated, { path: CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, exact, component: ConfirmAddSuggestedTokenPage }),
- h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
- h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),
- ])
+ <Switch>
+ <Route path={LOCK_ROUTE} component={Lock} exact />
+ <Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
+ <Initialized path={UNLOCK_ROUTE} component={UnlockPage} exact />
+ <Initialized path={RESTORE_VAULT_ROUTE} component={RestoreVaultPage} exact />
+ <Authenticated path={REVEAL_SEED_ROUTE} component={RevealSeedConfirmation} exact />
+ <Authenticated path={SETTINGS_ROUTE} component={Settings} />
+ <Authenticated path={NOTICE_ROUTE} component={NoticeScreen} exact />
+ <Authenticated path={`${CONFIRM_TRANSACTION_ROUTE}/:id?`} component={ConfirmTransaction} />
+ <Authenticated path={SEND_ROUTE} component={SendTransactionScreen} exact />
+ <Authenticated path={ADD_TOKEN_ROUTE} component={AddTokenPage} exact />
+ <Authenticated path={CONFIRM_ADD_TOKEN_ROUTE} component={ConfirmAddTokenPage} exact />
+ <Authenticated path={CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE} component={ConfirmAddSuggestedTokenPage} exact />
+ <Authenticated path={NEW_ACCOUNT_ROUTE} component={CreateAccountPage} />
+ <Authenticated path={DEFAULT_ROUTE} component={Home} exact />
+ </Switch>
)
}
+ onInitializationUnlockPage () {
+ const { location } = this.props
+ return Boolean(matchPath(location.pathname, { path: INITIALIZE_UNLOCK_ROUTE, exact: true }))
+ }
+
+ onConfirmPage () {
+ const { location } = this.props
+ return Boolean(matchPath(location.pathname, { path: CONFIRM_TRANSACTION_ROUTE, exact: false }))
+ }
+
+ hasProviderRequests () {
+ const { providerRequests } = this.props
+ return Array.isArray(providerRequests) && providerRequests.length > 0
+ }
+
+ hideAppHeader () {
+ const { location } = this.props
+
+ const isInitializing = Boolean(matchPath(location.pathname, {
+ path: INITIALIZE_ROUTE, exact: false,
+ }))
+
+ if (isInitializing && !this.onInitializationUnlockPage()) {
+ return true
+ }
+
+ if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
+ return true
+ }
+
+ if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP) {
+ return this.onConfirmPage() || this.hasProviderRequests()
+ }
+ }
+
render () {
const {
isLoading,
alertMessage,
loadingMessage,
network,
- isMouseUser,
provider,
frequentRpcListDetail,
currentView,
@@ -127,58 +164,47 @@ class App extends Component {
const { transaction: sidebarTransaction } = props || {}
return (
- h('.flex-column.full-height', {
- className: classnames({ 'mouse-user-styles': isMouseUser }),
- style: {
- overflowX: 'hidden',
- position: 'relative',
- alignItems: 'center',
- },
- tabIndex: '0',
- onClick: () => setMouseUserState(true),
- onKeyDown: (e) => {
+ <div
+ className="app"
+ onClick={() => setMouseUserState(true)}
+ onKeyDown={e => {
if (e.keyCode === 9) {
setMouseUserState(false)
}
- },
- }, [
-
- // global modal
- h(Modal, {}, []),
-
- // global alert
- h(Alert, {visible: this.props.alertOpen, msg: alertMessage}),
-
- h(AppHeader),
-
- // sidebar
- h(Sidebar, {
- sidebarOpen: sidebarIsOpen,
- sidebarShouldClose: sidebarTransaction && !submittedPendingTransactions.find(({ id }) => id === sidebarTransaction.id),
- hideSidebar: this.props.hideSidebar,
- transitionName: sidebarTransitionName,
- type: sidebarType,
- sidebarProps: sidebar.props,
- }),
-
- // network dropdown
- h(NetworkDropdown, {
- provider,
- frequentRpcListDetail,
- }, []),
-
- h(AccountMenu),
-
- h('div.main-container-wrapper', [
- isLoading && h(Loading, {
- loadingMessage: loadMessage,
- }),
- !isLoading && isLoadingNetwork && h(LoadingNetwork),
-
- // content
- this.renderRoutes(),
- ]),
- ])
+ }}
+ >
+ <Modal />
+ <Alert
+ visible={this.props.alertOpen}
+ msg={alertMessage}
+ />
+ {
+ !this.hideAppHeader() && (
+ <AppHeader
+ hideNetworkIndicator={this.onInitializationUnlockPage()}
+ disabled={this.onConfirmPage()}
+ />
+ )
+ }
+ <Sidebar
+ sidebarOpen={sidebarIsOpen}
+ sidebarShouldClose={sidebarTransaction && !submittedPendingTransactions.find(({ id }) => id === sidebarTransaction.id)}
+ hideSidebar={this.props.hideSidebar}
+ transitionName={sidebarTransitionName}
+ type={sidebarType}
+ sidebarProps={sidebar.props}
+ />
+ <NetworkDropdown
+ provider={provider}
+ frequentRpcListDetail={frequentRpcListDetail}
+ />
+ <AccountMenu />
+ <div className="main-container-wrapper">
+ { isLoading && <Loading loadingMessage={loadMessage} /> }
+ { !isLoading && isLoadingNetwork && <LoadingNetwork /> }
+ { this.renderRoutes() }
+ </div>
+ </div>
)
}
@@ -282,6 +308,7 @@ App.propTypes = {
setMouseUserState: PropTypes.func,
t: PropTypes.func,
providerId: PropTypes.string,
+ providerRequests: PropTypes.array,
}
function mapStateToProps (state) {
@@ -310,6 +337,7 @@ function mapStateToProps (state) {
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedTypedMessagesCount,
+ providerRequests,
} = metamask
const selected = address || Object.keys(accounts)[0]
@@ -357,6 +385,7 @@ function mapStateToProps (state) {
identities,
selected,
keyrings,
+ providerRequests,
}
}
diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app-header/app-header.component.js
index 83fcca620..f7d8c8598 100644
--- a/ui/app/components/app-header/app-header.component.js
+++ b/ui/app/components/app-header/app-header.component.js
@@ -1,20 +1,13 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { matchPath } from 'react-router-dom'
import Identicon from '../identicon'
-
-const {
- ENVIRONMENT_TYPE_NOTIFICATION,
- ENVIRONMENT_TYPE_POPUP,
-} = require('../../../../app/scripts/lib/enums')
-const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
+import { DEFAULT_ROUTE } from '../../routes'
const NetworkIndicator = require('../network')
export default class AppHeader extends PureComponent {
static propTypes = {
history: PropTypes.object,
- location: PropTypes.object,
network: PropTypes.string,
provider: PropTypes.object,
networkDropdownOpen: PropTypes.bool,
@@ -23,7 +16,8 @@ export default class AppHeader extends PureComponent {
toggleAccountMenu: PropTypes.func,
selectedAddress: PropTypes.string,
isUnlocked: PropTypes.bool,
- providerRequests: PropTypes.array,
+ hideNetworkIndicator: PropTypes.bool,
+ disabled: PropTypes.bool,
}
static contextTypes = {
@@ -41,34 +35,15 @@ export default class AppHeader extends PureComponent {
: hideNetworkDropdown()
}
- /**
- * Returns whether or not the user is in the middle of a confirmation prompt
- *
- * This accounts for both tx confirmations as well as provider approvals
- *
- * @returns {boolean}
- */
- isConfirming () {
- const { location, providerRequests } = this.props
- const confirmTxRouteMatch = matchPath(location.pathname, {
- exact: false,
- path: CONFIRM_TRANSACTION_ROUTE,
- })
- const isConfirmingTx = Boolean(confirmTxRouteMatch)
- const hasPendingProviderApprovals = Array.isArray(providerRequests) && providerRequests.length > 0
-
- return isConfirmingTx || hasPendingProviderApprovals
- }
-
renderAccountMenu () {
- const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props
+ const { isUnlocked, toggleAccountMenu, selectedAddress, disabled } = this.props
return isUnlocked && (
<div
className={classnames('account-menu__icon', {
- 'account-menu__icon--disabled': this.isConfirming(),
+ 'account-menu__icon--disabled': disabled,
})}
- onClick={() => this.isConfirming() || toggleAccountMenu()}
+ onClick={() => disabled || toggleAccountMenu()}
>
<Identicon
address={selectedAddress}
@@ -78,38 +53,16 @@ export default class AppHeader extends PureComponent {
)
}
- hideAppHeader () {
- const { location } = this.props
-
- const isInitializing = Boolean(matchPath(location.pathname, {
- path: INITIALIZE_ROUTE, exact: false,
- }))
-
- if (isInitializing) {
- return true
- }
-
- if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
- return true
- }
-
- if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP && this.isConfirming()) {
- return true
- }
- }
-
render () {
const {
+ history,
network,
provider,
- history,
isUnlocked,
+ hideNetworkIndicator,
+ disabled,
} = this.props
- if (this.hideAppHeader()) {
- return null
- }
-
return (
<div
className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
@@ -131,14 +84,18 @@ export default class AppHeader extends PureComponent {
/>
</div>
<div className="app-header__account-menu-container">
- <div className="app-header__network-component-wrapper">
- <NetworkIndicator
- network={network}
- provider={provider}
- onClick={event => this.handleNetworkIndicatorClick(event)}
- disabled={this.isConfirming()}
- />
- </div>
+ {
+ !hideNetworkIndicator && (
+ <div className="app-header__network-component-wrapper">
+ <NetworkIndicator
+ network={network}
+ provider={provider}
+ onClick={event => this.handleNetworkIndicatorClick(event)}
+ disabled={disabled}
+ />
+ </div>
+ )
+ }
{ this.renderAccountMenu() }
</div>
</div>
diff --git a/ui/app/components/app-header/app-header.container.js b/ui/app/components/app-header/app-header.container.js
index 8b719bdf6..30d3f8cc4 100644
--- a/ui/app/components/app-header/app-header.container.js
+++ b/ui/app/components/app-header/app-header.container.js
@@ -11,7 +11,6 @@ const mapStateToProps = state => {
const {
network,
provider,
- providerRequests,
selectedAddress,
isUnlocked,
} = metamask
@@ -20,7 +19,6 @@ const mapStateToProps = state => {
networkDropdownOpen,
network,
provider,
- providerRequests,
selectedAddress,
isUnlocked,
}
diff --git a/ui/app/components/breadcrumbs/breadcrumbs.component.js b/ui/app/components/breadcrumbs/breadcrumbs.component.js
new file mode 100644
index 000000000..6644836db
--- /dev/null
+++ b/ui/app/components/breadcrumbs/breadcrumbs.component.js
@@ -0,0 +1,29 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+export default class Breadcrumbs extends PureComponent {
+ static propTypes = {
+ className: PropTypes.string,
+ currentIndex: PropTypes.number,
+ total: PropTypes.number,
+ }
+
+ render () {
+ const { className, currentIndex, total } = this.props
+
+ return (
+ <div className={classnames('breadcrumbs', className)}>
+ {
+ Array(total).fill().map((_, i) => (
+ <div
+ key={i}
+ className="breadcrumb"
+ style={{backgroundColor: i === currentIndex ? '#D8D8D8' : '#FFFFFF'}}
+ />
+ ))
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/breadcrumbs/index.js b/ui/app/components/breadcrumbs/index.js
new file mode 100644
index 000000000..07a11574f
--- /dev/null
+++ b/ui/app/components/breadcrumbs/index.js
@@ -0,0 +1 @@
+export { default } from './breadcrumbs.component'
diff --git a/ui/app/components/breadcrumbs/index.scss b/ui/app/components/breadcrumbs/index.scss
new file mode 100644
index 000000000..e23aa7970
--- /dev/null
+++ b/ui/app/components/breadcrumbs/index.scss
@@ -0,0 +1,15 @@
+.breadcrumbs {
+ display: flex;
+ flex-flow: row nowrap;
+}
+
+.breadcrumb {
+ height: 10px;
+ width: 10px;
+ border: 1px solid #979797;
+ border-radius: 50%;
+}
+
+.breadcrumb + .breadcrumb {
+ margin-left: 10px;
+}
diff --git a/ui/app/components/breadcrumbs/tests/breadcrumbs.component.test.js b/ui/app/components/breadcrumbs/tests/breadcrumbs.component.test.js
new file mode 100644
index 000000000..5013c5b60
--- /dev/null
+++ b/ui/app/components/breadcrumbs/tests/breadcrumbs.component.test.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import Breadcrumbs from '../breadcrumbs.component'
+
+describe('Breadcrumbs Component', () => {
+ it('should render with the correct colors', () => {
+ const wrapper = shallow(
+ <Breadcrumbs
+ currentIndex={1}
+ total={3}
+ />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.breadcrumbs').length, 1)
+ assert.equal(wrapper.find('.breadcrumb').length, 3)
+ assert.equal(wrapper.find('.breadcrumb').at(0).props().style['backgroundColor'], '#FFFFFF')
+ assert.equal(wrapper.find('.breadcrumb').at(1).props().style['backgroundColor'], '#D8D8D8')
+ assert.equal(wrapper.find('.breadcrumb').at(2).props().style['backgroundColor'], '#FFFFFF')
+ })
+})
diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js
index 5c617585d..5d19219b4 100644
--- a/ui/app/components/button/button.component.js
+++ b/ui/app/components/button/button.component.js
@@ -8,6 +8,7 @@ const CLASSNAME_SECONDARY = 'btn-secondary'
const CLASSNAME_CONFIRM = 'btn-confirm'
const CLASSNAME_RAISED = 'btn-raised'
const CLASSNAME_LARGE = 'btn--large'
+const CLASSNAME_FIRST_TIME = 'btn--first-time'
const typeHash = {
default: CLASSNAME_DEFAULT,
@@ -15,6 +16,7 @@ const typeHash = {
secondary: CLASSNAME_SECONDARY,
confirm: CLASSNAME_CONFIRM,
raised: CLASSNAME_RAISED,
+ 'first-time': CLASSNAME_FIRST_TIME,
}
export default class Button extends Component {
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index f1ecbbc3d..33bbb4573 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -4,6 +4,8 @@
@import './app-header/index';
+@import './breadcrumbs/index';
+
@import './button-group/index';
@import './card/index';
diff --git a/ui/app/components/lock-icon/index.js b/ui/app/components/lock-icon/index.js
new file mode 100644
index 000000000..6b4df0e58
--- /dev/null
+++ b/ui/app/components/lock-icon/index.js
@@ -0,0 +1 @@
+export { default } from './lock-icon.component'
diff --git a/ui/app/components/lock-icon/lock-icon.component.js b/ui/app/components/lock-icon/lock-icon.component.js
new file mode 100644
index 000000000..d010cb6b2
--- /dev/null
+++ b/ui/app/components/lock-icon/lock-icon.component.js
@@ -0,0 +1,32 @@
+import React from 'react'
+
+export default function LockIcon (props) {
+ return (
+ <svg
+ version="1.1"
+ id="Capa_1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ x="0px"
+ y="0px"
+ width="401.998px"
+ height="401.998px"
+ viewBox="0 0 401.998 401.998"
+ style={{enableBackground: 'new 0 0 401.998 401.998'}}
+ xmlSpace="preserve"
+ {...props}
+ >
+ <g>
+ <path
+ d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
+ C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
+ h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
+ c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
+ C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
+ c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
+ z"
+ />
+ </g>
+ </svg>
+ )
+}
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 0a603db4e..990be260c 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -122,7 +122,8 @@ const MODALS = {
display: 'flex',
},
laptopModalStyle: {
- width: '850px',
+ width: 'initial',
+ maxWidth: '850px',
top: 'calc(10% + 10px)',
left: '0',
right: '0',
diff --git a/ui/app/components/page-container/index.scss b/ui/app/components/page-container/index.scss
index 6fc97820a..b71a3cb9d 100644
--- a/ui/app/components/page-container/index.scss
+++ b/ui/app/components/page-container/index.scss
@@ -42,6 +42,12 @@
justify-content: space-between;
}
+ &__bottom {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+
&__footer {
display: flex;
flex-flow: column;
diff --git a/ui/app/components/pages/authenticated.js b/ui/app/components/pages/authenticated.js
deleted file mode 100644
index 1f6b0be49..000000000
--- a/ui/app/components/pages/authenticated.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Redirect } = require('react-router-dom')
-const h = require('react-hyperscript')
-const MetamaskRoute = require('./metamask-route')
-const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes')
-
-const Authenticated = props => {
- const { isUnlocked, isInitialized } = props
-
- switch (true) {
- case isUnlocked && isInitialized:
- return h(MetamaskRoute, { ...props })
- case !isInitialized:
- return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
- default:
- return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
- }
-}
-
-Authenticated.propTypes = {
- isUnlocked: PropTypes.bool,
- isInitialized: PropTypes.bool,
-}
-
-const mapStateToProps = state => {
- const { metamask: { isUnlocked, isInitialized } } = state
- return {
- isUnlocked,
- isInitialized,
- }
-}
-
-module.exports = connect(mapStateToProps)(Authenticated)
diff --git a/ui/app/components/pages/first-time-flow/create-password/create-password.component.js b/ui/app/components/pages/first-time-flow/create-password/create-password.component.js
new file mode 100644
index 000000000..69b1e549f
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/create-password.component.js
@@ -0,0 +1,61 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { Switch, Route } from 'react-router-dom'
+import NewAccount from './new-account'
+import ImportWithSeedPhrase from './import-with-seed-phrase'
+import UniqueImage from './unique-image'
+import {
+ INITIALIZE_CREATE_PASSWORD_ROUTE,
+ INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
+ INITIALIZE_UNIQUE_IMAGE_ROUTE,
+ INITIALIZE_NOTICE_ROUTE,
+} from '../../../../routes'
+
+export default class CreatePassword extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ isInitialized: PropTypes.bool,
+ onCreateNewAccount: PropTypes.func,
+ onCreateNewAccountFromSeed: PropTypes.func,
+ }
+
+ componentDidMount () {
+ const { isInitialized, history } = this.props
+
+ if (isInitialized) {
+ history.push(INITIALIZE_NOTICE_ROUTE)
+ }
+ }
+
+ render () {
+ const { onCreateNewAccount, onCreateNewAccountFromSeed } = this.props
+
+ return (
+ <div className="first-time-flow__wrapper">
+ <Switch>
+ <Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImage} />
+ <Route
+ exact
+ path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ImportWithSeedPhrase
+ { ...props }
+ onSubmit={onCreateNewAccountFromSeed}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_CREATE_PASSWORD_ROUTE}
+ render={props => (
+ <NewAccount
+ { ...props }
+ onSubmit={onCreateNewAccount}
+ />
+ )}
+ />
+ </Switch>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/create-password/create-password.container.js b/ui/app/components/pages/first-time-flow/create-password/create-password.container.js
new file mode 100644
index 000000000..89106f016
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/create-password.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import CreatePassword from './create-password.component'
+
+const mapStateToProps = state => {
+ const { metamask: { isInitialized } } = state
+
+ return {
+ isInitialized,
+ }
+}
+
+export default connect(mapStateToProps)(CreatePassword)
diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
new file mode 100644
index 000000000..8d81e5d8e
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
@@ -0,0 +1,214 @@
+import {validateMnemonic} from 'bip39'
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import TextField from '../../../../text-field'
+import Button from '../../../../button'
+import Breadcrumbs from '../../../../breadcrumbs'
+import {
+ INITIALIZE_CREATE_PASSWORD_ROUTE,
+ INITIALIZE_NOTICE_ROUTE,
+} from '../../../../../routes'
+
+export default class ImportWithSeedPhrase extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ history: PropTypes.object,
+ onSubmit: PropTypes.func.isRequired,
+ }
+
+ state = {
+ seedPhrase: '',
+ password: '',
+ confirmPassword: '',
+ seedPhraseError: '',
+ passwordError: '',
+ confirmPasswordError: '',
+ }
+
+ parseSeedPhrase = (seedPhrase) => {
+ return seedPhrase
+ .match(/\w+/g)
+ .join(' ')
+ }
+
+ handleSeedPhraseChange (seedPhrase) {
+ let seedPhraseError = ''
+
+ if (seedPhrase) {
+ if (this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
+ seedPhraseError = this.context.t('seedPhraseReq')
+ } else if (!validateMnemonic(seedPhrase)) {
+ seedPhraseError = this.context.t('invalidSeedPhrase')
+ }
+ }
+
+ this.setState({ seedPhrase, seedPhraseError })
+ }
+
+ handlePasswordChange (password) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { confirmPassword } = state
+ let confirmPasswordError = ''
+ let passwordError = ''
+
+ if (password && password.length < 8) {
+ passwordError = t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ password,
+ passwordError,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { password } = state
+ let confirmPasswordError = ''
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ confirmPassword,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleImport = async event => {
+ event.preventDefault()
+
+ if (!this.isValid()) {
+ return
+ }
+
+ const { password, seedPhrase } = this.state
+ const { history, onSubmit } = this.props
+
+ try {
+ await onSubmit(password, seedPhrase)
+ history.push(INITIALIZE_NOTICE_ROUTE)
+ } catch (error) {
+ this.setState({ seedPhraseError: error.message })
+ }
+ }
+
+ isValid () {
+ const {
+ seedPhrase,
+ password,
+ confirmPassword,
+ passwordError,
+ confirmPasswordError,
+ seedPhraseError,
+ } = this.state
+
+ if (!password || !confirmPassword || !seedPhrase || password !== confirmPassword) {
+ return false
+ }
+
+ if (password.length < 8) {
+ return false
+ }
+
+ return !passwordError && !confirmPasswordError && !seedPhraseError
+ }
+
+ render () {
+ const { t } = this.context
+ const { seedPhraseError, passwordError, confirmPasswordError } = this.state
+
+ return (
+ <form
+ className="first-time-flow__form"
+ onSubmit={this.handleImport}
+ >
+ <div>
+ <a
+ onClick={e => {
+ e.preventDefault()
+ this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ </div>
+ <div className="first-time-flow__header">
+ { t('importAccountSeedPhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('secretPhrase') }
+ </div>
+ <div className="first-time-flow__textarea-wrapper">
+ <label>{ t('walletSeed') }</label>
+ <textarea
+ className="first-time-flow__textarea"
+ onChange={e => this.handleSeedPhraseChange(e.target.value)}
+ value={this.state.seedPhrase}
+ placeholder={t('seedPhrasePlaceholder')}
+ />
+ </div>
+ {
+ seedPhraseError && (
+ <span className="error">
+ { seedPhraseError }
+ </span>
+ )
+ }
+ <TextField
+ id="password"
+ label={t('newPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoComplete="new-password"
+ margin="normal"
+ largeLabel
+ />
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ largeLabel
+ />
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ disabled={!this.isValid()}
+ onClick={this.handleImport}
+ >
+ { t('import') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={2}
+ currentIndex={0}
+ />
+ </form>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/index.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/index.js
new file mode 100644
index 000000000..e5ff1fde5
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './import-with-seed-phrase.component'
diff --git a/ui/app/components/pages/first-time-flow/create-password/index.js b/ui/app/components/pages/first-time-flow/create-password/index.js
new file mode 100644
index 000000000..42e7436f9
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/index.js
@@ -0,0 +1 @@
+export { default } from './create-password.container'
diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/index.js b/ui/app/components/pages/first-time-flow/create-password/new-account/index.js
new file mode 100644
index 000000000..97db39cc3
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/new-account/index.js
@@ -0,0 +1 @@
+export { default } from './new-account.component'
diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js
new file mode 100644
index 000000000..54f8c1a70
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js
@@ -0,0 +1,178 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Breadcrumbs from '../../../../breadcrumbs'
+import Button from '../../../../button'
+import {
+ INITIALIZE_UNIQUE_IMAGE_ROUTE,
+ INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
+} from '../../../../../routes'
+import TextField from '../../../../text-field'
+
+export default class NewAccount extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ onSubmit: PropTypes.func.isRequired,
+ history: PropTypes.object.isRequired,
+ }
+
+ state = {
+ password: '',
+ confirmPassword: '',
+ passwordError: '',
+ confirmPasswordError: '',
+ }
+
+ isValid () {
+ const {
+ password,
+ confirmPassword,
+ passwordError,
+ confirmPasswordError,
+ } = this.state
+
+ if (!password || !confirmPassword || password !== confirmPassword) {
+ return false
+ }
+
+ if (password.length < 8) {
+ return false
+ }
+
+ return !passwordError && !confirmPasswordError
+ }
+
+ handlePasswordChange (password) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { confirmPassword } = state
+ let passwordError = ''
+ let confirmPasswordError = ''
+
+ if (password && password.length < 8) {
+ passwordError = t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ password,
+ passwordError,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { password } = state
+ let confirmPasswordError = ''
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ confirmPassword,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleCreate = async event => {
+ event.preventDefault()
+
+ if (!this.isValid()) {
+ return
+ }
+
+ const { password } = this.state
+ const { onSubmit, history } = this.props
+
+ try {
+ await onSubmit(password)
+ history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE)
+ } catch (error) {
+ this.setState({ passwordError: error.message })
+ }
+ }
+
+ handleImportWithSeedPhrase = event => {
+ const { history } = this.props
+
+ event.preventDefault()
+ history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
+ }
+
+ render () {
+ const { t } = this.context
+ const { password, confirmPassword, passwordError, confirmPasswordError } = this.state
+
+ return (
+ <div>
+ <div className="first-time-flow__header">
+ { t('createPassword') }
+ </div>
+ <form
+ className="first-time-flow__form"
+ onSubmit={this.handleCreate}
+ >
+ <TextField
+ id="create-password"
+ label={t('newPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoFocus
+ autoComplete="new-password"
+ margin="normal"
+ fullWidth
+ largeLabel
+ />
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ fullWidth
+ largeLabel
+ />
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ disabled={!this.isValid()}
+ onClick={this.handleCreate}
+ >
+ { t('create') }
+ </Button>
+ </form>
+ <a
+ href=""
+ className="first-time-flow__link create-password__import-link"
+ onClick={this.handleImportWithSeedPhrase}
+ >
+ { t('importWithSeedPhrase') }
+ </a>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={3}
+ currentIndex={0}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/index.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/index.js
new file mode 100644
index 000000000..0e97bf755
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/index.js
@@ -0,0 +1 @@
+export { default } from './unique-image.container'
diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js
new file mode 100644
index 000000000..41a566f0a
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js
@@ -0,0 +1,53 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Identicon from '../../../../identicon'
+import Breadcrumbs from '../../../../breadcrumbs'
+import Button from '../../../../button'
+import { INITIALIZE_NOTICE_ROUTE } from '../../../../../routes'
+
+export default class UniqueImageScreen extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ address: PropTypes.string,
+ history: PropTypes.object,
+ }
+
+ render () {
+ const { t } = this.context
+ const { address, history } = this.props
+
+ return (
+ <div>
+ <Identicon
+ className="first-time-flow__unique-image"
+ address={address}
+ diameter={70}
+ />
+ <div className="first-time-flow__header">
+ { t('yourUniqueAccountImage') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('yourUniqueAccountImageDescription1') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('yourUniqueAccountImageDescription2') }
+ </div>
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={() => history.push(INITIALIZE_NOTICE_ROUTE)}
+ >
+ { t('next') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={3}
+ currentIndex={0}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.container.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.container.js
new file mode 100644
index 000000000..34874aaec
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import UniqueImage from './unique-image.component'
+
+const mapStateToProps = ({ metamask }) => {
+ const { selectedAddress } = metamask
+
+ return {
+ address: selectedAddress,
+ }
+}
+
+export default connect(mapStateToProps)(UniqueImage)
diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js
new file mode 100644
index 000000000..9e8bce2c8
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js
@@ -0,0 +1,57 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { Redirect } from 'react-router-dom'
+import {
+ DEFAULT_ROUTE,
+ LOCK_ROUTE,
+ INITIALIZE_WELCOME_ROUTE,
+ INITIALIZE_NOTICE_ROUTE,
+ INITIALIZE_UNLOCK_ROUTE,
+ INITIALIZE_SEED_PHRASE_ROUTE,
+} from '../../../../routes'
+
+export default class FirstTimeFlowSwitch extends PureComponent {
+ static propTypes = {
+ completedOnboarding: PropTypes.bool,
+ isInitialized: PropTypes.bool,
+ isUnlocked: PropTypes.bool,
+ noActiveNotices: PropTypes.bool,
+ seedPhrase: PropTypes.string,
+ }
+
+ render () {
+ const {
+ completedOnboarding,
+ isInitialized,
+ isUnlocked,
+ noActiveNotices,
+ seedPhrase,
+ } = this.props
+
+ if (completedOnboarding) {
+ return <Redirect to={{ pathname: DEFAULT_ROUTE }} />
+ }
+
+ if (isUnlocked && !seedPhrase) {
+ return <Redirect to={{ pathname: LOCK_ROUTE }} />
+ }
+
+ if (!isInitialized) {
+ return <Redirect to={{ pathname: INITIALIZE_WELCOME_ROUTE }} />
+ }
+
+ if (!isUnlocked) {
+ return <Redirect to={{ pathname: INITIALIZE_UNLOCK_ROUTE }} />
+ }
+
+ if (!noActiveNotices) {
+ return <Redirect to={{ pathname: INITIALIZE_NOTICE_ROUTE }} />
+ }
+
+ if (seedPhrase) {
+ return <Redirect to={{ pathname: INITIALIZE_SEED_PHRASE_ROUTE }} />
+ }
+
+ return <Redirect to={{ pathname: INITIALIZE_WELCOME_ROUTE }} />
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js
new file mode 100644
index 000000000..8b7a74880
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux'
+import FirstTimeFlowSwitch from './first-time-flow-switch.component'
+
+const mapStateToProps = ({ metamask }) => {
+ const {
+ completedOnboarding,
+ isInitialized,
+ isUnlocked,
+ noActiveNotices,
+ } = metamask
+
+ return {
+ completedOnboarding,
+ isInitialized,
+ isUnlocked,
+ noActiveNotices,
+ }
+}
+
+export default connect(mapStateToProps)(FirstTimeFlowSwitch)
diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/index.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/index.js
new file mode 100644
index 000000000..3647756ef
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/index.js
@@ -0,0 +1 @@
+export { default } from './first-time-flow-switch.container'
diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.component.js b/ui/app/components/pages/first-time-flow/first-time-flow.component.js
new file mode 100644
index 000000000..cde077803
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/first-time-flow.component.js
@@ -0,0 +1,145 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { Switch, Route } from 'react-router-dom'
+import FirstTimeFlowSwitch from './first-time-flow-switch'
+import Welcome from './welcome'
+import Unlock from '../unlock-page'
+import CreatePassword from './create-password'
+import Notices from './notices'
+import SeedPhrase from './seed-phrase'
+import {
+ DEFAULT_ROUTE,
+ INITIALIZE_WELCOME_ROUTE,
+ INITIALIZE_CREATE_PASSWORD_ROUTE,
+ INITIALIZE_NOTICE_ROUTE,
+ INITIALIZE_SEED_PHRASE_ROUTE,
+ INITIALIZE_UNLOCK_ROUTE,
+} from '../../../routes'
+
+export default class FirstTimeFlow extends PureComponent {
+ static propTypes = {
+ completedOnboarding: PropTypes.bool,
+ createNewAccount: PropTypes.func,
+ createNewAccountFromSeed: PropTypes.func,
+ history: PropTypes.object,
+ isInitialized: PropTypes.bool,
+ isUnlocked: PropTypes.bool,
+ noActiveNotices: PropTypes.bool,
+ unlockAccount: PropTypes.func,
+ }
+
+ state = {
+ seedPhrase: '',
+ isImportedKeyring: false,
+ }
+
+ componentDidMount () {
+ const { completedOnboarding, history, isInitialized, isUnlocked } = this.props
+
+ if (completedOnboarding) {
+ history.push(DEFAULT_ROUTE)
+ return
+ }
+
+ if (isInitialized && !isUnlocked) {
+ history.push(INITIALIZE_UNLOCK_ROUTE)
+ return
+ }
+ }
+
+ handleCreateNewAccount = async password => {
+ const { createNewAccount } = this.props
+
+ try {
+ const seedPhrase = await createNewAccount(password)
+ this.setState({ seedPhrase })
+ } catch (error) {
+ throw new Error(error.message)
+ }
+ }
+
+ handleImportWithSeedPhrase = async (password, seedPhrase) => {
+ const { createNewAccountFromSeed } = this.props
+
+ try {
+ await createNewAccountFromSeed(password, seedPhrase)
+ this.setState({ isImportedKeyring: true })
+ } catch (error) {
+ throw new Error(error.message)
+ }
+ }
+
+ handleUnlock = async password => {
+ const { unlockAccount, history, noActiveNotices } = this.props
+
+ try {
+ const seedPhrase = await unlockAccount(password)
+ this.setState({ seedPhrase }, () => {
+ noActiveNotices
+ ? history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ : history.push(INITIALIZE_NOTICE_ROUTE)
+ })
+ } catch (error) {
+ throw new Error(error.message)
+ }
+ }
+
+ render () {
+ const { seedPhrase, isImportedKeyring } = this.state
+
+ return (
+ <div className="first-time-flow">
+ <Switch>
+ <Route
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <SeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_NOTICE_ROUTE}
+ render={props => (
+ <Notices
+ { ...props }
+ isImportedKeyring={isImportedKeyring}
+ />
+ )}
+ />
+ <Route
+ path={INITIALIZE_CREATE_PASSWORD_ROUTE}
+ render={props => (
+ <CreatePassword
+ { ...props }
+ onCreateNewAccount={this.handleCreateNewAccount}
+ onCreateNewAccountFromSeed={this.handleImportWithSeedPhrase}
+ />
+ )}
+ />
+ <Route
+ path={INITIALIZE_UNLOCK_ROUTE}
+ render={props => (
+ <Unlock
+ { ...props }
+ onSubmit={this.handleUnlock}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_WELCOME_ROUTE}
+ component={Welcome}
+ />
+ <Route
+ exact
+ path="*"
+ component={FirstTimeFlowSwitch}
+ />
+ </Switch>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.container.js b/ui/app/components/pages/first-time-flow/first-time-flow.container.js
new file mode 100644
index 000000000..782eddb74
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/first-time-flow.container.js
@@ -0,0 +1,30 @@
+import { connect } from 'react-redux'
+import FirstTimeFlow from './first-time-flow.component'
+import {
+ createNewVaultAndGetSeedPhrase,
+ createNewVaultAndRestore,
+ unlockAndGetSeedPhrase,
+} from '../../../actions'
+
+const mapStateToProps = state => {
+ const { metamask: { completedOnboarding, isInitialized, isUnlocked, noActiveNotices } } = state
+
+ return {
+ completedOnboarding,
+ isInitialized,
+ isUnlocked,
+ noActiveNotices,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ createNewAccount: password => dispatch(createNewVaultAndGetSeedPhrase(password)),
+ createNewAccountFromSeed: (password, seedPhrase) => {
+ return dispatch(createNewVaultAndRestore(password, seedPhrase))
+ },
+ unlockAccount: password => dispatch(unlockAndGetSeedPhrase(password)),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(FirstTimeFlow)
diff --git a/ui/app/components/pages/first-time-flow/index.js b/ui/app/components/pages/first-time-flow/index.js
new file mode 100644
index 000000000..5db42437c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/index.js
@@ -0,0 +1 @@
+export { default } from './first-time-flow.container'
diff --git a/ui/app/components/pages/first-time-flow/index.scss b/ui/app/components/pages/first-time-flow/index.scss
new file mode 100644
index 000000000..e3aca0694
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/index.scss
@@ -0,0 +1,99 @@
+@import './welcome/index';
+
+@import './seed-phrase/index';
+
+.first-time-flow {
+ width: 100%;
+ background-color: $white;
+
+ &__wrapper {
+ @media screen and (min-width: $break-large) {
+ padding: 60px 275px 0 275px;
+ }
+
+ @media screen and (max-width: 1100px) {
+ padding: 36px;
+ }
+ }
+
+ &__form {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__header {
+ font-size: 2.5rem;
+ margin-bottom: 24px;
+ }
+
+ &__subheader {
+ margin-bottom: 16px;
+ }
+
+ &__input {
+ max-width: 350px;
+ }
+
+ &__textarea-wrapper {
+ margin-bottom: 8px;
+ display: inline-flex;
+ padding: 0;
+ position: relative;
+ min-width: 0;
+ flex-direction: column;
+ max-width: 350px;
+ }
+
+ &__textarea-label {
+ margin-bottom: 9px;
+ color: #1B344D;
+ font-size: 18px;
+ }
+
+ &__textarea {
+ font-size: 1rem;
+ font-family: Roboto;
+ height: 190px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ padding: 16px;
+ margin-top: 8px;
+ }
+
+ &__breadcrumbs {
+ margin: 36px 0;
+ }
+
+ &__unique-image {
+ margin-bottom: 20px;
+ }
+
+ &__markdown {
+ border: 1px solid #979797;
+ border-radius: 8px;
+ background-color: $white;
+ height: 200px;
+ overflow-y: auto;
+ color: #757575;
+ font-size: .75rem;
+ line-height: 15px;
+ text-align: justify;
+ margin: 0;
+ padding: 16px 20px;
+ height: 30vh;
+ }
+
+ &__text-block {
+ margin-bottom: 24px;
+
+ @media screen and (max-width: $break-small) {
+ margin-bottom: 16px;
+ font-size: .875rem;
+ }
+ }
+
+ &__button {
+ margin: 35px 0 14px;
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/notices/index.js b/ui/app/components/pages/first-time-flow/notices/index.js
new file mode 100644
index 000000000..024daaa68
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/notices/index.js
@@ -0,0 +1 @@
+export { default } from './notices.container'
diff --git a/ui/app/components/pages/first-time-flow/notices/notices.component.js b/ui/app/components/pages/first-time-flow/notices/notices.component.js
new file mode 100644
index 000000000..fefaedd6f
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/notices/notices.component.js
@@ -0,0 +1,124 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Markdown from 'react-markdown'
+import debounce from 'lodash.debounce'
+import Button from '../../../button'
+import Identicon from '../../../identicon'
+import Breadcrumbs from '../../../breadcrumbs'
+import { DEFAULT_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../routes'
+
+export default class Notices extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ address: PropTypes.string.isRequired,
+ completeOnboarding: PropTypes.func,
+ history: PropTypes.object,
+ isImportedKeyring: PropTypes.bool,
+ markNoticeRead: PropTypes.func,
+ nextUnreadNotice: PropTypes.shape({
+ title: PropTypes.string,
+ date: PropTypes.string,
+ body: PropTypes.string,
+ }),
+ noActiveNotices: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ nextUnreadNotice: {},
+ }
+
+ state = {
+ atBottom: false,
+ }
+
+ componentDidMount () {
+ const { noActiveNotices, history } = this.props
+
+ if (noActiveNotices) {
+ history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ }
+
+ this.onScroll()
+ }
+
+ acceptTerms = async () => {
+ const {
+ completeOnboarding,
+ history,
+ isImportedKeyring,
+ markNoticeRead,
+ nextUnreadNotice,
+ } = this.props
+
+ const hasActiveNotices = await markNoticeRead(nextUnreadNotice)
+
+ if (!hasActiveNotices) {
+ if (isImportedKeyring) {
+ await completeOnboarding()
+ history.push(DEFAULT_ROUTE)
+ } else {
+ history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ }
+ } else {
+ this.setState({ atBottom: false }, () => this.onScroll())
+ }
+ }
+
+ onScroll = debounce(() => {
+ if (this.state.atBottom) {
+ return
+ }
+
+ const target = document.querySelector('.first-time-flow__markdown')
+
+ if (target) {
+ const { scrollTop, offsetHeight, scrollHeight } = target
+ const atBottom = scrollTop + offsetHeight >= scrollHeight
+
+ this.setState({ atBottom })
+ }
+ }, 25)
+
+ render () {
+ const { t } = this.context
+ const { isImportedKeyring, address, nextUnreadNotice: { title, body } } = this.props
+ const { atBottom } = this.state
+
+ return (
+ <div
+ className="first-time-flow__wrapper"
+ onScroll={this.onScroll}
+ >
+ <Identicon
+ className="first-time-flow__unique-image"
+ address={address}
+ diameter={70}
+ />
+ <div className="first-time-flow__header">
+ { title }
+ </div>
+ <Markdown
+ className="first-time-flow__markdown"
+ source={body}
+ skipHtml
+ />
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={atBottom && this.acceptTerms}
+ disabled={!atBottom}
+ >
+ { t('accept') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={isImportedKeyring ? 2 : 3}
+ currentIndex={1}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/notices/notices.container.js b/ui/app/components/pages/first-time-flow/notices/notices.container.js
new file mode 100644
index 000000000..c65c5b7de
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/notices/notices.container.js
@@ -0,0 +1,27 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import { markNoticeRead, setCompletedOnboarding } from '../../../../actions'
+import Notices from './notices.component'
+
+const mapStateToProps = ({ metamask }) => {
+ const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask
+
+ return {
+ address: selectedAddress,
+ nextUnreadNotice,
+ noActiveNotices,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ markNoticeRead: notice => dispatch(markNoticeRead(notice)),
+ completeOnboarding: () => dispatch(setCompletedOnboarding()),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(Notices)
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
new file mode 100644
index 000000000..bc0f73a27
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
@@ -0,0 +1,161 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import shuffle from 'lodash.shuffle'
+import Identicon from '../../../../identicon'
+import Button from '../../../../button'
+import Breadcrumbs from '../../../../breadcrumbs'
+import { DEFAULT_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../../routes'
+import { exportAsFile } from '../../../../../../app/util'
+import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state'
+
+export default class ConfirmSeedPhrase extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static defaultProps = {
+ seedPhrase: '',
+ }
+
+ static propTypes = {
+ address: PropTypes.string,
+ completeOnboarding: PropTypes.func,
+ history: PropTypes.object,
+ onSubmit: PropTypes.func,
+ openBuyEtherModal: PropTypes.func,
+ seedPhrase: PropTypes.string,
+ }
+
+ state = {
+ selectedSeedWords: [],
+ shuffledSeedWords: [],
+ // Hash of shuffledSeedWords index {Number} to selectedSeedWords index {Number}
+ selectedSeedWordsHash: {},
+ }
+
+ componentDidMount () {
+ const { seedPhrase = '' } = this.props
+ const shuffledSeedWords = shuffle(seedPhrase.split(' ')) || []
+ this.setState({ shuffledSeedWords })
+ }
+
+ handleExport = () => {
+ exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
+ }
+
+ handleSubmit = async () => {
+ const { completeOnboarding, history, openBuyEtherModal } = this.props
+
+ if (!this.isValid()) {
+ return
+ }
+
+ try {
+ await completeOnboarding()
+ history.push(DEFAULT_ROUTE)
+ openBuyEtherModal()
+ } catch (error) {
+ console.error(error.message)
+ }
+ }
+
+ handleSelectSeedWord = (word, shuffledIndex) => {
+ this.setState(selectSeedWord(word, shuffledIndex))
+ }
+
+ handleDeselectSeedWord = shuffledIndex => {
+ this.setState(deselectSeedWord(shuffledIndex))
+ }
+
+ isValid () {
+ const { seedPhrase } = this.props
+ const { selectedSeedWords } = this.state
+ return seedPhrase === selectedSeedWords.join(' ')
+ }
+
+ render () {
+ const { t } = this.context
+ const { address, history } = this.props
+ const { selectedSeedWords, shuffledSeedWords, selectedSeedWordsHash } = this.state
+
+ return (
+ <div>
+ <div className="confirm-seed-phrase__back-button">
+ <a
+ onClick={e => {
+ e.preventDefault()
+ history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ </div>
+ <Identicon
+ className="first-time-flow__unique-image"
+ address={address}
+ diameter={70}
+ />
+ <div className="first-time-flow__header">
+ { t('confirmSecretBackupPhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('selectEachPhrase') }
+ </div>
+ <div className="confirm-seed-phrase__selected-seed-words">
+ {
+ selectedSeedWords.map((word, index) => (
+ <div
+ key={index}
+ className="confirm-seed-phrase__seed-word"
+ >
+ { word }
+ </div>
+ ))
+ }
+ </div>
+ <div className="confirm-seed-phrase__shuffled-seed-words">
+ {
+ shuffledSeedWords.map((word, index) => {
+ const isSelected = index in selectedSeedWordsHash
+
+ return (
+ <div
+ key={index}
+ className={classnames(
+ 'confirm-seed-phrase__seed-word',
+ 'confirm-seed-phrase__seed-word--shuffled',
+ { 'confirm-seed-phrase__seed-word--selected': isSelected }
+ )}
+ onClick={() => {
+ if (!isSelected) {
+ this.handleSelectSeedWord(word, index)
+ } else {
+ this.handleDeselectSeedWord(index)
+ }
+ }}
+ >
+ { word }
+ </div>
+ )
+ })
+ }
+ </div>
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={this.handleSubmit}
+ disabled={!this.isValid()}
+ >
+ { t('confirm') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={3}
+ currentIndex={2}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js
new file mode 100644
index 000000000..5fa2bec1e
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import ConfirmSeedPhrase from './confirm-seed-phrase.component'
+import { setCompletedOnboarding, showModal } from '../../../../../actions'
+
+const mapDispatchToProps = dispatch => {
+ return {
+ completeOnboarding: () => dispatch(setCompletedOnboarding()),
+ openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
+ }
+}
+
+export default connect(null, mapDispatchToProps)(ConfirmSeedPhrase)
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
new file mode 100644
index 000000000..f2476fc5c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
@@ -0,0 +1,41 @@
+export function selectSeedWord (word, shuffledIndex) {
+ return function update (state) {
+ const { selectedSeedWords, selectedSeedWordsHash } = state
+ const nextSelectedIndex = selectedSeedWords.length
+
+ return {
+ selectedSeedWords: [ ...selectedSeedWords, word ],
+ selectedSeedWordsHash: { ...selectedSeedWordsHash, [shuffledIndex]: nextSelectedIndex },
+ }
+ }
+}
+
+export function deselectSeedWord (shuffledIndex) {
+ return function update (state) {
+ const {
+ selectedSeedWords: prevSelectedSeedWords,
+ selectedSeedWordsHash: prevSelectedSeedWordsHash,
+ } = state
+
+ const selectedSeedWords = [...prevSelectedSeedWords]
+ const indexToRemove = prevSelectedSeedWordsHash[shuffledIndex]
+ selectedSeedWords.splice(indexToRemove, 1)
+ const selectedSeedWordsHash = Object.keys(prevSelectedSeedWordsHash).reduce((acc, index) => {
+ const output = { ...acc }
+ const selectedSeedWordIndex = prevSelectedSeedWordsHash[index]
+
+ if (selectedSeedWordIndex < indexToRemove) {
+ output[index] = selectedSeedWordIndex
+ } else if (selectedSeedWordIndex > indexToRemove) {
+ output[index] = selectedSeedWordIndex - 1
+ }
+
+ return output
+ }, {})
+
+ return {
+ selectedSeedWords,
+ selectedSeedWordsHash,
+ }
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js
new file mode 100644
index 000000000..beb53b383
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-seed-phrase.container'
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
new file mode 100644
index 000000000..e0444571f
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
@@ -0,0 +1,44 @@
+.confirm-seed-phrase {
+ &__back-button {
+ margin-bottom: 12px;
+ }
+
+ &__selected-seed-words {
+ min-height: 190px;
+ max-width: 496px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ margin: 24px 0 36px;
+ padding: 12px;
+ }
+
+ &__shuffled-seed-words {
+ max-width: 496px;
+ }
+
+ &__seed-word {
+ display: inline-block;
+ color: #5B5D67;
+ background-color: #E7E7E7;
+ padding: 8px 18px;
+ min-width: 64px;
+ margin: 4px;
+ text-align: center;
+
+ &--selected {
+ background-color: #85D1CC;
+ color: $white;
+ }
+
+ &--shuffled {
+ cursor: pointer;
+ margin: 6px;
+ }
+
+ @media screen and (max-width: 575px) {
+ font-size: .875rem;
+ padding: 6px 18px;
+ }
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/index.js b/ui/app/components/pages/first-time-flow/seed-phrase/index.js
new file mode 100644
index 000000000..7355bfb2c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './seed-phrase.container'
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/index.scss b/ui/app/components/pages/first-time-flow/seed-phrase/index.scss
new file mode 100644
index 000000000..88b28950c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/index.scss
@@ -0,0 +1,36 @@
+@import './confirm-seed-phrase/index';
+
+@import './reveal-seed-phrase/index';
+
+.seed-phrase {
+
+ &__sections {
+ display: flex;
+
+ @media screen and (min-width: $break-large) {
+ flex-direction: row;
+ }
+
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ }
+ }
+
+ &__main {
+ flex: 3;
+ min-width: 0;
+ }
+
+ &__side {
+ flex: 2;
+ min-width: 0;
+
+ @media screen and (min-width: $break-large) {
+ margin-left: 48px;
+ }
+
+ @media screen and (max-width: $break-small) {
+ margin-top: 24px;
+ }
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js
new file mode 100644
index 000000000..4a1b191b5
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './reveal-seed-phrase.component'
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss
new file mode 100644
index 000000000..568359d31
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss
@@ -0,0 +1,53 @@
+.reveal-seed-phrase {
+ &__secret {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ padding: 18px;
+ margin-top: 36px;
+ max-width: 350px;
+ }
+
+ &__secret-words {
+ width: 310px;
+ font-size: 1.25rem;
+ text-align: center;
+
+ &--hidden {
+ filter: blur(5px);
+ }
+ }
+
+ &__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;
+ cursor: pointer;
+ }
+
+ &__reveal-button {
+ color: $white;
+ font-size: .75rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ margin-top: 8px;
+ text-align: center;
+ }
+
+ &__export-text {
+ color: $curious-blue;
+ cursor: pointer;
+ font-weight: 500;
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
new file mode 100644
index 000000000..bb822d1d5
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
@@ -0,0 +1,139 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Identicon from '../../../../identicon'
+import LockIcon from '../../../../lock-icon'
+import Button from '../../../../button'
+import Breadcrumbs from '../../../../breadcrumbs'
+import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE } from '../../../../../routes'
+import { exportAsFile } from '../../../../../../app/util'
+
+export default class RevealSeedPhrase extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ address: PropTypes.string,
+ history: PropTypes.object,
+ seedPhrase: PropTypes.string,
+ }
+
+ state = {
+ isShowingSeedPhrase: false,
+ }
+
+ handleExport = () => {
+ exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
+ }
+
+ handleNext = event => {
+ event.preventDefault()
+ const { isShowingSeedPhrase } = this.state
+ const { history } = this.props
+
+ if (!isShowingSeedPhrase) {
+ return
+ }
+
+ history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE)
+ }
+
+ renderSecretWordsContainer () {
+ const { t } = this.context
+ const { seedPhrase } = this.props
+ const { isShowingSeedPhrase } = this.state
+
+ return (
+ <div className="reveal-seed-phrase__secret">
+ <div className={classnames(
+ 'reveal-seed-phrase__secret-words',
+ { 'reveal-seed-phrase__secret-words--hidden': !isShowingSeedPhrase }
+ )}>
+ { seedPhrase }
+ </div>
+ {
+ !isShowingSeedPhrase && (
+ <div
+ className="reveal-seed-phrase__secret-blocker"
+ onClick={() => this.setState({ isShowingSeedPhrase: true })}
+ >
+ <LockIcon
+ width="28px"
+ height="35px"
+ fill="#FFFFFF"
+ />
+ <div className="reveal-seed-phrase__reveal-button">
+ { t('clickToRevealSeed') }
+ </div>
+ </div>
+ )
+ }
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const { address } = this.props
+ const { isShowingSeedPhrase } = this.state
+
+ return (
+ <div>
+ <Identicon
+ className="first-time-flow__unique-image"
+ address={address}
+ diameter={70}
+ />
+ <div className="seed-phrase__sections">
+ <div className="seed-phrase__main">
+ <div className="first-time-flow__header">
+ { t('secretBackupPhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('secretBackupPhraseDescription') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('secretBackupPhraseWarning') }
+ </div>
+ { this.renderSecretWordsContainer() }
+ </div>
+ <div className="seed-phrase__side">
+ <div className="first-time-flow__text-block">
+ { `${t('tips')}:` }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('storePhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('writePhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('memorizePhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ <a
+ className="reveal-seed-phrase__export-text"
+ onClick={this.handleExport}>
+ { t('downloadSecretBackup') }
+ </a>
+ </div>
+ </div>
+ </div>
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={this.handleNext}
+ disabled={!isShowingSeedPhrase}
+ >
+ { t('next') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={3}
+ currentIndex={2}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js
new file mode 100644
index 000000000..5f5b8a0b2
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js
@@ -0,0 +1,59 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { Switch, Route } from 'react-router-dom'
+import RevealSeedPhrase from './reveal-seed-phrase'
+import ConfirmSeedPhrase from './confirm-seed-phrase'
+import {
+ INITIALIZE_SEED_PHRASE_ROUTE,
+ INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE,
+ DEFAULT_ROUTE,
+} from '../../../../routes'
+
+export default class SeedPhrase extends PureComponent {
+ static propTypes = {
+ address: PropTypes.string,
+ history: PropTypes.object,
+ seedPhrase: PropTypes.string,
+ }
+
+ componentDidMount () {
+ const { seedPhrase, history } = this.props
+
+ if (!seedPhrase) {
+ history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ render () {
+ const { address, seedPhrase } = this.props
+
+ return (
+ <div className="first-time-flow__wrapper">
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ConfirmSeedPhrase
+ { ...props }
+ address={address}
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <RevealSeedPhrase
+ { ...props }
+ address={address}
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ </Switch>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js
new file mode 100644
index 000000000..4df024ffc
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import SeedPhrase from './seed-phrase.component'
+
+const mapStateToProps = state => {
+ const { metamask: { selectedAddress } } = state
+
+ return {
+ address: selectedAddress,
+ }
+}
+
+export default connect(mapStateToProps)(SeedPhrase)
diff --git a/ui/app/components/pages/first-time-flow/welcome/index.js b/ui/app/components/pages/first-time-flow/welcome/index.js
new file mode 100644
index 000000000..8abeddaa1
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/welcome/index.js
@@ -0,0 +1 @@
+export { default } from './welcome.container'
diff --git a/ui/app/components/pages/first-time-flow/welcome/index.scss b/ui/app/components/pages/first-time-flow/welcome/index.scss
new file mode 100644
index 000000000..7527ceb35
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/welcome/index.scss
@@ -0,0 +1,43 @@
+.welcome-page {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 400px;
+ padding: 0 18px;
+
+ &__wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ }
+
+ &__header {
+ font-size: 1.5rem;
+ margin-bottom: 14px;
+ }
+
+ &__description {
+ text-align: center;
+
+ @media screen and (max-width: 575px) {
+ font-size: .9rem;
+ }
+ }
+
+ &__button {
+ height: 54px;
+ width: 198px;
+ font-family: Roboto;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
+ color: $white;
+ font-size: 1.25rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ margin: 35px 0 14px;
+ transition: 200ms ease-in-out;
+ background-color: rgba(247, 134, 28, .9);
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/welcome/welcome.component.js b/ui/app/components/pages/first-time-flow/welcome/welcome.component.js
new file mode 100644
index 000000000..f28a8210d
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/welcome/welcome.component.js
@@ -0,0 +1,65 @@
+import EventEmitter from 'events'
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Mascot from '../../../mascot'
+import Button from '../../../button'
+import { INITIALIZE_CREATE_PASSWORD_ROUTE, INITIALIZE_NOTICE_ROUTE } from '../../../../routes'
+
+export default class Welcome extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ isInitialized: PropTypes.bool,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ constructor (props) {
+ super(props)
+
+ this.animationEventEmitter = new EventEmitter()
+ }
+
+ componentDidMount () {
+ const { history, isInitialized } = this.props
+
+ if (isInitialized) {
+ history.push(INITIALIZE_NOTICE_ROUTE)
+ }
+ }
+
+ handleContinue = () => {
+ this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <div className="welcome-page__wrapper">
+ <div className="welcome-page">
+ <Mascot
+ animationEventEmitter={this.animationEventEmitter}
+ width="225"
+ height="225"
+ />
+ <div className="welcome-page__header">
+ { t('welcome') }
+ </div>
+ <div className="welcome-page__description">
+ <div>{ t('metamaskDescription') }</div>
+ <div>{ t('holdEther') }</div>
+ </div>
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={this.handleContinue}
+ >
+ { t('continue') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/welcome/welcome.container.js b/ui/app/components/pages/first-time-flow/welcome/welcome.container.js
new file mode 100644
index 000000000..4362d89cb
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/welcome/welcome.container.js
@@ -0,0 +1,25 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import { closeWelcomeScreen } from '../../../../actions'
+import Welcome from './welcome.component'
+
+const mapStateToProps = ({ metamask }) => {
+ const { welcomeScreenSeen, isInitialized } = metamask
+
+ return {
+ welcomeScreenSeen,
+ isInitialized,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(Welcome)
diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js
index b9ec3c258..469c760a6 100644
--- a/ui/app/components/pages/home/home.component.js
+++ b/ui/app/components/pages/home/home.component.js
@@ -7,7 +7,7 @@ import TransactionView from '../../transaction-view'
import ProviderApproval from '../provider-approval'
import {
- INITIALIZE_BACKUP_PHRASE_ROUTE,
+ INITIALIZE_SEED_PHRASE_ROUTE,
RESTORE_VAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE,
@@ -59,7 +59,7 @@ export default class Home extends PureComponent {
// seed words
if (seedWords) {
- return <Redirect to={{ pathname: INITIALIZE_BACKUP_PHRASE_ROUTE }}/>
+ return <Redirect to={{ pathname: INITIALIZE_SEED_PHRASE_ROUTE }}/>
}
if (forgottenPassword) {
diff --git a/ui/app/components/pages/index.scss b/ui/app/components/pages/index.scss
index 6551278f5..6a0680f32 100644
--- a/ui/app/components/pages/index.scss
+++ b/ui/app/components/pages/index.scss
@@ -5,3 +5,7 @@
@import './confirm-add-token/index';
@import './settings/index';
+
+@import './first-time-flow/index';
+
+@import './keychains/index';
diff --git a/ui/app/components/pages/initialized.js b/ui/app/components/pages/initialized.js
deleted file mode 100644
index 3adf67b28..000000000
--- a/ui/app/components/pages/initialized.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Redirect } = require('react-router-dom')
-const h = require('react-hyperscript')
-const { INITIALIZE_ROUTE } = require('../../routes')
-const MetamaskRoute = require('./metamask-route')
-
-const Initialized = props => {
- return props.isInitialized
- ? h(MetamaskRoute, { ...props })
- : h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
-}
-
-Initialized.propTypes = {
- isInitialized: PropTypes.bool,
-}
-
-const mapStateToProps = state => {
- const { metamask: { isInitialized } } = state
- return {
- isInitialized,
- }
-}
-
-module.exports = connect(mapStateToProps)(Initialized)
diff --git a/ui/app/components/pages/keychains/index.scss b/ui/app/components/pages/keychains/index.scss
new file mode 100644
index 000000000..868185419
--- /dev/null
+++ b/ui/app/components/pages/keychains/index.scss
@@ -0,0 +1,197 @@
+.first-view-main-wrapper {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ justify-content: center;
+ padding: 0 10px;
+}
+
+.first-view-main {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+}
+
+@media screen and (min-width: 1281px) {
+ .first-view-main {
+ width: 62vw;
+ }
+}
+
+.import-account {
+ display: flex;
+ flex-flow: column nowrap;
+ margin: 60px 0 30px 0;
+ position: relative;
+ max-width: initial;
+}
+
+@media only screen and (max-width: 575px) {
+ .import-account{
+ margin: 24px;
+ display: flex;
+ flex-flow: column nowrap;
+ width: calc(100vw - 80px);
+ }
+
+ .import-account__title {
+ width: initial !important;
+ }
+
+ .first-view-main {
+ height: 100%;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ margin-top: 12px;
+ }
+
+ .first-view-phone-invisible {
+ display: none;
+ }
+
+ .first-time-flow__input {
+ width: 100%;
+ }
+
+ .import-account__secret-phrase {
+ width: initial !important;
+ height: initial !important;
+ min-height: 190px;
+ }
+}
+
+.import-account__title {
+ color: #1B344D;
+ font-size: 40px;
+ line-height: 51px;
+ margin-bottom: 10px;
+}
+
+.import-account__back-button {
+ margin-bottom: 18px;
+ color: #22232c;
+ font-size: 16px;
+ line-height: 21px;
+ position: absolute;
+ top: -25px;
+}
+
+.import-account__secret-phrase {
+ height: 190px;
+ width: 495px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ padding: 17px;
+ font-size: 16px;
+}
+
+.import-account__secret-phrase::placeholder {
+ color: #9B9B9B;
+ font-weight: 200;
+}
+
+.import-account__faq-link {
+ font-size: 18px;
+ line-height: 23px;
+ font-family: Roboto;
+}
+
+.import-account__selector-label {
+ color: #1B344D;
+ font-size: 16px;
+}
+
+.import-account__dropdown {
+ width: 325px;
+ border: 1px solid #CDCDCD;
+ border-radius: 4px;
+ background-color: #FFFFFF;
+ margin-top: 14px;
+ color: #5B5D67;
+ font-family: Roboto;
+ font-size: 18px;
+ line-height: 23px;
+ padding: 14px 21px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ cursor: pointer;
+}
+
+.import-account__description-text {
+ color: #757575;
+ font-size: 18px;
+ line-height: 23px;
+ margin-top: 21px;
+ font-family: Roboto;
+}
+
+.import-account__input-wrapper {
+ display: flex;
+ flex-flow: column nowrap;
+ margin-top: 30px;
+}
+
+.import-account__input-error-message {
+ margin-top: 10px;
+ width: 422px;
+ color: #FF001F;
+ font-size: 16px;
+ line-height: 21px;
+}
+
+.import-account__input-label {
+ margin-bottom: 9px;
+ color: #1B344D;
+ font-size: 18px;
+ line-height: 23px;
+}
+
+.import-account__input-label__disabled {
+ opacity: 0.5;
+}
+
+.import-account__input {
+ width: 350px;
+}
+
+@media only screen and (max-width: 575px) {
+ .import-account__input {
+ width: 100%;
+ }
+}
+
+.import-account__file-input {
+ display: none;
+}
+
+.import-account__file-input-label {
+ height: 53px;
+ width: 148px;
+ border: 1px solid #1B344D;
+ border-radius: 4px;
+ color: #1B344D;
+ font-family: Roboto;
+ font-size: 18px;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.import-account__file-picker-wrapper {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
+
+.import-account__file-name {
+ color: #000000;
+ font-family: Roboto;
+ font-size: 18px;
+ line-height: 23px;
+ margin-left: 22px;
+}
diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js
index d90a33e49..ce18d998c 100644
--- a/ui/app/components/pages/keychains/restore-vault.js
+++ b/ui/app/components/pages/keychains/restore-vault.js
@@ -7,6 +7,7 @@ import {
} from '../../../actions'
import { DEFAULT_ROUTE } from '../../../routes'
import TextField from '../../text-field'
+import Button from '../../button'
class RestoreVaultPage extends Component {
static contextTypes = {
@@ -160,13 +161,14 @@ class RestoreVaultPage extends Component {
margin="normal"
largeLabel
/>
- <button
+ <Button
+ type="first-time"
className="first-time-flow__button"
onClick={() => !disabled && this.onClick()}
disabled={disabled}
>
{this.context.t('restore')}
- </button>
+ </Button>
</div>
</div>
</div>
diff --git a/ui/app/components/pages/lock/index.js b/ui/app/components/pages/lock/index.js
new file mode 100644
index 000000000..7bfe2a61f
--- /dev/null
+++ b/ui/app/components/pages/lock/index.js
@@ -0,0 +1 @@
+export { default } from './lock.container'
diff --git a/ui/app/components/pages/lock/lock.component.js b/ui/app/components/pages/lock/lock.component.js
new file mode 100644
index 000000000..51f8742ed
--- /dev/null
+++ b/ui/app/components/pages/lock/lock.component.js
@@ -0,0 +1,26 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Loading from '../../loading-screen'
+import { DEFAULT_ROUTE } from '../../../routes'
+
+export default class Lock extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ isUnlocked: PropTypes.bool,
+ lockMetamask: PropTypes.func,
+ }
+
+ componentDidMount () {
+ const { lockMetamask, isUnlocked, history } = this.props
+
+ if (isUnlocked) {
+ lockMetamask().then(() => history.push(DEFAULT_ROUTE))
+ } else {
+ history.replace(DEFAULT_ROUTE)
+ }
+ }
+
+ render () {
+ return <Loading />
+ }
+}
diff --git a/ui/app/components/pages/lock/lock.container.js b/ui/app/components/pages/lock/lock.container.js
new file mode 100644
index 000000000..81d89ba21
--- /dev/null
+++ b/ui/app/components/pages/lock/lock.container.js
@@ -0,0 +1,24 @@
+import Lock from './lock.component'
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { lockMetamask } from '../../../actions'
+
+const mapStateToProps = state => {
+ const { metamask: { isUnlocked } } = state
+
+ return {
+ isUnlocked,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ lockMetamask: () => dispatch(lockMetamask()),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(Lock)
diff --git a/ui/app/components/pages/metamask-route.js b/ui/app/components/pages/metamask-route.js
deleted file mode 100644
index 23c5b5199..000000000
--- a/ui/app/components/pages/metamask-route.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Route } = require('react-router-dom')
-const h = require('react-hyperscript')
-
-const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => {
- return (
- h(Route, {
- ...props,
- component: isMascara && mascaraComponent ? mascaraComponent : component,
- })
- )
-}
-
-MetamaskRoute.propTypes = {
- component: PropTypes.func,
- mascaraComponent: PropTypes.func,
- isMascara: PropTypes.bool,
-}
-
-const mapStateToProps = state => {
- const { metamask: { isMascara } } = state
- return {
- isMascara,
- }
-}
-
-module.exports = connect(mapStateToProps)(MetamaskRoute)
diff --git a/ui/app/components/pages/unlock-page/index.scss b/ui/app/components/pages/unlock-page/index.scss
index 6bd52282d..3d44bd037 100644
--- a/ui/app/components/pages/unlock-page/index.scss
+++ b/ui/app/components/pages/unlock-page/index.scss
@@ -14,7 +14,6 @@
align-self: stretch;
justify-content: center;
flex: 1 0 auto;
- height: 100vh;
}
&__mascot-container {
diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js
index 94915df76..58a8b0566 100644
--- a/ui/app/components/pages/unlock-page/unlock-page.component.js
+++ b/ui/app/components/pages/unlock-page/unlock-page.component.js
@@ -2,12 +2,10 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Button from '@material-ui/core/Button'
import TextField from '../../text-field'
-import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
-import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import getCaretCoordinates from 'textarea-caret'
import { EventEmitter } from 'events'
import Mascot from '../../mascot'
-import { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } from '../../../routes'
+import { DEFAULT_ROUTE } from '../../../routes'
export default class UnlockPage extends Component {
static contextTypes = {
@@ -15,12 +13,11 @@ export default class UnlockPage extends Component {
}
static propTypes = {
- forgotPassword: PropTypes.func,
- tryUnlockMetamask: PropTypes.func,
- markPasswordForgotten: PropTypes.func,
history: PropTypes.object,
isUnlocked: PropTypes.bool,
- useOldInterface: PropTypes.func,
+ onImport: PropTypes.func,
+ onRestore: PropTypes.func,
+ onSubmit: PropTypes.func,
}
constructor (props) {
@@ -43,12 +40,12 @@ export default class UnlockPage extends Component {
}
}
- async handleSubmit (event) {
+ handleSubmit = async event => {
event.preventDefault()
event.stopPropagation()
const { password } = this.state
- const { tryUnlockMetamask, history } = this.props
+ const { onSubmit } = this.props
if (password === '' || this.submitting) {
return
@@ -58,9 +55,7 @@ export default class UnlockPage extends Component {
this.submitting = true
try {
- await tryUnlockMetamask(password)
- this.submitting = false
- history.push(DEFAULT_ROUTE)
+ await onSubmit(password)
} catch ({ message }) {
this.setState({ error: message })
this.submitting = false
@@ -99,7 +94,7 @@ export default class UnlockPage extends Component {
fullWidth
variant="raised"
size="large"
- onClick={event => this.handleSubmit(event)}
+ onClick={this.handleSubmit}
disableRipple
>
{ this.context.t('login') }
@@ -110,7 +105,7 @@ export default class UnlockPage extends Component {
render () {
const { password, error } = this.state
const { t } = this.context
- const { markPasswordForgotten, history } = this.props
+ const { onImport, onRestore } = this.props
return (
<div className="unlock-page__container">
@@ -128,7 +123,7 @@ export default class UnlockPage extends Component {
<div>{ t('unlockMessage') }</div>
<form
className="unlock-page__form"
- onSubmit={event => this.handleSubmit(event)}
+ onSubmit={this.handleSubmit}
>
<TextField
id="password"
@@ -147,27 +142,13 @@ export default class UnlockPage extends Component {
<div className="unlock-page__links">
<div
className="unlock-page__link"
- onClick={() => {
- markPasswordForgotten()
- history.push(RESTORE_VAULT_ROUTE)
-
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser()
- }
- }}
+ onClick={() => onRestore()}
>
{ t('restoreFromSeed') }
</div>
<div
className="unlock-page__link unlock-page__link--import"
- onClick={() => {
- markPasswordForgotten()
- history.push(RESTORE_VAULT_ROUTE)
-
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser()
- }
- }}
+ onClick={() => onImport()}
>
{ t('importUsingSeed') }
</div>
diff --git a/ui/app/components/pages/unlock-page/unlock-page.container.js b/ui/app/components/pages/unlock-page/unlock-page.container.js
index 18fed9b2e..5f302dc37 100644
--- a/ui/app/components/pages/unlock-page/unlock-page.container.js
+++ b/ui/app/components/pages/unlock-page/unlock-page.container.js
@@ -1,13 +1,14 @@
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
-
-const {
+import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
+import { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } from '../../../routes'
+import {
tryUnlockMetamask,
forgotPassword,
markPasswordForgotten,
-} = require('../../../actions')
-
+} from '../../../actions'
import UnlockPage from './unlock-page.component'
const mapStateToProps = state => {
@@ -25,7 +26,35 @@ const mapDispatchToProps = dispatch => {
}
}
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { markPasswordForgotten, tryUnlockMetamask, ...restDispatchProps } = dispatchProps
+ const { history, onSubmit: ownPropsSubmit, ...restOwnProps } = ownProps
+
+ const onImport = () => {
+ markPasswordForgotten()
+ history.push(RESTORE_VAULT_ROUTE)
+
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
+ global.platform.openExtensionInBrowser()
+ }
+ }
+
+ const onSubmit = async password => {
+ await tryUnlockMetamask(password)
+ history.push(DEFAULT_ROUTE)
+ }
+
+ return {
+ ...stateProps,
+ ...restDispatchProps,
+ ...restOwnProps,
+ onImport,
+ onRestore: onImport,
+ onSubmit: ownPropsSubmit || onSubmit,
+ }
+}
+
export default compose(
withRouter,
- connect(mapStateToProps, mapDispatchToProps)
+ connect(mapStateToProps, mapDispatchToProps, mergeProps)
)(UnlockPage)
diff --git a/ui/app/components/transaction-view-balance/index.scss b/ui/app/components/transaction-view-balance/index.scss
index 43e87459b..f3fd580d7 100644
--- a/ui/app/components/transaction-view-balance/index.scss
+++ b/ui/app/components/transaction-view-balance/index.scss
@@ -32,6 +32,7 @@
@media screen and (max-width: $break-small) {
font-size: 1.75rem;
width: 100%;
+ justify-content: center;
}
}
diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss
index c068028f8..ffccbd64f 100644
--- a/ui/app/css/index.scss
+++ b/ui/app/css/index.scss
@@ -11,8 +11,6 @@
@import './itcss/generic/index.scss';
-@import './itcss/base/index.scss';
-
@import './itcss/objects/index.scss';
@import './itcss/components/index.scss';
diff --git a/ui/app/css/itcss/base/index.scss b/ui/app/css/itcss/base/index.scss
deleted file mode 100644
index 1475e8bb5..000000000
--- a/ui/app/css/itcss/base/index.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Base
-
-.mouse-user-styles {
- button:focus {
- outline: 0;
- }
-}
diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss
index 5826a8b49..3e99d0ac6 100644
--- a/ui/app/css/itcss/components/buttons.scss
+++ b/ui/app/css/itcss/components/buttons.scss
@@ -87,6 +87,18 @@
min-width: initial;
}
+.btn--first-time {
+ height: 54px;
+ width: 198px;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
+ color: $white;
+ font-size: 1.25rem;
+ font-weight: 500;
+ transition: 200ms ease-in-out;
+ background-color: rgba(247, 134, 28, .9);
+ border-radius: 0;
+}
+
.btn--large {
min-height: 54px;
}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index b11b76f35..7eaf60ce8 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -52,6 +52,4 @@
@import './tooltip.scss';
-@import './welcome-screen.scss';
-
@import '../../../components/index';
diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss
index d7ff0b8ed..a99c58a23 100644
--- a/ui/app/css/itcss/components/loading-overlay.scss
+++ b/ui/app/css/itcss/components/loading-overlay.scss
@@ -1,7 +1,7 @@
.loading-overlay {
left: 0;
z-index: 51;
- position: absolute;
+ position: fixed;
flex-direction: column;
display: flex;
justify-content: center;
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index a016fdce3..9a0b81aed 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -8,6 +8,15 @@ $sub-mid-size-breakpoint-range: "screen and (min-width: #{$break-large}) and (ma
// Component Colors
$wallet-view-bg: $alabaster;
+.app {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow-x: hidden;
+ position: relative;
+ align-items: center;
+}
+
// Main container
.main-container {
// position: absolute;
@@ -24,8 +33,10 @@ $wallet-view-bg: $alabaster;
.main-container-wrapper {
display: flex;
- width: 100vw;
justify-content: center;
+ flex: 1 0 auto;
+ min-height: 0;
+ width: 100%;
}
//Account and transaction details
@@ -207,8 +218,6 @@ $wallet-view-bg: $alabaster;
}
.main-container {
- // margin-top: 41px;
- height: 100%;
width: 100%;
overflow-y: auto;
background-color: $white;
@@ -216,8 +225,6 @@ $wallet-view-bg: $alabaster;
.main-container-wrapper {
flex: 1;
- min-height: 0;
- width: 100%;
}
}
diff --git a/ui/app/css/itcss/components/welcome-screen.scss b/ui/app/css/itcss/components/welcome-screen.scss
deleted file mode 100644
index af1d67398..000000000
--- a/ui/app/css/itcss/components/welcome-screen.scss
+++ /dev/null
@@ -1,60 +0,0 @@
-.welcome-screen {
- display: flex;
- flex-flow: column;
- justify-content: center;
- align-items: center;
- font-family: Roboto;
- font-weight: 400;
- width: 100%;
- flex: 1 0 auto;
- padding: 70px 0;
- background: $white;
-
- @media screen and (max-width: 575px) {
- padding: 0;
- }
-
- &__info {
- display: flex;
- flex-flow: column;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
-
- &__header {
- font-size: 1.65em;
- margin-bottom: 14px;
-
- @media screen and (max-width: 575px) {
- font-size: 1.5em;
- }
- }
-
- &__copy {
- font-size: 1em;
- width: 400px;
- max-width: 90vw;
- text-align: center;
-
- @media screen and (max-width: 575px) {
- font-size: .9em;
- }
- }
- }
-
- &__button {
- height: 54px;
- width: 198px;
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
- color: #fff;
- font-size: 20px;
- font-weight: 500;
- line-height: 26px;
- text-align: center;
- text-transform: uppercase;
- margin: 35px 0 14px;
- transition: 200ms ease-in-out;
- background-color: rgba(247, 134, 28, .9);
- }
-}
diff --git a/ui/app/higher-order-components/authenticated/authenticated.component.js b/ui/app/higher-order-components/authenticated/authenticated.component.js
new file mode 100644
index 000000000..7b64d4895
--- /dev/null
+++ b/ui/app/higher-order-components/authenticated/authenticated.component.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Redirect, Route } from 'react-router-dom'
+import { UNLOCK_ROUTE, INITIALIZE_ROUTE } from '../../routes'
+
+export default function Authenticated (props) {
+ const { isUnlocked, completedOnboarding } = props
+
+ switch (true) {
+ case isUnlocked && completedOnboarding:
+ return <Route { ...props } />
+ case !completedOnboarding:
+ return <Redirect to={{ pathname: INITIALIZE_ROUTE }} />
+ default:
+ return <Redirect to={{ pathname: UNLOCK_ROUTE }} />
+ }
+}
+
+Authenticated.propTypes = {
+ isUnlocked: PropTypes.bool,
+ completedOnboarding: PropTypes.bool,
+}
diff --git a/ui/app/higher-order-components/authenticated/authenticated.container.js b/ui/app/higher-order-components/authenticated/authenticated.container.js
new file mode 100644
index 000000000..6124b0fcd
--- /dev/null
+++ b/ui/app/higher-order-components/authenticated/authenticated.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import Authenticated from './authenticated.component'
+
+const mapStateToProps = state => {
+ const { metamask: { isUnlocked, completedOnboarding } } = state
+ return {
+ isUnlocked,
+ completedOnboarding,
+ }
+}
+
+export default connect(mapStateToProps)(Authenticated)
diff --git a/ui/app/higher-order-components/authenticated/index.js b/ui/app/higher-order-components/authenticated/index.js
new file mode 100644
index 000000000..05632ed21
--- /dev/null
+++ b/ui/app/higher-order-components/authenticated/index.js
@@ -0,0 +1 @@
+export { default } from './authenticated.container'
diff --git a/ui/app/higher-order-components/initialized/index.js b/ui/app/higher-order-components/initialized/index.js
new file mode 100644
index 000000000..863fcb389
--- /dev/null
+++ b/ui/app/higher-order-components/initialized/index.js
@@ -0,0 +1 @@
+export { default } from './initialized.container.js'
diff --git a/ui/app/higher-order-components/initialized/initialized.component.js b/ui/app/higher-order-components/initialized/initialized.component.js
new file mode 100644
index 000000000..0736ceff4
--- /dev/null
+++ b/ui/app/higher-order-components/initialized/initialized.component.js
@@ -0,0 +1,14 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Redirect, Route } from 'react-router-dom'
+import { INITIALIZE_ROUTE } from '../../routes'
+
+export default function Initialized (props) {
+ return props.completedOnboarding
+ ? <Route { ...props } />
+ : <Redirect to={{ pathname: INITIALIZE_ROUTE }} />
+}
+
+Initialized.propTypes = {
+ completedOnboarding: PropTypes.bool,
+}
diff --git a/ui/app/higher-order-components/initialized/initialized.container.js b/ui/app/higher-order-components/initialized/initialized.container.js
new file mode 100644
index 000000000..0e7f72bcb
--- /dev/null
+++ b/ui/app/higher-order-components/initialized/initialized.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import Initialized from './initialized.component'
+
+const mapStateToProps = state => {
+ const { metamask: { completedOnboarding } } = state
+
+ return {
+ completedOnboarding,
+ }
+}
+
+export default connect(mapStateToProps)(Initialized)
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 97052ab87..632ec18f8 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -54,6 +54,7 @@ function reduceMetamask (state, action) {
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
+ completedOnboarding: false,
knownMethodData: {},
}, state.metamask)
@@ -378,6 +379,12 @@ function reduceMetamask (state, action) {
})
}
+ case actions.COMPLETE_ONBOARDING: {
+ return extend(metamaskState, {
+ completedOnboarding: true,
+ })
+ }
+
default:
return metamaskState
diff --git a/ui/app/routes.js b/ui/app/routes.js
index 76afed5db..fcf3d3e68 100644
--- a/ui/app/routes.js
+++ b/ui/app/routes.js
@@ -1,5 +1,6 @@
const DEFAULT_ROUTE = '/'
const UNLOCK_ROUTE = '/unlock'
+const LOCK_ROUTE = '/lock'
const SETTINGS_ROUTE = '/settings'
const INFO_ROUTE = '/settings/info'
const REVEAL_SEED_ROUTE = '/seed'
@@ -14,14 +15,17 @@ const CONNECT_HARDWARE_ROUTE = '/new-account/connect'
const SEND_ROUTE = '/send'
const NOTICE_ROUTE = '/notice'
const WELCOME_ROUTE = '/welcome'
+
const INITIALIZE_ROUTE = '/initialize'
+const INITIALIZE_WELCOME_ROUTE = '/initialize/welcome'
+const INITIALIZE_UNLOCK_ROUTE = '/initialize/unlock'
const INITIALIZE_CREATE_PASSWORD_ROUTE = '/initialize/create-password'
-const INITIALIZE_IMPORT_ACCOUNT_ROUTE = '/initialize/import-account'
-const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/import-with-seed-phrase'
-const INITIALIZE_UNIQUE_IMAGE_ROUTE = '/initialize/unique-image'
+const INITIALIZE_IMPORT_ACCOUNT_ROUTE = '/initialize/create-password/import-account'
+const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/create-password/import-with-seed-phrase'
+const INITIALIZE_UNIQUE_IMAGE_ROUTE = '/initialize/create-password/unique-image'
const INITIALIZE_NOTICE_ROUTE = '/initialize/notice'
-const INITIALIZE_BACKUP_PHRASE_ROUTE = '/initialize/backup-phrase'
-const INITIALIZE_CONFIRM_SEED_ROUTE = '/initialize/confirm-phrase'
+const INITIALIZE_SEED_PHRASE_ROUTE = '/initialize/seed-phrase'
+const INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE = '/initialize/seed-phrase/confirm'
const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'
const CONFIRM_SEND_ETHER_PATH = '/send-ether'
@@ -35,6 +39,7 @@ const SIGNATURE_REQUEST_PATH = '/signature-request'
module.exports = {
DEFAULT_ROUTE,
UNLOCK_ROUTE,
+ LOCK_ROUTE,
SETTINGS_ROUTE,
INFO_ROUTE,
REVEAL_SEED_ROUTE,
@@ -50,13 +55,15 @@ module.exports = {
NOTICE_ROUTE,
WELCOME_ROUTE,
INITIALIZE_ROUTE,
+ INITIALIZE_WELCOME_ROUTE,
+ INITIALIZE_UNLOCK_ROUTE,
INITIALIZE_CREATE_PASSWORD_ROUTE,
INITIALIZE_IMPORT_ACCOUNT_ROUTE,
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
INITIALIZE_UNIQUE_IMAGE_ROUTE,
INITIALIZE_NOTICE_ROUTE,
- INITIALIZE_BACKUP_PHRASE_ROUTE,
- INITIALIZE_CONFIRM_SEED_ROUTE,
+ INITIALIZE_SEED_PHRASE_ROUTE,
+ INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
CONFIRM_SEND_ETHER_PATH,
CONFIRM_SEND_TOKEN_PATH,
diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js
deleted file mode 100644
index 146661eb3..000000000
--- a/ui/app/welcome-screen.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import EventEmitter from 'events'
-import h from 'react-hyperscript'
-import { Component } from 'react'
-import PropTypes from 'prop-types'
-import {connect} from 'react-redux'
-import { withRouter } from 'react-router-dom'
-import { compose } from 'recompose'
-import {closeWelcomeScreen} from './actions'
-import Mascot from './components/mascot'
-import { INITIALIZE_CREATE_PASSWORD_ROUTE } from './routes'
-
-class WelcomeScreen extends Component {
- static propTypes = {
- closeWelcomeScreen: PropTypes.func.isRequired,
- welcomeScreenSeen: PropTypes.bool,
- history: PropTypes.object,
- t: PropTypes.func,
- }
-
- static contextTypes = {
- t: PropTypes.func,
- }
-
- constructor (props) {
- super(props)
- this.animationEventEmitter = new EventEmitter()
- }
-
- componentWillMount () {
- const { history, welcomeScreenSeen } = this.props
-
- if (welcomeScreenSeen) {
- history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
- }
- }
-
- initiateAccountCreation = () => {
- this.props.closeWelcomeScreen()
- this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
- }
-
- render () {
- return h('div.welcome-screen', [
-
- h('div.welcome-screen__info', [
-
- h(Mascot, {
- animationEventEmitter: this.animationEventEmitter,
- width: '225',
- height: '225',
- }),
-
- h('div.welcome-screen__info__header', this.context.t('welcome')),
-
- h('div.welcome-screen__info__copy', this.context.t('metamaskDescription')),
-
- h('div.welcome-screen__info__copy', this.context.t('holdEther')),
-
- h('button.welcome-screen__button', {
- onClick: this.initiateAccountCreation,
- }, this.context.t('continue')),
-
- ]),
-
- ])
- }
-}
-
-const mapStateToProps = ({ metamask: { welcomeScreenSeen } }) => {
- return {
- welcomeScreenSeen,
- }
-}
-
-export default compose(
- withRouter,
- connect(
- mapStateToProps,
- dispatch => ({
- closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
- })
- )
-)(WelcomeScreen)
diff --git a/ui/css.js b/ui/css.js
index 0d0f60806..d8f954434 100644
--- a/ui/css.js
+++ b/ui/css.js
@@ -5,7 +5,6 @@ module.exports = bundleCss
var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/output/index.css'), 'utf8'),
- 'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'),
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
}