diff --git a/js/src/modals/CreateAccount/AccountDetails/accountDetails.js b/js/src/modals/CreateAccount/AccountDetails/accountDetails.js
index 75a2ca9c4..137d87d94 100644
--- a/js/src/modals/CreateAccount/AccountDetails/accountDetails.js
+++ b/js/src/modals/CreateAccount/AccountDetails/accountDetails.js
@@ -25,9 +25,16 @@ import styles from '../createAccount.css';
@observer
export default class AccountDetails extends Component {
static propTypes = {
+ isConfirming: PropTypes.bool,
+ withRequiredBackup: PropTypes.bool,
createStore: PropTypes.object.isRequired
}
+ static defaultPropTypes = {
+ isConfirming: false,
+ withRequiredBackup: false
+ }
+
render () {
const { address, description, name } = this.props.createStore;
@@ -78,31 +85,103 @@ export default class AccountDetails extends Component {
);
}
- renderPhrase () {
- const { phrase } = this.props.createStore;
+ renderRequiredBackup () {
+ const { phraseBackedUp, phraseBackedUpError } = this.props.createStore;
- if (!phrase) {
+ if (!this.props.withRequiredBackup) {
return null;
}
return (
-
- }
- label={
-
- }
- readOnly
- value={ phrase }
- />
+
+
+ }
+ label={
+
+ }
+ onChange={ this.onEditPhraseBackedUp }
+ value={ phraseBackedUp }
+ />
+
);
}
+
+ renderPhrase () {
+ const { isConfirming } = this.props;
+ const { isTest, phrase, backupPhraseError } = this.props.createStore;
+
+ const hint = (
+
+ );
+ const label = (
+
+ );
+
+ if (!isConfirming) {
+ if (!phrase) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ { this.renderRequiredBackup() }
+
+ );
+ }
+
+ return (
+
+ );
+ }
+
+ onEditPhraseBackedUp = (ev) => {
+ this.props.createStore.setPhraseBackedUp(ev.target.value);
+ }
+
+ onEditPhrase = (ev) => {
+ this.props.createStore.setPhrase(ev.target.value);
+ }
}
diff --git a/js/src/modals/CreateAccount/createAccount.css b/js/src/modals/CreateAccount/createAccount.css
index 1dd338643..cbab4ee8d 100644
--- a/js/src/modals/CreateAccount/createAccount.css
+++ b/js/src/modals/CreateAccount/createAccount.css
@@ -131,3 +131,8 @@
padding: 0 4em 1.5em 4em;
text-align: center;
}
+
+.backupPhrase {
+ line-height: 1.618em;
+ margin-top: 1.5em;
+}
diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js
index 988a5f521..ec8308615 100644
--- a/js/src/modals/CreateAccount/createAccount.js
+++ b/js/src/modals/CreateAccount/createAccount.js
@@ -37,7 +37,7 @@ import NewImport from './NewImport';
import NewQr from './NewQr';
import RawKey from './RawKey';
import RecoveryPhrase from './RecoveryPhrase';
-import Store, { STAGE_CREATE, STAGE_INFO, STAGE_SELECT_TYPE } from './store';
+import Store, { STAGE_CREATE, STAGE_INFO, STAGE_SELECT_TYPE, STAGE_CONFIRM_BACKUP } from './store';
import TypeIcon from './TypeIcon';
import print from './print';
import recoveryPage from './recoveryPage.ejs';
@@ -61,6 +61,12 @@ const TITLES = {
defaultMessage='account information'
/>
),
+ backup: (
+
+ ),
import: (
)
};
-const STAGE_NAMES = [TITLES.type, TITLES.create, TITLES.info];
+const STAGE_NAMES = [TITLES.type, TITLES.create, TITLES.info, TITLES.backup];
const STAGE_IMPORT = [TITLES.type, TITLES.import, TITLES.info];
const STAGE_RESTORE = [TITLES.restore, TITLES.info];
const STAGE_QR = [TITLES.type, TITLES.qr, TITLES.info];
@@ -213,14 +219,25 @@ class CreateAccount extends Component {
}
return (
-
+
+ );
+
+ case STAGE_CONFIRM_BACKUP:
+ return (
+
);
}
}
renderDialogActions () {
const { restore } = this.props;
- const { createType, canCreate, isBusy, stage } = this.createStore;
+ const { createType, canCreate, isBusy, stage, phraseBackedUpError } = this.createStore;
const cancelBtn = (
)
: (
@@ -292,7 +309,7 @@ class CreateAccount extends Component {
/>
)
}
- onClick={ this.onCreate }
+ onClick={ createType === 'fromNew' ? this.createStore.nextStage : this.onCreate }
/>
];
@@ -314,6 +331,7 @@ class CreateAccount extends Component {
)
: null,
}
key='done'
label={
@@ -322,12 +340,55 @@ class CreateAccount extends Component {
defaultMessage='Done'
/>
}
- onClick={ this.onClose }
+ onClick={ createType === 'fromNew' ? this.onConfirmPhraseBackup : this.onClose }
+ />
+ ];
+
+ case STAGE_CONFIRM_BACKUP:
+ return [
+ }
+ key='done'
+ label={
+
+ }
+ onClick={ this.onCreateNew }
/>
];
}
}
+ onConfirmPhraseBackup = () => {
+ this.createStore.clearPhrase();
+ this.createStore.nextStage();
+ }
+
+ onCreateNew = () => {
+ this.createStore.setBusy(true);
+ this.createStore.computeBackupPhraseAddress()
+ .then(err => {
+ if (err) {
+ this.createStore.setBusy(false);
+ return;
+ }
+
+ return this.createStore.createAccount(this.vaultStore)
+ .then(() => {
+ this.createStore.clearPhrase();
+ this.createStore.setBusy(false);
+ this.props.onUpdate && this.props.onUpdate();
+ this.onClose();
+ });
+ })
+ .catch((error) => {
+ this.createStore.setBusy(false);
+ this.props.newError(error);
+ });
+ }
+
onCreate = () => {
return this.createStore
.createAccount(this.vaultStore)
@@ -341,6 +402,7 @@ class CreateAccount extends Component {
}
onClose = () => {
+ this.createStore.clearPhrase();
this.props.onClose && this.props.onClose();
}
diff --git a/js/src/modals/CreateAccount/errors.js b/js/src/modals/CreateAccount/errors.js
index bb5708275..3b9ed2669 100644
--- a/js/src/modals/CreateAccount/errors.js
+++ b/js/src/modals/CreateAccount/errors.js
@@ -46,6 +46,20 @@ export default {
/>
),
+ noMatchBackupPhrase: (
+
+ ),
+
+ noMatchPhraseBackedUp: (
+
+ ),
+
noName: (
)
+
};
diff --git a/js/src/modals/CreateAccount/store.js b/js/src/modals/CreateAccount/store.js
index 148658fc8..ab9879b8d 100644
--- a/js/src/modals/CreateAccount/store.js
+++ b/js/src/modals/CreateAccount/store.js
@@ -24,6 +24,7 @@ const FAKEPATH = 'C:\\fakepath\\';
const STAGE_SELECT_TYPE = 0;
const STAGE_CREATE = 1;
const STAGE_INFO = 2;
+const STAGE_CONFIRM_BACKUP = 3;
export default class Store {
@observable accounts = null;
@@ -41,6 +42,8 @@ export default class Store {
@observable passwordHint = '';
@observable passwordRepeat = '';
@observable phrase = '';
+ @observable backupPhraseAddress = null;
+ @observable phraseBackedUp = '';
@observable qrAddress = null;
@observable rawKey = '';
@observable rawKeyError = ERRORS.nokey;
@@ -104,17 +107,39 @@ export default class Store {
: ERRORS.noMatchPassword;
}
+ @computed get backupPhraseError () {
+ return !this.backupPhraseAddress || this.address === this.backupPhraseAddress
+ ? null
+ : ERRORS.noMatchBackupPhrase;
+ }
+
+ @computed get phraseBackedUpError () {
+ return this.phraseBackedUp === 'I have written down the phrase'
+ ? null
+ : ERRORS.noMatchPhraseBackedUp;
+ }
+
@computed get qrAddressValid () {
console.log('qrValid', this.qrAddress, this._api.util.isAddressValid(this.qrAddress));
return this._api.util.isAddressValid(this.qrAddress);
}
+ @action clearPhrase = () => {
+ transaction(() => {
+ this.phrase = '';
+ this.phraseBackedUp = '';
+ });
+ }
+
@action clearErrors = () => {
transaction(() => {
+ this.address = '';
this.description = '';
this.password = '';
this.passwordRepeat = '';
this.phrase = '';
+ this.backupPhraseAddress = null;
+ this.phraseBackedUp = '';
this.name = '';
this.nameError = ERRORS.noName;
this.qrAddress = null;
@@ -192,6 +217,26 @@ export default class Store {
});
}
+ @action setBackupPhraseAddress = (address) => {
+ this.backupPhraseAddress = address;
+ }
+
+ @action computeBackupPhraseAddress = () => {
+ return this._api.parity.phraseToAddress(this.phrase)
+ .then(address => {
+ this.setBackupPhraseAddress(address);
+ return address !== this.address;
+ })
+ .catch((error) => {
+ console.error('createAccount', error);
+ throw error;
+ });
+ }
+
+ @action setPhraseBackedUp = (backedUp) => {
+ this.phraseBackedUp = backedUp;
+ }
+
@action setPassword = (password) => {
this.password = password;
}
@@ -217,6 +262,7 @@ export default class Store {
.filter((part) => part.length);
this.phrase = phraseParts.join(' ');
+ this.backupPhraseAddress = null;
}
@action setRawKey = (rawKey) => {
@@ -460,7 +506,8 @@ export default class Store {
}
export {
- STAGE_CREATE,
STAGE_INFO,
+ STAGE_CONFIRM_BACKUP,
+ STAGE_CREATE,
STAGE_SELECT_TYPE
};
diff --git a/js/src/modals/FirstRun/firstRun.js b/js/src/modals/FirstRun/firstRun.js
index 60f375a84..088d1bc6c 100644
--- a/js/src/modals/FirstRun/firstRun.js
+++ b/js/src/modals/FirstRun/firstRun.js
@@ -52,6 +52,10 @@ const STAGE_NAMES = [
id='firstRun.title.recovery'
defaultMessage='recovery'
/>,
+ ,
+
);
case 4:
+ return (
+
+ );
+ case 5:
return (
);
@@ -141,7 +155,7 @@ class FirstRun extends Component {
renderDialogActions () {
const { hasAccounts } = this.props;
const { stage, hasAcceptedTnc } = this.state;
- const { canCreate } = this.createStore;
+ const { canCreate, phraseBackedUpError } = this.createStore;
switch (stage) {
case 0:
@@ -169,15 +183,10 @@ class FirstRun extends Component {
const buttons = [
}
- key='create'
- label={
-
- }
- onClick={ this.onCreate }
+ icon={ }
+ key='next'
+ label={ BUTTON_LABEL_NEXT }
+ onClick={ this.onNext }
/>
];
@@ -212,14 +221,30 @@ class FirstRun extends Component {
onClick={ this.printPhrase }
/>,
}
key='next'
label={ BUTTON_LABEL_NEXT }
- onClick={ this.onNext }
+ onClick={ this.onConfirmPhraseBackup }
/>
];
case 4:
+ return (
+ }
+ key='create'
+ label={
+
+ }
+ onClick={ this.onCreate }
+ />
+ );
+
+ case 5:
return (
}
@@ -244,6 +269,11 @@ class FirstRun extends Component {
}, onClose);
}
+ onConfirmPhraseBackup = () => {
+ this.createStore.clearPhrase();
+ this.onNext();
+ }
+
onNext = () => {
const { stage } = this.state;
@@ -261,11 +291,19 @@ class FirstRun extends Component {
onCreate = () => {
this.createStore.setBusy(true);
- return this.createStore
- .createAccount()
- .then(() => {
- this.onNext();
- this.createStore.setBusy(false);
+ this.createStore.computeBackupPhraseAddress()
+ .then(err => {
+ if (err) {
+ this.createStore.setBusy(false);
+ return;
+ }
+
+ return this.createStore.createAccount()
+ .then(() => {
+ this.createStore.clearPhrase();
+ this.createStore.setBusy(false);
+ this.onNext();
+ });
})
.catch((error) => {
this.createStore.setBusy(false);
diff --git a/js/src/ui/Form/Input/input.js b/js/src/ui/Form/Input/input.js
index cc75ef8b8..6eb506dc1 100644
--- a/js/src/ui/Form/Input/input.js
+++ b/js/src/ui/Form/Input/input.js
@@ -57,6 +57,7 @@ export default class Input extends Component {
PropTypes.string,
PropTypes.bool
]),
+ allowPaste: PropTypes.bool,
autoFocus: PropTypes.bool,
children: PropTypes.node,
className: PropTypes.string,
@@ -97,6 +98,7 @@ export default class Input extends Component {
static defaultProps = {
allowCopy: false,
+ allowPaste: true,
escape: 'initial',
hideUnderline: false,
onBlur: noop,
@@ -221,6 +223,12 @@ export default class Input extends Component {
}
onChange = (event, value) => {
+ if (!this.props.allowPaste) {
+ if (value.length - this.state.value.length > 8) {
+ return;
+ }
+ }
+
event.persist();
this.setValue(value, () => {