Merge pull request #4648 from MetaMask/update-import-from-seed-screen

Update import from seed screen on new ui.
feature/default_network_editable
Dan J Miller 6 years ago committed by GitHub
commit 6595f84d07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      app/_locales/en/messages.json
  2. 9
      test/e2e/beta/metamask-beta-ui.spec.js
  3. 2
      ui/app/app.js
  4. 307
      ui/app/components/pages/keychains/restore-vault.js
  5. 9
      ui/app/css/itcss/components/newui-sections.scss

@ -664,6 +664,9 @@
"restoreVault": { "restoreVault": {
"message": "Restore Vault" "message": "Restore Vault"
}, },
"restoreAccountWithSeed": {
"message": "Restore your Account with Seed Phrase"
},
"required": { "required": {
"message": "Required" "message": "Required"
}, },
@ -673,6 +676,9 @@
"walletSeed": { "walletSeed": {
"message": "Wallet Seed" "message": "Wallet Seed"
}, },
"restore": {
"message": "Restore"
},
"revealSeedWords": { "revealSeedWords": {
"message": "Reveal Seed Words" "message": "Reveal Seed Words"
}, },
@ -777,6 +783,9 @@
"sendTokens": { "sendTokens": {
"message": "Send Tokens" "message": "Send Tokens"
}, },
"separateEachWord": {
"message": "Separate each word with a single space"
},
"onlySendToEtherAddress": { "onlySendToEtherAddress": {
"message": "Only send ETH to an Ethereum address." "message": "Only send ETH to an Ethereum address."
}, },

@ -355,9 +355,12 @@ describe('MetaMask', function () {
await seedTextArea.sendKeys(testSeedPhrase) await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs) await delay(regularDelayMs)
await driver.findElement(By.id('password-box')).sendKeys('correct horse battery staple') const passwordInputs = await driver.findElements(By.css('input'))
await driver.findElement(By.id('password-box-confirm')).sendKeys('correct horse battery staple') await delay(regularDelayMs)
await driver.findElement(By.css('button:nth-child(2)')).click()
passwordInputs[0].sendKeys('correct horse battery staple')
passwordInputs[1].sendKeys('correct horse battery staple')
await driver.findElement(By.css('.first-time-flow__button')).click()
await delay(regularDelayMs) await delay(regularDelayMs)
}) })

@ -23,7 +23,7 @@ const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized') const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings') const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unlock-page') const UnlockPage = require('./components/pages/unlock-page')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault') const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed') const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token') const AddTokenPage = require('./components/pages/add-token')
const ConfirmAddTokenPage = require('./components/pages/confirm-add-token') const ConfirmAddTokenPage = require('./components/pages/confirm-add-token')

