Vault Management UI (round 2) (#4631)
* Add VaultMeta edit dialog * Updated (WIP) * Meta & password edit completed * Added SelectionList component for selections * Use SelectionList in DappPermisions * AddDapps uses SelectionList * Fix AccountCard to consistent height * Convert Signer defaults to SelectionList * Subtle selection border * Display account vault information * Allow invalid addresses to display icons (e.g. vaults) * Display vault on edit meta * Convert VaultAccounts to SelectionList * Allow editing of Vault in meta * Add tests for SectionList component * Add tests for VaultSelector component * Auto-focus description field (aligns with #4657) * Apply scroll fixes from lates commit in #4621 * Remove unneeded logs * Remove extra div, fixing ParityBar overflow * Disable save if password don't match * s/disabled/readOnly/ * string -> bool
This commit is contained in:
parent
9ff427caaf
commit
570e6f32b0
@ -21,9 +21,11 @@ import { connect } from 'react-redux';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import { newError } from '~/redux/actions';
|
import { newError } from '~/redux/actions';
|
||||||
import { Button, Form, Input, InputChip, Portal } from '~/ui';
|
import { Button, Form, Input, InputAddress, InputChip, Portal } from '~/ui';
|
||||||
import { CancelIcon, SaveIcon } from '~/ui/Icons';
|
import { CancelIcon, SaveIcon } from '~/ui/Icons';
|
||||||
|
import VaultStore from '~/views/Vaults/store';
|
||||||
|
|
||||||
|
import VaultSelector from '../VaultSelector';
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -39,6 +41,11 @@ class EditMeta extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
store = new Store(this.context.api, this.props.account);
|
store = new Store(this.context.api, this.props.account);
|
||||||
|
vaultStore = VaultStore.get(this.context.api);
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.vaultStore.loadVaults();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { description, name, nameError, tags } = this.store;
|
const { description, name, nameError, tags } = this.store;
|
||||||
@ -55,6 +62,7 @@ class EditMeta extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{ this.renderVaultSelector() }
|
||||||
<Form>
|
<Form>
|
||||||
<Input
|
<Input
|
||||||
autoFocus
|
autoFocus
|
||||||
@ -102,6 +110,7 @@ class EditMeta extends Component {
|
|||||||
onTokensChange={ this.store.setTags }
|
onTokensChange={ this.store.setTags }
|
||||||
tokens={ tags.slice() }
|
tokens={ tags.slice() }
|
||||||
/>
|
/>
|
||||||
|
{ this.renderVault() }
|
||||||
</Form>
|
</Form>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
@ -154,22 +163,87 @@ class EditMeta extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderVault () {
|
||||||
|
const { isAccount, vaultName } = this.store;
|
||||||
|
|
||||||
|
if (!isAccount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputAddress
|
||||||
|
allowCopy={ false }
|
||||||
|
allowInvalid
|
||||||
|
readOnly
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='editMeta.vault.hint'
|
||||||
|
defaultMessage='the vault this account is attached to'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='editMeta.vault.label'
|
||||||
|
defaultMessage='associated vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.toggleVaultSelector }
|
||||||
|
value={ vaultName }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderVaultSelector () {
|
||||||
|
const { isAccount, isVaultSelectorOpen, vaultName } = this.store;
|
||||||
|
|
||||||
|
if (!isAccount || !isVaultSelectorOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VaultSelector
|
||||||
|
onClose={ this.toggleVaultSelector }
|
||||||
|
onSelect={ this.setVaultName }
|
||||||
|
selected={ vaultName }
|
||||||
|
vaultStore={ this.vaultStore }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave = () => {
|
onSave = () => {
|
||||||
|
const { address, isAccount, meta, vaultName } = this.store;
|
||||||
|
|
||||||
if (this.store.hasError) {
|
if (this.store.hasError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.store
|
return this.store
|
||||||
.save()
|
.save()
|
||||||
|
.then(() => {
|
||||||
|
if (isAccount && (meta.vault !== vaultName)) {
|
||||||
|
return this.vaultStore.moveAccount(vaultName, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.then(this.onClose)
|
.then(this.onClose)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.props.newError(error);
|
this.props.newError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVaultName = (vaultName) => {
|
||||||
|
this.store.setVaultName(vaultName);
|
||||||
|
this.toggleVaultSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleVaultSelector = () => {
|
||||||
|
this.store.toggleVaultSelector();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
|
@ -45,7 +45,9 @@ function createApi () {
|
|||||||
return {
|
return {
|
||||||
parity: {
|
parity: {
|
||||||
setAccountName: sinon.stub().resolves(),
|
setAccountName: sinon.stub().resolves(),
|
||||||
setAccountMeta: sinon.stub().resolves()
|
setAccountMeta: sinon.stub().resolves(),
|
||||||
|
listVaults: sinon.stub().resolves([]),
|
||||||
|
listOpenedVaults: sinon.stub().resolves([])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,14 @@ import { validateName } from '~/util/validation';
|
|||||||
export default class Store {
|
export default class Store {
|
||||||
@observable address = null;
|
@observable address = null;
|
||||||
@observable isAccount = false;
|
@observable isAccount = false;
|
||||||
|
@observable isVaultSelectorOpen = false;
|
||||||
@observable description = null;
|
@observable description = null;
|
||||||
@observable meta = null;
|
@observable meta = null;
|
||||||
@observable name = null;
|
@observable name = null;
|
||||||
@observable nameError = null;
|
@observable nameError = null;
|
||||||
@observable passwordHint = null;
|
@observable passwordHint = null;
|
||||||
@observable tags = null;
|
@observable tags = null;
|
||||||
|
@observable vaultName = null;
|
||||||
|
|
||||||
constructor (api, account) {
|
constructor (api, account) {
|
||||||
const { address, name, meta, uuid } = account;
|
const { address, name, meta, uuid } = account;
|
||||||
@ -34,14 +36,15 @@ export default class Store {
|
|||||||
this._api = api;
|
this._api = api;
|
||||||
|
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.isAccount = !!uuid;
|
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.meta = meta || {};
|
this.meta = meta || {};
|
||||||
this.name = name || '';
|
this.name = name || '';
|
||||||
|
this.isAccount = !!uuid;
|
||||||
|
|
||||||
this.description = this.meta.description || '';
|
this.description = this.meta.description || '';
|
||||||
this.passwordHint = this.meta.passwordHint || '';
|
this.passwordHint = this.meta.passwordHint || '';
|
||||||
this.tags = this.meta.tags && this.meta.tags.peek() || [];
|
this.tags = this.meta.tags && this.meta.tags.peek() || [];
|
||||||
|
this.vaultName = this.meta.vault;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +77,14 @@ export default class Store {
|
|||||||
this.tags = tags.slice();
|
this.tags = tags.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setVaultName = (vaultName) => {
|
||||||
|
this.vaultName = vaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setVaultSelectorOpen = (isOpen) => {
|
||||||
|
this.isVaultSelectorOpen = isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
const meta = {
|
const meta = {
|
||||||
description: this.description,
|
description: this.description,
|
||||||
@ -94,4 +105,8 @@ export default class Store {
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleVaultSelector () {
|
||||||
|
this.setVaultSelectorOpen(!this.isVaultSelectorOpen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,8 +142,23 @@ describe('modals/EditMeta/Store', () => {
|
|||||||
expect(store.tags.peek()).to.deep.equal(['taga', 'tagb']);
|
expect(store.tags.peek()).to.deep.equal(['taga', 'tagb']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setVaultName', () => {
|
||||||
|
it('sets the name', () => {
|
||||||
|
store.setVaultName('testing');
|
||||||
|
expect(store.vaultName).to.equal('testing');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setVaultSelectorOpen', () => {
|
||||||
|
it('sets the state', () => {
|
||||||
|
store.setVaultSelectorOpen('testing');
|
||||||
|
expect(store.isVaultSelectorOpen).to.equal('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('operations', () => {
|
||||||
describe('save', () => {
|
describe('save', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createStore(ACCOUNT);
|
createStore(ACCOUNT);
|
||||||
@ -170,3 +185,11 @@ describe('modals/EditMeta/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toggleVaultSelector', () => {
|
||||||
|
it('inverts the selector state', () => {
|
||||||
|
store.toggleVaultSelector();
|
||||||
|
expect(store.isVaultSelectorOpen).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.body {
|
|
||||||
/* TODO: These styles are shared with CreateAccount - DRY up */
|
/* TODO: These styles are shared with CreateAccount - DRY up */
|
||||||
.passwords {
|
.passwords {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -35,4 +34,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group+.group {
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class VaultCreate extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={ styles.body }>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
error={ vaultNameError }
|
error={ vaultNameError }
|
||||||
hint={
|
hint={
|
||||||
|
17
js/src/modals/VaultMeta/index.js
Normal file
17
js/src/modals/VaultMeta/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultMeta';
|
298
js/src/modals/VaultMeta/vaultMeta.js
Normal file
298
js/src/modals/VaultMeta/vaultMeta.js
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { Checkbox } from 'material-ui';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
|
import { Button, Form, Input, Portal, VaultCard } from '~/ui';
|
||||||
|
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||||
|
import { CheckIcon, CloseIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import styles from '../VaultCreate/vaultCreate.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class VaultMeta extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
|
vaultStore: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
passwordEdit: false
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { isBusyMeta, isModalMetaOpen, vault, vaultDescription, vaultPassword, vaultPasswordRepeat, vaultPasswordRepeatError, vaultPasswordOld, vaultPasswordHint } = this.props.vaultStore;
|
||||||
|
const { passwordEdit } = this.state;
|
||||||
|
|
||||||
|
if (!isModalMetaOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal
|
||||||
|
busy={ isBusyMeta }
|
||||||
|
buttons={ [
|
||||||
|
<Button
|
||||||
|
disabled={ isBusyMeta }
|
||||||
|
icon={ <CloseIcon /> }
|
||||||
|
key='close'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.button.close'
|
||||||
|
defaultMessage='close'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
disabled={ isBusyMeta || !!vaultPasswordRepeatError }
|
||||||
|
icon={ <CheckIcon /> }
|
||||||
|
key='vault'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.button.save'
|
||||||
|
defaultMessage='save'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onExecute }
|
||||||
|
/>
|
||||||
|
] }
|
||||||
|
onClose={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.title'
|
||||||
|
defaultMessage='Edit Vault Metadata'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<VaultCard.Layout vault={ vault }>
|
||||||
|
<Form>
|
||||||
|
<div className={ styles.group }>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.description.hint'
|
||||||
|
defaultMessage='the description for this vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.description.label'
|
||||||
|
defaultMessage='vault description'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditDescription }
|
||||||
|
value={ vaultDescription }
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.passwordHint.hint'
|
||||||
|
defaultMessage='your password hint for this vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.passwordHint.label'
|
||||||
|
defaultMessage='password hint'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPasswordHint }
|
||||||
|
value={ vaultPasswordHint }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.group }>
|
||||||
|
<Checkbox
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.allowPassword'
|
||||||
|
defaultMessage='Change vault password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
checked={ passwordEdit }
|
||||||
|
onCheck={ this.onTogglePassword }
|
||||||
|
/>
|
||||||
|
<div className={ [styles.passwords, passwordEdit ? null : styles.disabled].join(' ') }>
|
||||||
|
<div className={ styles.password }>
|
||||||
|
<Input
|
||||||
|
disabled={ !passwordEdit }
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.currentPassword.hint'
|
||||||
|
defaultMessage='your current vault password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.currentPassword.label'
|
||||||
|
defaultMessage='current password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPasswordCurrent }
|
||||||
|
type='password'
|
||||||
|
value={ vaultPasswordOld }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={ [styles.passwords, passwordEdit ? null : styles.disabled].join(' ') }>
|
||||||
|
<div className={ styles.password }>
|
||||||
|
<Input
|
||||||
|
disabled={ !passwordEdit }
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.password.hint'
|
||||||
|
defaultMessage='a strong, unique password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.password.label'
|
||||||
|
defaultMessage='new password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPassword }
|
||||||
|
type='password'
|
||||||
|
value={ vaultPassword }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.password }>
|
||||||
|
<Input
|
||||||
|
disabled={ !passwordEdit }
|
||||||
|
error={ vaultPasswordRepeatError }
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.password2.hint'
|
||||||
|
defaultMessage='verify your new password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.editMeta.password2.label'
|
||||||
|
defaultMessage='new password (repeat)'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.onEditPasswordRepeat }
|
||||||
|
type='password'
|
||||||
|
value={ vaultPasswordRepeat }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={ passwordEdit ? null : styles.disabled }>
|
||||||
|
<PasswordStrength input={ vaultPassword } />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</VaultCard.Layout>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
|
||||||
|
// <InputChip
|
||||||
|
// addOnBlur
|
||||||
|
// hint={
|
||||||
|
// <FormattedMessage
|
||||||
|
// id='vaults.editMeta.tags.hint'
|
||||||
|
// defaultMessage='press <Enter> to add a tag'
|
||||||
|
// />
|
||||||
|
// }
|
||||||
|
// label={
|
||||||
|
// <FormattedMessage
|
||||||
|
// id='vaults.editMeta.tags.label'
|
||||||
|
// defaultMessage='(optional) tags'
|
||||||
|
// />
|
||||||
|
// }
|
||||||
|
// onTokensChange={ this.onEditTags }
|
||||||
|
// tokens={ vaultTags.slice() }
|
||||||
|
// />
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditDescription = (event, description) => {
|
||||||
|
this.props.vaultStore.setVaultDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPasswordCurrent = (event, password) => {
|
||||||
|
this.props.vaultStore.setVaultPasswordOld(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPassword = (event, password) => {
|
||||||
|
this.props.vaultStore.setVaultPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPasswordHint = (event, hint) => {
|
||||||
|
this.props.vaultStore.setVaultPasswordHint(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditPasswordRepeat = (event, password) => {
|
||||||
|
this.props.vaultStore.setVaultPasswordRepeat(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditTags = (tags) => {
|
||||||
|
this.props.vaultStore.setVaultTags(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTogglePassword = () => {
|
||||||
|
this.setState({
|
||||||
|
passwordEdit: !this.state.passwordEdit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecute = () => {
|
||||||
|
const { vaultPasswordRepeatError } = this.props.vaultStore;
|
||||||
|
const { passwordEdit } = this.state;
|
||||||
|
|
||||||
|
if (vaultPasswordRepeatError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
passwordEdit
|
||||||
|
? this.props.vaultStore.editVaultPassword()
|
||||||
|
: true
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
return this.props.vaultStore.editVaultMeta();
|
||||||
|
})
|
||||||
|
.catch(this.props.newError)
|
||||||
|
.then(this.onClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.setState({
|
||||||
|
passwordEdit: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.vaultStore.closeMetaModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(VaultMeta);
|
171
js/src/modals/VaultMeta/vaultMeta.spec.js
Normal file
171
js/src/modals/VaultMeta/vaultMeta.spec.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultMeta from './';
|
||||||
|
|
||||||
|
const VAULT = {
|
||||||
|
name: 'testVault'
|
||||||
|
};
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let reduxStore;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createReduxStore () {
|
||||||
|
reduxStore = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return reduxStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
isBusyMeta: false,
|
||||||
|
isModalMetaOpen: true,
|
||||||
|
vault: VAULT,
|
||||||
|
vaultDescription: '',
|
||||||
|
vaultTags: [],
|
||||||
|
vaultName: VAULT.name,
|
||||||
|
vaults: [VAULT],
|
||||||
|
closeMetaModal: sinon.stub(),
|
||||||
|
editVaultMeta: sinon.stub().resolves(true),
|
||||||
|
editVaultPassword: sinon.stub().resolves(true),
|
||||||
|
setVaultDescription: sinon.stub(),
|
||||||
|
setVaultPassword: sinon.stub(),
|
||||||
|
setVaultPasswordRepeat: sinon.stub(),
|
||||||
|
setVaultPasswordHint: sinon.stub(),
|
||||||
|
setVaultPasswordOld: sinon.stub(),
|
||||||
|
setVaultTags: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
component = shallow(
|
||||||
|
<VaultMeta vaultStore={ createVaultStore() } />,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
store: createReduxStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).find('VaultMeta').shallow();
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/VaultMeta', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event methods', () => {
|
||||||
|
describe('onEditDescription', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditDescription(null, 'testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into setVaultDescription', () => {
|
||||||
|
expect(vaultStore.setVaultDescription).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPassword', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPassword(null, 'testPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPassword', () => {
|
||||||
|
expect(vaultStore.setVaultPassword).to.have.been.calledWith('testPassword');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPasswordHint', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPasswordHint(null, 'testPasswordHint');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPasswordHint', () => {
|
||||||
|
expect(vaultStore.setVaultPasswordHint).to.have.been.calledWith('testPasswordHint');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPasswordCurrent', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPasswordCurrent(null, 'testPasswordOld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPasswordHint', () => {
|
||||||
|
expect(vaultStore.setVaultPasswordOld).to.have.been.calledWith('testPasswordOld');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditPasswordRepeat', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditPasswordRepeat(null, 'testPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setVaultPasswordRepeat', () => {
|
||||||
|
expect(vaultStore.setVaultPasswordRepeat).to.have.been.calledWith('testPassword');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onEditTags', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onEditTags('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into setVaultTags', () => {
|
||||||
|
expect(vaultStore.setVaultTags).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onClose', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instance.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into closeMetaModal', () => {
|
||||||
|
expect(vaultStore.closeMetaModal).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onExecute', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
return instance.onExecute();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into editVaultMeta', () => {
|
||||||
|
expect(vaultStore.editVaultMeta).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/modals/VaultSelector/index.js
Normal file
17
js/src/modals/VaultSelector/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './vaultSelector';
|
99
js/src/modals/VaultSelector/vaultSelector.js
Normal file
99
js/src/modals/VaultSelector/vaultSelector.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Portal, SelectionList, VaultCard } from '~/ui';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class VaultSelector extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
selected: PropTypes.string,
|
||||||
|
vaultStore: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<Portal
|
||||||
|
isChildModal
|
||||||
|
onClose={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.selector.title'
|
||||||
|
defaultMessage='Select Account Vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{ this.renderList() }
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderList () {
|
||||||
|
const { vaults } = this.props.vaultStore;
|
||||||
|
const openVaults = vaults.filter((vault) => vault.isOpen);
|
||||||
|
|
||||||
|
if (openVaults.length === 0) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.selector.noneAvailable'
|
||||||
|
defaultMessage='There are currently no vaults opened and available for selection. Create and open some first before attempting to select a vault for an account move.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectionList
|
||||||
|
items={ openVaults }
|
||||||
|
isChecked={ this.isSelected }
|
||||||
|
noStretch
|
||||||
|
onSelectClick={ this.onSelect }
|
||||||
|
renderItem={ this.renderVault }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderVault = (vault) => {
|
||||||
|
return (
|
||||||
|
<VaultCard
|
||||||
|
hideAccounts
|
||||||
|
hideButtons
|
||||||
|
vault={ vault }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected = (vault) => {
|
||||||
|
return this.props.selected === vault.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (vault) => {
|
||||||
|
this.props.onSelect(
|
||||||
|
this.props.selected === vault.name
|
||||||
|
? ''
|
||||||
|
: vault.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
}
|
169
js/src/modals/VaultSelector/vaultSelector.spec.js
Normal file
169
js/src/modals/VaultSelector/vaultSelector.spec.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultSelector from './';
|
||||||
|
|
||||||
|
const VAULTS_OPENED = [
|
||||||
|
{ name: 'A', isOpen: true },
|
||||||
|
{ name: 'B', isOpen: true }
|
||||||
|
];
|
||||||
|
const VAULTS_CLOSED = [
|
||||||
|
{ name: 'C' },
|
||||||
|
{ name: 'D' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let onClose;
|
||||||
|
let onSelect;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
vaults: VAULTS_OPENED.concat(VAULTS_CLOSED)
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
onClose = sinon.stub();
|
||||||
|
onSelect = sinon.stub();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<VaultSelector
|
||||||
|
onClose={ onClose }
|
||||||
|
onSelect={ onSelect }
|
||||||
|
selected='firstValue'
|
||||||
|
vaultStore={ createVaultStore() }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/VaultSelector', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('Portal', () => {
|
||||||
|
let portal;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
portal = component.find('Portal');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(portal.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens as a child modal', () => {
|
||||||
|
expect(portal.props().isChildModal).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the instance onClose', () => {
|
||||||
|
expect(portal.props().onClose).to.equal(instance.onClose);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SelectionList', () => {
|
||||||
|
let list;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
list = component.find('SelectionList');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(list.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the open vaults', () => {
|
||||||
|
expect(list.props().items).to.deep.equal(VAULTS_OPENED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes internal renderItem', () => {
|
||||||
|
expect(list.props().renderItem).to.equal(instance.renderVault);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes internal isChecked', () => {
|
||||||
|
expect(list.props().isChecked).to.equal(instance.isSelected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes internal onSelectClick', () => {
|
||||||
|
expect(list.props().onSelectClick).to.equal(instance.onSelect);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('renderVault', () => {
|
||||||
|
let card;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
card = instance.renderVault({ name: 'testVault' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders VaultCard', () => {
|
||||||
|
expect(card).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isSelected', () => {
|
||||||
|
it('returns true when vault name matches', () => {
|
||||||
|
expect(instance.isSelected({ name: 'firstValue' })).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when vault name does not match', () => {
|
||||||
|
expect(instance.isSelected({ name: 'testValue' })).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onSelect', () => {
|
||||||
|
it('calls into props onSelect', () => {
|
||||||
|
instance.onSelect({ name: 'testing' });
|
||||||
|
expect(onSelect).to.have.been.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes name when new selection made', () => {
|
||||||
|
instance.onSelect({ name: 'newValue' });
|
||||||
|
expect(onSelect).to.have.been.calledWith('newValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes empty name when current selection made', () => {
|
||||||
|
instance.onSelect({ name: 'firstValue' });
|
||||||
|
expect(onSelect).to.have.been.calledWith('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onClose', () => {
|
||||||
|
it('calls props onClose', () => {
|
||||||
|
instance.onClose();
|
||||||
|
expect(onClose).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -34,6 +34,8 @@ export UpgradeParity from './UpgradeParity';
|
|||||||
export VaultAccounts from './VaultAccounts';
|
export VaultAccounts from './VaultAccounts';
|
||||||
export VaultCreate from './VaultCreate';
|
export VaultCreate from './VaultCreate';
|
||||||
export VaultLock from './VaultLock';
|
export VaultLock from './VaultLock';
|
||||||
|
export VaultMeta from './VaultMeta';
|
||||||
|
export VaultSelector from './VaultSelector';
|
||||||
export VaultUnlock from './VaultUnlock';
|
export VaultUnlock from './VaultUnlock';
|
||||||
export Verification from './Verification';
|
export Verification from './Verification';
|
||||||
export WalletSettings from './WalletSettings';
|
export WalletSettings from './WalletSettings';
|
||||||
|
@ -31,6 +31,7 @@ class InputAddress extends Component {
|
|||||||
accountsInfo: PropTypes.object,
|
accountsInfo: PropTypes.object,
|
||||||
allowCopy: PropTypes.bool,
|
allowCopy: PropTypes.bool,
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
|
allowInvalid: PropTypes.bool,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
@ -112,9 +113,9 @@ class InputAddress extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderIcon () {
|
renderIcon () {
|
||||||
const { value, disabled, label, allowCopy, hideUnderline, readOnly } = this.props;
|
const { allowInvalid, value, disabled, label, allowCopy, hideUnderline, readOnly } = this.props;
|
||||||
|
|
||||||
if (!value || !value.length || !util.isAddressValid(value)) {
|
if (!value || !value.length || (!util.isAddressValid(value) && !allowInvalid)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,12 +23,13 @@ import styles from './layout.css';
|
|||||||
|
|
||||||
export default class Layout extends Component {
|
export default class Layout extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
vault: PropTypes.object.isRequired,
|
vault: PropTypes.object.isRequired,
|
||||||
withBorder: PropTypes.bool
|
withBorder: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { vault, withBorder } = this.props;
|
const { children, vault, withBorder } = this.props;
|
||||||
const { isOpen, meta, name } = vault;
|
const { isOpen, meta, name } = vault;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -59,6 +60,7 @@ export default class Layout extends Component {
|
|||||||
byline={ meta.description }
|
byline={ meta.description }
|
||||||
title={ name }
|
title={ name }
|
||||||
/>
|
/>
|
||||||
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -30,13 +30,16 @@ export default class VaultCard extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.array,
|
accounts: PropTypes.array,
|
||||||
buttons: PropTypes.array,
|
buttons: PropTypes.array,
|
||||||
|
children: PropTypes.node,
|
||||||
|
hideAccounts: PropTypes.bool,
|
||||||
|
hideButtons: PropTypes.bool,
|
||||||
vault: PropTypes.object.isRequired
|
vault: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
static Layout = Layout;
|
static Layout = Layout;
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { buttons, vault } = this.props;
|
const { children, vault } = this.props;
|
||||||
const { isOpen } = vault;
|
const { isOpen } = vault;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,26 +51,20 @@ export default class VaultCard extends Component {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={ styles.buttons }>
|
{ this.renderButtons() }
|
||||||
<Button
|
<Layout vault={ vault }>
|
||||||
className={ styles.status }
|
{ children }
|
||||||
disabled
|
</Layout>
|
||||||
icon={
|
|
||||||
isOpen
|
|
||||||
? <UnlockedIcon />
|
|
||||||
: <LockedIcon />
|
|
||||||
}
|
|
||||||
key='status'
|
|
||||||
/>
|
|
||||||
{ buttons }
|
|
||||||
</div>
|
|
||||||
<Layout vault={ vault } />
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
const { accounts } = this.props;
|
const { accounts, hideAccounts } = this.props;
|
||||||
|
|
||||||
|
if (hideAccounts) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!accounts || !accounts.length) {
|
if (!accounts || !accounts.length) {
|
||||||
return (
|
return (
|
||||||
@ -101,4 +98,29 @@ export default class VaultCard extends Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
const { buttons, hideButtons, vault } = this.props;
|
||||||
|
const { isOpen } = vault;
|
||||||
|
|
||||||
|
if (hideButtons) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.buttons }>
|
||||||
|
<Button
|
||||||
|
className={ styles.status }
|
||||||
|
disabled
|
||||||
|
icon={
|
||||||
|
isOpen
|
||||||
|
? <UnlockedIcon />
|
||||||
|
: <LockedIcon />
|
||||||
|
}
|
||||||
|
key='status'
|
||||||
|
/>
|
||||||
|
{ buttons }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
.addressline,
|
.addressline,
|
||||||
.infoline,
|
.infoline,
|
||||||
.uuidline,
|
.uuidline,
|
||||||
|
.vault,
|
||||||
.title {
|
.title {
|
||||||
margin-left: 72px;
|
margin-left: 72px;
|
||||||
}
|
}
|
||||||
@ -59,6 +60,15 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vault {
|
||||||
|
line-height: 32px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.addressline {
|
.addressline {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ export default class Header extends Component {
|
|||||||
<CopyToClipboard data={ address } />
|
<CopyToClipboard data={ address } />
|
||||||
<div className={ styles.address }>{ address }</div>
|
<div className={ styles.address }>{ address }</div>
|
||||||
</div>
|
</div>
|
||||||
|
{ this.renderVault() }
|
||||||
{ this.renderUuid() }
|
{ this.renderUuid() }
|
||||||
<div className={ styles.infoline }>
|
<div className={ styles.infoline }>
|
||||||
{ meta.description }
|
{ meta.description }
|
||||||
@ -154,4 +155,25 @@ export default class Header extends Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderVault () {
|
||||||
|
const { account } = this.props;
|
||||||
|
const { meta } = account;
|
||||||
|
|
||||||
|
if (!meta || !meta.vault) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.vault }>
|
||||||
|
<IdentityIcon
|
||||||
|
address={ meta.vault }
|
||||||
|
inline
|
||||||
|
/>
|
||||||
|
<div className={ styles.text }>
|
||||||
|
{ meta.vault }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,10 +27,12 @@ export default class Store {
|
|||||||
@observable isBusyCreate = false;
|
@observable isBusyCreate = false;
|
||||||
@observable isBusyLoad = false;
|
@observable isBusyLoad = false;
|
||||||
@observable isBusyLock = false;
|
@observable isBusyLock = false;
|
||||||
|
@observable isBusyMeta = false;
|
||||||
@observable isBusyUnlock = false;
|
@observable isBusyUnlock = false;
|
||||||
@observable isModalAccountsOpen = false;
|
@observable isModalAccountsOpen = false;
|
||||||
@observable isModalCreateOpen = false;
|
@observable isModalCreateOpen = false;
|
||||||
@observable isModalLockOpen = false;
|
@observable isModalLockOpen = false;
|
||||||
|
@observable isModalMetaOpen = false;
|
||||||
@observable isModalUnlockOpen = false;
|
@observable isModalUnlockOpen = false;
|
||||||
@observable selectedAccounts = {};
|
@observable selectedAccounts = {};
|
||||||
@observable vault = null;
|
@observable vault = null;
|
||||||
@ -41,7 +43,9 @@ export default class Store {
|
|||||||
@observable vaultDescription = '';
|
@observable vaultDescription = '';
|
||||||
@observable vaultPassword = '';
|
@observable vaultPassword = '';
|
||||||
@observable vaultPasswordHint = '';
|
@observable vaultPasswordHint = '';
|
||||||
|
@observable vaultPasswordOld = '';
|
||||||
@observable vaultPasswordRepeat = '';
|
@observable vaultPasswordRepeat = '';
|
||||||
|
@observable vaultTags = [];
|
||||||
|
|
||||||
constructor (api) {
|
constructor (api) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
@ -59,7 +63,9 @@ export default class Store {
|
|||||||
this.setVaultDescription('');
|
this.setVaultDescription('');
|
||||||
this.setVaultPassword('');
|
this.setVaultPassword('');
|
||||||
this.setVaultPasswordHint('');
|
this.setVaultPasswordHint('');
|
||||||
|
this.setVaultPasswordOld('');
|
||||||
this.setVaultPasswordRepeat('');
|
this.setVaultPasswordRepeat('');
|
||||||
|
this.setVaultTags([]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +85,10 @@ export default class Store {
|
|||||||
this.isBusyLock = isBusy;
|
this.isBusyLock = isBusy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setBusyMeta = (isBusy) => {
|
||||||
|
this.isBusyMeta = isBusy;
|
||||||
|
}
|
||||||
|
|
||||||
@action setBusyUnlock = (isBusy) => {
|
@action setBusyUnlock = (isBusy) => {
|
||||||
this.isBusyUnlock = isBusy;
|
this.isBusyUnlock = isBusy;
|
||||||
}
|
}
|
||||||
@ -104,6 +114,13 @@ export default class Store {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setModalMetaOpen = (isOpen) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setBusyMeta(false);
|
||||||
|
this.isModalMetaOpen = isOpen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@action setModalUnlockOpen = (isOpen) => {
|
@action setModalUnlockOpen = (isOpen) => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.setBusyUnlock(false);
|
this.setBusyUnlock(false);
|
||||||
@ -161,10 +178,18 @@ export default class Store {
|
|||||||
this.vaultPasswordHint = hint;
|
this.vaultPasswordHint = hint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setVaultPasswordOld = (password) => {
|
||||||
|
this.vaultPasswordOld = password;
|
||||||
|
}
|
||||||
|
|
||||||
@action setVaultPasswordRepeat = (password) => {
|
@action setVaultPasswordRepeat = (password) => {
|
||||||
this.vaultPasswordRepeat = password;
|
this.vaultPasswordRepeat = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setVaultTags = (tags) => {
|
||||||
|
this.vaultTags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
@action toggleSelectedAccount = (address) => {
|
@action toggleSelectedAccount = (address) => {
|
||||||
this.setSelectedAccounts(Object.assign({}, this.selectedAccounts, {
|
this.setSelectedAccounts(Object.assign({}, this.selectedAccounts, {
|
||||||
[address]: !this.selectedAccounts[address] })
|
[address]: !this.selectedAccounts[address] })
|
||||||
@ -183,6 +208,10 @@ export default class Store {
|
|||||||
this.setModalLockOpen(false);
|
this.setModalLockOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeMetaModal () {
|
||||||
|
this.setModalMetaOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
closeUnlockModal () {
|
closeUnlockModal () {
|
||||||
this.setModalUnlockOpen(false);
|
this.setModalUnlockOpen(false);
|
||||||
}
|
}
|
||||||
@ -209,6 +238,20 @@ export default class Store {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openMetaModal (name) {
|
||||||
|
transaction(() => {
|
||||||
|
this.clearVaultFields();
|
||||||
|
this.setVaultName(name);
|
||||||
|
|
||||||
|
if (this.vault && this.vault.meta) {
|
||||||
|
this.setVaultDescription(this.vault.meta.description);
|
||||||
|
this.setVaultPasswordHint(this.vault.meta.passwordHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setModalMetaOpen(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
openUnlockModal (name) {
|
openUnlockModal (name) {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.setVaultName(name);
|
this.setVaultName(name);
|
||||||
@ -268,7 +311,8 @@ export default class Store {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this._api.parity.setVaultMeta(this.vaultName, {
|
return this._api.parity.setVaultMeta(this.vaultName, {
|
||||||
description: this.vaultDescription,
|
description: this.vaultDescription,
|
||||||
passwordHint: this.vaultPasswordHint
|
passwordHint: this.vaultPasswordHint,
|
||||||
|
tags: this.vaultTags
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(this.loadVaults)
|
.then(this.loadVaults)
|
||||||
@ -282,6 +326,48 @@ export default class Store {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editVaultMeta () {
|
||||||
|
this.setBusyMeta(true);
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.setVaultMeta(this.vaultName, {
|
||||||
|
description: this.vaultDescription,
|
||||||
|
passwordHint: this.vaultPasswordHint,
|
||||||
|
tags: this.vaultTags
|
||||||
|
})
|
||||||
|
.then(this.loadVaults)
|
||||||
|
.then(() => {
|
||||||
|
this.setBusyMeta(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('editVaultMeta', error);
|
||||||
|
this.setBusyMeta(false);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editVaultPassword () {
|
||||||
|
this.setBusyMeta(true);
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.closeVault(this.vaultName)
|
||||||
|
.then(() => {
|
||||||
|
return this._api.parity.openVault(this.vaultName, this.vaultPasswordOld);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return this._api.parity.changeVaultPassword(this.vaultName, this.vaultPassword);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setBusyMeta(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('editVaultPassword', error);
|
||||||
|
this.loadVaults();
|
||||||
|
this.setBusyMeta(false);
|
||||||
|
throw new Error('Unable to change the vault password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
openVault () {
|
openVault () {
|
||||||
this.setBusyUnlock(true);
|
this.setBusyUnlock(true);
|
||||||
|
|
||||||
@ -307,8 +393,28 @@ export default class Store {
|
|||||||
outAccounts.map((address) => this._api.parity.changeVault(address, ''))
|
outAccounts.map((address) => this._api.parity.changeVault(address, ''))
|
||||||
])
|
])
|
||||||
.then(this.loadVaults)
|
.then(this.loadVaults)
|
||||||
|
.then(() => {
|
||||||
|
this.setBusyAccounts(false);
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('moveAccounts', error);
|
console.error('moveAccounts', error);
|
||||||
|
this.setBusyAccounts(false);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
moveAccount (vaultName, address) {
|
||||||
|
this.setBusyAccounts(true);
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.changeVault(address, vaultName)
|
||||||
|
.then(this.loadVaults)
|
||||||
|
.then(() => {
|
||||||
|
this.setBusyAccounts(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('moveAccount', error);
|
||||||
|
this.setBusyAccounts(false);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ describe('modals/Vaults/Store', () => {
|
|||||||
store.setVaultPassword('blah');
|
store.setVaultPassword('blah');
|
||||||
store.setVaultPasswordRepeat('bleh');
|
store.setVaultPasswordRepeat('bleh');
|
||||||
store.setVaultPasswordHint('hint');
|
store.setVaultPasswordHint('hint');
|
||||||
|
store.setVaultPasswordOld('old');
|
||||||
|
store.setVaultTags('tags');
|
||||||
|
|
||||||
store.clearVaultFields();
|
store.clearVaultFields();
|
||||||
});
|
});
|
||||||
@ -55,6 +57,8 @@ describe('modals/Vaults/Store', () => {
|
|||||||
expect(store.vaultPassword).to.equal('');
|
expect(store.vaultPassword).to.equal('');
|
||||||
expect(store.vaultPasswordRepeat).to.equal('');
|
expect(store.vaultPasswordRepeat).to.equal('');
|
||||||
expect(store.vaultPasswordHint).to.equal('');
|
expect(store.vaultPasswordHint).to.equal('');
|
||||||
|
expect(store.vaultPasswordOld).to.equal('');
|
||||||
|
expect(store.vaultTags.length).to.equal(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,6 +94,14 @@ describe('modals/Vaults/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setBusyMeta', () => {
|
||||||
|
it('sets the flag', () => {
|
||||||
|
store.setBusyMeta('busy');
|
||||||
|
|
||||||
|
expect(store.isBusyMeta).to.equal('busy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setBusyUnlock', () => {
|
describe('setBusyUnlock', () => {
|
||||||
it('sets the flag', () => {
|
it('sets the flag', () => {
|
||||||
store.setBusyUnlock('busy');
|
store.setBusyUnlock('busy');
|
||||||
@ -122,6 +134,14 @@ describe('modals/Vaults/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setModalMetaOpen', () => {
|
||||||
|
it('sets the flag', () => {
|
||||||
|
store.setModalMetaOpen('opened');
|
||||||
|
|
||||||
|
expect(store.isModalMetaOpen).to.equal('opened');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setModalUnlockOpen', () => {
|
describe('setModalUnlockOpen', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.setVaultPassword('testing');
|
store.setVaultPassword('testing');
|
||||||
@ -233,6 +253,14 @@ describe('modals/Vaults/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setVaultTags', () => {
|
||||||
|
it('sets the tags', () => {
|
||||||
|
store.setVaultTags('test');
|
||||||
|
|
||||||
|
expect(store.vaultTags).to.equal('test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('toggleSelectedAccount', () => {
|
describe('toggleSelectedAccount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.toggleSelectedAccount('123');
|
store.toggleSelectedAccount('123');
|
||||||
@ -301,6 +329,17 @@ describe('modals/Vaults/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('closeMetaModal', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setModalMetaOpen(true);
|
||||||
|
store.closeMetaModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the opened state to false', () => {
|
||||||
|
expect(store.isModalMetaOpen).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('closeUnlockModal', () => {
|
describe('closeUnlockModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.setModalUnlockOpen(true);
|
store.setModalUnlockOpen(true);
|
||||||
@ -364,6 +403,20 @@ describe('modals/Vaults/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('openMetaModal', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.openMetaModal('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the opened state to true', () => {
|
||||||
|
expect(store.isModalMetaOpen).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stores the name', () => {
|
||||||
|
expect(store.vaultName).to.equal('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('openUnlockModal', () => {
|
describe('openUnlockModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.openUnlockModal('testing');
|
store.openUnlockModal('testing');
|
||||||
@ -443,6 +496,7 @@ describe('modals/Vaults/Store', () => {
|
|||||||
store.setVaultPassword('testCreatePassword');
|
store.setVaultPassword('testCreatePassword');
|
||||||
store.setVaultPasswordRepeat('testCreatePassword');
|
store.setVaultPasswordRepeat('testCreatePassword');
|
||||||
store.setVaultPasswordHint('testCreateHint');
|
store.setVaultPasswordHint('testCreateHint');
|
||||||
|
store.setVaultTags('testTags');
|
||||||
|
|
||||||
return store.createVault();
|
return store.createVault();
|
||||||
});
|
});
|
||||||
@ -463,11 +517,71 @@ describe('modals/Vaults/Store', () => {
|
|||||||
it('calls into parity_setVaultMeta', () => {
|
it('calls into parity_setVaultMeta', () => {
|
||||||
expect(api.parity.setVaultMeta).to.have.been.calledWith('testCreateName', {
|
expect(api.parity.setVaultMeta).to.have.been.calledWith('testCreateName', {
|
||||||
description: 'testDescription',
|
description: 'testDescription',
|
||||||
passwordHint: 'testCreateHint'
|
passwordHint: 'testCreateHint',
|
||||||
|
tags: 'testTags'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('editVaultMeta', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.spy(store, 'setBusyMeta');
|
||||||
|
|
||||||
|
store.setVaultDescription('testDescription');
|
||||||
|
store.setVaultName('testCreateName');
|
||||||
|
store.setVaultPasswordHint('testCreateHint');
|
||||||
|
store.setVaultTags('testTags');
|
||||||
|
|
||||||
|
return store.editVaultMeta();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
store.setBusyMeta.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets and resets the busy flag', () => {
|
||||||
|
expect(store.setBusyMeta).to.have.been.calledWith(true);
|
||||||
|
expect(store.isBusyMeta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into parity_setVaultMeta', () => {
|
||||||
|
expect(api.parity.setVaultMeta).to.have.been.calledWith('testCreateName', {
|
||||||
|
description: 'testDescription',
|
||||||
|
passwordHint: 'testCreateHint',
|
||||||
|
tags: 'testTags'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('editVaultPassword', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.spy(store, 'setBusyMeta');
|
||||||
|
|
||||||
|
store.setVaultName('testName');
|
||||||
|
store.setVaultPasswordOld('oldPassword');
|
||||||
|
store.setVaultPassword('newPassword');
|
||||||
|
|
||||||
|
return store.editVaultPassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
store.setBusyMeta.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets and resets the busy flag', () => {
|
||||||
|
expect(store.setBusyMeta).to.have.been.calledWith(true);
|
||||||
|
expect(store.isBusyMeta).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into parity_openVault', () => {
|
||||||
|
expect(api.parity.openVault).to.have.been.calledWith('testName', 'oldPassword');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into parity_changeVaultPassword', () => {
|
||||||
|
expect(api.parity.changeVaultPassword).to.have.been.calledWith('testName', 'newPassword');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('openVault', () => {
|
describe('openVault', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.spy(store, 'setBusyUnlock');
|
sinon.spy(store, 'setBusyUnlock');
|
||||||
@ -512,5 +626,25 @@ describe('modals/Vaults/Store', () => {
|
|||||||
expect(api.parity.changeVault).to.have.been.calledWith('C', '');
|
expect(api.parity.changeVault).to.have.been.calledWith('C', '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('moveAccount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.spy(store, 'setBusyAccounts');
|
||||||
|
|
||||||
|
return store.moveAccount('testVault', 'A');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
store.setBusyAccounts.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the busy flag', () => {
|
||||||
|
expect(store.setBusyAccounts).to.have.been.calledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into parity_changeVault', () => {
|
||||||
|
expect(api.parity.changeVault).to.have.been.calledWith('A', 'testVault');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -19,9 +19,9 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { VaultAccounts, VaultCreate, VaultLock, VaultUnlock } from '~/modals';
|
import { VaultAccounts, VaultCreate, VaultLock, VaultMeta, VaultUnlock } from '~/modals';
|
||||||
import { Button, Container, Page, SectionList, VaultCard } from '~/ui';
|
import { Button, Container, Page, SectionList, VaultCard } from '~/ui';
|
||||||
import { AccountsIcon, AddIcon, LockedIcon, UnlockedIcon } from '~/ui/Icons';
|
import { AccountsIcon, AddIcon, EditIcon, LockedIcon, UnlockedIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
import styles from './vaults.css';
|
import styles from './vaults.css';
|
||||||
@ -70,6 +70,7 @@ class Vaults extends Component {
|
|||||||
<VaultAccounts vaultStore={ this.vaultStore } />
|
<VaultAccounts vaultStore={ this.vaultStore } />
|
||||||
<VaultCreate vaultStore={ this.vaultStore } />
|
<VaultCreate vaultStore={ this.vaultStore } />
|
||||||
<VaultLock vaultStore={ this.vaultStore } />
|
<VaultLock vaultStore={ this.vaultStore } />
|
||||||
|
<VaultMeta vaultStore={ this.vaultStore } />
|
||||||
<VaultUnlock vaultStore={ this.vaultStore } />
|
<VaultUnlock vaultStore={ this.vaultStore } />
|
||||||
{ this.renderList() }
|
{ this.renderList() }
|
||||||
</Page>
|
</Page>
|
||||||
@ -109,6 +110,10 @@ class Vaults extends Component {
|
|||||||
this.onOpenAccounts(name);
|
this.onOpenAccounts(name);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
const onClickEdit = () => {
|
||||||
|
this.onOpenEdit(name);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
const onClickOpen = () => {
|
const onClickOpen = () => {
|
||||||
isOpen
|
isOpen
|
||||||
? this.onOpenLockVault(name)
|
? this.onOpenLockVault(name)
|
||||||
@ -133,13 +138,24 @@ class Vaults extends Component {
|
|||||||
}
|
}
|
||||||
onClick={ onClickAccounts }
|
onClick={ onClickAccounts }
|
||||||
/>,
|
/>,
|
||||||
|
<Button
|
||||||
|
icon={ <EditIcon /> }
|
||||||
|
key='edit'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='vaults.button.edit'
|
||||||
|
defaultMessage='edit'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ onClickEdit }
|
||||||
|
/>,
|
||||||
<Button
|
<Button
|
||||||
icon={ <LockedIcon /> }
|
icon={ <LockedIcon /> }
|
||||||
key='close'
|
key='close'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='vaults.button.close'
|
id='vaults.button.close'
|
||||||
defaultMessage='close vault'
|
defaultMessage='close'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onClick={ onClickOpen }
|
onClick={ onClickOpen }
|
||||||
@ -152,7 +168,7 @@ class Vaults extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='vaults.button.open'
|
id='vaults.button.open'
|
||||||
defaultMessage='open vault'
|
defaultMessage='open'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onClick={ onClickOpen }
|
onClick={ onClickOpen }
|
||||||
@ -172,10 +188,18 @@ class Vaults extends Component {
|
|||||||
this.vaultStore.openCreateModal();
|
this.vaultStore.openCreateModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOpenEdit = (name) => {
|
||||||
|
this.vaultStore.openMetaModal(name);
|
||||||
|
}
|
||||||
|
|
||||||
onOpenLockVault = (name) => {
|
onOpenLockVault = (name) => {
|
||||||
this.vaultStore.openLockModal(name);
|
this.vaultStore.openLockModal(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOpenMeta = (name) => {
|
||||||
|
this.vaultStore.openMetaModal(name);
|
||||||
|
}
|
||||||
|
|
||||||
onOpenUnlockVault = (name) => {
|
onOpenUnlockVault = (name) => {
|
||||||
this.vaultStore.openUnlockModal(name);
|
this.vaultStore.openUnlockModal(name);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ function render (props = {}) {
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('modals/Vaults', () => {
|
describe('views/Vaults', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
@ -150,6 +150,22 @@ describe('modals/Vaults', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('onOpenEdit', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.spy(instance.vaultStore, 'openMetaModal');
|
||||||
|
|
||||||
|
instance.onOpenEdit('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance.vaultStore.openMetaModal.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into vaultStore.openMetaModal', () => {
|
||||||
|
expect(instance.vaultStore.openMetaModal).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('onOpenLockVault', () => {
|
describe('onOpenLockVault', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.spy(instance.vaultStore, 'openLockModal');
|
sinon.spy(instance.vaultStore, 'openLockModal');
|
||||||
|
@ -63,7 +63,8 @@ export function createApi () {
|
|||||||
getVaultMeta: sinon.stub().resolves(TEST_VAULTS_META),
|
getVaultMeta: sinon.stub().resolves(TEST_VAULTS_META),
|
||||||
newVault: sinon.stub().resolves(true),
|
newVault: sinon.stub().resolves(true),
|
||||||
openVault: sinon.stub().resolves(true),
|
openVault: sinon.stub().resolves(true),
|
||||||
setVaultMeta: sinon.stub().resolves(true)
|
setVaultMeta: sinon.stub().resolves(true),
|
||||||
|
changeVaultPassword: sinon.stub().resolves(true)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user