aboutsummaryrefslogtreecommitdiffstats
path: root/ui
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 /ui
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
Diffstat (limited to 'ui')
-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
81 files changed, 2485 insertions, 448 deletions
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'),
}