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 { RefreshIcon } from '~/ui/Icons';
|
||||
|
||||
import ChangeVault from '../ChangeVault';
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@observer
|
||||
export default class CreateAccount extends Component {
|
||||
static propTypes = {
|
||||
newError: PropTypes.func.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
store: PropTypes.object.isRequired,
|
||||
vaultStore: PropTypes.object
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -123,6 +125,10 @@ export default class CreateAccount extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<PasswordStrength input={ password } />
|
||||
<ChangeVault
|
||||
store={ this.props.store }
|
||||
vaultStore={ this.props.vaultStore }
|
||||
/>
|
||||
{ this.renderIdentitySelector() }
|
||||
{ this.renderIdentities() }
|
||||
</Form>
|
||||
|
@ -20,12 +20,15 @@ import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Form, FileSelect, Input } from '~/ui';
|
||||
|
||||
import ChangeVault from '../ChangeVault';
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@observer
|
||||
export default class NewImport extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
store: PropTypes.object.isRequired,
|
||||
vaultStore: PropTypes.object
|
||||
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -88,6 +91,10 @@ export default class NewImport extends Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ChangeVault
|
||||
store={ this.props.store }
|
||||
vaultStore={ this.props.vaultStore }
|
||||
/>
|
||||
{ this.renderFileSelector() }
|
||||
</Form>
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { Form, Input } from '~/ui';
|
||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||
|
||||
import ChangeVault from '../ChangeVault';
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@observer
|
||||
@ -30,7 +31,8 @@ export default class RawKey extends Component {
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
store: PropTypes.object.isRequired,
|
||||
vaultStore: PropTypes.object
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -131,6 +133,10 @@ export default class RawKey extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<PasswordStrength input={ password } />
|
||||
<ChangeVault
|
||||
store={ this.props.store }
|
||||
vaultStore={ this.props.vaultStore }
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
@ -22,12 +22,14 @@ import { Checkbox } from 'material-ui';
|
||||
import { Form, Input } from '~/ui';
|
||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||
|
||||
import ChangeVault from '../ChangeVault';
|
||||
import styles from '../createAccount.css';
|
||||
|
||||
@observer
|
||||
export default class RecoveryPhrase extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
store: PropTypes.object.isRequired,
|
||||
vaultStore: PropTypes.object
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -127,6 +129,10 @@ export default class RecoveryPhrase extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<PasswordStrength input={ password } />
|
||||
<ChangeVault
|
||||
store={ this.props.store }
|
||||
vaultStore={ this.props.vaultStore }
|
||||
/>
|
||||
<Checkbox
|
||||
checked={ isWindowsPhrase }
|
||||
className={ styles.checkbox }
|
||||
|
@ -109,6 +109,7 @@
|
||||
display: flex;
|
||||
|
||||
.icon {
|
||||
color: rgb(167, 151, 0) !important;
|
||||
flex: 0 0 56px;
|
||||
height: 56px !important;
|
||||
margin-right: 0.75em;
|
||||
|
@ -20,11 +20,13 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg';
|
||||
import { createIdentityImg } from '~/api/util/identity';
|
||||
import { newError } from '~/redux/actions';
|
||||
import { Button, ModalBox, Portal } from '~/ui';
|
||||
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 AccountDetailsGeth from './AccountDetailsGeth';
|
||||
@ -82,13 +84,19 @@ class CreateAccount extends Component {
|
||||
}
|
||||
|
||||
store = new Store(this.context.api, this.props.accounts);
|
||||
vaultStore = VaultStore.get(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
return this.vaultStore.loadVaults();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { createType, stage } = this.store;
|
||||
const { isBusy, createType, stage } = this.store;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
buttons={ this.renderDialogActions() }
|
||||
busy={ isBusy }
|
||||
activeStep={ stage }
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
@ -120,6 +128,7 @@ class CreateAccount extends Component {
|
||||
<NewAccount
|
||||
newError={ this.props.newError }
|
||||
store={ this.store }
|
||||
vaultStore={ this.vaultStore }
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -132,18 +141,27 @@ class CreateAccount extends Component {
|
||||
|
||||
if (createType === 'fromPhrase') {
|
||||
return (
|
||||
<RecoveryPhrase store={ this.store } />
|
||||
<RecoveryPhrase
|
||||
store={ this.store }
|
||||
vaultStore={ this.vaultStore }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (createType === 'fromRaw') {
|
||||
return (
|
||||
<RawKey store={ this.store } />
|
||||
<RawKey
|
||||
store={ this.store }
|
||||
vaultStore={ this.vaultStore }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NewImport store={ this.store } />
|
||||
<NewImport
|
||||
store={ this.store }
|
||||
vaultStore={ this.vaultStore }
|
||||
/>
|
||||
);
|
||||
|
||||
case STAGE_INFO:
|
||||
@ -266,7 +284,7 @@ class CreateAccount extends Component {
|
||||
this.store.setBusy(true);
|
||||
|
||||
return this.store
|
||||
.createAccount()
|
||||
.createAccount(this.vaultStore)
|
||||
.then(() => {
|
||||
this.store.setBusy(false);
|
||||
this.store.nextStage();
|
||||
|
@ -42,7 +42,9 @@ function createApi () {
|
||||
newAccountFromWallet: sinon.stub().resolves(ADDRESS),
|
||||
phraseToAddress: () => Promise.resolve(`${++counter}`),
|
||||
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 rawKeyError = ERRORS.nokey;
|
||||
@observable stage = STAGE_SELECT_TYPE;
|
||||
@observable vaultName = '';
|
||||
@observable walletFile = '';
|
||||
@observable walletFileError = ERRORS.noFile;
|
||||
@observable walletJson = '';
|
||||
@ -95,6 +96,7 @@ export default class Store {
|
||||
this.nameError = null;
|
||||
this.rawKey = '';
|
||||
this.rawKeyError = null;
|
||||
this.vaultName = '';
|
||||
this.walletFile = '';
|
||||
this.walletFileError = null;
|
||||
this.walletJson = '';
|
||||
@ -134,6 +136,10 @@ export default class Store {
|
||||
this.gethImported = gethImported;
|
||||
}
|
||||
|
||||
@action setVaultName = (vaultName) => {
|
||||
this.vaultName = vaultName;
|
||||
}
|
||||
|
||||
@action setWindowsPhrase = (isWindowsPhrase = false) => {
|
||||
this.isWindowsPhrase = isWindowsPhrase;
|
||||
}
|
||||
@ -220,7 +226,28 @@ export default class Store {
|
||||
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) {
|
||||
case 'fromGeth':
|
||||
return this.createAccountFromGeth();
|
||||
|
@ -22,8 +22,20 @@ import { ACCOUNTS, ADDRESS, GETH_ADDRESSES, createApi } from './createAccount.te
|
||||
|
||||
let api;
|
||||
let store;
|
||||
let vaultStore;
|
||||
|
||||
function createVaultStore () {
|
||||
vaultStore = {
|
||||
moveAccount: sinon.stub().resolves(),
|
||||
listVaults: sinon.stub().resolves()
|
||||
};
|
||||
|
||||
return vaultStore;
|
||||
}
|
||||
|
||||
function createStore (loadGeth) {
|
||||
createVaultStore();
|
||||
|
||||
api = createApi();
|
||||
store = new Store(api, ACCOUNTS, loadGeth);
|
||||
|
||||
@ -65,8 +77,9 @@ describe('modals/CreateAccount/Store', () => {
|
||||
describe('@action', () => {
|
||||
describe('clearErrors', () => {
|
||||
beforeEach(() => {
|
||||
store.setName('');
|
||||
store.setPassword('123');
|
||||
store.setName('testing');
|
||||
store.setPassword('testing');
|
||||
store.setVaultName('testing');
|
||||
store.setRawKey('test');
|
||||
store.setWalletFile('test');
|
||||
store.setWalletJson('test');
|
||||
@ -75,10 +88,13 @@ describe('modals/CreateAccount/Store', () => {
|
||||
it('clears all errors', () => {
|
||||
store.clearErrors();
|
||||
|
||||
expect(store.name).to.equal('');
|
||||
expect(store.nameError).to.be.null;
|
||||
expect(store.password).to.equal('');
|
||||
expect(store.passwordRepeatError).to.be.null;
|
||||
expect(store.rawKey).to.equal('');
|
||||
expect(store.rawKeyError).to.be.null;
|
||||
expect(store.vaultName).to.equal('');
|
||||
expect(store.walletFile).to.equal('');
|
||||
expect(store.walletFileError).to.be.null;
|
||||
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', () => {
|
||||
it('sets the filepath', () => {
|
||||
store.setWalletFile('testing');
|
||||
@ -384,12 +407,22 @@ describe('modals/CreateAccount/Store', () => {
|
||||
let createAccountFromWalletSpy;
|
||||
let createAccountFromPhraseSpy;
|
||||
let createAccountFromRawSpy;
|
||||
let busySpy;
|
||||
|
||||
beforeEach(() => {
|
||||
createAccountFromGethSpy = sinon.spy(store, 'createAccountFromGeth');
|
||||
createAccountFromWalletSpy = sinon.spy(store, 'createAccountFromWallet');
|
||||
createAccountFromPhraseSpy = sinon.spy(store, 'createAccountFromPhrase');
|
||||
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', () => {
|
||||
@ -399,39 +432,69 @@ describe('modals/CreateAccount/Store', () => {
|
||||
|
||||
it('calls createAccountFromGeth on createType === fromGeth', () => {
|
||||
store.setCreateType('fromGeth');
|
||||
store.createAccount();
|
||||
|
||||
return store.createAccount().then(() => {
|
||||
expect(createAccountFromGethSpy).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createAccountFromWallet on createType === fromJSON', () => {
|
||||
store.setCreateType('fromJSON');
|
||||
store.createAccount();
|
||||
|
||||
return store.createAccount().then(() => {
|
||||
expect(createAccountFromWalletSpy).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createAccountFromPhrase on createType === fromNew', () => {
|
||||
store.setCreateType('fromNew');
|
||||
store.createAccount();
|
||||
|
||||
return store.createAccount().then(() => {
|
||||
expect(createAccountFromPhraseSpy).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createAccountFromPhrase on createType === fromPhrase', () => {
|
||||
store.setCreateType('fromPhrase');
|
||||
store.createAccount();
|
||||
|
||||
return store.createAccount().then(() => {
|
||||
expect(createAccountFromPhraseSpy).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createAccountFromWallet on createType === fromPresale', () => {
|
||||
store.setCreateType('fromPresale');
|
||||
store.createAccount();
|
||||
|
||||
return store.createAccount().then(() => {
|
||||
expect(createAccountFromWalletSpy).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createAccountFromRaw on createType === fromRaw', () => {
|
||||
store.setCreateType('fromRaw');
|
||||
store.createAccount();
|
||||
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -37,25 +37,27 @@ class DeleteAccount extends Component {
|
||||
}
|
||||
|
||||
state = {
|
||||
isBusy: false,
|
||||
password: ''
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { password } = this.state;
|
||||
const { isBusy, password } = this.state;
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
busy={ isBusy }
|
||||
className={ styles.body }
|
||||
onConfirm={ this.onDeleteConfirmed }
|
||||
onDeny={ this.closeDeleteDialog }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='deleteAccount.title'
|
||||
defaultMessage='confirm removal'
|
||||
/>
|
||||
}
|
||||
visible
|
||||
>
|
||||
<div className={ styles.hero }>
|
||||
<FormattedMessage
|
||||
@ -117,9 +119,13 @@ class DeleteAccount extends Component {
|
||||
const { account, newError } = this.props;
|
||||
const { password } = this.state;
|
||||
|
||||
this.setState({ isBusy: true });
|
||||
|
||||
return api.parity
|
||||
.killAccount(account.address, password)
|
||||
.then((result) => {
|
||||
this.setState({ isBusy: true });
|
||||
|
||||
if (result === true) {
|
||||
router.push('/accounts');
|
||||
this.closeDeleteDialog();
|
||||
@ -128,6 +134,7 @@ class DeleteAccount extends Component {
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({ isBusy: false });
|
||||
console.error('onDeleteConfirmed', error);
|
||||
newError(new Error(`Deletion failed: ${error.message}`));
|
||||
});
|
||||
|
@ -21,11 +21,10 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
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 VaultStore from '~/views/Vaults/store';
|
||||
|
||||
import VaultSelector from '../VaultSelector';
|
||||
import Store from './store';
|
||||
|
||||
@observer
|
||||
@ -48,11 +47,12 @@ class EditMeta extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { description, name, nameError, tags } = this.store;
|
||||
const { description, isBusy, name, nameError, tags } = this.store;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
buttons={ this.renderActions() }
|
||||
busy={ isBusy }
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
title={
|
||||
@ -62,7 +62,6 @@ class EditMeta extends Component {
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ this.renderVaultSelector() }
|
||||
<Form>
|
||||
<Input
|
||||
autoFocus
|
||||
@ -110,7 +109,7 @@ class EditMeta extends Component {
|
||||
onTokensChange={ this.store.setTags }
|
||||
tokens={ tags.slice() }
|
||||
/>
|
||||
{ this.renderVault() }
|
||||
{ this.renderVaultSelector() }
|
||||
</Form>
|
||||
</Portal>
|
||||
);
|
||||
@ -163,7 +162,7 @@ class EditMeta extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderVault () {
|
||||
renderVaultSelector () {
|
||||
const { isAccount, vaultName } = this.store;
|
||||
|
||||
if (!isAccount) {
|
||||
@ -171,40 +170,9 @@ class EditMeta extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<InputAddress
|
||||
allowCopy={ false }
|
||||
allowInvalid
|
||||
readOnly
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='editMeta.vault.hint'
|
||||
defaultMessage='the vault this account is attached to'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='editMeta.vault.label'
|
||||
defaultMessage='associated vault'
|
||||
/>
|
||||
}
|
||||
onClick={ this.toggleVaultSelector }
|
||||
value={ vaultName }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderVaultSelector () {
|
||||
const { isAccount, isVaultSelectorOpen, vaultName } = this.store;
|
||||
|
||||
if (!isAccount || !isVaultSelectorOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VaultSelector
|
||||
onClose={ this.toggleVaultSelector }
|
||||
<VaultSelect
|
||||
onSelect={ this.setVaultName }
|
||||
selected={ vaultName }
|
||||
value={ vaultName }
|
||||
vaultStore={ this.vaultStore }
|
||||
/>
|
||||
);
|
||||
@ -215,21 +183,12 @@ class EditMeta extends Component {
|
||||
}
|
||||
|
||||
onSave = () => {
|
||||
const { address, isAccount, meta, vaultName } = this.store;
|
||||
|
||||
if (this.store.hasError) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.store
|
||||
.save()
|
||||
.then(() => {
|
||||
if (isAccount && (meta.vault !== vaultName)) {
|
||||
return this.vaultStore.moveAccount(vaultName, address);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.save(this.vaultStore)
|
||||
.then(this.onClose)
|
||||
.catch((error) => {
|
||||
this.props.newError(error);
|
||||
@ -238,11 +197,6 @@ class EditMeta extends Component {
|
||||
|
||||
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 {
|
||||
@observable address = null;
|
||||
@observable isAccount = false;
|
||||
@observable isVaultSelectorOpen = false;
|
||||
@observable isBusy = false;
|
||||
@observable description = null;
|
||||
@observable meta = null;
|
||||
@observable name = null;
|
||||
@ -73,6 +73,10 @@ export default class Store {
|
||||
this.passwordHint = passwordHint;
|
||||
}
|
||||
|
||||
@action setBusy = (isBusy) => {
|
||||
this.isBusy = isBusy;
|
||||
}
|
||||
|
||||
@action setTags = (tags) => {
|
||||
this.tags = tags.slice();
|
||||
}
|
||||
@ -81,11 +85,9 @@ export default class Store {
|
||||
this.vaultName = vaultName;
|
||||
}
|
||||
|
||||
@action setVaultSelectorOpen = (isOpen) => {
|
||||
this.isVaultSelectorOpen = isOpen;
|
||||
}
|
||||
save (vaultStore) {
|
||||
this.setBusy(true);
|
||||
|
||||
save () {
|
||||
const meta = {
|
||||
description: this.description,
|
||||
tags: this.tags.peek()
|
||||
@ -100,13 +102,20 @@ export default class Store {
|
||||
this._api.parity.setAccountName(this.address, this.name),
|
||||
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) => {
|
||||
console.error('onSave', error);
|
||||
this.setBusy(false);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
toggleVaultSelector () {
|
||||
this.setVaultSelectorOpen(!this.isVaultSelectorOpen);
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,24 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Store from './store';
|
||||
import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
let vaultStore;
|
||||
|
||||
function createVaultStore () {
|
||||
return {
|
||||
moveAccount: sinon.stub().resolves(true)
|
||||
};
|
||||
}
|
||||
|
||||
function createStore (account) {
|
||||
api = createApi();
|
||||
vaultStore = createVaultStore();
|
||||
|
||||
store = new Store(api, account);
|
||||
|
||||
@ -108,6 +118,13 @@ describe('modals/EditMeta/Store', () => {
|
||||
createStore(ADDRESS);
|
||||
});
|
||||
|
||||
describe('setBusy', () => {
|
||||
it('sets the isBusy flag', () => {
|
||||
store.setBusy('testing');
|
||||
expect(store.isBusy).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDescription', () => {
|
||||
it('sets the description', () => {
|
||||
store.setDescription('description');
|
||||
@ -149,27 +166,57 @@ describe('modals/EditMeta/Store', () => {
|
||||
expect(store.vaultName).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVaultSelectorOpen', () => {
|
||||
it('sets the state', () => {
|
||||
store.setVaultSelectorOpen('testing');
|
||||
expect(store.isVaultSelectorOpen).to.equal('testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('save', () => {
|
||||
beforeEach(() => {
|
||||
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', () => {
|
||||
store.setName('test name');
|
||||
store.save();
|
||||
|
||||
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', () => {
|
||||
store.setDescription('some new description');
|
||||
@ -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 { 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
|
||||
export default class VaultSelector extends Component {
|
||||
@ -48,10 +50,9 @@ export default class VaultSelector extends Component {
|
||||
}
|
||||
|
||||
renderList () {
|
||||
const { vaults } = this.props.vaultStore;
|
||||
const openVaults = vaults.filter((vault) => vault.isOpen);
|
||||
const { vaultsOpened } = this.props.vaultStore;
|
||||
|
||||
if (openVaults.length === 0) {
|
||||
if (vaultsOpened.length === 0) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='vaults.selector.noneAvailable'
|
||||
@ -62,7 +63,7 @@ export default class VaultSelector extends Component {
|
||||
|
||||
return (
|
||||
<SelectionList
|
||||
items={ openVaults }
|
||||
items={ vaultsOpened }
|
||||
isChecked={ this.isSelected }
|
||||
noStretch
|
||||
onSelectClick={ this.onSelect }
|
||||
|
@ -28,6 +28,7 @@ const VAULTS_CLOSED = [
|
||||
{ name: 'C' },
|
||||
{ name: 'D' }
|
||||
];
|
||||
const VAULTS_ALL = VAULTS_OPENED.concat(VAULTS_CLOSED);
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
@ -37,7 +38,8 @@ let vaultStore;
|
||||
|
||||
function createVaultStore () {
|
||||
vaultStore = {
|
||||
vaults: VAULTS_OPENED.concat(VAULTS_CLOSED)
|
||||
vaults: VAULTS_ALL,
|
||||
vaultsOpened: VAULTS_OPENED
|
||||
};
|
||||
|
||||
return vaultStore;
|
||||
|
@ -18,7 +18,7 @@
|
||||
.balances {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 1em 0 0 0;
|
||||
margin: 0.75em 0 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
.certifications {
|
||||
margin-top: 1em;
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
.certification,
|
||||
@ -43,7 +43,7 @@
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
margin-right: 0.5em;
|
||||
margin-top: 1em;
|
||||
padding: 0.3em 0.6em 0.2em 2.6em;
|
||||
padding: 0.3em 0.6em 0.2em 3em;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:last-child {
|
||||
@ -52,7 +52,7 @@
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: -.25em;
|
||||
top: -0.25em;
|
||||
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 Select from './Select';
|
||||
export TypedInput from './TypedInput';
|
||||
export VaultSelect from './VaultSelect';
|
||||
|
||||
export default from './form';
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
.item {
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
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 Errors from './Errors';
|
||||
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 GasPriceSelector from './GasPriceSelector';
|
||||
export Icons from './Icons';
|
||||
@ -56,4 +56,5 @@ export Tooltips, { Tooltip } from './Tooltips';
|
||||
export TxHash from './TxHash';
|
||||
export TxList from './TxList';
|
||||
export VaultCard from './VaultCard';
|
||||
export VaultTag from './VaultTag';
|
||||
export Warning from './Warning';
|
||||
|
@ -66,6 +66,7 @@
|
||||
.text {
|
||||
display: inline-block;
|
||||
opacity: 0.25;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
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';
|
||||
|
||||
@ -69,7 +69,6 @@ export default class Header extends Component {
|
||||
{ address }
|
||||
</div>
|
||||
</div>
|
||||
{ this.renderVault() }
|
||||
{ this.renderUuid() }
|
||||
<div className={ styles.infoline }>
|
||||
{ meta.description }
|
||||
@ -81,6 +80,7 @@ export default class Header extends Component {
|
||||
balance={ balance }
|
||||
/>
|
||||
<Certifications address={ address } />
|
||||
{ this.renderVault() }
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.tags }>
|
||||
@ -169,15 +169,7 @@ export default class Header extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.vault }>
|
||||
<IdentityIcon
|
||||
address={ meta.vault }
|
||||
inline
|
||||
/>
|
||||
<div className={ styles.text }>
|
||||
{ meta.vault }
|
||||
</div>
|
||||
</div>
|
||||
<VaultTag vault={ meta.vault } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { isEqual } from 'lodash';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
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 { arrayOrObjectProptype, nullableProptype } from '~/util/proptypes';
|
||||
|
||||
@ -117,6 +117,7 @@ class Summary extends Component {
|
||||
{ this.renderDescription(account.meta) }
|
||||
{ this.renderOwners() }
|
||||
{ this.renderCertifications() }
|
||||
{ this.renderVault(account.meta) }
|
||||
</div>
|
||||
}
|
||||
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) {
|
||||
|
@ -56,6 +56,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
margin-top: -3.25em;
|
||||
}
|
||||
|
||||
.owners {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -68,10 +72,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
margin-top: -3.25em;
|
||||
}
|
||||
|
||||
&:not(:hover) {
|
||||
.tags {
|
||||
display: none;
|
||||
|
@ -37,6 +37,7 @@ export default class Store {
|
||||
@observable selectedAccounts = {};
|
||||
@observable vault = null;
|
||||
@observable vaults = [];
|
||||
@observable vaultsOpened = [];
|
||||
@observable vaultNames = [];
|
||||
@observable vaultName = '';
|
||||
@observable vaultNameError = ERRORS.noName;
|
||||
@ -143,6 +144,7 @@ export default class Store {
|
||||
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 }
|
||||
]);
|
||||
});
|
||||
|
||||
it('sets the opened vaults', () => {
|
||||
expect(store.vaultsOpened.peek()).to.deep.equal([
|
||||
{ name: 'TEST', meta: 'metaTest', isOpen: true }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(store, 'setBusyMeta');
|
||||
|
Loading…
Reference in New Issue
Block a user