Implement Import Account Screen

This commit is contained in:
Jacky Chan 2017-08-29 00:52:59 -07:00 committed by Chi Kei Chan
parent 1a9b217558
commit 449bce5eea
5 changed files with 328 additions and 16 deletions

View File

@ -8,6 +8,7 @@ class CreatePasswordScreen extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
createAccount: PropTypes.func.isRequired,
goToImportAccount: PropTypes.func.isRequired,
next: PropTypes.func.isRequired
}
@ -43,7 +44,7 @@ class CreatePasswordScreen extends Component {
}
render() {
const { isLoading } = this.props
const { isLoading, goToImportAccount } = this.props
return isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
@ -74,7 +75,10 @@ class CreatePasswordScreen extends Component {
<a
href=""
className="first-time-flow__link create-password__import-link"
onClick={e => e.preventDefault()}
onClick={e => {
e.preventDefault()
goToImportAccount()
}}
>
Import an account
</a>

View File

@ -0,0 +1,185 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {importNewAccount} from '../../../../ui/app/actions'
import {connect} from 'react-redux';
const Input = ({ label, placeholder, onChange, errorMessage, type = 'text' }) => (
<div className="import-account__input-wrapper">
<div className="import-account__input-label">{label}</div>
<input
type={type}
placeholder={placeholder}
className={classnames('first-time-flow__input import-account__input', {
'first-time-flow__input--error': errorMessage
})}
onChange={onChange}
/>
<div className="import-account__input-error-message">{errorMessage}</div>
</div>
)
class ImportAccountScreen extends Component {
static OPTIONS = {
PRIVATE_KEY: 'private_key',
JSON_FILE: 'json_file',
};
static propTypes = {
warning: PropTypes.string,
back: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
importNewAccount: PropTypes.func.isRequired,
};
state = {
selectedOption: ImportAccountScreen.OPTIONS.PRIVATE_KEY,
privateKey: '',
jsonFile: {},
}
isValid() {
const { OPTIONS } = ImportAccountScreen;
const { privateKey, jsonFile, password } = this.state;
switch (this.state.selectedOption) {
case OPTIONS.JSON_FILE:
return Boolean(jsonFile && password)
case OPTIONS.PRIVATE_KEY:
default:
return Boolean(privateKey)
}
}
onClick = () => {
const { OPTIONS } = ImportAccountScreen;
const { importNewAccount, next } = this.props;
const { privateKey, jsonFile, password } = this.state;
switch (this.state.selectedOption) {
case OPTIONS.JSON_FILE:
return importNewAccount('JSON File', [ jsonFile, password ])
.then(next)
case OPTIONS.PRIVATE_KEY:
default:
return importNewAccount('Private Key', [ privateKey ])
.then(next)
}
}
renderPrivateKey() {
return Input({
label: 'Add Private Key String',
placeholder: 'Enter private key',
onChange: e => this.setState({ privateKey: e.target.value }),
errorMessage: this.props.warning && 'Something went wrong. Please make sure your private key is correct.'
})
}
renderJsonFile() {
const { jsonFile: { name } } = this.state;
const { warning } = this.props;
return (
<div className="">
<div className="import-account__input-wrapper">
<div className="import-account__input-label">Upload File</div>
<div className="import-account__file-picker-wrapper">
<input
type="file"
id="file"
className="import-account__file-input"
onChange={e => this.setState({ jsonFile: e.target.files[0] })}
/>
<label
htmlFor="file"
className={classnames('import-account__file-input-label', {
'import-account__file-input-label--error': warning
})}
>
Choose File
</label>
<div className="import-account__file-name">{name}</div>
</div>
<div className="import-account__input-error-message">
{warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'}
</div>
</div>
{Input({
label: 'Enter Password',
placeholder: 'Enter Password',
type: 'password',
onChange: e => this.setState({ password: e.target.value }),
errorMessage: warning && 'Please make sure your password is correct.'
})}
</div>
)
}
renderContent() {
const { OPTIONS } = ImportAccountScreen;
switch (this.state.selectedOption) {
case OPTIONS.JSON_FILE:
return this.renderJsonFile()
case OPTIONS.PRIVATE_KEY:
default:
return this.renderPrivateKey()
}
}
render() {
const { OPTIONS } = ImportAccountScreen;
const { selectedOption } = this.state;
return (
<div className="import-account">
<a
className="import-account__back-button"
onClick={e => {
e.preventDefault()
this.props.back()
}}
href="#"
>
{`< Back`}
</a>
<div className="import-account__title">
Import an Account
</div>
<div className="import-account__selector-label">
How would you like to import your account?
</div>
<select
className="import-account__dropdown"
value={selectedOption}
onChange={e => this.setState({ selectedOption: e.target.value })}
>
<option value={OPTIONS.PRIVATE_KEY}>Private Key</option>
<option value={OPTIONS.JSON_FILE}>JSON File</option>
</select>
{this.renderContent()}
<button
className="first-time-flow__button"
disabled={!this.isValid()}
onClick={this.onClick}
>
Import
</button>
<a
href="https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file"
className="first-time-flow__link import-account__faq-link"
target="_blank"
>
File import not working?
</a>
</div>
)
}
}
export default connect(
({ appState: { isLoading, warning } }) => ({ isLoading, warning }),
dispatch => ({
importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args))
})
)(ImportAccountScreen)

View File

@ -9,7 +9,8 @@ $primary
.create-password,
.unique-image,
.tou,
.backup-phrase {
.backup-phrase,
.import-account {
display: flex;
flex-flow: column nowrap;
margin: 67px 0 0 146px;
@ -27,7 +28,8 @@ $primary
.create-password__title,
.unique-image__title,
.tou__title,
.backup-phrase__title {
.backup-phrase__title,
.import-account__title {
width: 280px;
color: #1B344D;
font-size: 40px;
@ -166,7 +168,9 @@ $primary
}
.backup-phrase__back-button,
.backup-phrase__back-button:hover {
.backup-phrase__back-button:hover,
.import-account__back-button,
.import-account__back-button:hover {
position: absolute;
top: 24px;
color: #22232C;
@ -219,6 +223,108 @@ button.backup-phrase__confirm-seed-option:hover {
transform: scale(1);
}
.import-account__faq-link {
font-size: 18px;
line-height: 23px;
font-family: Montserrat Light;
}
.import-account__selector-label {
color: #1B344D;
font-family: Montserrat Light;
font-size: 18px;
line-height: 23px;
}
.import-account__dropdown {
width: 325px;
border: 1px solid #CDCDCD;
border-radius: 4px;
background-color: #FFFFFF;
margin-top: 14px;
color: #5B5D67;
font-family: Montserrat Light;
font-size: 18px;
line-height: 23px;
padding: 14px 21px;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
}
.import-account__description-text {
color: #757575;
font-size: 18px;
line-height: 23px;
margin-top: 21px;
font-family: Montserrat UltraLight;
}
.import-account__input-wrapper {
display: flex;
flex-flow: column nowrap;
margin-top: 30px;
}
.first-time-flow__input--error {
border: 1px solid #FF001F !important;
}
.import-account__input-error-message {
margin-top: 10px;
width: 422px;
color: #FF001F;
font-family: Montserrat Light;
font-size: 16px;
line-height: 21px;
}
.import-account__input-label {
margin-bottom: 9px;
color: #1B344D;
font-family: Montserrat Light;
font-size: 18px;
line-height: 23px;
text-transform: uppercase;
}
.import-account__input {
width: 325px !important;
}
.import-account__file-input {
display: none;
}
.import-account__file-input-label {
height: 53px;
width: 148px;
border: 1px solid #1B344D;
border-radius: 4px;
color: #1B344D;
font-family: Montserrat Light;
font-size: 18px;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
cursor: pointer;
}
.import-account__file-picker-wrapper {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.import-account__file-name {
color: #000000;
font-family: Montserrat Light;
font-size: 18px;
line-height: 23px;
margin-left: 22px;
}
.first-time-flow__input {
width: 350px;
font-size: 18px;

View File

@ -4,6 +4,7 @@ import CreatePasswordScreen from './create-password-screen'
import UniqueImageScreen from './unique-image-screen'
import NoticeScreen from './notice-screen'
import BackupPhraseScreen from './backup-phrase-screen'
import ImportAccountScreen from './import-account-screen'
class FirstTimeFlow extends Component {
@ -21,6 +22,7 @@ class FirstTimeFlow extends Component {
static SCREEN_TYPE = {
CREATE_PASSWORD: 'create_password',
IMPORT_ACCOUNT: 'import_account',
UNIQUE_IMAGE: 'unique_image',
NOTICE: 'notice',
BACK_UP_PHRASE: 'back_up_phrase',
@ -43,7 +45,7 @@ class FirstTimeFlow extends Component {
const {isInitialized, seedWords, noActiveNotices} = this.props;
const {SCREEN_TYPE} = FirstTimeFlow
// return SCREEN_TYPE.UNIQUE_IMAGE
// return SCREEN_TYPE.IMPORT_ACCOUNT
if (!isInitialized) {
return SCREEN_TYPE.CREATE_PASSWORD
@ -66,6 +68,14 @@ class FirstTimeFlow extends Component {
return (
<CreatePasswordScreen
next={() => this.setScreenType(SCREEN_TYPE.UNIQUE_IMAGE)}
goToImportAccount={() => this.setScreenType(SCREEN_TYPE.IMPORT_ACCOUNT)}
/>
)
case SCREEN_TYPE.IMPORT_ACCOUNT:
return (
<ImportAccountScreen
back={() => this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)}
next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
/>
)
case SCREEN_TYPE.UNIQUE_IMAGE:

View File

@ -310,18 +310,25 @@ function importNewAccount (strategy, args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
log.debug(`background.importAccountWithStrategy`)
background.importAccountWithStrategy(strategy, args, (err) => {
if (err) return dispatch(actions.displayWarning(err.message))
log.debug(`background.getState`)
background.getState((err, newState) => {
dispatch(actions.hideLoadingIndication())
return new Promise((resolve, reject) => {
background.importAccountWithStrategy(strategy, args, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateMetamaskState(newState))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: newState.selectedAddress,
log.debug(`background.getState`)
background.getState((err, newState) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateMetamaskState(newState))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: newState.selectedAddress,
})
resolve(newState)
})
})
})