mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Add BackupPhraseScreen
This commit is contained in:
parent
fd4fbdc0cd
commit
1a9b217558
232
mascara/src/app/first-time/backup-phrase-screen.js
Normal file
232
mascara/src/app/first-time/backup-phrase-screen.js
Normal 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)
|
@ -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;
|
||||
|
@ -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 />
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user