1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Merge pull request #4059 from MetaMask/i-4033-seed-words

Use new design for Reveal Seed screen. Persist seed words only in the first time flow.
This commit is contained in:
kumavis 2018-04-30 12:04:53 -06:00 committed by GitHub
commit 5f3f8c85fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 477 additions and 427 deletions

View File

@ -98,6 +98,9 @@
"clickCopy": { "clickCopy": {
"message": "Click to Copy" "message": "Click to Copy"
}, },
"close": {
"message": "Close"
},
"confirm": { "confirm": {
"message": "Confirm" "message": "Confirm"
}, },
@ -259,6 +262,9 @@
"enterPasswordConfirm": { "enterPasswordConfirm": {
"message": "Enter your password to confirm" "message": "Enter your password to confirm"
}, },
"enterPasswordContinue": {
"message": "Enter password to continue"
},
"passwordNotLongEnough": { "passwordNotLongEnough": {
"message": "Password not long enough" "message": "Password not long enough"
}, },
@ -331,6 +337,9 @@
"gasPriceRequired": { "gasPriceRequired": {
"message": "Gas Price Required" "message": "Gas Price Required"
}, },
"generatingTransaction": {
"message": "Generating transaction"
},
"getEther": { "getEther": {
"message": "Get Ether" "message": "Get Ether"
}, },
@ -476,6 +485,9 @@
"metamaskDescription": { "metamaskDescription": {
"message": "MetaMask is a secure identity vault for Ethereum." "message": "MetaMask is a secure identity vault for Ethereum."
}, },
"metamaskSeedWords": {
"message": "MetaMask Seed Words"
},
"min": { "min": {
"message": "Minimum" "message": "Minimum"
}, },
@ -549,6 +561,9 @@
"message": "or", "message": "or",
"description": "choice between creating or importing a new account" "description": "choice between creating or importing a new account"
}, },
"password": {
"message": "Password"
},
"passwordCorrect": { "passwordCorrect": {
"message": "Please make sure your password is correct." "message": "Please make sure your password is correct."
}, },
@ -634,8 +649,17 @@
"revealSeedWords": { "revealSeedWords": {
"message": "Reveal Seed Words" "message": "Reveal Seed Words"
}, },
"revealSeedWordsTitle": {
"message": "Seed Phrase"
},
"revealSeedWordsDescription": {
"message": "If you ever change browsers or move computers, you will need this seed phrase to access your accounts. Save them somewhere safe and secret."
},
"revealSeedWordsWarningTitle": {
"message": "DO NOT share this phrase with anyone!"
},
"revealSeedWordsWarning": { "revealSeedWordsWarning": {
"message": "Do not recover your seed words in a public place! These words can be used to steal all your accounts." "message": "These words can be used to steal all your accounts."
}, },
"revert": { "revert": {
"message": "Revert" "message": "Revert"
@ -677,6 +701,9 @@
"reprice_subtitle": { "reprice_subtitle": {
"message": "Increase your gas price to attempt to overwrite and speed up your transaction" "message": "Increase your gas price to attempt to overwrite and speed up your transaction"
}, },
"saveAsCsvFile": {
"message": "Save as CSV File"
},
"saveAsFile": { "saveAsFile": {
"message": "Save as File", "message": "Save as File",
"description": "Account export process" "description": "Account export process"
@ -909,7 +936,7 @@
"youSign": { "youSign": {
"message": "You are signing" "message": "You are signing"
}, },
"generatingTransaction": { "yourPrivateSeedPhrase": {
"message": "Generating transaction" "message": "Your private seed phrase"
} }
} }

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="17px" viewBox="0 0 18 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>374E58A5-C29E-4921-83E7-889FA06D6408</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Reveal-Seedphrase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Seed-phrase-2" transform="translate(-39.000000, -379.000000)">
<g id="Group-2">
<g id="Group-8" transform="translate(16.000000, 248.000000)">
<g id="Group-6" transform="translate(23.336478, 120.000000)">
<g id="Group-5" transform="translate(0.408805, 11.000000)">
<g id="copy-to-clipboard">
<rect id="Rectangle-18" stroke="#3098DC" stroke-width="2" x="1" y="1" width="12.0220126" height="12"></rect>
<rect id="Rectangle-18-Copy-2" fill="#FFFFFF" x="2.1572327" y="2" width="14.0220126" height="14"></rect>
<rect id="Rectangle-18-Copy" stroke="#3098DC" stroke-width="2" x="4.23584906" y="4" width="12.0220126" height="12"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,15 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg width="20px" height="18px" viewBox="0 0 20 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!-- Generator: sketchtool 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <title>50559280-0739-419A-8E87-3CDD16A6996A</title>
width="24.088px" height="24px" viewBox="138.01 0 24.088 24" enable-background="new 138.01 0 24.088 24" xml:space="preserve" fill="#F7861C"> <desc>Created with sketchtool.</desc>
<g> <defs></defs>
<polygon fill="#F7861C" points="157.551,17.075 156.55,17.075 156.55,19.149 142.569,19.149 142.569,17.075 141.568,17.075 <g id="Reveal-Seedphrase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
141.568,20.145 141.955,20.145 141.955,20.15 157.006,20.15 157.006,20.145 157.551,20.145 "/> <g id="Seed-phrase-2" transform="translate(-212.000000, -379.000000)" stroke="#259DE5" stroke-width="2">
<polygon fill="#F7861C" points="152.555,10.275 152.555,11.26 152.555,11.268 151.562,11.268 151.562,12.252 150.565,12.252 <g id="Group-2">
150.565,4.171 149.564,4.171 149.564,12.236 148.564,12.236 148.564,11.252 147.564,11.252 147.564,11.236 147.564,11.221 <g id="Group-8" transform="translate(16.000000, 248.000000)">
147.564,10.236 146.563,10.236 146.563,11.221 146.563,11.236 146.563,12.221 147.563,12.221 147.563,12.236 147.563,12.252 <g id="Group-6" transform="translate(23.336478, 120.000000)">
147.563,13.236 148.563,13.236 148.563,14.221 149.564,14.221 149.564,15.725 150.565,15.725 150.565,14.236 151.563,14.236 <g id="Group-3" transform="translate(174.000000, 11.000000)">
151.563,13.252 152.563,13.252 152.563,12.268 152.563,12.26 153.556,12.26 153.556,11.275 153.556,11.26 153.556,10.275 "/> <g id="Group-4">
</g> <g id="download">
<polyline id="Path-5" points="0 11 0 17 17 17 17 11"></polyline>
<path d="M8.5,0 L8.5,11" id="Path-6"></path>
<polyline id="Path-7" points="3.1875 7 8.5 11 13.8125 7"></polyline>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

22
app/images/warning.svg Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="33px" height="32px" viewBox="0 0 33 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Group 7</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Reveal-Seedphrase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Seed-phrase-2" transform="translate(-29.000000, -155.000000)">
<g id="Group-2" transform="translate(0.000000, 132.000000)">
<g id="Group" transform="translate(28.000000, 19.000000)">
<g id="Group-19-Copy-2" transform="translate(0.000000, 3.000000)">
<g id="Group-7">
<path d="M20.1321134,3.85444772 L32.5721829,26.6020033 C33.367162,28.0556794 32.8331826,29.8785746 31.3795065,30.6735537 C30.9381289,30.9149321 30.4431378,31.0414403 29.9400695,31.0414403 L5.05993054,31.0414403 C3.40307629,31.0414403 2.05993054,29.6982946 2.05993054,28.0414403 C2.05993054,27.538372 2.18643873,27.0433809 2.42781712,26.6020033 L14.8678866,3.85444772 C15.6628657,2.40077162 17.4857609,1.86679221 18.939437,2.66177133 C19.442875,2.93708896 19.8567958,3.35100977 20.1321134,3.85444772 Z" id="Triangle-2-Copy" stroke="#FF001F" stroke-width="2"></path>
<rect id="Rectangle-5" fill="#FF001F" x="16" y="9" width="3" height="13"></rect>
<rect id="Rectangle-5-Copy" fill="#FF001F" x="16" y="24" width="3" height="3"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -101,6 +101,7 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data) this.setData(data)
} }
ConfigManager.prototype.getShouldShowSeedWords = function () { ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.getData() var data = this.getData()
return data.showSeedWords return data.showSeedWords
@ -116,27 +117,6 @@ ConfigManager.prototype.getSeedWords = function () {
var data = this.getData() var data = this.getData()
return data.seedWords return data.seedWords
} }
/**
* Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal
* the seed words and not during the first time flow.
* @param {boolean} reveal - Value to set the isRevealingSeedWords flag.
*/
ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) {
const data = this.getData()
data.isRevealingSeedWords = reveal
this.setData(data)
}
/**
* Returns the isRevealingSeedWords flag.
* @returns {boolean|undefined}
*/
ConfigManager.prototype.getIsRevealingSeedWords = function () {
const data = this.getData()
return data.isRevealingSeedWords
}
ConfigManager.prototype.setRpcTarget = function (rpcUrl) { ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
var config = this.getConfig() var config = this.getConfig()
config.provider = { config.provider = {

View File

@ -309,7 +309,6 @@ module.exports = class MetamaskController extends EventEmitter {
lostAccounts: this.configManager.getLostAccounts(), lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(), seedWords: this.configManager.getSeedWords(),
forgottenPassword: this.configManager.getPasswordForgotten(), forgottenPassword: this.configManager.getPasswordForgotten(),
isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()),
}, },
} }
} }
@ -351,7 +350,6 @@ module.exports = class MetamaskController extends EventEmitter {
clearSeedWordCache: this.clearSeedWordCache.bind(this), clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this), resetAccount: nodeify(this.resetAccount, this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager),
// vault management // vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController), submitPassword: nodeify(keyringController.submitPassword, keyringController),

