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,38 +432,68 @@ describe('modals/CreateAccount/Store', () => {
 | 
			
		||||
 | 
			
		||||
      it('calls createAccountFromGeth on createType === 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', () => {
 | 
			
		||||
        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', () => {
 | 
			
		||||
        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', () => {
 | 
			
		||||
        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', () => {
 | 
			
		||||
        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', () => {
 | 
			
		||||
        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', () => {
 | 
			
		||||
 | 
			
		||||
@ -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,26 +166,56 @@ 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();
 | 
			
		||||
 | 
			
		||||
        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', () => {
 | 
			
		||||
@ -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