diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js
index c1f070262..15b037b42 100644
--- a/js/src/api/subscriptions/personal.js
+++ b/js/src/api/subscriptions/personal.js
@@ -119,7 +119,6 @@ export default class Personal {
case 'parity_removeAddress':
case 'parity_setAccountName':
case 'parity_setAccountMeta':
- case 'parity_changeVault':
this._accountsInfo();
return;
diff --git a/js/src/modals/CreateAccount/createAccount.css b/js/src/modals/CreateAccount/createAccount.css
index 5c8bc7e5c..d58e0edfd 100644
--- a/js/src/modals/CreateAccount/createAccount.css
+++ b/js/src/modals/CreateAccount/createAccount.css
@@ -19,23 +19,24 @@
line-height: 1.618em;
}
-.password {
- flex: 0 1 50%;
- width: 50%;
- box-sizing: border-box;
-
- &:nth-child(odd) {
- padding-right: 0.25rem;
- }
-
- &:nth-child(even) {
- padding-left: 0.25rem;
- }
-}
-
+/* TODO: 2 column layout can be made generic, now duplicated in Vaults */
.passwords {
display: flex;
flex-wrap: wrap;
+
+ .password {
+ box-sizing: border-box;
+ flex: 0 1 50%;
+ width: 50%;
+
+ &:nth-child(odd) {
+ padding-right: 0.25rem;
+ }
+
+ &:nth-child(even) {
+ padding-left: 0.25rem;
+ }
+ }
}
.identities, .selector {
diff --git a/js/src/modals/CreateAccount/errors.js b/js/src/modals/CreateAccount/errors.js
index 487bf9729..bb5708275 100644
--- a/js/src/modals/CreateAccount/errors.js
+++ b/js/src/modals/CreateAccount/errors.js
@@ -18,37 +18,44 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
export default {
+ duplicateName: (
+
+ ),
+
noFile: (
),
noKey: (
),
noMatchPassword: (
),
noName: (
),
invalidKey: (
)
diff --git a/js/src/modals/VaultAccounts/index.js b/js/src/modals/VaultAccounts/index.js
new file mode 100644
index 000000000..506a569f3
--- /dev/null
+++ b/js/src/modals/VaultAccounts/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 './vaultAccounts';
diff --git a/js/src/modals/VaultAccounts/vaultAccounts.css b/js/src/modals/VaultAccounts/vaultAccounts.css
new file mode 100644
index 000000000..1960376f3
--- /dev/null
+++ b/js/src/modals/VaultAccounts/vaultAccounts.css
@@ -0,0 +1,48 @@
+/* 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 .
+*/
+
+/* TODO: These overlap with DappPermissions now, make DRY */
+/* (selection component or just styles?) */
+.iconDisabled {
+ opacity: 0.15;
+}
+
+.item {
+ display: flex;
+ flex: 1;
+ position: relative;
+
+ .overlay {
+ position: absolute;
+ right: 0.5em;
+ top: 0.5em;
+ }
+}
+
+.selected,
+.unselected {
+ margin-bottom: 0.25em;
+ width: 100%;
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.selected {
+ background: rgba(255, 255, 255, 0.15) !important;
+}
diff --git a/js/src/modals/VaultAccounts/vaultAccounts.js b/js/src/modals/VaultAccounts/vaultAccounts.js
new file mode 100644
index 000000000..96f170f79
--- /dev/null
+++ b/js/src/modals/VaultAccounts/vaultAccounts.js
@@ -0,0 +1,195 @@
+// 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 { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { newError } from '~/redux/actions';
+import { personalAccountsInfo } from '~/redux/providers/personalActions';
+import { AccountCard, Button, Portal, SectionList } from '~/ui';
+import { CancelIcon, CheckIcon } from '~/ui/Icons';
+
+import styles from './vaultAccounts.css';
+
+@observer
+class VaultAccounts extends Component {
+ static contextTypes = {
+ api: PropTypes.object.isRequired
+ };
+
+ static propTypes = {
+ accounts: PropTypes.object.isRequired,
+ newError: PropTypes.func.isRequired,
+ personalAccountsInfo: PropTypes.func.isRequired,
+ vaultStore: PropTypes.object.isRequired
+ };
+
+ render () {
+ const { accounts } = this.props;
+ const { isBusyAccounts, isModalAccountsOpen, selectedAccounts } = this.props.vaultStore;
+
+ if (!isModalAccountsOpen) {
+ return null;
+ }
+
+ const vaultAccounts = Object
+ .keys(accounts)
+ .filter((address) => accounts[address].uuid)
+ .map((address) => accounts[address]);
+
+ return (
+ }
+ key='cancel'
+ label={
+
+ }
+ onClick={ this.onClose }
+ />,
+ }
+ key='execute'
+ label={
+
+ }
+ onClick={ this.onExecute }
+ />
+ ] }
+ busy={ isBusyAccounts }
+ onClose={ this.onClose }
+ open
+ title={
+
+ }
+ >
+
+
+ );
+ }
+
+ // TODO: There are a lot of similarities between the dapp permissions selector
+ // (although that has defaults) and this one. A genrerix multi-select component
+ // would be applicable going forward. (Originals passed in, new selections back)
+ renderAccount = (account) => {
+ const { vaultName, selectedAccounts } = this.props.vaultStore;
+ const isInVault = account.meta.vault === vaultName;
+ const isSelected = isInVault
+ ? !selectedAccounts[account.address]
+ : selectedAccounts[account.address];
+
+ const onSelect = () => {
+ this.props.vaultStore.toggleSelectedAccount(account.address);
+ };
+
+ return (
+
+
+
+ {
+ isSelected
+ ?
+ :
+ }
+
+
+ );
+ }
+
+ onClose = () => {
+ this.props.vaultStore.closeAccountsModal();
+ }
+
+ onExecute = () => {
+ const { api } = this.context;
+ const { accounts, personalAccountsInfo, vaultStore } = this.props;
+ const { vaultName, selectedAccounts } = this.props.vaultStore;
+
+ const vaultAccounts = Object
+ .keys(accounts)
+ .filter((address) => accounts[address].uuid && selectedAccounts[address])
+ .map((address) => accounts[address]);
+
+ return vaultStore
+ .moveAccounts(
+ vaultName,
+ vaultAccounts
+ .filter((account) => account.meta.vault !== vaultName)
+ .map((account) => account.address),
+ vaultAccounts
+ .filter((account) => account.meta.vault === vaultName)
+ .map((account) => account.address)
+ )
+ .catch(this.props.newError)
+ .then(() => {
+ // TODO: We manually call parity_allAccountsInfo after all the promises
+ // have been resolved. If bulk moves do become available in the future,
+ // subscriptions can transparently take care of this instead of calling
+ // and manually dispatching an update. (Using subscriptions currently
+ // means allAccountsInfo is called after each and every move call)
+ return api.parity
+ .allAccountsInfo()
+ .then(personalAccountsInfo);
+ })
+ .then(this.onClose);
+ }
+}
+
+function mapStateToProps (state) {
+ const { accounts } = state.personal;
+
+ return { accounts };
+}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ newError,
+ personalAccountsInfo
+ }, dispatch);
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(VaultAccounts);
diff --git a/js/src/modals/VaultAccounts/vaultAccounts.spec.js b/js/src/modals/VaultAccounts/vaultAccounts.spec.js
new file mode 100644
index 000000000..dc477a453
--- /dev/null
+++ b/js/src/modals/VaultAccounts/vaultAccounts.spec.js
@@ -0,0 +1,179 @@
+// 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 VaultAccounts from './';
+
+const ACCOUNT_A = '0x1234567890123456789012345678901234567890';
+const ACCOUNT_B = '0x0123456789012345678901234567890123456789';
+const ACCOUNT_C = '0x9012345678901234567890123456789012345678';
+const ACCOUNT_D = '0x8901234567890123456789012345678901234567';
+const VAULTNAME = 'testVault';
+const ACCOUNTS = {
+ [ACCOUNT_A]: {
+ address: ACCOUNT_A,
+ uuid: null
+ },
+ [ACCOUNT_B]: {
+ address: ACCOUNT_B,
+ uuid: ACCOUNT_B,
+ meta: {
+ vault: 'somethingElse'
+ }
+ },
+ [ACCOUNT_C]: {
+ address: ACCOUNT_C,
+ uuid: ACCOUNT_C,
+ meta: {
+ vault: VAULTNAME
+ }
+ },
+ [ACCOUNT_D]: {
+ address: ACCOUNT_D,
+ uuid: ACCOUNT_D,
+ meta: {
+ vault: VAULTNAME
+ }
+ }
+};
+
+let api;
+let component;
+let instance;
+let reduxStore;
+let vaultStore;
+
+function createApi () {
+ api = {
+ parity: {
+ allAccountsInfo: sinon.stub().resolves({})
+ }
+ };
+
+ return api;
+}
+
+function createReduxStore () {
+ reduxStore = {
+ dispatch: sinon.stub(),
+ subscribe: sinon.stub(),
+ getState: () => {
+ return {
+ personal: {
+ accounts: ACCOUNTS
+ }
+ };
+ }
+ };
+
+ return reduxStore;
+}
+
+function createVaultStore () {
+ vaultStore = {
+ isBusyAccounts: false,
+ isModalAccountsOpen: true,
+ selectedAccounts: { [ACCOUNT_B]: true, [ACCOUNT_C]: true },
+ vaultName: VAULTNAME,
+ closeAccountsModal: sinon.stub(),
+ moveAccounts: sinon.stub().resolves(true),
+ toggleSelectedAccount: sinon.stub()
+ };
+
+ return vaultStore;
+}
+
+function render () {
+ component = shallow(
+ ,
+ {
+ context: {
+ store: createReduxStore()
+ }
+ }
+ ).find('VaultAccounts').shallow({
+ context: {
+ api: createApi()
+ }
+ });
+ instance = component.instance();
+
+ return component;
+}
+
+describe('modals/VaultAccounts', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('components', () => {
+ describe('SectionList', () => {
+ let sectionList;
+
+ beforeEach(() => {
+ sectionList = component.find('SectionList');
+ });
+
+ it('has the filtered accounts', () => {
+ expect(sectionList.props().items).to.deep.equal([
+ ACCOUNTS[ACCOUNT_B], ACCOUNTS[ACCOUNT_C], ACCOUNTS[ACCOUNT_D]
+ ]);
+ });
+
+ it('renders via renderAccount', () => {
+ expect(sectionList.props().renderItem).to.equal(instance.renderAccount);
+ });
+ });
+ });
+
+ describe('event handlers', () => {
+ describe('onClose', () => {
+ beforeEach(() => {
+ instance.onClose();
+ });
+
+ it('calls into closeAccountsModal', () => {
+ expect(vaultStore.closeAccountsModal).to.have.been.called;
+ });
+ });
+
+ describe('onExecute', () => {
+ beforeEach(() => {
+ sinon.spy(instance, 'onClose');
+ return instance.onExecute();
+ });
+
+ afterEach(() => {
+ instance.onClose.restore();
+ });
+
+ it('calls into moveAccounts', () => {
+ expect(vaultStore.moveAccounts).to.have.been.calledWith(VAULTNAME, [ACCOUNT_B], [ACCOUNT_C]);
+ });
+
+ it('closes modal', () => {
+ expect(instance.onClose).to.have.been.called;
+ });
+ });
+ });
+});
diff --git a/js/src/modals/VaultCreate/index.js b/js/src/modals/VaultCreate/index.js
new file mode 100644
index 000000000..24bf5bf06
--- /dev/null
+++ b/js/src/modals/VaultCreate/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 './vaultCreate';
diff --git a/js/src/modals/VaultCreate/vaultCreate.css b/js/src/modals/VaultCreate/vaultCreate.css
new file mode 100644
index 000000000..fcd2e26a3
--- /dev/null
+++ b/js/src/modals/VaultCreate/vaultCreate.css
@@ -0,0 +1,38 @@
+/* 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 .
+*/
+
+.body {
+ /* TODO: These styles are shared with CreateAccount - DRY up */
+ .passwords {
+ display: flex;
+ flex-wrap: wrap;
+
+ .password {
+ box-sizing: border-box;
+ flex: 0 1 50%;
+ width: 50%;
+
+ &:nth-child(odd) {
+ padding-right: 0.25rem;
+ }
+
+ &:nth-child(even) {
+ padding-left: 0.25rem;
+ }
+ }
+ }
+}
diff --git a/js/src/modals/VaultCreate/vaultCreate.js b/js/src/modals/VaultCreate/vaultCreate.js
new file mode 100644
index 000000000..ba27e1a9c
--- /dev/null
+++ b/js/src/modals/VaultCreate/vaultCreate.js
@@ -0,0 +1,227 @@
+// 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 { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { newError } from '~/redux/actions';
+import { Button, Input, Portal } from '~/ui';
+import PasswordStrength from '~/ui/Form/PasswordStrength';
+import { CheckIcon, CloseIcon } from '~/ui/Icons';
+
+import styles from './vaultCreate.css';
+
+@observer
+class VaultCreate extends Component {
+ static propTypes = {
+ newError: PropTypes.func.isRequired,
+ vaultStore: PropTypes.object.isRequired
+ }
+
+ render () {
+ const { isBusyCreate, isModalCreateOpen, vaultDescription, vaultName, vaultNameError, vaultPassword, vaultPasswordHint, vaultPasswordRepeat, vaultPasswordRepeatError } = this.props.vaultStore;
+ const hasError = !!vaultNameError || !!vaultPasswordRepeatError;
+
+ if (!isModalCreateOpen) {
+ return null;
+ }
+
+ return (
+ }
+ key='close'
+ label={
+
+ }
+ onClick={ this.onClose }
+ />,
+ }
+ key='vault'
+ label={
+
+ }
+ onClick={ this.onCreate }
+ />
+ ] }
+ onClose={ this.onClose }
+ open
+ title={
+
+ }
+ >
+
+
+ );
+ }
+
+ onEditDescription = (event, description) => {
+ this.props.vaultStore.setVaultDescription(description);
+ }
+
+ onEditName = (event, name) => {
+ this.props.vaultStore.setVaultName(name);
+ }
+
+ onEditPassword = (event, password) => {
+ this.props.vaultStore.setVaultPassword(password);
+ }
+
+ onEditPasswordHint = (event, hint) => {
+ this.props.vaultStore.setVaultPasswordHint(hint);
+ }
+
+ onEditPasswordRepeat = (event, password) => {
+ this.props.vaultStore.setVaultPasswordRepeat(password);
+ }
+
+ onCreate = () => {
+ const { vaultNameError, vaultPasswordRepeatError } = this.props.vaultStore;
+
+ if (vaultNameError || vaultPasswordRepeatError) {
+ return;
+ }
+
+ return this.props.vaultStore
+ .createVault()
+ .catch(this.props.newError)
+ .then(this.onClose);
+ }
+
+ onClose = () => {
+ this.props.vaultStore.closeCreateModal();
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ newError
+ }, dispatch);
+}
+
+export default connect(
+ null,
+ mapDispatchToProps
+)(VaultCreate);
diff --git a/js/src/modals/VaultCreate/vaultCreate.spec.js b/js/src/modals/VaultCreate/vaultCreate.spec.js
new file mode 100644
index 000000000..f807ac0b4
--- /dev/null
+++ b/js/src/modals/VaultCreate/vaultCreate.spec.js
@@ -0,0 +1,162 @@
+// 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 VaultCreate from './';
+
+let component;
+let instance;
+let reduxStore;
+let vaultStore;
+
+function vaultReduxStore () {
+ reduxStore = {
+ dispatch: sinon.stub(),
+ subscribe: sinon.stub(),
+ getState: sinon.stub()
+ };
+
+ return reduxStore;
+}
+
+function vaultVaultStore () {
+ vaultStore = {
+ isBusyCreate: false,
+ isModalCreateOpen: true,
+ vaultDescription: 'initialDesc',
+ vaultName: 'initialName',
+ vaultPassword: 'initialPassword',
+ vaultPasswordRepeat: 'initialPassword',
+ vaultPasswordHint: 'initialHint',
+ closeCreateModal: sinon.stub(),
+ createVault: sinon.stub().resolves(true),
+ setVaultDescription: sinon.stub(),
+ setVaultName: sinon.stub(),
+ setVaultPassword: sinon.stub(),
+ setVaultPasswordHint: sinon.stub(),
+ setVaultPasswordRepeat: sinon.stub()
+ };
+
+ return vaultStore;
+}
+
+function render () {
+ component = shallow(
+ ,
+ {
+ context: {
+ store: vaultReduxStore()
+ }
+ }
+ ).find('VaultCreate').shallow();
+ instance = component.instance();
+
+ return component;
+}
+
+describe('modals/VaultCreate', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('event handlers', () => {
+ describe('onClose', () => {
+ beforeEach(() => {
+ instance.onClose();
+ });
+
+ it('calls into closeCreateModal', () => {
+ expect(vaultStore.closeCreateModal).to.have.been.called;
+ });
+ });
+
+ describe('onCreate', () => {
+ beforeEach(() => {
+ sinon.spy(instance, 'onClose');
+ return instance.onCreate();
+ });
+
+ afterEach(() => {
+ instance.onClose.restore();
+ });
+
+ it('calls into createVault', () => {
+ expect(vaultStore.createVault).to.have.been.called;
+ });
+
+ it('closes modal', () => {
+ expect(instance.onClose).to.have.been.called;
+ });
+ });
+
+ describe('onEditDescription', () => {
+ beforeEach(() => {
+ instance.onEditDescription(null, 'testDescription');
+ });
+
+ it('calls setVaultDescription', () => {
+ expect(vaultStore.setVaultDescription).to.have.been.calledWith('testDescription');
+ });
+ });
+
+ describe('onEditName', () => {
+ beforeEach(() => {
+ instance.onEditName(null, 'testName');
+ });
+
+ it('calls setVaultName', () => {
+ expect(vaultStore.setVaultName).to.have.been.calledWith('testName');
+ });
+ });
+
+ 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('onEditPasswordRepeat', () => {
+ beforeEach(() => {
+ instance.onEditPasswordRepeat(null, 'testPassword');
+ });
+
+ it('calls setVaultPasswordRepeat', () => {
+ expect(vaultStore.setVaultPasswordRepeat).to.have.been.calledWith('testPassword');
+ });
+ });
+ });
+});
diff --git a/js/src/modals/VaultLock/index.js b/js/src/modals/VaultLock/index.js
new file mode 100644
index 000000000..ffdab6191
--- /dev/null
+++ b/js/src/modals/VaultLock/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 './vaultLock';
diff --git a/js/src/modals/VaultLock/vaultLock.js b/js/src/modals/VaultLock/vaultLock.js
new file mode 100644
index 000000000..74135d544
--- /dev/null
+++ b/js/src/modals/VaultLock/vaultLock.js
@@ -0,0 +1,92 @@
+// 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 { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { newError } from '~/redux/actions';
+import { ConfirmDialog, VaultCard } from '~/ui';
+
+import styles from '../VaultUnlock/vaultUnlock.css';
+
+@observer
+class VaultLock extends Component {
+ static propTypes = {
+ newError: PropTypes.func.isRequired,
+ vaultStore: PropTypes.object.isRequired
+ }
+
+ render () {
+ const { isBusyLock, isModalLockOpen, vault } = this.props.vaultStore;
+
+ if (!isModalLockOpen) {
+ return null;
+ }
+
+ return (
+
+ }
+ >
+
+
+
+
+
+ );
+ }
+
+ onExecute = () => {
+ return this.props.vaultStore
+ .closeVault()
+ .catch(this.props.newError)
+ .then(this.onClose);
+ }
+
+ onClose = () => {
+ this.props.vaultStore.closeLockModal();
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ newError
+ }, dispatch);
+}
+
+export default connect(
+ null,
+ mapDispatchToProps
+)(VaultLock);
diff --git a/js/src/modals/VaultLock/vaultLock.spec.js b/js/src/modals/VaultLock/vaultLock.spec.js
new file mode 100644
index 000000000..53f19241d
--- /dev/null
+++ b/js/src/modals/VaultLock/vaultLock.spec.js
@@ -0,0 +1,131 @@
+// 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 VaultLock 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 = {
+ isBusyLock: false,
+ isModalLockOpen: true,
+ vault: VAULT,
+ vaultName: VAULT.name,
+ vaults: [VAULT],
+ closeLockModal: sinon.stub(),
+ closeVault: sinon.stub().resolves(true)
+ };
+
+ return vaultStore;
+}
+
+function render () {
+ component = shallow(
+ ,
+ {
+ context: {
+ store: createReduxStore()
+ }
+ }
+ ).find('VaultLock').shallow();
+ instance = component.instance();
+
+ return component;
+}
+
+describe('modals/VaultLock', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('ConfirmDialog', () => {
+ let dialog;
+
+ beforeEach(() => {
+ dialog = component.find('ConfirmDialog');
+ });
+
+ it('renders the dialog', () => {
+ expect(dialog.get(0)).to.be.ok;
+ });
+
+ it('passes onConfirm as onExecute', () => {
+ expect(dialog.props().onConfirm).to.equal(instance.onExecute);
+ });
+
+ it('passes onDeny as onClose', () => {
+ expect(dialog.props().onDeny).to.equal(instance.onClose);
+ });
+ });
+
+ describe('event methods', () => {
+ describe('onExecute', () => {
+ beforeEach(() => {
+ sinon.stub(instance, 'onClose');
+ return instance.onExecute();
+ });
+
+ afterEach(() => {
+ instance.onClose.restore();
+ });
+
+ it('closes the modal', () => {
+ expect(instance.onClose).to.have.been.called;
+ });
+
+ it('calls into vaultStore.closeVault', () => {
+ expect(vaultStore.closeVault).to.have.been.called;
+ });
+ });
+
+ describe('onClose', () => {
+ beforeEach(() => {
+ instance.onClose();
+ });
+
+ it('calls into closeLockModal', () => {
+ expect(vaultStore.closeLockModal).to.have.been.called;
+ });
+ });
+ });
+});
diff --git a/js/src/modals/VaultUnlock/index.js b/js/src/modals/VaultUnlock/index.js
new file mode 100644
index 000000000..71ef6c7e8
--- /dev/null
+++ b/js/src/modals/VaultUnlock/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 './vaultUnlock';
diff --git a/js/src/modals/VaultUnlock/vaultUnlock.css b/js/src/modals/VaultUnlock/vaultUnlock.css
new file mode 100644
index 000000000..44032193c
--- /dev/null
+++ b/js/src/modals/VaultUnlock/vaultUnlock.css
@@ -0,0 +1,27 @@
+/* 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 .
+*/
+
+.passwordHint {
+ color: rgba(255, 255, 255, 0.5);
+ font-size: 0.75em;
+ text-align: left;
+}
+
+.textbox {
+ line-height: 1.5em;
+ margin-bottom: 1.5em;
+}
diff --git a/js/src/modals/VaultUnlock/vaultUnlock.js b/js/src/modals/VaultUnlock/vaultUnlock.js
new file mode 100644
index 000000000..147777b23
--- /dev/null
+++ b/js/src/modals/VaultUnlock/vaultUnlock.js
@@ -0,0 +1,118 @@
+// 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 { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { newError } from '~/redux/actions';
+import { ConfirmDialog, Input, VaultCard } from '~/ui';
+
+import styles from './vaultUnlock.css';
+
+@observer
+class VaultUnlock extends Component {
+ static propTypes = {
+ newError: PropTypes.func.isRequired,
+ vaultStore: PropTypes.object.isRequired
+ }
+
+ render () {
+ const { isBusyUnlock, isModalUnlockOpen, vault, vaultPassword } = this.props.vaultStore;
+
+ if (!isModalUnlockOpen) {
+ return null;
+ }
+
+ return (
+
+ }
+ >
+
+
+
+ );
+ }
+
+ onEditPassword = (event, password) => {
+ this.props.vaultStore.setVaultPassword(password);
+ }
+
+ onClose = () => {
+ this.props.vaultStore.closeUnlockModal();
+ }
+
+ onExecute = () => {
+ return this.props.vaultStore
+ .openVault()
+ .catch(this.props.newError)
+ .then(this.onClose);
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ newError
+ }, dispatch);
+}
+
+export default connect(
+ null,
+ mapDispatchToProps
+)(VaultUnlock);
diff --git a/js/src/modals/VaultUnlock/vaultUnlock.spec.js b/js/src/modals/VaultUnlock/vaultUnlock.spec.js
new file mode 100644
index 000000000..b1696833d
--- /dev/null
+++ b/js/src/modals/VaultUnlock/vaultUnlock.spec.js
@@ -0,0 +1,146 @@
+// 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 VaultUnlock from './';
+
+const VAULT = {
+ name: 'testVault',
+ meta: {
+ passwordHint: 'some hint'
+ }
+};
+
+let component;
+let instance;
+let reduxStore;
+let vaultStore;
+
+function createReduxStore () {
+ reduxStore = {
+ dispatch: sinon.stub(),
+ subscribe: sinon.stub(),
+ getState: () => {
+ return {};
+ }
+ };
+
+ return reduxStore;
+}
+
+function createVaultStore () {
+ vaultStore = {
+ isBusyUnlock: false,
+ isModalUnlockOpen: true,
+ vault: VAULT,
+ vaultName: VAULT.name,
+ vaultPassword: 'testPassword',
+ vaults: [VAULT],
+ closeUnlockModal: sinon.stub(),
+ openVault: sinon.stub().resolves(true),
+ setVaultPassword: sinon.stub()
+ };
+
+ return vaultStore;
+}
+
+function render () {
+ component = shallow(
+ ,
+ {
+ context: {
+ store: createReduxStore()
+ }
+ }
+ ).find('VaultUnlock').shallow();
+ instance = component.instance();
+
+ return component;
+}
+
+describe('modals/VaultUnlock', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('ConfirmDialog', () => {
+ let dialog;
+
+ beforeEach(() => {
+ dialog = component.find('ConfirmDialog');
+ });
+
+ it('renders the dialog', () => {
+ expect(dialog.get(0)).to.be.ok;
+ });
+
+ it('passes onConfirm as onExecute', () => {
+ expect(dialog.props().onConfirm).to.equal(instance.onExecute);
+ });
+
+ it('passes onDeny as onClose', () => {
+ expect(dialog.props().onDeny).to.equal(instance.onClose);
+ });
+ });
+
+ describe('event methods', () => {
+ describe('onExecute', () => {
+ beforeEach(() => {
+ sinon.stub(instance, 'onClose');
+ return instance.onExecute();
+ });
+
+ afterEach(() => {
+ instance.onClose.restore();
+ });
+
+ it('closes the modal', () => {
+ expect(instance.onClose).to.have.been.called;
+ });
+
+ it('calls into vaultStore.openVault', () => {
+ expect(vaultStore.openVault).to.have.been.called;
+ });
+ });
+
+ describe('onClose', () => {
+ beforeEach(() => {
+ instance.onClose();
+ });
+
+ it('calls into closeUnlockModal', () => {
+ expect(vaultStore.closeUnlockModal).to.have.been.called;
+ });
+ });
+
+ describe('onEditPassword', () => {
+ beforeEach(() => {
+ instance.onEditPassword(null, 'someVaultPassword');
+ });
+
+ it('calls into vaultStore.setVaultPassword', () => {
+ expect(vaultStore.setVaultPassword).to.have.been.calledWith('someVaultPassword');
+ });
+ });
+ });
+});
diff --git a/js/src/modals/index.js b/js/src/modals/index.js
index d412b5296..0e760d501 100644
--- a/js/src/modals/index.js
+++ b/js/src/modals/index.js
@@ -14,44 +14,26 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import AddAddress from './AddAddress';
-import AddContract from './AddContract';
-import CreateAccount from './CreateAccount';
-import CreateWallet from './CreateWallet';
-import DappPermissions from './DappPermissions';
-import DappsVisible from './AddDapps';
-import DeleteAccount from './DeleteAccount';
-import DeployContract from './DeployContract';
-import EditMeta from './EditMeta';
-import ExecuteContract from './ExecuteContract';
-import FirstRun from './FirstRun';
-import LoadContract from './LoadContract';
-import SaveContract from './SaveContract';
-import Shapeshift from './Shapeshift';
-import Verification from './Verification';
-import Transfer from './Transfer';
-import PasswordManager from './PasswordManager';
-import UpgradeParity from './UpgradeParity';
-import WalletSettings from './WalletSettings';
-
-export {
- AddAddress,
- AddContract,
- CreateAccount,
- CreateWallet,
- DappPermissions,
- DappsVisible,
- DeleteAccount,
- DeployContract,
- EditMeta,
- ExecuteContract,
- FirstRun,
- LoadContract,
- SaveContract,
- Shapeshift,
- Verification,
- Transfer,
- PasswordManager,
- UpgradeParity,
- WalletSettings
-};
+export AddAddress from './AddAddress';
+export AddContract from './AddContract';
+export CreateAccount from './CreateAccount';
+export CreateWallet from './CreateWallet';
+export DappPermissions from './DappPermissions';
+export DappsVisible from './AddDapps';
+export DeleteAccount from './DeleteAccount';
+export DeployContract from './DeployContract';
+export EditMeta from './EditMeta';
+export ExecuteContract from './ExecuteContract';
+export FirstRun from './FirstRun';
+export LoadContract from './LoadContract';
+export PasswordManager from './PasswordManager';
+export SaveContract from './SaveContract';
+export Shapeshift from './Shapeshift';
+export Transfer from './Transfer';
+export UpgradeParity from './UpgradeParity';
+export VaultAccounts from './VaultAccounts';
+export VaultCreate from './VaultCreate';
+export VaultLock from './VaultLock';
+export VaultUnlock from './VaultUnlock';
+export Verification from './Verification';
+export WalletSettings from './WalletSettings';
diff --git a/js/src/routes.js b/js/src/routes.js
index c4c414108..cc2c876ee 100644
--- a/js/src/routes.js
+++ b/js/src/routes.js
@@ -19,7 +19,7 @@ import {
Contract, Contracts, Dapp, Dapps, HistoryStore, Home,
Settings, SettingsBackground, SettingsParity, SettingsProxy,
SettingsViews, Signer, Status,
- Wallet, Web, WriteContract
+ Vaults, Wallet, Web, WriteContract
} from '~/views';
import builtinDapps from '~/views/Dapps/builtin.json';
@@ -57,6 +57,7 @@ const accountsRoutes = [
accountsHistory.add(params.address, 'account');
}
},
+ { path: '/vaults', component: Vaults },
{
path: '/wallet/:address',
component: Wallet,
diff --git a/js/src/ui/ConfirmDialog/confirmDialog.css b/js/src/ui/ConfirmDialog/confirmDialog.css
index 7c6db48d2..20e5f3cea 100644
--- a/js/src/ui/ConfirmDialog/confirmDialog.css
+++ b/js/src/ui/ConfirmDialog/confirmDialog.css
@@ -14,6 +14,7 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see .
*/
+
.body {
text-align: center;
}
diff --git a/js/src/ui/ConfirmDialog/confirmDialog.js b/js/src/ui/ConfirmDialog/confirmDialog.js
index e38c4d473..02f400e93 100644
--- a/js/src/ui/ConfirmDialog/confirmDialog.js
+++ b/js/src/ui/ConfirmDialog/confirmDialog.js
@@ -20,7 +20,7 @@ import { FormattedMessage } from 'react-intl';
import { nodeOrStringProptype } from '~/util/proptypes';
import Button from '../Button';
-import Modal from '../Modal';
+import Portal from '../Portal';
import { CancelIcon, CheckIcon } from '../Icons';
import styles from './confirmDialog.css';
@@ -42,47 +42,58 @@ export default class ConfirmDialog extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
+ disabledConfirm: PropTypes.bool,
+ disabledDeny: PropTypes.bool,
+ busy: PropTypes.bool,
iconConfirm: PropTypes.node,
iconDeny: PropTypes.node,
labelConfirm: PropTypes.string,
labelDeny: PropTypes.string,
onConfirm: PropTypes.func.isRequired,
onDeny: PropTypes.func.isRequired,
+ open: PropTypes.bool,
title: nodeOrStringProptype().isRequired,
- visible: PropTypes.bool.isRequired
+ visible: PropTypes.bool
}
render () {
- const { children, className, title, visible } = this.props;
+ const { busy, children, className, disabledConfirm, disabledDeny, iconConfirm, iconDeny, labelConfirm, labelDeny, onConfirm, onDeny, open, title, visible } = this.props;
+
+ // TODO: visible is for compatibility with existing, open aligns with Portal.
+ // (Cleanup once all uses of ConfirmDialog has been migrated)
+ if (!visible && !open) {
+ return null;
+ }
return (
- }
+ key='deny'
+ label={ labelDeny || DEFAULT_NO }
+ onClick={ onDeny }
+ />,
+ }
+ key='confirm'
+ label={ labelConfirm || DEFAULT_YES }
+ onClick={ onConfirm }
+ />
+ ] }
+ busy={ busy }
className={ className }
- actions={ this.renderActions() }
+ isSmallModal
+ onClose={ onDeny }
title={ title }
- visible={ visible }
+ open
>
{ children }
-
+
);
}
-
- renderActions () {
- const { iconConfirm, iconDeny, labelConfirm, labelDeny, onConfirm, onDeny } = this.props;
-
- return [
- }
- label={ labelDeny || DEFAULT_NO }
- onClick={ onDeny }
- />,
- }
- label={ labelConfirm || DEFAULT_YES }
- onClick={ onConfirm }
- />
- ];
- }
}
diff --git a/js/src/ui/ConfirmDialog/confirmDialog.spec.js b/js/src/ui/ConfirmDialog/confirmDialog.spec.js
index 07a349db0..cdc1e8be8 100644
--- a/js/src/ui/ConfirmDialog/confirmDialog.spec.js
+++ b/js/src/ui/ConfirmDialog/confirmDialog.spec.js
@@ -15,41 +15,24 @@
// along with Parity. If not, see .
import { shallow } from 'enzyme';
-import React, { PropTypes } from 'react';
+import React from 'react';
import sinon from 'sinon';
-import muiTheme from '../Theme';
-
import ConfirmDialog from './';
let component;
-let instance;
let onConfirm;
let onDeny;
-function createRedux () {
- return {
- dispatch: sinon.stub(),
- subscribe: sinon.stub(),
- getState: () => {
- return {
- settings: {
- backgroundSeed: 'xyz'
- }
- };
- }
- };
-}
-
function render (props = {}) {
onConfirm = sinon.stub();
onDeny = sinon.stub();
- if (props.visible === undefined) {
- props.visible = true;
+ if (props.open === undefined) {
+ props.open = true;
}
- const baseComponent = shallow(
+ component = shallow(
);
- instance = baseComponent.instance();
- component = baseComponent.find('Connect(Modal)').shallow({
- childContextTypes: {
- muiTheme: PropTypes.object,
- store: PropTypes.object
- },
- context: {
- muiTheme,
- store: createRedux()
- }
- });
-
return component;
}
describe('ui/ConfirmDialog', () => {
+ beforeEach(() => {
+ render();
+ });
+
it('renders defaults', () => {
- expect(render()).to.be.ok;
+ expect(component).to.be.ok;
});
it('renders the body as provided', () => {
- expect(render().find('div[id="testContent"]').text()).to.equal('some test content');
+ expect(component.find('div[id="testContent"]').text()).to.equal('some test content');
});
- describe('properties', () => {
+ describe('Portal properties', () => {
let props;
beforeEach(() => {
- props = render().props();
- });
-
- it('passes the actions', () => {
- expect(props.actions).to.deep.equal(instance.renderActions());
+ props = component.find('Portal').props();
});
it('passes title', () => {
expect(props.title).to.equal('test title');
});
- it('passes visiblity flag', () => {
- expect(props.visible).to.be.true;
+ it('passes open flag', () => {
+ expect(props.open).to.be.true;
});
- });
- describe('renderActions', () => {
- describe('defaults', () => {
+ it('passes the small flag', () => {
+ expect(props.isSmallModal).to.be.true;
+ });
+
+ it('maps onClose to onDeny', () => {
+ expect(props.onClose).to.equal(onDeny);
+ });
+
+ describe('buttons', () => {
let buttons;
beforeEach(() => {
- render();
- buttons = instance.renderActions();
+ buttons = component.props().buttons;
+ });
+
+ it('passes the buttons', () => {
+ expect(buttons.length).to.equal(2);
});
it('renders with supplied onConfim/onDeny callbacks', () => {
@@ -129,29 +109,27 @@ describe('ui/ConfirmDialog', () => {
expect(buttons[0].props.icon.type.displayName).to.equal('ContentClear');
expect(buttons[1].props.icon.type.displayName).to.equal('NavigationCheck');
});
- });
- describe('overrides', () => {
- let buttons;
-
- beforeEach(() => {
- render({
- labelConfirm: 'labelConfirm',
- labelDeny: 'labelDeny',
- iconConfirm: 'iconConfirm',
- iconDeny: 'iconDeny'
+ describe('overrides', () => {
+ beforeEach(() => {
+ render({
+ labelConfirm: 'labelConfirm',
+ labelDeny: 'labelDeny',
+ iconConfirm: 'iconConfirm',
+ iconDeny: 'iconDeny'
+ });
+ buttons = component.props().buttons;
});
- buttons = instance.renderActions();
- });
- it('renders supplied labels', () => {
- expect(buttons[0].props.label).to.equal('labelDeny');
- expect(buttons[1].props.label).to.equal('labelConfirm');
- });
+ it('renders supplied labels', () => {
+ expect(buttons[0].props.label).to.equal('labelDeny');
+ expect(buttons[1].props.label).to.equal('labelConfirm');
+ });
- it('renders supplied icons', () => {
- expect(buttons[0].props.icon).to.equal('iconDeny');
- expect(buttons[1].props.icon).to.equal('iconConfirm');
+ it('renders supplied icons', () => {
+ expect(buttons[0].props.icon).to.equal('iconDeny');
+ expect(buttons[1].props.icon).to.equal('iconConfirm');
+ });
});
});
});
diff --git a/js/src/ui/Container/Title/title.css b/js/src/ui/Container/Title/title.css
index b597aea3e..64317600c 100644
--- a/js/src/ui/Container/Title/title.css
+++ b/js/src/ui/Container/Title/title.css
@@ -26,6 +26,7 @@ $smallFontSize: 0.75rem;
color: $bylineColor;
display: -webkit-box;
line-height: $bylineLineHeight;
+ min-height: $bylineMaxHeight;
max-height: $bylineMaxHeight;
overflow: hidden;
position: relative;
@@ -45,5 +46,8 @@ $smallFontSize: 0.75rem;
.title {
line-height: $titleLineHeight;
margin: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
text-transform: uppercase;
+ white-space: nowrap;
}
diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css
index 9f40897e1..a0e213198 100644
--- a/js/src/ui/Container/container.css
+++ b/js/src/ui/Container/container.css
@@ -16,33 +16,39 @@
*/
$background: rgba(18, 18, 18, 0.85);
-$backgroundOverlay: rgba(18, 18, 18, 1);
+$backgroundHover: rgba(18, 18, 18, 1);
+$transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
.container {
background: $background;
flex: 1;
height: 100%;
padding: 0em;
- transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
+ position: relative;
+ transition: $transitionAll;
width: 100%;
.hoverOverlay {
- background: $backgroundOverlay;
- display: none;
+ background: $background;
left: 0;
margin-top: -1.5em;
+ opacity: inherit;
padding: 0 1.5em 1.5em 1.5em;
position: absolute;
right: 0;
top: 100%;
+ transition: $transitionAll;
+ transform: scale(0.5, 0);
+ transform-origin: top center;
z-index: 100;
}
&:hover {
- background: $backgroundOverlay;
+ background: $backgroundHover;
.hoverOverlay {
- display: block;
+ background: $backgroundHover;
+ transform: scale(1, 1);
}
}
}
diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js
index 62cb02105..eef9b9962 100644
--- a/js/src/ui/Icons/index.js
+++ b/js/src/ui/Icons/index.js
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+export AccountsIcon from 'material-ui/svg-icons/action/account-balance-wallet';
export AddIcon from 'material-ui/svg-icons/content/add';
export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file';
export CancelIcon from 'material-ui/svg-icons/content/clear';
@@ -28,6 +29,7 @@ export DeleteIcon from 'material-ui/svg-icons/action/delete';
export DoneIcon from 'material-ui/svg-icons/action/done-all';
export EditIcon from 'material-ui/svg-icons/content/create';
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
+export KeyIcon from 'material-ui/svg-icons/communication/vpn-key';
export LinkIcon from 'material-ui/svg-icons/content/link';
export LockedIcon from 'material-ui/svg-icons/action/lock';
export MoveIcon from 'material-ui/svg-icons/action/open-with';
@@ -41,6 +43,7 @@ export SnoozeIcon from 'material-ui/svg-icons/av/snooze';
export StarCircleIcon from 'material-ui/svg-icons/action/stars';
export StarIcon from 'material-ui/svg-icons/toggle/star';
export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border';
+export UnlockedIcon from 'material-ui/svg-icons/action/lock-open';
export VerifyIcon from 'material-ui/svg-icons/action/verified-user';
export VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
export VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
diff --git a/js/src/ui/Icons/index.spec.js b/js/src/ui/Icons/index.spec.js
new file mode 100644
index 000000000..88b2f4e6d
--- /dev/null
+++ b/js/src/ui/Icons/index.spec.js
@@ -0,0 +1,28 @@
+// 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 { createElement } from 'react';
+
+import * as Icons from './';
+
+describe('ui/Icons', () => {
+ Object.keys(Icons).forEach((icon) => {
+ it(`contains & renders ${icon}`, () => {
+ expect(shallow(createElement(Icons[icon]))).to.be.ok;
+ });
+ });
+});
diff --git a/js/src/ui/Page/page.js b/js/src/ui/Page/page.js
index abf0ce660..0f02d70a8 100644
--- a/js/src/ui/Page/page.js
+++ b/js/src/ui/Page/page.js
@@ -31,22 +31,20 @@ export default class Page extends Component {
render () {
const { buttons, className, children, title } = this.props;
- const classes = `${styles.layout} ${className}`;
- let actionbar = null;
-
- if (title || buttons) {
- actionbar = (
-
- );
- }
return (
- { actionbar }
-
+ {
+ title || buttons
+ ? (
+
+ )
+ : null
+ }
+
{ children }
diff --git a/js/src/ui/Page/page.spec.js b/js/src/ui/Page/page.spec.js
new file mode 100644
index 000000000..606ce276b
--- /dev/null
+++ b/js/src/ui/Page/page.spec.js
@@ -0,0 +1,79 @@
+// 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 Page from './';
+
+const BUTTONS = ['buttonA', 'buttonB'];
+const CLASSNAME = 'testClass';
+const TESTTEXT = 'testing children';
+const TITLE = 'test title';
+
+let component;
+
+function render () {
+ component = shallow(
+
+
.
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import Title from './';
+
+let component;
+let instance;
+
+function render (props = {}) {
+ component = shallow(
+
+ );
+ instance = component.instance();
+
+ return component;
+}
+
+describe('ui/Title', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('instance methods', () => {
+ describe('renderSteps', () => {
+ let stepper;
+
+ beforeEach(() => {
+ render({ steps: ['stepA', 'stepB'] });
+ stepper = shallow(instance.renderSteps());
+ });
+
+ it('renders the Stepper', () => {
+ expect(stepper.find('Stepper').get(0)).to.be.ok;
+ });
+ });
+
+ describe('renderTimeline', () => {
+ let steps;
+
+ beforeEach(() => {
+ render({ steps: ['stepA', 'StepB'] });
+ steps = instance.renderTimeline();
+ });
+
+ it('renders the Step', () => {
+ expect(steps.length).to.equal(2);
+ });
+ });
+
+ describe('renderWaiting', () => {
+ let waiting;
+
+ beforeEach(() => {
+ render({ busy: true });
+ waiting = shallow(instance.renderWaiting());
+ });
+
+ it('renders the LinearProgress', () => {
+ expect(waiting.find('LinearProgress').get(0)).to.be.ok;
+ });
+ });
+ });
+});
diff --git a/js/src/ui/VaultCard/Layout/index.js b/js/src/ui/VaultCard/Layout/index.js
new file mode 100644
index 000000000..997ab2462
--- /dev/null
+++ b/js/src/ui/VaultCard/Layout/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 './layout';
diff --git a/js/src/ui/VaultCard/Layout/layout.css b/js/src/ui/VaultCard/Layout/layout.css
new file mode 100644
index 000000000..4d593fbdd
--- /dev/null
+++ b/js/src/ui/VaultCard/Layout/layout.css
@@ -0,0 +1,45 @@
+/* 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 .
+*/
+
+$imageHeight: 56px;
+
+.layout {
+ display: flex;
+ min-height: $imageHeight;
+ overflow: hidden;
+ text-align: left;
+
+ &.border {
+ background: rgba(0, 0, 0, 0.25);
+ padding: 1.5em;
+ }
+
+ .identityIcon {
+ margin-right: 1em;
+ vertical-align: top;
+
+ &.locked {
+ filter: grayscale(100%);
+ opacity: 0.33;
+ }
+ }
+
+ .info {
+ flex: 1;
+ overflow: hidden;
+ }
+}
diff --git a/js/src/ui/VaultCard/Layout/layout.js b/js/src/ui/VaultCard/Layout/layout.js
new file mode 100644
index 000000000..f525abe5a
--- /dev/null
+++ b/js/src/ui/VaultCard/Layout/layout.js
@@ -0,0 +1,66 @@
+// 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 React, { Component, PropTypes } from 'react';
+
+import Title from '~/ui/Title';
+import IdentityIcon from '~/ui/IdentityIcon';
+
+import styles from './layout.css';
+
+export default class Layout extends Component {
+ static propTypes = {
+ vault: PropTypes.object.isRequired,
+ withBorder: PropTypes.bool
+ };
+
+ render () {
+ const { vault, withBorder } = this.props;
+ const { isOpen, meta, name } = vault;
+
+ return (
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/js/src/ui/VaultCard/Layout/layout.spec.js b/js/src/ui/VaultCard/Layout/layout.spec.js
new file mode 100644
index 000000000..f097f7a9b
--- /dev/null
+++ b/js/src/ui/VaultCard/Layout/layout.spec.js
@@ -0,0 +1,90 @@
+// 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 Layout from './';
+
+const DESCRIPTION = 'some description';
+const NAME = 'testName';
+
+let component;
+
+function render () {
+ component = shallow(
+
+ );
+
+ return component;
+}
+
+describe('ui/VaultCard/Layout', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('components', () => {
+ describe('IdentityIcon', () => {
+ let icon;
+
+ beforeEach(() => {
+ icon = component.find('Connect(IdentityIcon)');
+ });
+
+ it('renders', () => {
+ expect(icon.get(0)).to.be.ok;
+ });
+
+ it('passes the name as address key', () => {
+ expect(icon.props().address).to.equal(NAME);
+ });
+ });
+
+ describe('Title', () => {
+ let title;
+
+ beforeEach(() => {
+ title = component.find('Title');
+ });
+
+ it('renders', () => {
+ expect(title.get(0)).to.be.ok;
+ });
+
+ it('passes the name as title', () => {
+ expect(title.props().title).to.equal(NAME);
+ });
+
+ it('passes the description as byline', () => {
+ expect(title.props().byline).to.equal(DESCRIPTION);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/VaultCard/index.js b/js/src/ui/VaultCard/index.js
new file mode 100644
index 000000000..3cbb730d9
--- /dev/null
+++ b/js/src/ui/VaultCard/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 './vaultCard';
diff --git a/js/src/ui/VaultCard/vaultCard.css b/js/src/ui/VaultCard/vaultCard.css
new file mode 100644
index 000000000..b85e96209
--- /dev/null
+++ b/js/src/ui/VaultCard/vaultCard.css
@@ -0,0 +1,62 @@
+/* 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 .
+*/
+
+.container {
+ text-align: left;
+
+ .accounts {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-top: 1.5em;
+
+ .account {
+ margin: 0.5em;
+ }
+ }
+
+ .empty {
+ margin-top: 1.5em;
+ opacity: 0.5;
+ text-align: center;
+ }
+
+ .buttons {
+ margin: -1em -1em 0.75em -1em;
+ text-align: right;
+
+ button.status {
+ min-width: 2em !important;
+ }
+
+ button:not(.status) {
+ display: none !important;
+ }
+ }
+
+ &:hover {
+ .buttons {
+ button.status {
+ display: none !important;
+ }
+
+ button:not(.status) {
+ display: inline-block !important;
+ }
+ }
+ }
+}
diff --git a/js/src/ui/VaultCard/vaultCard.js b/js/src/ui/VaultCard/vaultCard.js
new file mode 100644
index 000000000..5d04b9880
--- /dev/null
+++ b/js/src/ui/VaultCard/vaultCard.js
@@ -0,0 +1,102 @@
+// 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 React, { Component, PropTypes } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router';
+
+import Button from '~/ui/Button';
+import Container from '~/ui/Container';
+import IdentityIcon from '~/ui/IdentityIcon';
+import { LockedIcon, UnlockedIcon } from '~/ui/Icons';
+
+import Layout from './Layout';
+import styles from './vaultCard.css';
+
+export default class VaultCard extends Component {
+ static propTypes = {
+ accounts: PropTypes.array,
+ buttons: PropTypes.array,
+ vault: PropTypes.object.isRequired
+ };
+
+ static Layout = Layout;
+
+ render () {
+ const { buttons, vault } = this.props;
+ const { isOpen } = vault;
+
+ return (
+
+