View File

@ -247,7 +247,7 @@ gulp.task('dev:scss', createScssBuildTask({
src: 'ui/app/css/index.scss', src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output', dest: 'ui/app/css/output',
devMode: true, devMode: true,
pattern: 'ui/app/css/**/*.scss', pattern: 'ui/app/**/*.scss',
})) }))
function createScssBuildTask({ src, dest, devMode, pattern }) { function createScssBuildTask({ src, dest, devMode, pattern }) {

View File

@ -8,7 +8,6 @@ import Identicon from '../../../../ui/app/components/identicon'
import Breadcrumbs from './breadcrumbs' import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen' import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes' import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
import { confirmSeedWords } from '../../../../ui/app/actions'
const LockIcon = props => ( const LockIcon = props => (
<svg <svg
@ -45,8 +44,6 @@ class BackupPhraseScreen extends Component {
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
seedWords: PropTypes.string, seedWords: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
isRevealingSeedWords: PropTypes.bool,
clearSeedWords: PropTypes.func,
}; };
static defaultProps = { static defaultProps = {
@ -61,14 +58,6 @@ class BackupPhraseScreen extends Component {
} }
componentWillMount () { componentWillMount () {
this.checkSeedWords()
}
componentDidUpdate () {
this.checkSeedWords()
}
checkSeedWords () {
const { seedWords, history } = this.props const { seedWords, history } = this.props
if (!seedWords) { if (!seedWords) {
@ -103,29 +92,9 @@ class BackupPhraseScreen extends Component {
) )
} }
renderSubmitButton () {
const { isRevealingSeedWords, clearSeedWords, history } = this.props
const { isShowingSecret } = this.state
return isRevealingSeedWords
? <button
className="first-time-flow__button"
onClick={() => clearSeedWords().then(() => history.push(DEFAULT_ROUTE))}
disabled={!isShowingSecret}
>
Done
</button>
: <button
className="first-time-flow__button"
onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
disabled={!isShowingSecret}
>
Next
</button>
}
renderSecretScreen () { renderSecretScreen () {
const { isRevealingSeedWords } = this.props const { isShowingSecret } = this.state
const { history } = this.props
return ( return (
<div className="backup-phrase__content-wrapper"> <div className="backup-phrase__content-wrapper">
@ -152,8 +121,14 @@ class BackupPhraseScreen extends Component {
</div> </div>
</div> </div>
<div className="backup-phrase__next-button"> <div className="backup-phrase__next-button">
{ this.renderSubmitButton() } <button
{ !isRevealingSeedWords && <Breadcrumbs total={3} currentIndex={1} />} className="first-time-flow__button"
onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
disabled={!isShowingSecret}
>
Next
</button>
<Breadcrumbs total={3} currentIndex={1} />
</div> </div>
</div> </div>
) )
@ -175,25 +150,13 @@ class BackupPhraseScreen extends Component {
} }
} }
const mapStateToProps = ({ metamask, appState }) => {
const { selectedAddress, seedWords, isRevealingSeedWords } = metamask
const { isLoading } = appState
return {
seedWords,
isRevealingSeedWords,
isLoading,
address: selectedAddress,
}
}
const mapDispatchToProps = dispatch => {
return {
clearSeedWords: () => dispatch(confirmSeedWords()),
}
}
export default compose( export default compose(
withRouter, withRouter,
connect(mapStateToProps, mapDispatchToProps), connect(
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
seedWords,
isLoading,
address: selectedAddress,
})
)
)(BackupPhraseScreen) )(BackupPhraseScreen)

View File

@ -83,7 +83,7 @@ var actions = {
REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION', REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION',
revealSeedConfirmation: revealSeedConfirmation, revealSeedConfirmation: revealSeedConfirmation,
requestRevealSeed: requestRevealSeed, requestRevealSeed: requestRevealSeed,
requestRevealSeedWords,
// unlock screen // unlock screen
UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS', UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS',
UNLOCK_FAILED: 'UNLOCK_FAILED', UNLOCK_FAILED: 'UNLOCK_FAILED',
@ -345,11 +345,13 @@ function transitionBackward () {
} }
} }
function clearSeedWordCache () { function confirmSeedWords () {
log.debug(`background.clearSeedWordCache`)
return dispatch => { return dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
background.clearSeedWordCache((err, account) => { background.clearSeedWordCache((err, account) => {
dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
return reject(err) return reject(err)
@ -363,22 +365,6 @@ function clearSeedWordCache () {
} }
} }
function confirmSeedWords () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
const account = await dispatch(clearSeedWordCache())
return dispatch(setIsRevealingSeedWords(false))
.then(() => {
dispatch(actions.hideLoadingIndication())
return account
})
.catch(() => {
dispatch(actions.hideLoadingIndication())
return account
})
}
}
function createNewVaultAndRestore (password, seed) { function createNewVaultAndRestore (password, seed) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
@ -441,6 +427,30 @@ function revealSeedConfirmation () {
} }
} }
function verifyPassword (password) {
return new Promise((resolve, reject) => {
background.submitPassword(password, error => {
if (error) {
return reject(error)
}
resolve(true)
})
})
}
function verifySeedPhrase () {
return new Promise((resolve, reject) => {
background.verifySeedPhrase((error, seedWords) => {
if (error) {
return reject(error)
}
resolve(seedWords)
})
})
}
function requestRevealSeed (password) { function requestRevealSeed (password) {
return dispatch => { return dispatch => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
@ -460,13 +470,29 @@ function requestRevealSeed (password) {
} }
dispatch(actions.showNewVaultSeed(result)) dispatch(actions.showNewVaultSeed(result))
dispatch(actions.hideLoadingIndication())
resolve() resolve()
}) })
}) })
}) })
.then(() => dispatch(setIsRevealingSeedWords(true))) }
.then(() => dispatch(actions.hideLoadingIndication())) }
.catch(() => dispatch(actions.hideLoadingIndication()))
function requestRevealSeedWords (password) {
return async dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`background.submitPassword`)
try {
await verifyPassword(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)
}
} }
} }
@ -1923,11 +1949,3 @@ function updateNetworkEndpointType (networkEndpointType) {
value: networkEndpointType, value: networkEndpointType,
} }
} }
function setIsRevealingSeedWords (reveal) {
return dispatch => {
log.debug(`background.setIsRevealingSeedWords`)
background.setIsRevealingSeedWords(reveal)
return forceUpdateMetamaskState(dispatch)
}
}

