diff options
author | Dan J Miller <danjm.com@gmail.com> | 2019-08-02 11:57:26 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-02 11:57:26 +0800 |
commit | 3eff4787757ac22d0a469c91599cdcfd97a2a98c (patch) | |
tree | d0ba3b8b5fbbb9f6d68ba227fa3fd8410b766912 /ui | |
parent | 189e126f6184b4351a9f11d8dc063d7abd5e9bbe (diff) | |
download | tangerine-wallet-browser-3eff4787757ac22d0a469c91599cdcfd97a2a98c.tar.gz tangerine-wallet-browser-3eff4787757ac22d0a469c91599cdcfd97a2a98c.tar.zst tangerine-wallet-browser-3eff4787757ac22d0a469c91599cdcfd97a2a98c.zip |
I5849 incremental account security (#6874)
* Implements ability to defer seed phrase backup to later
* Adds incremental-security.spec.js, including test dapp that sends signed tx with stand alone localhost provider
* Update metamask-responsive-ui for incremental account security changes
* Update backup-notification style and fix responsiveness of seed phrase screen
* Remove uneeded files from send-eth-with-private-key-test/
* Apply linguist flags in .gitattributes for send-eth-with-private-key-test/ethereumjs-tx.js
* Improve docs in controllers/onboarding.js
* Clean up metamask-extension/test/e2e/send-eth-with-private-key-test/index.html
* Remove unnecessary newlines in a couple first-time-flow/ files
* Fix import of backup-notification in home.component
* Fix git attrs file
Diffstat (limited to 'ui')
26 files changed, 375 insertions, 25 deletions
diff --git a/ui/app/components/app/backup-notification/backup-notification.component.js b/ui/app/components/app/backup-notification/backup-notification.component.js new file mode 100644 index 000000000..dba79186c --- /dev/null +++ b/ui/app/components/app/backup-notification/backup-notification.component.js @@ -0,0 +1,50 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Button from '../../ui/button' +import { + INITIALIZE_SEED_PHRASE_ROUTE, +} from '../../../helpers/constants/routes' + +export default class BackupNotification extends PureComponent { + static propTypes = { + history: PropTypes.object, + showSeedPhraseBackupAfterOnboarding: PropTypes.func, + } + + static contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, + } + + handleSubmit = () => { + const { history, showSeedPhraseBackupAfterOnboarding } = this.props + showSeedPhraseBackupAfterOnboarding() + history.push(INITIALIZE_SEED_PHRASE_ROUTE) + } + + render () { + const { t } = this.context + + return ( + <div className="backup-notification"> + <div className="backup-notification__header"> + <img + className="backup-notification__icon" + src="images/meta-shield.svg" + /> + <div className="backup-notification__text">Backup your Secret Recovery code to keep your wallet and funds secure.</div> + <i className="fa fa-info-circle"></i> + </div> + <div className="backup-notification__buttons"> + <Button + type="primary" + className="backup-notification__submit-button" + onClick={this.handleSubmit} + > + { t('backupNow') } + </Button> + </div> + </div> + ) + } +} diff --git a/ui/app/components/app/backup-notification/backup-notification.container.js b/ui/app/components/app/backup-notification/backup-notification.container.js new file mode 100644 index 000000000..6996770bc --- /dev/null +++ b/ui/app/components/app/backup-notification/backup-notification.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import BackupNotification from './backup-notification.component' +import { showSeedPhraseBackupAfterOnboarding } from '../../../store/actions' + +const mapDispatchToProps = dispatch => { + return { + showSeedPhraseBackupAfterOnboarding: () => dispatch(showSeedPhraseBackupAfterOnboarding()), + } +} + +export default compose( + withRouter, + connect(null, mapDispatchToProps) +)(BackupNotification) diff --git a/ui/app/components/app/backup-notification/index.js b/ui/app/components/app/backup-notification/index.js new file mode 100644 index 000000000..a1cbfe75a --- /dev/null +++ b/ui/app/components/app/backup-notification/index.js @@ -0,0 +1 @@ +export { default } from './backup-notification.container' diff --git a/ui/app/components/app/backup-notification/index.scss b/ui/app/components/app/backup-notification/index.scss new file mode 100644 index 000000000..2d76c6ce4 --- /dev/null +++ b/ui/app/components/app/backup-notification/index.scss @@ -0,0 +1,75 @@ +.backup-notification { + background: rgba(36, 41, 46, 0.9); + box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.12); + border-radius: 8px; + height: 116px; + padding: 16px; + margin: 8px; + + display: flex; + flex-flow: column; + justify-content: space-between; + + position: absolute; + right: 0; + bottom: 0; + + &__header { + display: flex; + } + + &__text { + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 12px; + color: #FFFFFF; + margin-left: 10px; + margin-right: 8px; + } + + .fa-info-circle { + color: #6A737D; + } + + &__ignore-button { + border: 2px solid #6A737D; + box-sizing: border-box; + border-radius: 6px; + color: $white; + background-color: rgba(36, 41, 46, 0.9); + height: 34px; + width: 155px; + padding: 0; + + &:hover { + border-color: #6A737D; + background-color: #6A737D; + } + } + + &__submit-button { + border: 2px solid #6A737D; + box-sizing: border-box; + border-radius: 6px; + color: $white; + background-color: rgba(36, 41, 46, 0.9); + height: 34px; + width: 155px; + padding: 0; + + &:hover { + background-color: #3b4046; + } + + &:active { + background-color:#141618; + } + } + + &__buttons { + display: flex; + width: 130px; + align-self: flex-end; + } +}
\ No newline at end of file diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 9b7da8c2e..1f70ba974 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -81,3 +81,5 @@ @import '../ui/toggle-button/index'; @import 'home-notification/index'; + +@import 'backup-notification/index'; diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index 10ed7d155..6fe2a3a9a 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -73,6 +73,7 @@ function reduceApp (state, action) { networksTabSelectedRpcUrl: '', networksTabIsInAddMode: false, loadingMethodData: false, + showingSeedPhraseBackupAfterOnboarding: false, }, state.appState) switch (action.type) { @@ -756,6 +757,16 @@ function reduceApp (state, action) { loadingMethodData: false, }) + case actions.SHOW_SEED_PHRASE_BACKUP_AFTER_ONBOARDING: + return extend(appState, { + showingSeedPhraseBackupAfterOnboarding: true, + }) + + case actions.HIDE_SEED_PHRASE_BACKUP_AFTER_ONBOARDING: + return extend(appState, { + showingSeedPhraseBackupAfterOnboarding: false, + }) + default: return appState diff --git a/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js b/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js index 6124b0fcd..8fc637332 100644 --- a/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js +++ b/ui/app/helpers/higher-order-components/authenticated/authenticated.container.js @@ -3,6 +3,7 @@ import Authenticated from './authenticated.component' const mapStateToProps = state => { const { metamask: { isUnlocked, completedOnboarding } } = state + return { isUnlocked, completedOnboarding, diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index a5cf0f752..48eff96cb 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -17,6 +17,7 @@ export default class ImportWithSeedPhrase extends PureComponent { static propTypes = { history: PropTypes.object, onSubmit: PropTypes.func.isRequired, + setSeedPhraseBackedUp: PropTypes.func, } state = { @@ -126,7 +127,7 @@ export default class ImportWithSeedPhrase extends PureComponent { } const { password, seedPhrase } = this.state - const { history, onSubmit } = this.props + const { history, onSubmit, setSeedPhraseBackedUp } = this.props try { await onSubmit(password, this.parseSeedPhrase(seedPhrase)) @@ -137,7 +138,10 @@ export default class ImportWithSeedPhrase extends PureComponent { name: 'Import Complete', }, }) - history.push(INITIALIZE_END_OF_FLOW_ROUTE) + + setSeedPhraseBackedUp(true).then(() => { + history.push(INITIALIZE_END_OF_FLOW_ROUTE) + }) } catch (error) { this.setState({ seedPhraseError: error.message }) } diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js new file mode 100644 index 000000000..0cfeee1f4 --- /dev/null +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.container.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux' +import ImportWithSeedPhrase from './import-with-seed-phrase.component' +import { + setSeedPhraseBackedUp, +} from '../../../../store/actions' + +const mapDispatchToProps = dispatch => { + return { + setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), + } +} + +export default connect(null, mapDispatchToProps)(ImportWithSeedPhrase) diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js index e5ff1fde5..9d4ad7d0f 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js @@ -1 +1 @@ -export { default } from './import-with-seed-phrase.component' +export { default } from './import-with-seed-phrase.container' diff --git a/ui/app/pages/first-time-flow/first-time-flow.component.js b/ui/app/pages/first-time-flow/first-time-flow.component.js index 0d206bf42..df9631e15 100644 --- a/ui/app/pages/first-time-flow/first-time-flow.component.js +++ b/ui/app/pages/first-time-flow/first-time-flow.component.js @@ -30,6 +30,9 @@ export default class FirstTimeFlow extends PureComponent { isUnlocked: PropTypes.bool, unlockAccount: PropTypes.func, nextRoute: PropTypes.string, + showingSeedPhraseBackupAfterOnboarding: PropTypes.bool, + seedPhraseBackedUp: PropTypes.bool, + verifySeedPhrase: PropTypes.func, } state = { @@ -38,9 +41,16 @@ export default class FirstTimeFlow extends PureComponent { } componentDidMount () { - const { completedOnboarding, history, isInitialized, isUnlocked } = this.props + const { + completedOnboarding, + history, + isInitialized, + isUnlocked, + showingSeedPhraseBackupAfterOnboarding, + seedPhraseBackedUp, + } = this.props - if (completedOnboarding) { + if (completedOnboarding && (!showingSeedPhraseBackupAfterOnboarding || seedPhraseBackedUp)) { history.push(DEFAULT_ROUTE) return } @@ -88,6 +98,7 @@ export default class FirstTimeFlow extends PureComponent { render () { const { seedPhrase, isImportedKeyring } = this.state + const { verifySeedPhrase } = this.props return ( <div className="first-time-flow"> @@ -98,6 +109,7 @@ export default class FirstTimeFlow extends PureComponent { <SeedPhrase { ...props } seedPhrase={seedPhrase} + verifySeedPhrase={verifySeedPhrase} /> )} /> diff --git a/ui/app/pages/first-time-flow/first-time-flow.container.js b/ui/app/pages/first-time-flow/first-time-flow.container.js index 16025a489..76fd12bcd 100644 --- a/ui/app/pages/first-time-flow/first-time-flow.container.js +++ b/ui/app/pages/first-time-flow/first-time-flow.container.js @@ -5,16 +5,19 @@ import { createNewVaultAndGetSeedPhrase, createNewVaultAndRestore, unlockAndGetSeedPhrase, + verifySeedPhrase, } from '../../store/actions' const mapStateToProps = state => { - const { metamask: { completedOnboarding, isInitialized, isUnlocked } } = state + const { metamask: { completedOnboarding, isInitialized, isUnlocked, seedPhraseBackedUp }, appState: { showingSeedPhraseBackupAfterOnboarding } } = state return { completedOnboarding, isInitialized, isUnlocked, nextRoute: getFirstTimeFlowTypeRoute(state), + showingSeedPhraseBackupAfterOnboarding, + seedPhraseBackedUp, } } @@ -25,6 +28,7 @@ const mapDispatchToProps = dispatch => { return dispatch(createNewVaultAndRestore(password, seedPhrase)) }, unlockAccount: password => dispatch(unlockAndGetSeedPhrase(password)), + verifySeedPhrase: () => verifySeedPhrase(), } } diff --git a/ui/app/pages/first-time-flow/index.scss b/ui/app/pages/first-time-flow/index.scss index 6c65cfdae..dec80cb60 100644 --- a/ui/app/pages/first-time-flow/index.scss +++ b/ui/app/pages/first-time-flow/index.scss @@ -26,6 +26,10 @@ .app-header__metafox-logo { margin-bottom: 40px; + + @media screen and (max-width: $break-small) { + margin-bottom: 0px; + } } } diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js index 4cfc38fdf..9256c3d8d 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js @@ -6,6 +6,7 @@ import Button from '../../../../components/ui/button' import { INITIALIZE_END_OF_FLOW_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE, + DEFAULT_ROUTE, } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' import DraggableSeed from './draggable-seed.component' @@ -88,7 +89,7 @@ export default class ConfirmSeedPhrase extends PureComponent { } handleSubmit = async () => { - const { history } = this.props + const { history, setSeedPhraseBackedUp, showingSeedPhraseBackupAfterOnboarding, hideSeedPhraseBackupAfterOnboarding } = this.props if (!this.isValid()) { return @@ -102,7 +103,15 @@ export default class ConfirmSeedPhrase extends PureComponent { name: 'Verify Complete', }, }) - history.push(INITIALIZE_END_OF_FLOW_ROUTE) + + setSeedPhraseBackedUp(true).then(() => { + if (showingSeedPhraseBackupAfterOnboarding) { + hideSeedPhraseBackupAfterOnboarding() + history.push(DEFAULT_ROUTE) + } else { + history.push(INITIALIZE_END_OF_FLOW_ROUTE) + } + }) } catch (error) { console.error(error.message) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js new file mode 100644 index 000000000..ac5a26979 --- /dev/null +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux' +import ConfirmSeedPhrase from './confirm-seed-phrase.component' +import { + setSeedPhraseBackedUp, + hideSeedPhraseBackupAfterOnboarding, +} from '../../../../store/actions' + +const mapStateToProps = state => { + const { appState: { showingSeedPhraseBackupAfterOnboarding } } = state + + return { + showingSeedPhraseBackupAfterOnboarding, + } +} + +const mapDispatchToProps = dispatch => { + return { + setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), + hideSeedPhraseBackupAfterOnboarding: () => dispatch(hideSeedPhraseBackupAfterOnboarding()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(ConfirmSeedPhrase) diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js index c7b511503..beb53b383 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js @@ -1 +1 @@ -export { default } from './confirm-seed-phrase.component' +export { default } from './confirm-seed-phrase.container' diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js index 4a1b191b5..a528f95a2 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js @@ -1 +1 @@ -export { default } from './reveal-seed-phrase.component' +export { default } from './reveal-seed-phrase.container' diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss index 8a47447ed..dfe9868cf 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss @@ -1,4 +1,12 @@ .reveal-seed-phrase { + @media screen and (max-width: 576px) { + display: flex; + flex-direction: column; + width: 96%; + margin-left: 2%; + margin-right: 2%; + } + &__secret { position: relative; display: flex; @@ -54,4 +62,12 @@ button { margin-top: 0xp; } + + &__buttons { + display: flex; + + .first-time-flow__button:last-of-type { + margin-left: 20px; + } + } } diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js index 4e9948a0e..78981bae8 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import LockIcon from '../../../../components/ui/lock-icon' import Button from '../../../../components/ui/button' -import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE } from '../../../../helpers/constants/routes' +import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' export default class RevealSeedPhrase extends PureComponent { @@ -15,6 +15,8 @@ export default class RevealSeedPhrase extends PureComponent { static propTypes = { history: PropTypes.object, seedPhrase: PropTypes.string, + setSeedPhraseBackedUp: PropTypes.func, + setCompletedOnboarding: PropTypes.func, } state = { @@ -45,6 +47,24 @@ export default class RevealSeedPhrase extends PureComponent { history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE) } + handleSkip = event => { + event.preventDefault() + const { history, setSeedPhraseBackedUp, setCompletedOnboarding } = this.props + + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Remind me later', + }, + }) + + Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) + .then(() => { + history.push(DEFAULT_ROUTE) + }) + } + renderSecretWordsContainer () { const { t } = this.context const { seedPhrase } = this.props @@ -129,14 +149,23 @@ export default class RevealSeedPhrase extends PureComponent { </div> </div> </div> - <Button - type="primary" - className="first-time-flow__button" - onClick={this.handleNext} - disabled={!isShowingSeedPhrase} - > - { t('next') } - </Button> + <div className="reveal-seed-phrase__buttons"> + <Button + type="secondary" + className="first-time-flow__button" + onClick={this.handleSkip} + > + { t('remindMeLater') } + </Button> + <Button + type="primary" + className="first-time-flow__button" + onClick={this.handleNext} + disabled={!isShowingSeedPhrase} + > + { t('next') } + </Button> + </div> </div> ) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js new file mode 100644 index 000000000..7ada36afc --- /dev/null +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux' +import RevealSeedPhrase from './reveal-seed-phrase.component' +import { + setCompletedOnboarding, + setSeedPhraseBackedUp, +} from '../../../../store/actions' + +const mapDispatchToProps = dispatch => { + return { + setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), + setCompletedOnboarding: () => dispatch(setCompletedOnboarding()), + } +} + +export default connect(null, mapDispatchToProps)(RevealSeedPhrase) diff --git a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js index f4557115a..79cb27c52 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js @@ -17,18 +17,31 @@ export default class SeedPhrase extends PureComponent { address: PropTypes.string, history: PropTypes.object, seedPhrase: PropTypes.string, + verifySeedPhrase: PropTypes.func, + } + + state = { + verifiedSeedPhrase: '', } componentDidMount () { - const { seedPhrase, history } = this.props + const { seedPhrase, history, verifySeedPhrase } = this.props if (!seedPhrase) { - history.push(DEFAULT_ROUTE) + verifySeedPhrase() + .then(verifiedSeedPhrase => { + if (!verifiedSeedPhrase) { + history.push(DEFAULT_ROUTE) + } else { + this.setState({ verifiedSeedPhrase }) + } + }) } } render () { const { seedPhrase } = this.props + const { verifiedSeedPhrase } = this.state return ( <DragDropContextProvider backend={HTML5Backend}> @@ -41,7 +54,7 @@ export default class SeedPhrase extends PureComponent { render={props => ( <ConfirmSeedPhrase { ...props } - seedPhrase={seedPhrase} + seedPhrase={seedPhrase || verifiedSeedPhrase} /> )} /> @@ -51,7 +64,7 @@ export default class SeedPhrase extends PureComponent { render={props => ( <RevealSeedPhrase { ...props } - seedPhrase={seedPhrase} + seedPhrase={seedPhrase || verifiedSeedPhrase} /> )} /> diff --git a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js index 8339a6f6f..3d5f7f066 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js +++ b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js @@ -131,7 +131,7 @@ describe('ConfirmSeedPhrase Component', () => { assert.deepEqual(root.state().pendingSeedIndices, [2, 0, 1]) }) - it('should submit correctly', () => { + it('should submit correctly', async () => { const originalSeed = ['鼠', '牛', '虎', '兔', '龍', '蛇', '馬', '羊', '猴', '雞', '狗', '豬'] const metricsEventSpy = sinon.spy() const pushSpy = sinon.spy() @@ -139,6 +139,7 @@ describe('ConfirmSeedPhrase Component', () => { { seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬', history: { push: pushSpy }, + setSeedPhraseBackedUp: () => Promise.resolve(), }, { metricsEvent: metricsEventSpy, @@ -157,6 +158,9 @@ describe('ConfirmSeedPhrase Component', () => { root.update() root.find('.first-time-flow__button').simulate('click') + + await (new Promise(resolve => setTimeout(resolve, 100))) + assert.deepEqual(metricsEventSpy.args[0][0], { eventOpts: { category: 'Onboarding', diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index 1fd12a359..e59106537 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -6,6 +6,7 @@ import HomeNotification from '../../components/app/home-notification' import WalletView from '../../components/app/wallet-view' import TransactionView from '../../components/app/transaction-view' import ProviderApproval from '../provider-approval' +import BackupNotification from '../../components/app/backup-notification' import { RESTORE_VAULT_ROUTE, @@ -38,6 +39,7 @@ export default class Home extends PureComponent { unsetMigratedPrivacyMode: PropTypes.func, viewingUnconnectedDapp: PropTypes.bool.isRequired, forceApproveProviderRequestByOrigin: PropTypes.func, + shouldShowSeedPhraseReminder: PropTypes.bool, } componentWillMount () { @@ -74,6 +76,7 @@ export default class Home extends PureComponent { unsetMigratedPrivacyMode, viewingUnconnectedDapp, forceApproveProviderRequestByOrigin, + shouldShowSeedPhraseReminder, } = this.props if (forgottenPassword) { @@ -85,7 +88,6 @@ export default class Home extends PureComponent { <ProviderApproval providerRequest={providerRequests[0]} /> ) } - return ( <div className="main-container"> <div className="account-and-transaction-details"> @@ -124,6 +126,11 @@ export default class Home extends PureComponent { ) : null } + { + shouldShowSeedPhraseReminder + ? (<BackupNotification />) + : null + } </TransactionView> ) : null } diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index 81a3946c5..4195fbe79 100644 --- a/ui/app/pages/home/home.container.js +++ b/ui/app/pages/home/home.container.js @@ -3,6 +3,7 @@ import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' +import { getCurrentEthBalance } from '../../selectors/selectors' import { forceApproveProviderRequestByOrigin, unsetMigratedPrivacyMode, @@ -21,7 +22,9 @@ const mapStateToProps = state => { featureFlags: { privacyMode, } = {}, + seedPhraseBackedUp, } = metamask + const accountBalance = getCurrentEthBalance(state) const { forgottenPassword } = appState const isUnconnected = Boolean(activeTab && privacyMode && !approvedOrigins[activeTab.origin]) @@ -36,6 +39,7 @@ const mapStateToProps = state => { showPrivacyModeNotification: migratedPrivacyMode, activeTab, viewingUnconnectedDapp: isUnconnected && isPopup, + shouldShowSeedPhraseReminder: parseInt(accountBalance, 16) > 0 && !seedPhraseBackedUp, } } diff --git a/ui/app/pages/settings/networks-tab/networks-tab.component.js b/ui/app/pages/settings/networks-tab/networks-tab.component.js index a44872843..40e1a902f 100644 --- a/ui/app/pages/settings/networks-tab/networks-tab.component.js +++ b/ui/app/pages/settings/networks-tab/networks-tab.component.js @@ -126,6 +126,7 @@ export default class NetworksTab extends PureComponent { renderNetworksList () { const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props + return ( <div className={classnames('networks-tab__networks-list', { diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 726deb59d..9d8c7c6b2 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -375,6 +375,15 @@ var actions = { LOADING_TOKEN_PARAMS_STARTED: 'LOADING_TOKEN_PARAMS_STARTED', loadingTokenParamsFinished, LOADING_TOKEN_PARAMS_FINISHED: 'LOADING_TOKEN_PARAMS_FINISHED', + + setSeedPhraseBackedUp, + showSeedPhraseBackupAfterOnboarding, + SHOW_SEED_PHRASE_BACKUP_AFTER_ONBOARDING: 'SHOW_SEED_PHRASE_BACKUP_AFTER_ONBOARDING', + hideSeedPhraseBackupAfterOnboarding, + HIDE_SEED_PHRASE_BACKUP_AFTER_ONBOARDING: 'HIDE_SEED_PHRASE_BACKUP_AFTER_ONBOARDING', + + verifySeedPhrase, + SET_SEED_PHRASE_BACKED_UP_TO_TRUE: 'SET_SEED_PHRASE_BACKED_UP_TO_TRUE', } module.exports = actions @@ -2772,3 +2781,30 @@ function unsetMigratedPrivacyMode () { background.unsetMigratedPrivacyMode() } } + +function setSeedPhraseBackedUp (seedPhraseBackupState) { + return (dispatch) => { + log.debug(`background.setSeedPhraseBackedUp`) + return new Promise((resolve, reject) => { + background.setSeedPhraseBackedUp(seedPhraseBackupState, (err) => { + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + return forceUpdateMetamaskState(dispatch).then(() => resolve()) + }) + }) + } +} + +function showSeedPhraseBackupAfterOnboarding () { + return { + type: actions.SHOW_SEED_PHRASE_BACKUP_AFTER_ONBOARDING, + } +} + +function hideSeedPhraseBackupAfterOnboarding () { + return { + type: actions.HIDE_SEED_PHRASE_BACKUP_AFTER_ONBOARDING, + } +} |