Vault Management UI (round 3) (#4652)
* Render Dapps via SectionList * Initial rendering of accounts via SectionList * Width vars * Allow classNames in certifications & tags * Overlay of info on hover * Adjust hover balances * Large owner icons (align with vaults) * Consistent block mined at message * Attach ParityBackground to html * Adjust page padding to align * Lint fixes * Link to different types of addresses * Make content parts clickable only (a within a) * Force Chrome hardware acceleration * Trust the vendors... don't go crazy with transform :) * Use faster & default transitions * 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 * Display type icons in creation dialog * Complimentary colours * Convert Signer defaults to SelectionList * Fix Geth import - actually pass addresses through * Work from addresses returned via RPC * Display actual addresses imported (not selected) * Update tests to cover bug fixed * Prettyfy Geth import * Description on selection actions * SelectionList as entry point * Update failing tests * Subtle selection border * Styling updates for account details * Add ModalBox summary * AddAddress updated * 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 ModalBox component * Add tests for VaultSelector component * Add vaultsOpened in store * Add ~/ui/Form/VaultSelect * WIP * Fix failing tests * Move account to vault when selected * Fix circular build deps * EditMeta uses Form/VaultSelect * Vault move into meta store (alignment) * Re-apply stretch fix * Display vault in account summary * Add busy indicators to relevant modals * Auto-focus description field (aligns with #4657) * Remove extra container (double scrolling) * Remove unused container style * Apply scroll fixes from lates commit in #4621 * Remove unneeded logs * Remove extra div, fixing ParityBar overflow * Make dapp iframe background white * Stop event propgation on tag click * ChangeVault component (re-usable) * Use ChangeVault component * Pass vaultStores in * Icon highlight colour * Tag-ify vault name display * ChangeVault location * Bothced merge, selector rendering twice * Value can be undefined (no vault) * Close selector on Select bug * Fix toggle botched merge * Update tests * Add Vault Tags to Account Header
This commit is contained in:
parent
cb118f1936
commit
1548201551
51
js/src/modals/CreateAccount/ChangeVault/changeVault.js
Normal file
51
js/src/modals/CreateAccount/ChangeVault/changeVault.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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 { VaultSelect } from '~/ui';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class ChangeVault extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
store: PropTypes.object.isRequired,
|
||||||
|
vaultStore: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { store, vaultStore } = this.props;
|
||||||
|
const { vaultName } = store;
|
||||||
|
|
||||||
|
if (!vaultStore || vaultStore.vaultsOpened.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VaultSelect
|
||||||
|
onSelect={ this.onSelect }
|
||||||
|
value={ vaultName }
|
||||||
|
vaultStore={ vaultStore }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (vaultName) => {
|
||||||
|
const { store } = this.props;
|
||||||
|
|
||||||
|
store.setVaultName(vaultName);
|
||||||
|
}
|
||||||
|
}
|
100
js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js
Normal file
100
js/src/modals/CreateAccount/ChangeVault/changeVault.spec.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// 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 ChangeVault from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let store;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createStore () {
|
||||||
|
store = {
|
||||||
|
setVaultName: sinon.stub(),
|
||||||
|
vaultName: 'testing'
|
||||||
|
};
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
vaultsOpened: ['testing']
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
component = shallow(
|
||||||
|
<ChangeVault
|
||||||
|
store={ createStore() }
|
||||||
|
vaultStore={ createVaultStore() }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/CreateAccount/ChangeVault', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('VaultSelect', () => {
|
||||||
|
let select;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
select = component.find('VaultSelect');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(select.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes onSelect as instance method', () => {
|
||||||
|
expect(select.props().onSelect).to.equal(instance.onSelect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the value', () => {
|
||||||
|
expect(select.props().value).to.equal('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the vaultStore', () => {
|
||||||
|
expect(select.props().vaultStore).to.equal(vaultStore);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('onSelect', () => {
|
||||||
|
it('calls into store setVaultName', () => {
|
||||||
|
instance.onSelect('newName');
|
||||||
|
expect(store.setVaultName).to.have.been.calledWith('newName');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
js/src/modals/CreateAccount/ChangeVault/index.js
Normal file
17
js/src/modals/CreateAccount/ChangeVault/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 './changeVault';
|
@ -24,13 +24,15 @@ import { Form, Input, IdentityIcon } from '~/ui';
|
|||||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||||
import { RefreshIcon } from '~/ui/Icons';
|
import { RefreshIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import ChangeVault from '../ChangeVault';
|
||||||
import styles from '../createAccount.css';
|
import styles from '../createAccount.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class CreateAccount extends Component {
|
export default class CreateAccount extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
newError: PropTypes.func.isRequired,
|
newError: PropTypes.func.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired,
|
||||||
|
vaultStore: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -123,6 +125,10 @@ export default class CreateAccount extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PasswordStrength input={ password } />
|
<PasswordStrength input={ password } />
|
||||||
|
<ChangeVault
|
||||||
|
store={ this.props.store }
|
||||||
|
vaultStore={ this.props.vaultStore }
|
||||||
|
/>
|
||||||
{ this.renderIdentitySelector() }
|
{ this.renderIdentitySelector() }
|
||||||
{ this.renderIdentities() }
|
{ this.renderIdentities() }
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -20,12 +20,15 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import { Form, FileSelect, Input } from '~/ui';
|
import { Form, FileSelect, Input } from '~/ui';
|
||||||
|
|
||||||
|
import ChangeVault from '../ChangeVault';
|
||||||
import styles from '../createAccount.css';
|
import styles from '../createAccount.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class NewImport extends Component {
|
export default class NewImport extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired,
|
||||||
|
vaultStore: PropTypes.object
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -88,6 +91,10 @@ export default class NewImport extends Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ChangeVault
|
||||||
|
store={ this.props.store }
|
||||||
|
vaultStore={ this.props.vaultStore }
|
||||||
|
/>
|
||||||
{ this.renderFileSelector() }
|
{ this.renderFileSelector() }
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -21,6 +21,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { Form, Input } from '~/ui';
|
import { Form, Input } from '~/ui';
|
||||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||||
|
|
||||||
|
import ChangeVault from '../ChangeVault';
|
||||||
import styles from '../createAccount.css';
|
import styles from '../createAccount.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -30,7 +31,8 @@ export default class RawKey extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired,
|
||||||
|
vaultStore: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -131,6 +133,10 @@ export default class RawKey extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PasswordStrength input={ password } />
|
<PasswordStrength input={ password } />
|
||||||
|
<ChangeVault
|
||||||
|
store={ this.props.store }
|
||||||
|
vaultStore={ this.props.vaultStore }
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,14 @@ import { Checkbox } from 'material-ui';
|
|||||||
import { Form, Input } from '~/ui';
|
import { Form, Input } from '~/ui';
|
||||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||||
|
|
||||||
|
import ChangeVault from '../ChangeVault';
|
||||||
import styles from '../createAccount.css';
|
import styles from '../createAccount.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class RecoveryPhrase extends Component {
|
export default class RecoveryPhrase extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired,
|
||||||
|
vaultStore: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -127,6 +129,10 @@ export default class RecoveryPhrase extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PasswordStrength input={ password } />
|
<PasswordStrength input={ password } />
|
||||||
|
<ChangeVault
|
||||||
|
store={ this.props.store }
|
||||||
|
vaultStore={ this.props.vaultStore }
|
||||||
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={ isWindowsPhrase }
|
checked={ isWindowsPhrase }
|
||||||
className={ styles.checkbox }
|
className={ styles.checkbox }
|
||||||
|
@ -109,6 +109,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
color: rgb(167, 151, 0) !important;
|
||||||
flex: 0 0 56px;
|
flex: 0 0 56px;
|
||||||
height: 56px !important;
|
height: 56px !important;
|
||||||
margin-right: 0.75em;
|
margin-right: 0.75em;
|
||||||
|
@ -20,11 +20,13 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg';
|
||||||
import { createIdentityImg } from '~/api/util/identity';
|
import { createIdentityImg } from '~/api/util/identity';
|
||||||
import { newError } from '~/redux/actions';
|
import { newError } from '~/redux/actions';
|
||||||
import { Button, ModalBox, Portal } from '~/ui';
|
import { Button, ModalBox, Portal } from '~/ui';
|
||||||
import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons';
|
import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons';
|
||||||
import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg';
|
|
||||||
|
import VaultStore from '~/views/Vaults/store';
|
||||||
|
|
||||||
import AccountDetails from './AccountDetails';
|
import AccountDetails from './AccountDetails';
|
||||||
import AccountDetailsGeth from './AccountDetailsGeth';
|
import AccountDetailsGeth from './AccountDetailsGeth';
|
||||||
@ -82,13 +84,19 @@ class CreateAccount extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
store = new Store(this.context.api, this.props.accounts);
|
store = new Store(this.context.api, this.props.accounts);
|
||||||
|
vaultStore = VaultStore.get(this.context.api);
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
return this.vaultStore.loadVaults();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { createType, stage } = this.store;
|
const { isBusy, createType, stage } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal
|
<Portal
|
||||||
buttons={ this.renderDialogActions() }
|
buttons={ this.renderDialogActions() }
|
||||||
|
busy={ isBusy }
|
||||||
activeStep={ stage }
|
activeStep={ stage }
|
||||||
onClose={ this.onClose }
|
onClose={ this.onClose }
|
||||||
open
|
open
|
||||||
@ -120,6 +128,7 @@ class CreateAccount extends Component {
|
|||||||
<NewAccount
|
<NewAccount
|
||||||
newError={ this.props.newError }
|
newError={ this.props.newError }
|
||||||
store={ this.store }
|
store={ this.store }
|
||||||
|
vaultStore={ this.vaultStore }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -132,18 +141,27 @@ class CreateAccount extends Component {
|
|||||||
|
|
||||||
if (createType === 'fromPhrase') {
|
if (createType === 'fromPhrase') {
|
||||||
return (
|
return (
|
||||||
<RecoveryPhrase store={ this.store } />
|
<RecoveryPhrase
|
||||||
|
store={ this.store }
|
||||||
|
vaultStore={ this.vaultStore }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createType === 'fromRaw') {
|
if (createType === 'fromRaw') {
|
||||||
return (
|
return (
|
||||||
<RawKey store={ this.store } />
|
<RawKey
|
||||||
|
store={ this.store }
|
||||||
|
vaultStore={ this.vaultStore }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewImport store={ this.store } />
|
<NewImport
|
||||||
|
store={ this.store }
|
||||||
|
vaultStore={ this.vaultStore }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
case STAGE_INFO:
|
case STAGE_INFO:
|
||||||
@ -266,7 +284,7 @@ class CreateAccount extends Component {
|
|||||||
this.store.setBusy(true);
|
this.store.setBusy(true);
|
||||||
|
|
||||||
return this.store
|
return this.store
|
||||||
.createAccount()
|
.createAccount(this.vaultStore)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.store.setBusy(false);
|
this.store.setBusy(false);
|
||||||
this.store.nextStage();
|
this.store.nextStage();
|
||||||
|
@ -42,7 +42,9 @@ function createApi () {
|
|||||||
newAccountFromWallet: sinon.stub().resolves(ADDRESS),
|
newAccountFromWallet: sinon.stub().resolves(ADDRESS),
|
||||||
phraseToAddress: () => Promise.resolve(`${++counter}`),
|
phraseToAddress: () => Promise.resolve(`${++counter}`),
|
||||||
setAccountMeta: sinon.stub().resolves(),
|
setAccountMeta: sinon.stub().resolves(),
|
||||||
setAccountName: sinon.stub().resolves()
|
setAccountName: sinon.stub().resolves(),
|
||||||
|
listVaults: sinon.stub().resolves([]),
|
||||||
|
listOpenedVaults: sinon.stub().resolves([])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ export default class Store {
|
|||||||
@observable rawKey = '';
|
@observable rawKey = '';
|
||||||
@observable rawKeyError = ERRORS.nokey;
|
@observable rawKeyError = ERRORS.nokey;
|
||||||
@observable stage = STAGE_SELECT_TYPE;
|
@observable stage = STAGE_SELECT_TYPE;
|
||||||
|
@observable vaultName = '';
|
||||||
@observable walletFile = '';
|
@observable walletFile = '';
|
||||||
@observable walletFileError = ERRORS.noFile;
|
@observable walletFileError = ERRORS.noFile;
|
||||||
@observable walletJson = '';
|
@observable walletJson = '';
|
||||||
@ -95,6 +96,7 @@ export default class Store {
|
|||||||
this.nameError = null;
|
this.nameError = null;
|
||||||
this.rawKey = '';
|
this.rawKey = '';
|
||||||
this.rawKeyError = null;
|
this.rawKeyError = null;
|
||||||
|
this.vaultName = '';
|
||||||
this.walletFile = '';
|
this.walletFile = '';
|
||||||
this.walletFileError = null;
|
this.walletFileError = null;
|
||||||
this.walletJson = '';
|
this.walletJson = '';
|
||||||
@ -134,6 +136,10 @@ export default class Store {
|
|||||||
this.gethImported = gethImported;
|
this.gethImported = gethImported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setVaultName = (vaultName) => {
|
||||||
|
this.vaultName = vaultName;
|
||||||
|
}
|
||||||
|
|
||||||
@action setWindowsPhrase = (isWindowsPhrase = false) => {
|
@action setWindowsPhrase = (isWindowsPhrase = false) => {
|
||||||
this.isWindowsPhrase = isWindowsPhrase;
|
this.isWindowsPhrase = isWindowsPhrase;
|
||||||
}
|
}
|
||||||
@ -220,7 +226,28 @@ export default class Store {
|
|||||||
this.stage--;
|
this.stage--;
|
||||||
}
|
}
|
||||||
|
|
||||||
createAccount = () => {
|
createAccount = (vaultStore) => {
|
||||||
|
this.setBusy(true);
|
||||||
|
|
||||||
|
return this
|
||||||
|
._createAccount()
|
||||||
|
.then(() => {
|
||||||
|
if (vaultStore && this.vaultName && this.vaultName.length) {
|
||||||
|
return vaultStore.moveAccount(this.vaultName, this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setBusy(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.setBusy(false);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_createAccount = () => {
|
||||||
switch (this.createType) {
|
switch (this.createType) {
|
||||||
case 'fromGeth':
|
case 'fromGeth':
|
||||||
return this.createAccountFromGeth();
|
return this.createAccountFromGeth();
|
||||||
|
@ -22,8 +22,20 @@ import { ACCOUNTS, ADDRESS, GETH_ADDRESSES, createApi } from './createAccount.te
|
|||||||
|
|
||||||
let api;
|
let api;
|
||||||
let store;
|
let store;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
moveAccount: sinon.stub().resolves(),
|
||||||
|
listVaults: sinon.stub().resolves()
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
function createStore (loadGeth) {
|
function createStore (loadGeth) {
|
||||||
|
createVaultStore();
|
||||||
|
|
||||||
api = createApi();
|
api = createApi();
|
||||||
store = new Store(api, ACCOUNTS, loadGeth);
|
store = new Store(api, ACCOUNTS, loadGeth);
|
||||||
|
|
||||||
@ -65,8 +77,9 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
describe('@action', () => {
|
describe('@action', () => {
|
||||||
describe('clearErrors', () => {
|
describe('clearErrors', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.setName('');
|
store.setName('testing');
|
||||||
store.setPassword('123');
|
store.setPassword('testing');
|
||||||
|
store.setVaultName('testing');
|
||||||
store.setRawKey('test');
|
store.setRawKey('test');
|
||||||
store.setWalletFile('test');
|
store.setWalletFile('test');
|
||||||
store.setWalletJson('test');
|
store.setWalletJson('test');
|
||||||
@ -75,10 +88,13 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
it('clears all errors', () => {
|
it('clears all errors', () => {
|
||||||
store.clearErrors();
|
store.clearErrors();
|
||||||
|
|
||||||
|
expect(store.name).to.equal('');
|
||||||
expect(store.nameError).to.be.null;
|
expect(store.nameError).to.be.null;
|
||||||
|
expect(store.password).to.equal('');
|
||||||
expect(store.passwordRepeatError).to.be.null;
|
expect(store.passwordRepeatError).to.be.null;
|
||||||
expect(store.rawKey).to.equal('');
|
expect(store.rawKey).to.equal('');
|
||||||
expect(store.rawKeyError).to.be.null;
|
expect(store.rawKeyError).to.be.null;
|
||||||
|
expect(store.vaultName).to.equal('');
|
||||||
expect(store.walletFile).to.equal('');
|
expect(store.walletFile).to.equal('');
|
||||||
expect(store.walletFileError).to.be.null;
|
expect(store.walletFileError).to.be.null;
|
||||||
expect(store.walletJson).to.equal('');
|
expect(store.walletJson).to.equal('');
|
||||||
@ -198,6 +214,13 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setVaultName', () => {
|
||||||
|
it('sets the vault name', () => {
|
||||||
|
store.setVaultName('testVault');
|
||||||
|
expect(store.vaultName).to.equal('testVault');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setWalletFile', () => {
|
describe('setWalletFile', () => {
|
||||||
it('sets the filepath', () => {
|
it('sets the filepath', () => {
|
||||||
store.setWalletFile('testing');
|
store.setWalletFile('testing');
|
||||||
@ -384,12 +407,22 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
let createAccountFromWalletSpy;
|
let createAccountFromWalletSpy;
|
||||||
let createAccountFromPhraseSpy;
|
let createAccountFromPhraseSpy;
|
||||||
let createAccountFromRawSpy;
|
let createAccountFromRawSpy;
|
||||||
|
let busySpy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createAccountFromGethSpy = sinon.spy(store, 'createAccountFromGeth');
|
createAccountFromGethSpy = sinon.spy(store, 'createAccountFromGeth');
|
||||||
createAccountFromWalletSpy = sinon.spy(store, 'createAccountFromWallet');
|
createAccountFromWalletSpy = sinon.spy(store, 'createAccountFromWallet');
|
||||||
createAccountFromPhraseSpy = sinon.spy(store, 'createAccountFromPhrase');
|
createAccountFromPhraseSpy = sinon.spy(store, 'createAccountFromPhrase');
|
||||||
createAccountFromRawSpy = sinon.spy(store, 'createAccountFromRaw');
|
createAccountFromRawSpy = sinon.spy(store, 'createAccountFromRaw');
|
||||||
|
busySpy = sinon.spy(store, 'setBusy');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
store.createAccountFromGeth.restore();
|
||||||
|
store.createAccountFromWallet.restore();
|
||||||
|
store.createAccountFromPhrase.restore();
|
||||||
|
store.createAccountFromRaw.restore();
|
||||||
|
store.setBusy.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error on invalid createType', () => {
|
it('throws error on invalid createType', () => {
|
||||||
@ -399,38 +432,68 @@ describe('modals/CreateAccount/Store', () => {
|
|||||||
|
|
||||||
it('calls createAccountFromGeth on createType === fromGeth', () => {
|
it('calls createAccountFromGeth on createType === fromGeth', () => {
|
||||||
store.setCreateType('fromGeth');
|
store.setCreateType('fromGeth');
|
||||||
store.createAccount();
|
|
||||||
expect(createAccountFromGethSpy).to.have.been.called;
|
return store.createAccount().then(() => {
|
||||||
|
expect(createAccountFromGethSpy).to.have.been.called;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls createAccountFromWallet on createType === fromJSON', () => {
|
it('calls createAccountFromWallet on createType === fromJSON', () => {
|
||||||
store.setCreateType('fromJSON');
|
store.setCreateType('fromJSON');
|
||||||
store.createAccount();
|
|
||||||
expect(createAccountFromWalletSpy).to.have.been.called;
|
return store.createAccount().then(() => {
|
||||||
|
expect(createAccountFromWalletSpy).to.have.been.called;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls createAccountFromPhrase on createType === fromNew', () => {
|
it('calls createAccountFromPhrase on createType === fromNew', () => {
|
||||||
store.setCreateType('fromNew');
|
store.setCreateType('fromNew');
|
||||||
store.createAccount();
|
|
||||||
expect(createAccountFromPhraseSpy).to.have.been.called;
|
return store.createAccount().then(() => {
|
||||||
|
expect(createAccountFromPhraseSpy).to.have.been.called;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls createAccountFromPhrase on createType === fromPhrase', () => {
|
it('calls createAccountFromPhrase on createType === fromPhrase', () => {
|
||||||
store.setCreateType('fromPhrase');
|
store.setCreateType('fromPhrase');
|
||||||
store.createAccount();
|
|
||||||
expect(createAccountFromPhraseSpy).to.have.been.called;
|
return store.createAccount().then(() => {
|
||||||
|
expect(createAccountFromPhraseSpy).to.have.been.called;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls createAccountFromWallet on createType === fromPresale', () => {
|
it('calls createAccountFromWallet on createType === fromPresale', () => {
|
||||||
store.setCreateType('fromPresale');
|
store.setCreateType('fromPresale');
|
||||||
store.createAccount();
|
|
||||||
expect(createAccountFromWalletSpy).to.have.been.called;
|
return store.createAccount().then(() => {
|
||||||
|
expect(createAccountFromWalletSpy).to.have.been.called;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls createAccountFromRaw on createType === fromRaw', () => {
|
it('calls createAccountFromRaw on createType === fromRaw', () => {
|
||||||
store.setCreateType('fromRaw');
|
store.setCreateType('fromRaw');
|
||||||
store.createAccount();
|
|
||||||
expect(createAccountFromRawSpy).to.have.been.called;
|
return store.createAccount().then(() => {
|
||||||
|
expect(createAccountFromRawSpy).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('moves account to vault when vaultName set', () => {
|
||||||
|
store.setCreateType('fromNew');
|
||||||
|
store.setVaultName('testing');
|
||||||
|
|
||||||
|
return store.createAccount(vaultStore).then(() => {
|
||||||
|
expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets and rests the busy flag', () => {
|
||||||
|
store.setCreateType('fromNew');
|
||||||
|
|
||||||
|
return store.createAccount().then(() => {
|
||||||
|
expect(busySpy).to.have.been.calledWith(true);
|
||||||
|
expect(busySpy).to.have.been.calledWith(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createAccountFromGeth', () => {
|
describe('createAccountFromGeth', () => {
|
||||||
|
@ -37,25 +37,27 @@ class DeleteAccount extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
isBusy: false,
|
||||||
password: ''
|
password: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account } = this.props;
|
const { account } = this.props;
|
||||||
const { password } = this.state;
|
const { isBusy, password } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
busy={ isBusy }
|
||||||
className={ styles.body }
|
className={ styles.body }
|
||||||
onConfirm={ this.onDeleteConfirmed }
|
onConfirm={ this.onDeleteConfirmed }
|
||||||
onDeny={ this.closeDeleteDialog }
|
onDeny={ this.closeDeleteDialog }
|
||||||
|
open
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deleteAccount.title'
|
id='deleteAccount.title'
|
||||||
defaultMessage='confirm removal'
|
defaultMessage='confirm removal'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
visible
|
|
||||||
>
|
>
|
||||||
<div className={ styles.hero }>
|
<div className={ styles.hero }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -117,9 +119,13 @@ class DeleteAccount extends Component {
|
|||||||
const { account, newError } = this.props;
|
const { account, newError } = this.props;
|
||||||
const { password } = this.state;
|
const { password } = this.state;
|
||||||
|
|
||||||
|
this.setState({ isBusy: true });
|
||||||
|
|
||||||
return api.parity
|
return api.parity
|
||||||
.killAccount(account.address, password)
|
.killAccount(account.address, password)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
this.setState({ isBusy: true });
|
||||||
|
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
router.push('/accounts');
|
router.push('/accounts');
|
||||||
this.closeDeleteDialog();
|
this.closeDeleteDialog();
|
||||||
@ -128,6 +134,7 @@ class DeleteAccount extends Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
this.setState({ isBusy: false });
|
||||||
console.error('onDeleteConfirmed', error);
|
console.error('onDeleteConfirmed', error);
|
||||||
newError(new Error(`Deletion failed: ${error.message}`));
|
newError(new Error(`Deletion failed: ${error.message}`));
|
||||||
});
|
});
|
||||||
|
@ -21,11 +21,10 @@ 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, InputAddress, InputChip, Portal } from '~/ui';
|
import { Button, Form, Input, InputChip, Portal, VaultSelect } from '~/ui';
|
||||||
import { CancelIcon, SaveIcon } from '~/ui/Icons';
|
import { CancelIcon, SaveIcon } from '~/ui/Icons';
|
||||||
import VaultStore from '~/views/Vaults/store';
|
import VaultStore from '~/views/Vaults/store';
|
||||||
|
|
||||||
import VaultSelector from '../VaultSelector';
|
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -48,11 +47,12 @@ class EditMeta extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { description, name, nameError, tags } = this.store;
|
const { description, isBusy, name, nameError, tags } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal
|
<Portal
|
||||||
buttons={ this.renderActions() }
|
buttons={ this.renderActions() }
|
||||||
|
busy={ isBusy }
|
||||||
onClose={ this.onClose }
|
onClose={ this.onClose }
|
||||||
open
|
open
|
||||||
title={
|
title={
|
||||||
@ -62,7 +62,6 @@ class EditMeta extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{ this.renderVaultSelector() }
|
|
||||||
<Form>
|
<Form>
|
||||||
<Input
|
<Input
|
||||||
autoFocus
|
autoFocus
|
||||||
@ -110,7 +109,7 @@ class EditMeta extends Component {
|
|||||||
onTokensChange={ this.store.setTags }
|
onTokensChange={ this.store.setTags }
|
||||||
tokens={ tags.slice() }
|
tokens={ tags.slice() }
|
||||||
/>
|
/>
|
||||||
{ this.renderVault() }
|
{ this.renderVaultSelector() }
|
||||||
</Form>
|
</Form>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
@ -163,7 +162,7 @@ class EditMeta extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderVault () {
|
renderVaultSelector () {
|
||||||
const { isAccount, vaultName } = this.store;
|
const { isAccount, vaultName } = this.store;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
@ -171,40 +170,9 @@ class EditMeta extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputAddress
|
<VaultSelect
|
||||||
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 }
|
onSelect={ this.setVaultName }
|
||||||
selected={ vaultName }
|
value={ vaultName }
|
||||||
vaultStore={ this.vaultStore }
|
vaultStore={ this.vaultStore }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -215,21 +183,12 @@ class EditMeta extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(this.vaultStore)
|
||||||
.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);
|
||||||
@ -238,11 +197,6 @@ class EditMeta extends Component {
|
|||||||
|
|
||||||
setVaultName = (vaultName) => {
|
setVaultName = (vaultName) => {
|
||||||
this.store.setVaultName(vaultName);
|
this.store.setVaultName(vaultName);
|
||||||
this.toggleVaultSelector();
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleVaultSelector = () => {
|
|
||||||
this.store.toggleVaultSelector();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ 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 isBusy = false;
|
||||||
@observable description = null;
|
@observable description = null;
|
||||||
@observable meta = null;
|
@observable meta = null;
|
||||||
@observable name = null;
|
@observable name = null;
|
||||||
@ -73,6 +73,10 @@ export default class Store {
|
|||||||
this.passwordHint = passwordHint;
|
this.passwordHint = passwordHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setBusy = (isBusy) => {
|
||||||
|
this.isBusy = isBusy;
|
||||||
|
}
|
||||||
|
|
||||||
@action setTags = (tags) => {
|
@action setTags = (tags) => {
|
||||||
this.tags = tags.slice();
|
this.tags = tags.slice();
|
||||||
}
|
}
|
||||||
@ -81,11 +85,9 @@ export default class Store {
|
|||||||
this.vaultName = vaultName;
|
this.vaultName = vaultName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setVaultSelectorOpen = (isOpen) => {
|
save (vaultStore) {
|
||||||
this.isVaultSelectorOpen = isOpen;
|
this.setBusy(true);
|
||||||
}
|
|
||||||
|
|
||||||
save () {
|
|
||||||
const meta = {
|
const meta = {
|
||||||
description: this.description,
|
description: this.description,
|
||||||
tags: this.tags.peek()
|
tags: this.tags.peek()
|
||||||
@ -100,13 +102,20 @@ export default class Store {
|
|||||||
this._api.parity.setAccountName(this.address, this.name),
|
this._api.parity.setAccountName(this.address, this.name),
|
||||||
this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta))
|
this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta))
|
||||||
])
|
])
|
||||||
|
.then(() => {
|
||||||
|
if (vaultStore && this.isAccount && (this.meta.vault !== this.vaultName)) {
|
||||||
|
return vaultStore.moveAccount(this.vaultName, this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setBusy(false);
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('onSave', error);
|
console.error('onSave', error);
|
||||||
|
this.setBusy(false);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleVaultSelector () {
|
|
||||||
this.setVaultSelectorOpen(!this.isVaultSelectorOpen);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,24 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js';
|
import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js';
|
||||||
|
|
||||||
let api;
|
let api;
|
||||||
let store;
|
let store;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
return {
|
||||||
|
moveAccount: sinon.stub().resolves(true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createStore (account) {
|
function createStore (account) {
|
||||||
api = createApi();
|
api = createApi();
|
||||||
|
vaultStore = createVaultStore();
|
||||||
|
|
||||||
store = new Store(api, account);
|
store = new Store(api, account);
|
||||||
|
|
||||||
@ -108,6 +118,13 @@ describe('modals/EditMeta/Store', () => {
|
|||||||
createStore(ADDRESS);
|
createStore(ADDRESS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setBusy', () => {
|
||||||
|
it('sets the isBusy flag', () => {
|
||||||
|
store.setBusy('testing');
|
||||||
|
expect(store.isBusy).to.equal('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setDescription', () => {
|
describe('setDescription', () => {
|
||||||
it('sets the description', () => {
|
it('sets the description', () => {
|
||||||
store.setDescription('description');
|
store.setDescription('description');
|
||||||
@ -149,26 +166,56 @@ describe('modals/EditMeta/Store', () => {
|
|||||||
expect(store.vaultName).to.equal('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('operations', () => {
|
||||||
describe('save', () => {
|
describe('save', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createStore(ACCOUNT);
|
createStore(ACCOUNT);
|
||||||
|
sinon.spy(store, 'setBusy');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
store.setBusy.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the busy flag, clearing it when done', () => {
|
||||||
|
return store.save().then(() => {
|
||||||
|
expect(store.setBusy).to.have.been.calledWith(true);
|
||||||
|
expect(store.setBusy).to.have.been.calledWith(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls parity.setAccountName with the set value', () => {
|
it('calls parity.setAccountName with the set value', () => {
|
||||||
store.setName('test name');
|
store.setName('test name');
|
||||||
store.save();
|
|
||||||
|
|
||||||
expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
|
return store.save().then(() => {
|
||||||
|
expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls parity.setAccountMeta with the adjusted values', () => {
|
||||||
|
store.setDescription('some new description');
|
||||||
|
store.setPasswordHint('some new passwordhint');
|
||||||
|
store.setTags(['taga']);
|
||||||
|
|
||||||
|
return store.save().then(() => {
|
||||||
|
expect(api.parity.setAccountMeta).to.have.been.calledWith(
|
||||||
|
ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
|
||||||
|
description: 'some new description',
|
||||||
|
passwordHint: 'some new passwordhint',
|
||||||
|
tags: ['taga']
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('moves vault account when applicable', () => {
|
||||||
|
store.setVaultName('testing');
|
||||||
|
|
||||||
|
return store.save(vaultStore).then(() => {
|
||||||
|
expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ACCOUNT.address);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls parity.setAccountMeta with the adjusted values', () => {
|
it('calls parity.setAccountMeta with the adjusted values', () => {
|
||||||
@ -185,11 +232,4 @@ describe('modals/EditMeta/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toggleVaultSelector', () => {
|
|
||||||
it('inverts the selector state', () => {
|
|
||||||
store.toggleVaultSelector();
|
|
||||||
expect(store.isVaultSelectorOpen).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,9 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Portal, SelectionList, VaultCard } from '~/ui';
|
import Portal from '~/ui/Portal';
|
||||||
|
import SelectionList from '~/ui/SelectionList';
|
||||||
|
import VaultCard from '~/ui/VaultCard';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class VaultSelector extends Component {
|
export default class VaultSelector extends Component {
|
||||||
@ -48,10 +50,9 @@ export default class VaultSelector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderList () {
|
renderList () {
|
||||||
const { vaults } = this.props.vaultStore;
|
const { vaultsOpened } = this.props.vaultStore;
|
||||||
const openVaults = vaults.filter((vault) => vault.isOpen);
|
|
||||||
|
|
||||||
if (openVaults.length === 0) {
|
if (vaultsOpened.length === 0) {
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='vaults.selector.noneAvailable'
|
id='vaults.selector.noneAvailable'
|
||||||
@ -62,7 +63,7 @@ export default class VaultSelector extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectionList
|
<SelectionList
|
||||||
items={ openVaults }
|
items={ vaultsOpened }
|
||||||
isChecked={ this.isSelected }
|
isChecked={ this.isSelected }
|
||||||
noStretch
|
noStretch
|
||||||
onSelectClick={ this.onSelect }
|
onSelectClick={ this.onSelect }
|
||||||
|
@ -28,6 +28,7 @@ const VAULTS_CLOSED = [
|
|||||||
{ name: 'C' },
|
{ name: 'C' },
|
||||||
{ name: 'D' }
|
{ name: 'D' }
|
||||||
];
|
];
|
||||||
|
const VAULTS_ALL = VAULTS_OPENED.concat(VAULTS_CLOSED);
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
let instance;
|
let instance;
|
||||||
@ -37,7 +38,8 @@ let vaultStore;
|
|||||||
|
|
||||||
function createVaultStore () {
|
function createVaultStore () {
|
||||||
vaultStore = {
|
vaultStore = {
|
||||||
vaults: VAULTS_OPENED.concat(VAULTS_CLOSED)
|
vaults: VAULTS_ALL,
|
||||||
|
vaultsOpened: VAULTS_OPENED
|
||||||
};
|
};
|
||||||
|
|
||||||
return vaultStore;
|
return vaultStore;
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
.balances {
|
.balances {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 1em 0 0 0;
|
margin: 0.75em 0 0 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.certifications {
|
.certifications {
|
||||||
margin-top: 1em;
|
margin-top: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.certification,
|
.certification,
|
||||||
@ -43,7 +43,7 @@
|
|||||||
background-color: rgba(255, 255, 255, 0.07);
|
background-color: rgba(255, 255, 255, 0.07);
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
padding: 0.3em 0.6em 0.2em 2.6em;
|
padding: 0.3em 0.6em 0.2em 3em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -.25em;
|
top: -0.25em;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
js/src/ui/Form/VaultSelect/index.js
Normal file
17
js/src/ui/Form/VaultSelect/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 './vaultSelect';
|
109
js/src/ui/Form/VaultSelect/vaultSelect.js
Normal file
109
js/src/ui/Form/VaultSelect/vaultSelect.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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 React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import VaultSelector from '~/modals/VaultSelector';
|
||||||
|
import VaultStore from '~/views/Vaults/store';
|
||||||
|
|
||||||
|
import InputAddress from '../InputAddress';
|
||||||
|
|
||||||
|
export default class VaultSelect extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
api: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
value: PropTypes.string,
|
||||||
|
vaultStore: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isOpen: false
|
||||||
|
};
|
||||||
|
|
||||||
|
vaultStore = this.props.vaultStore || VaultStore.get(this.context.api);
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
return this.vaultStore.loadVaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { value } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ this.renderSelector() }
|
||||||
|
<InputAddress
|
||||||
|
allowCopy={ false }
|
||||||
|
allowInvalid
|
||||||
|
disabled
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.vaultSelect.hint'
|
||||||
|
defaultMessage='the vault this account is attached to'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.vaultSelect.label'
|
||||||
|
defaultMessage='associated vault'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.openSelector }
|
||||||
|
value={ (value || '').toUpperCase() }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSelector () {
|
||||||
|
const { value } = this.props;
|
||||||
|
const { isOpen } = this.state;
|
||||||
|
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VaultSelector
|
||||||
|
onClose={ this.closeSelector }
|
||||||
|
onSelect={ this.onSelect }
|
||||||
|
selected={ value }
|
||||||
|
vaultStore={ this.vaultStore }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
openSelector = () => {
|
||||||
|
this.setState({
|
||||||
|
isOpen: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSelector = () => {
|
||||||
|
this.setState({
|
||||||
|
isOpen: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (vaultName) => {
|
||||||
|
this.props.onSelect(vaultName);
|
||||||
|
this.closeSelector();
|
||||||
|
}
|
||||||
|
}
|
90
js/src/ui/Form/VaultSelect/vaultSelect.spec.js
Normal file
90
js/src/ui/Form/VaultSelect/vaultSelect.spec.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import VaultSelect from './';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let onSelect;
|
||||||
|
let vaultStore;
|
||||||
|
|
||||||
|
function createVaultStore () {
|
||||||
|
vaultStore = {
|
||||||
|
loadVaults: sinon.stub().resolves(true)
|
||||||
|
};
|
||||||
|
|
||||||
|
return vaultStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
onSelect = sinon.stub();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<VaultSelect
|
||||||
|
onSelect={ onSelect }
|
||||||
|
value='initialValue'
|
||||||
|
vaultStore={ createVaultStore() }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/Form/VaultSelect', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('InputAddress', () => {
|
||||||
|
let input;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
input = component.find('Connect(InputAddress)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(input.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes value from props', () => {
|
||||||
|
expect(input.props().value).to.equal('INITIALVALUE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes instance openSelector to onClick', () => {
|
||||||
|
expect(input.props().onClick).to.equal(instance.openSelector);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('onSelect', () => {
|
||||||
|
it('calls into props', () => {
|
||||||
|
instance.onSelect('testing');
|
||||||
|
expect(onSelect).to.have.been.calledWith('testing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -29,5 +29,6 @@ export Label from './Label';
|
|||||||
export RadioButtons from './RadioButtons';
|
export RadioButtons from './RadioButtons';
|
||||||
export Select from './Select';
|
export Select from './Select';
|
||||||
export TypedInput from './TypedInput';
|
export TypedInput from './TypedInput';
|
||||||
|
export VaultSelect from './VaultSelect';
|
||||||
|
|
||||||
export default from './form';
|
export default from './form';
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
.item {
|
.item {
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
17
js/src/ui/VaultTag/index.js
Normal file
17
js/src/ui/VaultTag/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 './vaultTag';
|
48
js/src/ui/VaultTag/vaultTag.css
Normal file
48
js/src/ui/VaultTag/vaultTag.css
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* TODO: These tag styles are shared with Balances & Certifications - should be made into
|
||||||
|
/* a component that can take a list of tags and render them in the correct format
|
||||||
|
*/
|
||||||
|
.vault {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0.75em 0 0;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
.vaultBody {
|
||||||
|
margin: 0.75em 0.5em 0 0;
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
border-radius: 16px;
|
||||||
|
max-height: 24px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 32px !important;
|
||||||
|
margin: -4px 1em 0 0;
|
||||||
|
width: 32px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
45
js/src/ui/VaultTag/vaultTag.js
Normal file
45
js/src/ui/VaultTag/vaultTag.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import IdentityIcon from '~/ui/IdentityIcon';
|
||||||
|
|
||||||
|
import styles from './vaultTag.css';
|
||||||
|
|
||||||
|
export default class VaultTag extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
vault: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { vault } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.vault }>
|
||||||
|
<div className={ styles.vaultBody }>
|
||||||
|
<IdentityIcon
|
||||||
|
address={ vault }
|
||||||
|
inline
|
||||||
|
/>
|
||||||
|
<div className={ styles.text }>
|
||||||
|
{ vault }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ export DappCard from './DappCard';
|
|||||||
export DappIcon from './DappIcon';
|
export DappIcon from './DappIcon';
|
||||||
export Errors from './Errors';
|
export Errors from './Errors';
|
||||||
export Features, { FEATURES, FeaturesStore } from './Features';
|
export Features, { FEATURES, FeaturesStore } from './Features';
|
||||||
export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
|
export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput, VaultSelect } from './Form';
|
||||||
export GasPriceEditor from './GasPriceEditor';
|
export GasPriceEditor from './GasPriceEditor';
|
||||||
export GasPriceSelector from './GasPriceSelector';
|
export GasPriceSelector from './GasPriceSelector';
|
||||||
export Icons from './Icons';
|
export Icons from './Icons';
|
||||||
@ -56,4 +56,5 @@ export Tooltips, { Tooltip } from './Tooltips';
|
|||||||
export TxHash from './TxHash';
|
export TxHash from './TxHash';
|
||||||
export TxList from './TxList';
|
export TxList from './TxList';
|
||||||
export VaultCard from './VaultCard';
|
export VaultCard from './VaultCard';
|
||||||
|
export VaultTag from './VaultTag';
|
||||||
export Warning from './Warning';
|
export Warning from './Warning';
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
.text {
|
.text {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags } from '~/ui';
|
import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags, VaultTag } from '~/ui';
|
||||||
|
|
||||||
import styles from './header.css';
|
import styles from './header.css';
|
||||||
|
|
||||||
@ -69,7 +69,6 @@ export default class Header extends Component {
|
|||||||
{ address }
|
{ address }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ this.renderVault() }
|
|
||||||
{ this.renderUuid() }
|
{ this.renderUuid() }
|
||||||
<div className={ styles.infoline }>
|
<div className={ styles.infoline }>
|
||||||
{ meta.description }
|
{ meta.description }
|
||||||
@ -81,6 +80,7 @@ export default class Header extends Component {
|
|||||||
balance={ balance }
|
balance={ balance }
|
||||||
/>
|
/>
|
||||||
<Certifications address={ address } />
|
<Certifications address={ address } />
|
||||||
|
{ this.renderVault() }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.tags }>
|
<div className={ styles.tags }>
|
||||||
@ -169,15 +169,7 @@ export default class Header extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.vault }>
|
<VaultTag vault={ meta.vault } />
|
||||||
<IdentityIcon
|
|
||||||
address={ meta.vault }
|
|
||||||
inline
|
|
||||||
/>
|
|
||||||
<div className={ styles.text }>
|
|
||||||
{ meta.vault }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import { isEqual } from 'lodash';
|
|||||||
import ReactTooltip from 'react-tooltip';
|
import ReactTooltip from 'react-tooltip';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Balance, Container, ContainerTitle, CopyToClipboard, IdentityIcon, IdentityName, Tags } from '~/ui';
|
import { Balance, Container, ContainerTitle, CopyToClipboard, IdentityIcon, IdentityName, Tags, VaultTag } from '~/ui';
|
||||||
import Certifications from '~/ui/Certifications';
|
import Certifications from '~/ui/Certifications';
|
||||||
import { arrayOrObjectProptype, nullableProptype } from '~/util/proptypes';
|
import { arrayOrObjectProptype, nullableProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
@ -117,6 +117,7 @@ class Summary extends Component {
|
|||||||
{ this.renderDescription(account.meta) }
|
{ this.renderDescription(account.meta) }
|
||||||
{ this.renderOwners() }
|
{ this.renderOwners() }
|
||||||
{ this.renderCertifications() }
|
{ this.renderCertifications() }
|
||||||
|
{ this.renderVault(account.meta) }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
link={ this.getLink() }
|
link={ this.getLink() }
|
||||||
@ -287,6 +288,16 @@ class Summary extends Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderVault (meta) {
|
||||||
|
if (!meta || !meta.vault) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VaultTag vault={ meta.vault } />
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
|
@ -56,6 +56,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
margin-top: -3.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.owners {
|
.owners {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -68,10 +72,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
|
||||||
margin-top: -3.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:hover) {
|
&:not(:hover) {
|
||||||
.tags {
|
.tags {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -37,6 +37,7 @@ export default class Store {
|
|||||||
@observable selectedAccounts = {};
|
@observable selectedAccounts = {};
|
||||||
@observable vault = null;
|
@observable vault = null;
|
||||||
@observable vaults = [];
|
@observable vaults = [];
|
||||||
|
@observable vaultsOpened = [];
|
||||||
@observable vaultNames = [];
|
@observable vaultNames = [];
|
||||||
@observable vaultName = '';
|
@observable vaultName = '';
|
||||||
@observable vaultNameError = ERRORS.noName;
|
@observable vaultNameError = ERRORS.noName;
|
||||||
@ -143,6 +144,7 @@ export default class Store {
|
|||||||
isOpen: openedVaults.includes(name)
|
isOpen: openedVaults.includes(name)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
this.vaultsOpened = this.vaults.filter((vault) => vault.isOpen);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +180,12 @@ describe('modals/Vaults/Store', () => {
|
|||||||
{ name: 'some', meta: 'metaSome', isOpen: false }
|
{ name: 'some', meta: 'metaSome', isOpen: false }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets the opened vaults', () => {
|
||||||
|
expect(store.vaultsOpened.peek()).to.deep.equal([
|
||||||
|
{ name: 'TEST', meta: 'metaTest', isOpen: true }
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setVaultDescription', () => {
|
describe('setVaultDescription', () => {
|
||||||
@ -553,6 +559,36 @@ describe('modals/Vaults/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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', () => {
|
describe('editVaultPassword', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.spy(store, 'setBusyMeta');
|
sinon.spy(store, 'setBusyMeta');
|
||||||
|
Loading…
Reference in New Issue
Block a user