View File

@ -24,7 +24,7 @@ const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings') const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unlock') const UnlockPage = require('./components/pages/unlock')
const RestoreVaultPage = require('./components/pages/keychains/restore-vault') const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token') const AddTokenPage = require('./components/pages/add-token')
const CreateAccountPage = require('./components/pages/create-account') const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice') const NoticeScreen = require('./components/pages/notice')
@ -56,20 +56,11 @@ const {
class App extends Component { class App extends Component {
componentWillMount () { componentWillMount () {
const { const { currentCurrency, setCurrentCurrencyToUSD } = this.props
currentCurrency,
setCurrentCurrencyToUSD,
isRevealingSeedWords,
clearSeedWords,
} = this.props
if (!currentCurrency) { if (!currentCurrency) {
setCurrentCurrencyToUSD() setCurrentCurrencyToUSD()
} }
if (isRevealingSeedWords) {
clearSeedWords()
}
} }
renderRoutes () { renderRoutes () {
@ -402,8 +393,6 @@ App.propTypes = {
isMouseUser: PropTypes.bool, isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func, setMouseUserState: PropTypes.func,
t: PropTypes.func, t: PropTypes.func,
isRevealingSeedWords: PropTypes.bool,
clearSeedWords: PropTypes.func,
} }
function mapStateToProps (state) { function mapStateToProps (state) {
@ -484,7 +473,6 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
clearSeedWords: () => dispatch(actions.confirmSeedWords()),
} }
} }

View File

@ -0,0 +1,45 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const copyToClipboard = require('copy-to-clipboard')
const { exportAsFile } = require('../../util')
class ExportTextContainer extends Component {
render () {
const { text = '', filename = '' } = this.props
const { t } = this.context
return (
h('.export-text-container', [
h('.export-text-container__text-container', [
h('.export-text-container__text', text),
]),
h('.export-text-container__buttons-container', [
h('.export-text-container__button.export-text-container__button--copy', {
onClick: () => copyToClipboard(text),
}, [
h('img', { src: 'images/copy-to-clipboard.svg' }),
h('.export-text-container__button-text', t('copyToClipboard')),
]),
h('.export-text-container__button', {
onClick: () => exportAsFile(filename, text),
}, [
h('img', { src: 'images/download.svg' }),
h('.export-text-container__button-text', t('saveAsCsvFile')),
]),
]),
])
)
}
}
ExportTextContainer.propTypes = {
text: PropTypes.string,
filename: PropTypes.string,
}
ExportTextContainer.contextTypes = {
t: PropTypes.func,
}
module.exports = ExportTextContainer

View File

@ -0,0 +1,52 @@
.export-text-container {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
border: 1px solid $alto;
border-radius: 4px;
font-weight: 400;
&__text-container {
width: 100%;
display: flex;
justify-content: center;
padding: 20px;
border-radius: 4px;
background: $alabaster;
}
&__text {
resize: none;
border: none;
background: $alabaster;
font-size: 20px;
text-align: center;
}
&__buttons-container {
display: flex;
flex-direction: row;
border-top: 1px solid $alto;
width: 100%;
}
&__button {
padding: 10px;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
cursor: pointer;
color: $curious-blue;
&--copy {
border-right: 1px solid $alto;
}
}
&__button-text {
padding-left: 10px;
}
}

View File

@ -0,0 +1,2 @@
const ExportTextContainer = require('./export-text-container.component')
module.exports = ExportTextContainer

View File

@ -21,7 +21,7 @@ const QrView = require('../../components/qr-code')
// Routes // Routes
const { const {
REVEAL_SEED_ROUTE, INITIALIZE_BACKUP_PHRASE_ROUTE,
RESTORE_VAULT_ROUTE, RESTORE_VAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE, CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE, NOTICE_ROUTE,
@ -69,7 +69,7 @@ class Home extends Component {
log.debug('rendering seed words') log.debug('rendering seed words')
return h(Redirect, { return h(Redirect, {
to: { to: {
pathname: REVEAL_SEED_ROUTE, pathname: INITIALIZE_BACKUP_PHRASE_ROUTE,
}, },
}) })
} }

View File

@ -2,11 +2,27 @@ const { Component } = require('react')
const { connect } = require('react-redux') const { connect } = require('react-redux')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const { exportAsFile } = require('../../../util') const classnames = require('classnames')
const { requestRevealSeed, confirmSeedWords } = require('../../../actions')
const { requestRevealSeedWords } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes') const { DEFAULT_ROUTE } = require('../../../routes')
const ExportTextContainer = require('../../export-text-container')
const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN'
const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN'
class RevealSeedPage extends Component { class RevealSeedPage extends Component {
constructor (props) {
super(props)
this.state = {
screen: PASSWORD_PROMPT_SCREEN,
password: '',
seedWords: null,
error: null,
}
}
componentDidMount () { componentDidMount () {
const passwordBox = document.getElementById('password-box') const passwordBox = document.getElementById('password-box')
if (passwordBox) { if (passwordBox) {
@ -14,182 +30,135 @@ class RevealSeedPage extends Component {
} }
} }
checkConfirmation (event) { handleSubmit (event) {
if (event.key === 'Enter') {
event.preventDefault() event.preventDefault()
this.revealSeedWords() this.setState({ seedWords: null, error: null })
} this.props.requestRevealSeedWords(this.state.password)
.then(seedWords => this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }))
.catch(error => this.setState({ error: error.message }))
} }
revealSeedWords () { renderWarning () {
const password = document.getElementById('password-box').value
this.props.requestRevealSeed(password)
}
renderSeed () {
const { seedWords, confirmSeedWords, history } = this.props
return ( return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [ h('.page-container__warning-container', [
h('img.page-container__warning-icon', {
h('h3.flex-center.text-transform-uppercase', { src: 'images/warning.svg',
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginTop: 36,
marginBottom: 8,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Vault Created',
]),
h('div', {
style: {
fontSize: '1em',
marginTop: '10px',
textAlign: 'center',
},
}, [
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'),
]),
h('textarea.twelve-word-phrase', {
readOnly: true,
value: seedWords,
}), }),
h('.page-container__warning-message', [
h('button.primary', { h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]),
onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)), h('div', [this.context.t('revealSeedWordsWarning')]),
style: { ]),
margin: '24px',
fontSize: '0.9em',
marginBottom: '10px',
},
}, 'I\'ve copied it somewhere safe'),
h('button.primary', {
onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords),
style: {
margin: '10px',
fontSize: '0.9em',
},
}, 'Save Seed Words As File'),
]) ])
) )
} }
renderConfirmation () { renderContent () {
const { history, warning, inProgress } = this.props return this.state.screen === PASSWORD_PROMPT_SCREEN
? this.renderPasswordPromptContent()
: this.renderRevealSeedContent()
}
renderPasswordPromptContent () {
const { t } = this.context
return ( return (
h('.initialize-screen.flex-column.flex-center.flex-grow', { h('form', {
style: { maxWidth: '420px' }, onSubmit: event => this.handleSubmit(event),
}, [ }, [
h('label.input-label', {
h('h3.flex-center.text-transform-uppercase', { htmlFor: 'password-box',
style: { }, t('enterPasswordContinue')),
background: '#EBEBEB', h('.input-group', [
color: '#AEAEAE', h('input.form-control', {
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Reveal Seed Words',
]),
h('.div', {
style: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
justifyContent: 'center',
},
}, [
h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
// confirmation
h('input.large-input.letter-spacey', {
type: 'password', type: 'password',
placeholder: t('password'),
id: 'password-box', id: 'password-box',
placeholder: 'Enter your password to confirm', value: this.state.password,
onKeyPress: this.checkConfirmation.bind(this), onChange: event => this.setState({ password: event.target.value }),
style: { className: classnames({ 'form-control--error': this.state.error }),
width: 260,
marginTop: '12px',
},
}), }),
h('.flex-row.flex-start', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: () => history.push(DEFAULT_ROUTE),
}, 'CANCEL'),
// submit
h('button.primary', {
style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
]), ]),
this.state.error && h('.reveal-seed__error', this.state.error),
])
)
}
warning && ( renderRevealSeedContent () {
h('span.error', { const { t } = this.context
style: {
margin: '20px',
},
}, warning.split('-'))
),
inProgress && ( return (
h('span.in-progress-notification', 'Generating Seed...') h('div', [
), h('label.reveal-seed__label', t('yourPrivateSeedPhrase')),
]), h(ExportTextContainer, {
text: this.state.seedWords,
filename: t('metamaskSeedWords'),
}),
])
)
}
renderFooter () {
return this.state.screen === PASSWORD_PROMPT_SCREEN
? this.renderPasswordPromptFooter()
: this.renderRevealSeedFooter()
}
renderPasswordPromptFooter () {
return (
h('.page-container__footer', [
h('button.btn-secondary--lg.page-container__footer-button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
h('button.btn-primary--lg.page-container__footer-button', {
onClick: event => this.handleSubmit(event),
disabled: this.state.password === '',
}, this.context.t('next')),
])
)
}
renderRevealSeedFooter () {
return (
h('.page-container__footer', [
h('button.btn-secondary--lg.page-container__footer-button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('close')),
]) ])
) )
} }
render () { render () {
return this.props.seedWords return (
? this.renderSeed() h('.page-container', [
: this.renderConfirmation() h('.page-container__header', [
h('.page-container__title', this.context.t('revealSeedWordsTitle')),
h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')),
]),
h('.page-container__content', [
this.renderWarning(),
h('.reveal-seed__content', [
this.renderContent(),
]),
]),
this.renderFooter(),
])
)
} }
} }
RevealSeedPage.propTypes = { RevealSeedPage.propTypes = {
requestRevealSeed: PropTypes.func, requestRevealSeedWords: PropTypes.func,
confirmSeedWords: PropTypes.func,
seedWords: PropTypes.string,
inProgress: PropTypes.bool,
history: PropTypes.object, history: PropTypes.object,
warning: PropTypes.string,
} }
const mapStateToProps = state => { RevealSeedPage.contextTypes = {
const { appState: { warning }, metamask: { seedWords } } = state t: PropTypes.func,
return {
warning,
seedWords,
}
} }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
requestRevealSeed: password => dispatch(requestRevealSeed(password)), requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)),
confirmSeedWords: () => dispatch(confirmSeedWords()),
} }
} }
module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage) module.exports = connect(null, mapDispatchToProps)(RevealSeedPage)

