diff options
author | Jacky Chan <jchan@uber.com> | 2017-08-23 19:04:11 +0800 |
---|---|---|
committer | Chi Kei Chan <chikeichan@gmail.com> | 2017-10-21 12:51:37 +0800 |
commit | 1a9b217558fd7a3a528a068c6820f2d905d62e9d (patch) | |
tree | 076102860067042a6be818eb41bb904fc4805463 | |
parent | fd4fbdc0cd70e31764a497946f565757b204b616 (diff) | |
download | tangerine-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.js | 232 | ||||
-rw-r--r-- | mascara/src/app/first-time/index.css | 160 | ||||
-rw-r--r-- | mascara/src/app/first-time/index.js | 7 | ||||
-rw-r--r-- | mascara/src/app/first-time/unique-image-screen.js | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | ui/app/actions.js | 18 |
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) + }) }) } } |