@ -1,178 +1,189 @@
const { withRouter } = require('react-router-dom') import React, { Component } from 'react'
const PropTypes = require('prop-types') import PropTypes from 'prop-types'
const { compose } = require('recompose') import {connect} from 'react-redux'
const PersistentForm = require('../../../../lib/persistent-form') import {
const connect = require('../../../metamask-connect') createNewVaultAndRestore,
const h = require('react-hyperscript') unMarkPasswordForgotten,
const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions') } from '../../../actions'
const { DEFAULT_ROUTE } = require('../../../routes') import { DEFAULT_ROUTE } from '../../../routes'
const log = require('loglevel') import TextField from '../../text-field'
class RestoreVaultPage extends PersistentForm { class RestoreVaultPage extends Component {
constructor (props) { static contextTypes = {
super(props) t: PropTypes.func,
}
this.state = {
error: null, static propTypes = {
} warning: PropTypes.string,
} createNewVaultAndRestore: PropTypes.func.isRequired,
leaveImportSeedScreenState: PropTypes.func,
history: PropTypes.object,
isLoading: PropTypes.bool,
};
createOnEnter (event) { state = {
if (event.key === 'Enter') { seedPhrase: '',
this.createNewVaultAndRestore() password: '',
} confirmPassword: '',
seedPhraseError: null,
passwordError: null,
confirmPasswordError: null,
} }
cancel () { parseSeedPhrase = (seedPhrase) => {
this.props.unMarkPasswordForgotten() return seedPhrase
.then(this.props.history.push(DEFAULT_ROUTE)) .match(/\w+/g)
.join(' ')
} }
createNewVaultAndRestore () { handleSeedPhraseChange (seedPhrase) {
this.setState({ error: null }) let seedPhraseError = null
// check password if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
var passwordBox = document.getElementById('password-box') seedPhraseError = this.context.t('seedPhraseReq')
var password = passwordBox.value
var passwordConfirmBox = document.getElementById('password-box-confirm')
var passwordConfirm = passwordConfirmBox.value
if (password.length < 8) {
this.setState({ error: 'Password not long enough' })
return
} }
if (password !== passwordConfirm) { this.setState({ seedPhrase, seedPhraseError })
this.setState({ error: 'Passwords don\'t match' })
return
} }
// check seed handlePasswordChange (password) {
var seedBox = document.querySelector('textarea.twelve-word-phrase') const { confirmPassword } = this.state
var seed = seedBox.value.trim() let confirmPasswordError = null
if (seed.split(' ').length !== 12) { let passwordError = null
this.setState({ error: 'Seed phrases are 12 words long' })
return
}
// submit if (password && password.length < 8) {
this.props.createNewVaultAndRestore(password, seed) passwordError = this.context.t('passwordNotLongEnough')
.then(() => this.props.history.push(DEFAULT_ROUTE))
.catch(({ message }) => {
this.setState({ error: message })
log.error(message)
})
} }
render () { if (confirmPassword && password !== confirmPassword) {
const { error } = this.state confirmPasswordError = this.context.t('passwordsDontMatch')
this.persistentFormParentId = 'restore-vault-form' }
return ( this.setState({ password, passwordError, confirmPasswordError })
h('.initialize-screen.flex-column.flex-center.flex-grow', [ }
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
this.props.t('restoreVault'),
]),
// wallet seed entry
h('h3', 'Wallet Seed'),
h('textarea.twelve-word-phrase.letter-spacey', {
dataset: {
persistentFormId: 'wallet-seed',
},
placeholder: this.props.t('secretPhrase'),
}),
// password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: this.props.t('newPassword8Chars'),
dataset: {
persistentFormId: 'password',
},
style: {
width: 260,
marginTop: 12,
},
}),
// confirm password
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: this.props.t('confirmPassword'),
onKeyPress: this.createOnEnter.bind(this),
dataset: {
persistentFormId: 'password-confirmation',
},
style: {
width: 260,
marginTop: 16,
},
}),
error && ( handleConfirmPasswordChange (confirmPassword) {
h('span.error.in-progress-notification', error) const { password } = this.state
), let confirmPasswordError = null
// submit if (password !== confirmPassword) {
h('.flex-row.flex-space-between', { confirmPasswordError = this.context.t('passwordsDontMatch')
style: { }
marginTop: 30,
width: '50%',
},
}, [
// cancel this.setState({ confirmPassword, confirmPasswordError })
h('button.primary', { }
onClick: () => this.cancel(),
}, this.props.t('cancel')),
// submit onClick = () => {
h('button.primary', { const { password, seedPhrase } = this.state
onClick: this.createNewVaultAndRestore.bind(this), const {
}, this.props.t('ok')), createNewVaultAndRestore,
leaveImportSeedScreenState,
history,
} = this.props
]), leaveImportSeedScreenState()
]) createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
) .then(() => history.push(DEFAULT_ROUTE))
}
} }
RestoreVaultPage.propTypes = { hasError () {
history: PropTypes.object, const { passwordError, confirmPasswordError, seedPhraseError } = this.state
return passwordError || confirmPasswordError || seedPhraseError
} }
const mapStateToProps = state => { render () {
const { appState: { warning, forgottenPassword } } = state const {
seedPhrase,
password,
confirmPassword,
seedPhraseError,
passwordError,
confirmPasswordError,
} = this.state
const { t } = this.context
const { isLoading } = this.props
const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
return { return (
warning, <div className="first-view-main-wrapper">
forgottenPassword, <div className="first-view-main">
<div className="import-account">
<a
className="import-account__back-button"
onClick={e => {
e.preventDefault()
this.props.history.goBack()
}}
href="#"
>
{`< Back`}
</a>
<div className="import-account__title">
{ this.context.t('restoreAccountWithSeed') }
</div>
<div className="import-account__selector-label">
{ this.context.t('secretPhrase') }
</div>
<div className="import-account__input-wrapper">
<label className="import-account__input-label">Wallet Seed</label>
<textarea
className="import-account__secret-phrase"
onChange={e => this.handleSeedPhraseChange(e.target.value)}
value={this.state.seedPhrase}
placeholder={this.context.t('separateEachWord')}
/>
</div>
<span className="error">
{ seedPhraseError }
</span>
<TextField
id="password"
label={t('newPassword')}
type="password"
className="first-time-flow__input"
value={this.state.password}
onChange={event => this.handlePasswordChange(event.target.value)}
error={passwordError}
autoComplete="new-password"
margin="normal"
largeLabel
/>
<TextField
id="confirm-password"
label={t('confirmPassword')}
type="password"
className="first-time-flow__input"
value={this.state.confirmPassword}
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
error={confirmPasswordError}
autoComplete="confirm-password"
margin="normal"
largeLabel
/>
<button
className="first-time-flow__button"
onClick={() => !disabled && this.onClick()}
disabled={disabled}
>
{this.context.t('restore')}
</button>
</div>
</div>
</div>
)
} }
} }
const mapDispatchToProps = dispatch => { RestoreVaultPage.contextTypes = {
return { t: PropTypes.func,
createNewVaultAndRestore: (password, seed) => {
return dispatch(createNewVaultAndRestore(password, seed))
},
unMarkPasswordForgotten: () => dispatch(unMarkPasswordForgotten()),
}
} }
module.exports = compose( export default connect(
withRouter, ({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
connect(mapStateToProps, mapDispatchToProps) dispatch => ({
leaveImportSeedScreenState: () => {
dispatch(unMarkPasswordForgotten())
},
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
})
)(RestoreVaultPage) )(RestoreVaultPage)

@ -332,3 +332,12 @@ $wallet-view-bg: $alabaster;
align-items: center; align-items: center;
flex: 1 0 auto; flex: 1 0 auto;
} }
.first-view-main-wrapper {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
padding: 0 10px;
background: white;
}

Loading…
Cancel
Save