View File

@ -61,3 +61,5 @@
@import './welcome-screen.scss'; @import './welcome-screen.scss';
@import './sender-to-recipient.scss'; @import './sender-to-recipient.scss';
@import '../../../components/export-text-container/export-text-container.scss';

View File

@ -1 +1,3 @@
@import './unlock.scss'; @import './unlock.scss';
@import './reveal-seed.scss';

View File

@ -0,0 +1,17 @@
.reveal-seed {
&__content {
padding: 20px;
}
&__label {
padding-bottom: 10px;
font-weight: 400;
display: inline-block;
}
&__error {
color: $crimson;
font-size: 14px;
padding-top: 5px;
}
}

View File

@ -207,6 +207,27 @@ input.large-input {
&__content { &__content {
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
min-height: 250px;
max-height: 400px;
}
&__warning-container {
background: $linen;
padding: 20px;
display: flex;
align-items: start;
}
&__warning-message {
padding-left: 15px;
}
&__warning-title {
font-weight: 500;
}
&__warning-icon {
padding-top: 5px;
} }
} }
@ -237,3 +258,49 @@ input.large-input {
border-radius: 0; border-radius: 0;
} }
} }
@media screen and (min-width: 576px) {
.page-container {
height: 600px;
flex: 0 0 auto;
}
}
.input-label {
padding-bottom: 10px;
font-weight: 400;
display: inline-block;
}
input.form-control {
padding-left: 10px;
font-size: 14px;
height: 40px;
border: 1px solid $alto;
border-radius: 3px;
width: 100%;
&::-webkit-input-placeholder {
font-weight: 100;
color: $dusty-gray;
}
&::-moz-placeholder {
font-weight: 100;
color: $dusty-gray;
}
&:-ms-input-placeholder {
font-weight: 100;
color: $dusty-gray;
}
&:-moz-placeholder {
font-weight: 100;
color: $dusty-gray;
}
&--error {
border: 1px solid $monzo;
}
}

