diff --git a/mascara/src/app/first-time/backup-phrase-screen.js b/mascara/src/app/first-time/backup-phrase-screen.js
new file mode 100644
index 000000000..19c441734
--- /dev/null
+++ b/mascara/src/app/first-time/backup-phrase-screen.js
@@ -0,0 +1,232 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux';
+import classnames from 'classnames'
+import Identicon from '../../../../ui/app/components/identicon'
+import {confirmSeedWords} from '../../../../ui/app/actions'
+import Breadcrumbs from './breadcrumbs'
+
+const LockIcon = props => (
+
+
+
+
+
+);
+
+class BackupPhraseScreen extends Component {
+ static propTypes = {
+ address: PropTypes.string.isRequired,
+ seedWords: PropTypes.string.isRequired,
+ next: PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ seedWords: ''
+ };
+
+ static PAGE = {
+ SECRET: 'secret',
+ CONFIRM: 'confirm'
+ };
+
+ state = {
+ isShowingSecret: false,
+ page: BackupPhraseScreen.PAGE.SECRET,
+ selectedSeeds: []
+ }
+
+ renderSecretWordsContainer() {
+ const { isShowingSecret } = this.state;
+ return (
+
+
+ {this.props.seedWords}
+
+ {!isShowingSecret && (
+
+
+ this.setState({ isShowingSecret: true })}
+ >
+ Click here to reveal secret words
+
+
+ )}
+
+ );
+ }
+
+ renderSecretScreen() {
+ const { isShowingSecret } = this.state
+ return (
+
+
+
Secret Backup Phrase
+
+ Your secret backup phrase makes it easy to back up and restore your account.
+
+
+ WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever.
+
+ {this.renderSecretWordsContainer()}
+
isShowingSecret && this.setState({
+ isShowingSecret: false,
+ page: BackupPhraseScreen.PAGE.CONFIRM
+ })}
+ disabled={!isShowingSecret}
+ >
+ Next
+
+
+
+
+
Tips:
+
+ Store this phrase in a password manager like 1password.
+
+
+ Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
+
+
+ Memorize this phrase.
+
+
+
+ )
+ }
+
+ renderConfirmationScreen() {
+ const { seedWords, confirmSeedWords, next } = this.props;
+ const { selectedSeeds } = this.state;
+ const isValid = seedWords === selectedSeeds.join(' ')
+
+ return (
+
+
+
Confirm your Secret Backup Phrase
+
+ Please select each phrase in order to make sure it is correct.
+
+
+ {selectedSeeds.map((word, i) => (
+
+ {word}
+
+ ))}
+
+
+ {seedWords.split(' ').map((word, i) => {
+ const isSelected = selectedSeeds.includes(word)
+ return (
+ {
+ if (!isSelected) {
+ this.setState({
+ selectedSeeds: [...selectedSeeds, word]
+ })
+ } else {
+ this.setState({
+ selectedSeeds: selectedSeeds.filter(seed => seed !== word)
+ })
+ }
+ }}
+ >
+ {word}
+
+ )
+ })}
+
+
isValid && confirmSeedWords().then(next)}
+ disabled={!isValid}
+ >
+ Confirm
+
+
+
+ )
+ }
+
+ renderBack() {
+ return this.state.page === BackupPhraseScreen.PAGE.CONFIRM
+ ? (
+ {
+ e.preventDefault()
+ this.setState({
+ page: BackupPhraseScreen.PAGE.SECRET
+ })
+ }}
+ href="#"
+ >
+ {`< Back`}
+
+ )
+ : null
+ }
+
+ renderContent() {
+ switch(this.state.page) {
+ case BackupPhraseScreen.PAGE.CONFIRM:
+ return this.renderConfirmationScreen();
+ case BackupPhraseScreen.PAGE.SECRET:
+ default:
+ return this.renderSecretScreen();
+ }
+ }
+
+ render() {
+ return (
+
+ {this.renderBack()}
+
+ {this.renderContent()}
+
+ )
+ }
+}
+
+export default connect(
+ ({ metamask: { selectedAddress, seedWords } }) => ({
+ seedWords,
+ address: selectedAddress
+ }),
+ dispatch => ({
+ confirmSeedWords: () => dispatch(confirmSeedWords())
+ })
+)(BackupPhraseScreen)
diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
index c10d4f9ce..e9951059b 100644
--- a/mascara/src/app/first-time/index.css
+++ b/mascara/src/app/first-time/index.css
@@ -8,7 +8,8 @@ $primary
.create-password,
.unique-image,
-.tou {
+.tou,
+.backup-phrase {
display: flex;
flex-flow: column nowrap;
margin: 67px 0 0 146px;
@@ -19,9 +20,14 @@ $primary
max-width: 46rem;
}
+.backup-phrase {
+ max-width: 100%;
+}
+
.create-password__title,
.unique-image__title,
-.tou__title {
+.tou__title,
+.backup-phrase__title {
width: 280px;
color: #1B344D;
font-size: 40px;
@@ -30,6 +36,11 @@ $primary
margin-bottom: 24px;
}
+.tou__title,
+.backup-phrase__title {
+ width: 480px;
+}
+
.create-password__confirm-input {
margin-top: 15px;
}
@@ -39,20 +50,29 @@ $primary
}
.unique-image__title,
-.tou__title {
+.tou__title,
+.backup-phrase__title {
margin-top: 24px;
}
-.unique-image__body-text {
- width: 335px;
+.unique-image__body-text,
+.backup-phrase__body-text {
color: #1B344D;
font-size: 16px;
line-height: 23px;
font-family: Montserrat UltraLight;
}
-.unique-image__body-text +
.unique-image__body-text {
+ width: 335px;
+}
+
+.unique-image__body-text +
+.unique-image__body-text,
+.backup-phrase__body-text +
+.backup-phrase__body-text,
+.backup-phrase__tips-text +
+.backup-phrase__tips-text {
margin-top: 24px;
}
@@ -71,6 +91,134 @@ $primary
padding: 22px 30px;
}
+.backup-phrase__content-wrapper {
+ display: flex;
+ flex: row nowrap;
+}
+
+.backup-phrase__body-text {
+ width: 450px;
+}
+
+.backup-phrase__tips {
+ margin: 40px 85px;
+ width: 285px;
+}
+
+.backup-phrase__tips-text {
+ color: #5B5D67;
+ font-size: 16px;
+ line-height: 23px;
+ font-family: Montserrat UltraLight;
+}
+
+.backup-phrase__secret {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ width: 349px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ padding: 20px 0;
+ margin-top: 36px;
+}
+
+.backup-phrase__secret-words {
+ width: 310px;
+ color: #5B5D67;
+ font-family: Montserrat Light;
+ font-size: 20px;
+ line-height: 26px;
+ text-align: center;
+}
+
+.backup-phrase__secret-words--hidden {
+ filter: blur(5px);
+}
+
+.backup-phrase__secret-blocker {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(0,0,0,0.6);
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ padding: 13px 0 18px;
+}
+
+.backup-phrase__reveal-button {
+ border: 1px solid #979797;
+ border-radius: 4px;
+ background: none;
+ box-shadow: none;
+ color: #FFFFFF;
+ font-family: Montserrat Regular;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 15px;
+ text-align: center;
+ text-transform: uppercase;
+ margin-top: 10px;
+}
+
+.backup-phrase__back-button,
+.backup-phrase__back-button:hover {
+ position: absolute;
+ top: 24px;
+ color: #22232C;
+ font-family: Montserrat Regular;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 21px;
+}
+
+button.backup-phrase__reveal-button:hover {
+ transform: scale(1);
+}
+
+.backup-phrase__confirm-secret {
+ height: 190px;
+ width: 495px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ margin: 25px 0 36px;
+ padding: 17px;
+}
+
+.backup-phrase__confirm-seed-options {
+ display: flex;
+ flex-flow: row wrap;
+ width: 465px;
+ position: relative;
+ left: -7px;
+}
+
+.backup-phrase__confirm-seed-option {
+ color: #5B5D67;
+ font-family: Montserrat Light;
+ font-size: 16px;
+ line-height: 21px;
+ background-color: #E7E7E7;
+ padding: 8px 19px;
+ box-shadow: none;
+ min-width: 65px;
+ margin: 7px;
+}
+
+.backup-phrase__confirm-seed-option--selected {
+ background-color: #85D1CC;
+ color: #FFFFFF;
+}
+
+button.backup-phrase__confirm-seed-option:hover {
+ transform: scale(1);
+}
+
.first-time-flow__input {
width: 350px;
font-size: 18px;
diff --git a/mascara/src/app/first-time/index.js b/mascara/src/app/first-time/index.js
index a81c4c11d..d15bb3ce1 100644
--- a/mascara/src/app/first-time/index.js
+++ b/mascara/src/app/first-time/index.js
@@ -3,6 +3,7 @@ import {connect} from 'react-redux';
import CreatePasswordScreen from './create-password-screen'
import UniqueImageScreen from './unique-image-screen'
import NoticeScreen from './notice-screen'
+import BackupPhraseScreen from './backup-phrase-screen'
class FirstTimeFlow extends Component {
@@ -79,6 +80,12 @@ class FirstTimeFlow extends Component {
next={() => this.setScreenType(SCREEN_TYPE.BACK_UP_PHRASE)}
/>
)
+ case SCREEN_TYPE.BACK_UP_PHRASE:
+ return (
+ this.setScreenType(SCREEN_TYPE.BUY_ETHER)}
+ />
+ )
default:
return
}
diff --git a/mascara/src/app/first-time/unique-image-screen.js b/mascara/src/app/first-time/unique-image-screen.js
index ae1512d47..a32a91eb1 100644
--- a/mascara/src/app/first-time/unique-image-screen.js
+++ b/mascara/src/app/first-time/unique-image-screen.js
@@ -5,7 +5,7 @@ import Breadcrumbs from './breadcrumbs'
class UniqueImageScreen extends Component {
static propTypes = {
- address: PropTypes.string.isRequired,
+ address: PropTypes.string,
next: PropTypes.func.isRequired
}
diff --git a/package.json b/package.json
index 502b070cb..0e99ce5ca 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"bluebird": "^3.5.0",
"bn.js": "^4.11.7",
"browserify-derequire": "^0.9.4",
+ "classnames": "^2.2.5",
"client-sw-ready-event": "^3.3.0",
"clone": "^2.1.1",
"copy-to-clipboard": "^3.0.8",
diff --git a/ui/app/actions.js b/ui/app/actions.js
index f026fd0ab..ec18f099e 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -215,14 +215,18 @@ function confirmSeedWords () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
- background.clearSeedWordCache((err, account) => {
- dispatch(actions.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
+ return new Promise((resolve, reject) => {
+ background.clearSeedWordCache((err, account) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ reject(err)
+ }
- log.info('Seed word cache cleared. ' + account)
- dispatch(actions.showAccountDetail(account))
+ log.info('Seed word cache cleared. ' + account)
+ dispatch(actions.showAccountDetail(account))
+ resolve(account)
+ })
})
}
}