aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacky Chan <jchan@uber.com>2017-08-23 19:04:11 +0800
committerChi Kei Chan <chikeichan@gmail.com>2017-10-21 12:51:37 +0800
commit1a9b217558fd7a3a528a068c6820f2d905d62e9d (patch)
tree076102860067042a6be818eb41bb904fc4805463
parentfd4fbdc0cd70e31764a497946f565757b204b616 (diff)
downloadtangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.tar
tangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.tar.gz
tangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.tar.bz2
tangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.tar.lz
tangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.tar.xz
tangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.tar.zst
tangerine-wallet-browser-1a9b217558fd7a3a528a068c6820f2d905d62e9d.zip
Add BackupPhraseScreen
-rw-r--r--mascara/src/app/first-time/backup-phrase-screen.js232
-rw-r--r--mascara/src/app/first-time/index.css160
-rw-r--r--mascara/src/app/first-time/index.js7
-rw-r--r--mascara/src/app/first-time/unique-image-screen.js2
-rw-r--r--package.json1
-rw-r--r--ui/app/actions.js18
6 files changed, 406 insertions, 14 deletions
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 => (
+ <svg
+ version="1.1"
+ id="Capa_1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ x="0px"
+ y="0px"
+ width="401.998px"
+ height="401.998px"
+ viewBox="0 0 401.998 401.998"
+ style={{enableBackground: 'new 0 0 401.998 401.998'}}
+ xmlSpace="preserve"
+ {...props}
+ >
+ <g>
+ <path
+ d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
+ C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
+ h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
+ c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
+ C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
+ c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
+ z"
+ />
+ </g>
+ </svg>
+);
+
+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 (
+ <div className="backup-phrase__secret">
+ <div className={classnames('backup-phrase__secret-words', {
+ 'backup-phrase__secret-words--hidden': !isShowingSecret
+ })}>
+ {this.props.seedWords}
+ </div>
+ {!isShowingSecret && (
+ <div className="backup-phrase__secret-blocker">
+ <LockIcon width="28px" height="35px" fill="#FFFFFF" />
+ <button
+ className="backup-phrase__reveal-button"
+ onClick={() => this.setState({ isShowingSecret: true })}
+ >
+ Click here to reveal secret words
+ </button>
+ </div>
+ )}
+ </div>
+ );
+ }
+
+ renderSecretScreen() {
+ const { isShowingSecret } = this.state
+ return (
+ <div className="backup-phrase__content-wrapper">
+ <div>
+ <div className="backup-phrase__title">Secret Backup Phrase</div>
+ <div className="backup-phrase__body-text">
+ Your secret backup phrase makes it easy to back up and restore your account.
+ </div>
+ <div className="backup-phrase__body-text">
+ WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever.
+ </div>
+ {this.renderSecretWordsContainer()}
+ <button
+ className="first-time-flow__button"
+ onClick={() => isShowingSecret && this.setState({
+ isShowingSecret: false,
+ page: BackupPhraseScreen.PAGE.CONFIRM
+ })}
+ disabled={!isShowingSecret}
+ >
+ Next
+ </button>
+ <Breadcrumbs total={3} currentIndex={1} />
+ </div>
+ <div className="backup-phrase__tips">
+ <div className="backup-phrase__tips-text">Tips:</div>
+ <div className="backup-phrase__tips-text">
+ Store this phrase in a password manager like 1password.
+ </div>
+ <div className="backup-phrase__tips-text">
+ 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.
+ </div>
+ <div className="backup-phrase__tips-text">
+ Memorize this phrase.
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderConfirmationScreen() {
+ const { seedWords, confirmSeedWords, next } = this.props;
+ const { selectedSeeds } = this.state;
+ const isValid = seedWords === selectedSeeds.join(' ')
+
+ return (
+ <div className="backup-phrase__content-wrapper">
+ <div>
+ <div className="backup-phrase__title">Confirm your Secret Backup Phrase</div>
+ <div className="backup-phrase__body-text">
+ Please select each phrase in order to make sure it is correct.
+ </div>
+ <div className="backup-phrase__confirm-secret">
+ {selectedSeeds.map((word, i) => (
+ <button
+ key={i}
+ className="backup-phrase__confirm-seed-option"
+ >
+ {word}
+ </button>
+ ))}
+ </div>
+ <div className="backup-phrase__confirm-seed-options">
+ {seedWords.split(' ').map((word, i) => {
+ const isSelected = selectedSeeds.includes(word)
+ return (
+ <button
+ key={i}
+ className={classnames('backup-phrase__confirm-seed-option', {
+ 'backup-phrase__confirm-seed-option--selected': isSelected
+ })}
+ onClick={() => {
+ if (!isSelected) {
+ this.setState({
+ selectedSeeds: [...selectedSeeds, word]
+ })
+ } else {
+ this.setState({
+ selectedSeeds: selectedSeeds.filter(seed => seed !== word)
+ })
+ }
+ }}
+ >
+ {word}
+ </button>
+ )
+ })}
+ </div>
+ <button
+ className="first-time-flow__button"
+ onClick={() => isValid && confirmSeedWords().then(next)}
+ disabled={!isValid}
+ >
+ Confirm
+ </button>
+ </div>
+ </div>
+ )
+ }
+
+ renderBack() {
+ return this.state.page === BackupPhraseScreen.PAGE.CONFIRM
+ ? (
+ <a
+ className="backup-phrase__back-button"
+ onClick={e => {
+ e.preventDefault()
+ this.setState({
+ page: BackupPhraseScreen.PAGE.SECRET
+ })
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ )
+ : null
+ }
+
+ renderContent() {
+ switch(this.state.page) {
+ case BackupPhraseScreen.PAGE.CONFIRM:
+ return this.renderConfirmationScreen();
+ case BackupPhraseScreen.PAGE.SECRET:
+ default:
+ return this.renderSecretScreen();
+ }
+ }
+
+ render() {
+ return (
+ <div className="backup-phrase">
+ {this.renderBack()}
+ <Identicon address={this.props.address} diameter={70} />
+ {this.renderContent()}
+ </div>
+ )
+ }
+}
+
+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 (
+ <BackupPhraseScreen
+ next={() => this.setScreenType(SCREEN_TYPE.BUY_ETHER)}
+ />
+ )
default:
return <noscript />
}
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)
+ })
})
}
}