aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2018-05-01 02:04:53 +0800
committerGitHub <noreply@github.com>2018-05-01 02:04:53 +0800
commit5f3f8c85fcc5bd378ef1a886434a3e98546fee7f (patch)
treef4665fdeae73f010a676da4ec3df563e31ff01bd
parent9b1c07c75045f4a7124e4f0fcd416535f88182ae (diff)
parent61b8c3efff3b12e117c80db80c237dcfce0c0f49 (diff)
downloadtangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.tar
tangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.tar.gz
tangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.tar.bz2
tangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.tar.lz
tangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.tar.xz
tangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.tar.zst
tangerine-wallet-browser-5f3f8c85fcc5bd378ef1a886434a3e98546fee7f.zip
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.
-rw-r--r--app/_locales/en/messages.json33
-rw-r--r--app/images/copy-to-clipboard.svg24
-rw-r--r--app/images/download.svg41
-rw-r--r--app/images/warning.svg22
-rw-r--r--app/scripts/lib/config-manager.js22
-rw-r--r--app/scripts/metamask-controller.js2
-rw-r--r--gulpfile.js2
-rw-r--r--mascara/src/app/first-time/seed-screen.js71
-rw-r--r--ui/app/actions.js78
-rw-r--r--ui/app/app.js16
-rw-r--r--ui/app/components/export-text-container/export-text-container.component.js45
-rw-r--r--ui/app/components/export-text-container/export-text-container.scss52
-rw-r--r--ui/app/components/export-text-container/index.js2
-rw-r--r--ui/app/components/pages/home.js4
-rw-r--r--ui/app/components/pages/keychains/reveal-seed.js259
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/pages/index.scss2
-rw-r--r--ui/app/css/itcss/components/pages/reveal-seed.scss17
-rw-r--r--ui/app/css/itcss/generic/index.scss67
-rw-r--r--ui/app/css/itcss/settings/variables.scss1
-rw-r--r--ui/app/keychains/hd/recover-seed/confirmation.js138
21 files changed, 475 insertions, 425 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 3b20ab49a..a40c2635c 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -98,6 +98,9 @@
"clickCopy": {
"message": "Click to Copy"
},
+ "close": {
+ "message": "Close"
+ },
"confirm": {
"message": "Confirm"
},
@@ -259,6 +262,9 @@
"enterPasswordConfirm": {
"message": "Enter your password to confirm"
},
+ "enterPasswordContinue": {
+ "message": "Enter password to continue"
+ },
"passwordNotLongEnough": {
"message": "Password not long enough"
},
@@ -331,6 +337,9 @@
"gasPriceRequired": {
"message": "Gas Price Required"
},
+ "generatingTransaction": {
+ "message": "Generating transaction"
+ },
"getEther": {
"message": "Get Ether"
},
@@ -476,6 +485,9 @@
"metamaskDescription": {
"message": "MetaMask is a secure identity vault for Ethereum."
},
+ "metamaskSeedWords": {
+ "message": "MetaMask Seed Words"
+ },
"min": {
"message": "Minimum"
},
@@ -549,6 +561,9 @@
"message": "or",
"description": "choice between creating or importing a new account"
},
+ "password": {
+ "message": "Password"
+ },
"passwordCorrect": {
"message": "Please make sure your password is correct."
},
@@ -634,8 +649,17 @@
"revealSeedWords": {
"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": {
- "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": {
"message": "Revert"
@@ -677,6 +701,9 @@
"reprice_subtitle": {
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
},
+ "saveAsCsvFile": {
+ "message": "Save as CSV File"
+ },
"saveAsFile": {
"message": "Save as File",
"description": "Account export process"
@@ -909,7 +936,7 @@
"youSign": {
"message": "You are signing"
},
- "generatingTransaction": {
- "message": "Generating transaction"
+ "yourPrivateSeedPhrase": {
+ "message": "Your private seed phrase"
}
}
diff --git a/app/images/copy-to-clipboard.svg b/app/images/copy-to-clipboard.svg
new file mode 100644
index 000000000..c67c2aa84
--- /dev/null
+++ b/app/images/copy-to-clipboard.svg
@@ -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> \ No newline at end of file
diff --git a/app/images/download.svg b/app/images/download.svg
index 137a1190e..b55066414 100644
--- a/app/images/download.svg
+++ b/app/images/download.svg
@@ -1,15 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<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"
- 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">
-<g>
- <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
- 141.568,20.145 141.955,20.145 141.955,20.15 157.006,20.15 157.006,20.145 157.551,20.145 "/>
- <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
- 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
- 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
- 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
- 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>
-</svg>
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+ <!-- Generator: sketchtool 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
+ <title>50559280-0739-419A-8E87-3CDD16A6996A</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(-212.000000, -379.000000)" stroke="#259DE5" stroke-width="2">
+ <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-3" transform="translate(174.000000, 11.000000)">
+ <g id="Group-4">
+ <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> \ No newline at end of file
diff --git a/app/images/warning.svg b/app/images/warning.svg
new file mode 100644
index 000000000..9c8d697d7
--- /dev/null
+++ b/app/images/warning.svg
@@ -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> \ No newline at end of file
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index c10ff2f4e..221746467 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -101,6 +101,7 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data)
}
+
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.getData()
return data.showSeedWords
@@ -116,27 +117,6 @@ ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
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) {
var config = this.getConfig()
config.provider = {
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index edde38819..c4a73d8ea 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -309,7 +309,6 @@ module.exports = class MetamaskController extends EventEmitter {
lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(),
forgottenPassword: this.configManager.getPasswordForgotten(),
- isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()),
},
}
}
@@ -351,7 +350,6 @@ module.exports = class MetamaskController extends EventEmitter {
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
- setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager),
// vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController),
diff --git a/gulpfile.js b/gulpfile.js
index e844a4cba..4dca7089f 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -247,7 +247,7 @@ gulp.task('dev:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: true,
- pattern: 'ui/app/css/**/*.scss',
+ pattern: 'ui/app/**/*.scss',
}))
function createScssBuildTask({ src, dest, devMode, pattern }) {
diff --git a/mascara/src/app/first-time/seed-screen.js b/mascara/src/app/first-time/seed-screen.js
index 9af9ca3be..d004be77b 100644
--- a/mascara/src/app/first-time/seed-screen.js
+++ b/mascara/src/app/first-time/seed-screen.js
@@ -8,7 +8,6 @@ import Identicon from '../../../../ui/app/components/identicon'
import Breadcrumbs from './breadcrumbs'
import LoadingScreen from './loading-screen'
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
-import { confirmSeedWords } from '../../../../ui/app/actions'
const LockIcon = props => (
<svg
@@ -45,8 +44,6 @@ class BackupPhraseScreen extends Component {
address: PropTypes.string.isRequired,
seedWords: PropTypes.string,
history: PropTypes.object,
- isRevealingSeedWords: PropTypes.bool,
- clearSeedWords: PropTypes.func,
};
static defaultProps = {
@@ -61,14 +58,6 @@ class BackupPhraseScreen extends Component {
}
componentWillMount () {
- this.checkSeedWords()
- }
-
- componentDidUpdate () {
- this.checkSeedWords()
- }
-
- checkSeedWords () {
const { seedWords, history } = this.props
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 () {
- const { isRevealingSeedWords } = this.props
+ const { isShowingSecret } = this.state
+ const { history } = this.props
return (
<div className="backup-phrase__content-wrapper">
@@ -152,8 +121,14 @@ class BackupPhraseScreen extends Component {
</div>
</div>
<div className="backup-phrase__next-button">
- { this.renderSubmitButton() }
- { !isRevealingSeedWords && <Breadcrumbs total={3} currentIndex={1} />}
+ <button
+ className="first-time-flow__button"
+ onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
+ disabled={!isShowingSecret}
+ >
+ Next
+ </button>
+ <Breadcrumbs total={3} currentIndex={1} />
</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(
withRouter,
- connect(mapStateToProps, mapDispatchToProps),
+ connect(
+ ({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
+ seedWords,
+ isLoading,
+ address: selectedAddress,
+ })
+ )
)(BackupPhraseScreen)
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 81d9c333b..62875c629 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -83,7 +83,7 @@ var actions = {
REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION',
revealSeedConfirmation: revealSeedConfirmation,
requestRevealSeed: requestRevealSeed,
-
+ requestRevealSeedWords,
// unlock screen
UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS',
UNLOCK_FAILED: 'UNLOCK_FAILED',
@@ -345,11 +345,13 @@ function transitionBackward () {
}
}
-function clearSeedWordCache () {
- log.debug(`background.clearSeedWordCache`)
+function confirmSeedWords () {
return dispatch => {
+ dispatch(actions.showLoadingIndication())
+ log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => {
background.clearSeedWordCache((err, account) => {
+ dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
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) {
return (dispatch) => {
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) {
return dispatch => {
dispatch(actions.showLoadingIndication())
@@ -460,13 +470,29 @@ function requestRevealSeed (password) {
}
dispatch(actions.showNewVaultSeed(result))
+ dispatch(actions.hideLoadingIndication())
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,
}
}
-
-function setIsRevealingSeedWords (reveal) {
- return dispatch => {
- log.debug(`background.setIsRevealingSeedWords`)
- background.setIsRevealingSeedWords(reveal)
- return forceUpdateMetamaskState(dispatch)
- }
-}
diff --git a/ui/app/app.js b/ui/app/app.js
index 0b38b1326..5af63dc9c 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -24,7 +24,7 @@ const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unlock')
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 CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice')
@@ -56,20 +56,11 @@ const {
class App extends Component {
componentWillMount () {
- const {
- currentCurrency,
- setCurrentCurrencyToUSD,
- isRevealingSeedWords,
- clearSeedWords,
- } = this.props
+ const { currentCurrency, setCurrentCurrencyToUSD } = this.props
if (!currentCurrency) {
setCurrentCurrencyToUSD()
}
-
- if (isRevealingSeedWords) {
- clearSeedWords()
- }
}
renderRoutes () {
@@ -402,8 +393,6 @@ App.propTypes = {
isMouseUser: PropTypes.bool,
setMouseUserState: PropTypes.func,
t: PropTypes.func,
- isRevealingSeedWords: PropTypes.bool,
- clearSeedWords: PropTypes.func,
}
function mapStateToProps (state) {
@@ -484,7 +473,6 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
- clearSeedWords: () => dispatch(actions.confirmSeedWords()),
}
}
diff --git a/ui/app/components/export-text-container/export-text-container.component.js b/ui/app/components/export-text-container/export-text-container.component.js
new file mode 100644
index 000000000..c2546fa9b
--- /dev/null
+++ b/ui/app/components/export-text-container/export-text-container.component.js
@@ -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
diff --git a/ui/app/components/export-text-container/export-text-container.scss b/ui/app/components/export-text-container/export-text-container.scss
new file mode 100644
index 000000000..a42de8233
--- /dev/null
+++ b/ui/app/components/export-text-container/export-text-container.scss
@@ -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;
+ }
+}
diff --git a/ui/app/components/export-text-container/index.js b/ui/app/components/export-text-container/index.js
new file mode 100644
index 000000000..b2864a717
--- /dev/null
+++ b/ui/app/components/export-text-container/index.js
@@ -0,0 +1,2 @@
+const ExportTextContainer = require('./export-text-container.component')
+module.exports = ExportTextContainer
diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js
index 90b8e1d37..9110f8202 100644
--- a/ui/app/components/pages/home.js
+++ b/ui/app/components/pages/home.js
@@ -21,7 +21,7 @@ const QrView = require('../../components/qr-code')
// Routes
const {
- REVEAL_SEED_ROUTE,
+ INITIALIZE_BACKUP_PHRASE_ROUTE,
RESTORE_VAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE,
@@ -69,7 +69,7 @@ class Home extends Component {
log.debug('rendering seed words')
return h(Redirect, {
to: {
- pathname: REVEAL_SEED_ROUTE,
+ pathname: INITIALIZE_BACKUP_PHRASE_ROUTE,
},
})
}
diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js
index 247f3c8e2..685c81074 100644
--- a/ui/app/components/pages/keychains/reveal-seed.js
+++ b/ui/app/components/pages/keychains/reveal-seed.js
@@ -2,11 +2,27 @@ const { Component } = require('react')
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const { exportAsFile } = require('../../../util')
-const { requestRevealSeed, confirmSeedWords } = require('../../../actions')
+const classnames = require('classnames')
+
+const { requestRevealSeedWords } = require('../../../actions')
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 {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ screen: PASSWORD_PROMPT_SCREEN,
+ password: '',
+ seedWords: null,
+ error: null,
+ }
+ }
+
componentDidMount () {
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
@@ -14,182 +30,135 @@ class RevealSeedPage extends Component {
}
}
- checkConfirmation (event) {
- if (event.key === 'Enter') {
- event.preventDefault()
- this.revealSeedWords()
- }
+ handleSubmit (event) {
+ event.preventDefault()
+ 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 () {
- const password = document.getElementById('password-box').value
- this.props.requestRevealSeed(password)
- }
-
- renderSeed () {
- const { seedWords, confirmSeedWords, history } = this.props
-
+ renderWarning () {
return (
- h('.initialize-screen.flex-column.flex-center.flex-grow', [
-
- h('h3.flex-center.text-transform-uppercase', {
- 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-container', [
+ h('img.page-container__warning-icon', {
+ src: 'images/warning.svg',
}),
-
- h('button.primary', {
- onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)),
- 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'),
+ h('.page-container__warning-message', [
+ h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]),
+ h('div', [this.context.t('revealSeedWordsWarning')]),
+ ]),
])
)
}
- renderConfirmation () {
- const { history, warning, inProgress } = this.props
+ renderContent () {
+ return this.state.screen === PASSWORD_PROMPT_SCREEN
+ ? this.renderPasswordPromptContent()
+ : this.renderRevealSeedContent()
+ }
+
+ renderPasswordPromptContent () {
+ const { t } = this.context
return (
- h('.initialize-screen.flex-column.flex-center.flex-grow', {
- style: { maxWidth: '420px' },
+ h('form', {
+ onSubmit: event => this.handleSubmit(event),
}, [
-
- 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', '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', {
+ h('label.input-label', {
+ htmlFor: 'password-box',
+ }, t('enterPasswordContinue')),
+ h('.input-group', [
+ h('input.form-control', {
type: 'password',
+ placeholder: t('password'),
id: 'password-box',
- placeholder: 'Enter your password to confirm',
- onKeyPress: this.checkConfirmation.bind(this),
- style: {
- width: 260,
- marginTop: '12px',
- },
+ value: this.state.password,
+ onChange: event => this.setState({ password: event.target.value }),
+ className: classnames({ 'form-control--error': this.state.error }),
}),
+ ]),
+ this.state.error && h('.reveal-seed__error', this.state.error),
+ ])
+ )
+ }
- 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'),
+ renderRevealSeedContent () {
+ const { t } = this.context
- ]),
+ return (
+ h('div', [
+ h('label.reveal-seed__label', t('yourPrivateSeedPhrase')),
+ h(ExportTextContainer, {
+ text: this.state.seedWords,
+ filename: t('metamaskSeedWords'),
+ }),
+ ])
+ )
+ }
- warning && (
- h('span.error', {
- style: {
- margin: '20px',
- },
- }, warning.split('-'))
- ),
-
- inProgress && (
- h('span.in-progress-notification', 'Generating Seed...')
- ),
- ]),
+ 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 () {
- return this.props.seedWords
- ? this.renderSeed()
- : this.renderConfirmation()
+ return (
+ h('.page-container', [
+ 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 = {
- requestRevealSeed: PropTypes.func,
- confirmSeedWords: PropTypes.func,
- seedWords: PropTypes.string,
- inProgress: PropTypes.bool,
+ requestRevealSeedWords: PropTypes.func,
history: PropTypes.object,
- warning: PropTypes.string,
}
-const mapStateToProps = state => {
- const { appState: { warning }, metamask: { seedWords } } = state
-
- return {
- warning,
- seedWords,
- }
+RevealSeedPage.contextTypes = {
+ t: PropTypes.func,
}
const mapDispatchToProps = dispatch => {
return {
- requestRevealSeed: password => dispatch(requestRevealSeed(password)),
- confirmSeedWords: () => dispatch(confirmSeedWords()),
+ requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)),
}
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage)
+module.exports = connect(null, mapDispatchToProps)(RevealSeedPage)
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index 959eb9d15..1c544e162 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -61,3 +61,5 @@
@import './welcome-screen.scss';
@import './sender-to-recipient.scss';
+
+@import '../../../components/export-text-container/export-text-container.scss';
diff --git a/ui/app/css/itcss/components/pages/index.scss b/ui/app/css/itcss/components/pages/index.scss
index 82446fd7a..d0b59da53 100644
--- a/ui/app/css/itcss/components/pages/index.scss
+++ b/ui/app/css/itcss/components/pages/index.scss
@@ -1 +1,3 @@
@import './unlock.scss';
+
+@import './reveal-seed.scss';
diff --git a/ui/app/css/itcss/components/pages/reveal-seed.scss b/ui/app/css/itcss/components/pages/reveal-seed.scss
new file mode 100644
index 000000000..b8f13af4a
--- /dev/null
+++ b/ui/app/css/itcss/components/pages/reveal-seed.scss
@@ -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;
+ }
+}
diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss
index 92321394b..7a64810c4 100644
--- a/ui/app/css/itcss/generic/index.scss
+++ b/ui/app/css/itcss/generic/index.scss
@@ -207,6 +207,27 @@ input.large-input {
&__content {
height: 100%;
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;
}
}
+
+@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;
+ }
+}
diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss
index 51548306f..814d7a382 100644
--- a/ui/app/css/itcss/settings/variables.scss
+++ b/ui/app/css/itcss/settings/variables.scss
@@ -54,6 +54,7 @@ $saffron: #f6c343;
$dodger-blue: #3099f2;
$zumthor: #edf7ff;
$ecstasy: #f7861c;
+$linen: #fdf4f4;
/*
Z-Indicies
diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js
deleted file mode 100644
index eb588415f..000000000
--- a/ui/app/keychains/hd/recover-seed/confirmation.js
+++ /dev/null
@@ -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))
-}