.
+
+export default from './vaultMeta';
diff --git a/js/src/modals/VaultMeta/vaultMeta.js b/js/src/modals/VaultMeta/vaultMeta.js
new file mode 100644
index 000000000..5089af642
--- /dev/null
+++ b/js/src/modals/VaultMeta/vaultMeta.js
@@ -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
.
+
+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 (
+
}
+ key='close'
+ label={
+
+ }
+ onClick={ this.onClose }
+ />,
+
}
+ key='vault'
+ label={
+
+ }
+ onClick={ this.onExecute }
+ />
+ ] }
+ onClose={ this.onClose }
+ open
+ title={
+
+ }
+ >
+
+
+
+
+ );
+
+ //
+ // }
+ // label={
+ //
+ // }
+ // 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);
diff --git a/js/src/modals/VaultMeta/vaultMeta.spec.js b/js/src/modals/VaultMeta/vaultMeta.spec.js
new file mode 100644
index 000000000..c616c8e6a
--- /dev/null
+++ b/js/src/modals/VaultMeta/vaultMeta.spec.js
@@ -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
.
+
+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(
+
,
+ {
+ 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;
+ });
+ });
+ });
+});
diff --git a/js/src/modals/VaultSelector/index.js b/js/src/modals/VaultSelector/index.js
new file mode 100644
index 000000000..95e5998a4
--- /dev/null
+++ b/js/src/modals/VaultSelector/index.js
@@ -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
.
+
+export default from './vaultSelector';
diff --git a/js/src/modals/VaultSelector/vaultSelector.js b/js/src/modals/VaultSelector/vaultSelector.js
new file mode 100644
index 000000000..a2ae48294
--- /dev/null
+++ b/js/src/modals/VaultSelector/vaultSelector.js
@@ -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
.
+
+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 (
+
+ }
+ >
+ { this.renderList() }
+
+ );
+ }
+
+ renderList () {
+ const { vaults } = this.props.vaultStore;
+ const openVaults = vaults.filter((vault) => vault.isOpen);
+
+ if (openVaults.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ }
+
+ renderVault = (vault) => {
+ return (
+
+ );
+ }
+
+ isSelected = (vault) => {
+ return this.props.selected === vault.name;
+ }
+
+ onSelect = (vault) => {
+ this.props.onSelect(
+ this.props.selected === vault.name
+ ? ''
+ : vault.name
+ );
+ }
+
+ onClose = () => {
+ this.props.onClose();
+ }
+}
diff --git a/js/src/modals/VaultSelector/vaultSelector.spec.js b/js/src/modals/VaultSelector/vaultSelector.spec.js
new file mode 100644
index 000000000..2be5c4637
--- /dev/null
+++ b/js/src/modals/VaultSelector/vaultSelector.spec.js
@@ -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
.
+
+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(
+
+ );
+ 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;
+ });
+ });
+ });
+});
diff --git a/js/src/modals/index.js b/js/src/modals/index.js
index 0e760d501..e64c90dce 100644
--- a/js/src/modals/index.js
+++ b/js/src/modals/index.js
@@ -34,6 +34,8 @@ export UpgradeParity from './UpgradeParity';
export VaultAccounts from './VaultAccounts';
export VaultCreate from './VaultCreate';
export VaultLock from './VaultLock';
+export VaultMeta from './VaultMeta';
+export VaultSelector from './VaultSelector';
export VaultUnlock from './VaultUnlock';
export Verification from './Verification';
export WalletSettings from './WalletSettings';
diff --git a/js/src/ui/Form/InputAddress/inputAddress.js b/js/src/ui/Form/InputAddress/inputAddress.js
index 6a4f6c990..12e2eb02d 100644
--- a/js/src/ui/Form/InputAddress/inputAddress.js
+++ b/js/src/ui/Form/InputAddress/inputAddress.js
@@ -31,6 +31,7 @@ class InputAddress extends Component {
accountsInfo: PropTypes.object,
allowCopy: PropTypes.bool,
autoFocus: PropTypes.bool,
+ allowInvalid: PropTypes.bool,
className: PropTypes.string,
disabled: PropTypes.bool,
error: PropTypes.string,
@@ -112,9 +113,9 @@ class InputAddress extends Component {
}
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;
}
diff --git a/js/src/ui/VaultCard/Layout/layout.js b/js/src/ui/VaultCard/Layout/layout.js
index f525abe5a..8ef795791 100644
--- a/js/src/ui/VaultCard/Layout/layout.js
+++ b/js/src/ui/VaultCard/Layout/layout.js
@@ -23,12 +23,13 @@ import styles from './layout.css';
export default class Layout extends Component {
static propTypes = {
+ children: PropTypes.node,
vault: PropTypes.object.isRequired,
withBorder: PropTypes.bool
};
render () {
- const { vault, withBorder } = this.props;
+ const { children, vault, withBorder } = this.props;
const { isOpen, meta, name } = vault;
return (
@@ -59,6 +60,7 @@ export default class Layout extends Component {
byline={ meta.description }
title={ name }
/>
+ { children }