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

Add BackupPhraseScreen

This commit is contained in:
Jacky Chan 2017-08-23 04:04:11 -07:00 committed by Chi Kei Chan
parent fd4fbdc0cd
commit 1a9b217558
6 changed files with 406 additions and 14 deletions

View File

@ -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)

View File

@ -8,7 +8,8 @@ $primary
.create-password, .create-password,
.unique-image, .unique-image,
.tou { .tou,
.backup-phrase {
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
margin: 67px 0 0 146px; margin: 67px 0 0 146px;
@ -19,9 +20,14 @@ $primary
max-width: 46rem; max-width: 46rem;
} }
.backup-phrase {
max-width: 100%;
}
.create-password__title, .create-password__title,
.unique-image__title, .unique-image__title,
.tou__title { .tou__title,
.backup-phrase__title {
width: 280px; width: 280px;
color: #1B344D; color: #1B344D;
font-size: 40px; font-size: 40px;
@ -30,6 +36,11 @@ $primary
margin-bottom: 24px; margin-bottom: 24px;
} }
.tou__title,
.backup-phrase__title {
width: 480px;
}
.create-password__confirm-input { .create-password__confirm-input {
margin-top: 15px; margin-top: 15px;
} }
@ -39,20 +50,29 @@ $primary
} }
.unique-image__title, .unique-image__title,
.tou__title { .tou__title,
.backup-phrase__title {
margin-top: 24px; margin-top: 24px;
} }
.unique-image__body-text { .unique-image__body-text,
width: 335px; .backup-phrase__body-text {
color: #1B344D; color: #1B344D;
font-size: 16px; font-size: 16px;
line-height: 23px; line-height: 23px;
font-family: Montserrat UltraLight; font-family: Montserrat UltraLight;
} }
.unique-image__body-text +
.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; margin-top: 24px;
} }
@ -71,6 +91,134 @@ $primary
padding: 22px 30px; 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 { .first-time-flow__input {
width: 350px; width: 350px;
font-size: 18px; font-size: 18px;

View File

@ -3,6 +3,7 @@ import {connect} from 'react-redux';
import CreatePasswordScreen from './create-password-screen' import CreatePasswordScreen from './create-password-screen'
import UniqueImageScreen from './unique-image-screen' import UniqueImageScreen from './unique-image-screen'
import NoticeScreen from './notice-screen' import NoticeScreen from './notice-screen'
import BackupPhraseScreen from './backup-phrase-screen'
class FirstTimeFlow extends Component { class FirstTimeFlow extends Component {
@ -79,6 +80,12 @@ class FirstTimeFlow extends Component {
next={() => this.setScreenType(SCREEN_TYPE.BACK_UP_PHRASE)} next={() => this.setScreenType(SCREEN_TYPE.BACK_UP_PHRASE)}
/> />
) )
case SCREEN_TYPE.BACK_UP_PHRASE:
return (
<BackupPhraseScreen
next={() => this.setScreenType(SCREEN_TYPE.BUY_ETHER)}
/>
)
default: default:
return <noscript /> return <noscript />
} }

View File

@ -5,7 +5,7 @@ import Breadcrumbs from './breadcrumbs'
class UniqueImageScreen extends Component { class UniqueImageScreen extends Component {
static propTypes = { static propTypes = {
address: PropTypes.string.isRequired, address: PropTypes.string,
next: PropTypes.func.isRequired next: PropTypes.func.isRequired
} }

View File

@ -56,6 +56,7 @@
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"bn.js": "^4.11.7", "bn.js": "^4.11.7",
"browserify-derequire": "^0.9.4", "browserify-derequire": "^0.9.4",
"classnames": "^2.2.5",
"client-sw-ready-event": "^3.3.0", "client-sw-ready-event": "^3.3.0",
"clone": "^2.1.1", "clone": "^2.1.1",
"copy-to-clipboard": "^3.0.8", "copy-to-clipboard": "^3.0.8",

View File

@ -215,14 +215,18 @@ function confirmSeedWords () {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`) log.debug(`background.clearSeedWordCache`)
return new Promise((resolve, reject) => {
background.clearSeedWordCache((err, account) => { background.clearSeedWordCache((err, account) => {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
return dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
reject(err)
} }
log.info('Seed word cache cleared. ' + account) log.info('Seed word cache cleared. ' + account)
dispatch(actions.showAccountDetail(account)) dispatch(actions.showAccountDetail(account))
resolve(account)
})
}) })
} }
} }