aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mascara/src/app/first-time/create-password-screen.js8
-rw-r--r--mascara/src/app/first-time/import-account-screen.js185
-rw-r--r--mascara/src/app/first-time/index.css112
-rw-r--r--mascara/src/app/first-time/index.js12
-rw-r--r--ui/app/actions.js27
5 files changed, 328 insertions, 16 deletions
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
? <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>
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' }) => (
+ <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)
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 (
<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:
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)
})
})
})