View File

@ -54,6 +54,7 @@ $saffron: #f6c343;
$dodger-blue: #3099f2; $dodger-blue: #3099f2;
$zumthor: #edf7ff; $zumthor: #edf7ff;
$ecstasy: #f7861c; $ecstasy: #f7861c;
$linen: #fdf4f4;
/* /*
Z-Indicies Z-Indicies

View File

@ -1,138 +0,0 @@
const inherits = require('util').inherits
const Component = require('react').Component
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../../actions')
const { withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const {
DEFAULT_ROUTE,
INITIALIZE_BACKUP_PHRASE_ROUTE,
} = require('../../../routes')
RevealSeedConfirmation.contextTypes = {
t: PropTypes.func,
}
module.exports = compose(
withRouter,
connect(mapStateToProps)
)(RevealSeedConfirmation)
inherits(RevealSeedConfirmation, Component)
function RevealSeedConfirmation () {
Component.call(this)
}
function mapStateToProps (state) {
return {
warning: state.appState.warning,
}
}
RevealSeedConfirmation.prototype.render = function () {
const props = this.props
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', {
style: { maxWidth: '420px' },
}, [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Reveal Seed Words',
]),
h('.div', {
style: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
justifyContent: 'center',
},
}, [
h('h4', this.context.t('revealSeedWordsWarning')),
// confirmation
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: this.context.t('enterPasswordConfirm'),
onKeyPress: this.checkConfirmation.bind(this),
style: {
width: 260,
marginTop: '12px',
},
}),
h('.flex-row.flex-start', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: this.goHome.bind(this),
}, 'CANCEL'),
// submit
h('button.primary', {
style: { marginLeft: '10px' },
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
]),
(props.warning) && (
h('span.error', {
style: {
margin: '20px',
},
}, props.warning.split('-'))
),
props.inProgress && (
h('span.in-progress-notification', this.context.t('generatingSeed'))
),
]),
])
)
}
RevealSeedConfirmation.prototype.componentDidMount = function () {
document.getElementById('password-box').focus()
}
RevealSeedConfirmation.prototype.goHome = function () {
this.props.dispatch(actions.showConfigPage(false))
this.props.dispatch(actions.confirmSeedWords())
.then(() => this.props.history.push(DEFAULT_ROUTE))
}
// create vault
RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.revealSeedWords()
}
}
RevealSeedConfirmation.prototype.revealSeedWords = function () {
var password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password))
.then(() => this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE))
}