diff options
Diffstat (limited to 'ui/app/pages')
18 files changed, 183 insertions, 25 deletions
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', { |