From 449bce5eea5a5ee049828121876340ae226351b7 Mon Sep 17 00:00:00 2001 From: Jacky Chan Date: Tue, 29 Aug 2017 00:52:59 -0700 Subject: [PATCH] Implement Import Account Screen --- .../app/first-time/create-password-screen.js | 8 +- .../app/first-time/import-account-screen.js | 185 ++++++++++++++++++ mascara/src/app/first-time/index.css | 112 ++++++++++- mascara/src/app/first-time/index.js | 12 +- ui/app/actions.js | 27 ++- 5 files changed, 328 insertions(+), 16 deletions(-) create mode 100644 mascara/src/app/first-time/import-account-screen.js diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js index e4b0425ce..8dd7d44af 100644 --- a/mascara/src/app/first-time/create-password-screen.js +++ b/mascara/src/app/first-time/create-password-screen.js @@ -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 ? @@ -74,7 +75,10 @@ class CreatePasswordScreen extends Component { e.preventDefault()} + onClick={e => { + e.preventDefault() + goToImportAccount() + }} > Import an account diff --git a/mascara/src/app/first-time/import-account-screen.js b/mascara/src/app/first-time/import-account-screen.js new file mode 100644 index 000000000..17be90c2a --- /dev/null +++ b/mascara/src/app/first-time/import-account-screen.js @@ -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' }) => ( +
+
{label}
+ +
{errorMessage}
+
+) + +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 ( +
+
+
Upload File
+
+ this.setState({ jsonFile: e.target.files[0] })} + /> + +
{name}
+
+
+ {warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'} +
+
+ {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.' + })} +
+ ) + } + + 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 ( +
+ { + e.preventDefault() + this.props.back() + }} + href="#" + > + {`< Back`} + +
+ Import an Account +
+
+ How would you like to import your account? +
+ + {this.renderContent()} + + + File import not working? + +
+ ) + } +} + +export default connect( + ({ appState: { isLoading, warning } }) => ({ isLoading, warning }), + dispatch => ({ + importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args)) + }) +)(ImportAccountScreen) diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css index e9951059b..da8f801e8 100644 --- a/mascara/src/app/first-time/index.css +++ b/mascara/src/app/first-time/index.css @@ -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; diff --git a/mascara/src/app/first-time/index.js b/mascara/src/app/first-time/index.js index d15bb3ce1..1ba6ed28c 100644 --- a/mascara/src/app/first-time/index.js +++ b/mascara/src/app/first-time/index.js @@ -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 ( this.setScreenType(SCREEN_TYPE.UNIQUE_IMAGE)} + goToImportAccount={() => this.setScreenType(SCREEN_TYPE.IMPORT_ACCOUNT)} + /> + ) + case SCREEN_TYPE.IMPORT_ACCOUNT: + return ( + this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)} + next={() => this.setScreenType(SCREEN_TYPE.NOTICE)} /> ) case SCREEN_TYPE.UNIQUE_IMAGE: diff --git a/ui/app/actions.js b/ui/app/actions.js index ec18f099e..04bba6bf2 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -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) }) }) })