Export acc js (#4973)

* Export account RPC

* Removing GethDirectory and ParityDirectory

* js export accounts as json

* js export accounts as json

* api - then - catch

* final touches

* pass

* oops

* individual accounts

* refactoring

* refactor one

* refactor one

* refactor two

* some grumble fixes

* file name changes

* constructor

* constructor

* git recognize file name change

* spec and updates

* specs

* one tiny fix

* one tiny fix

* grumbles

* more grumbles

* sliders

* ff

* pointer default

* grumbles

* almost ready

* lots of updates

* accountList

* stupid debuglog

* bug fix

* bug fix

* some more good ol fashioned updates

* filter accounts

* clean

* update spec

* ff

* ff-f

* balances fix
This commit is contained in:
Craig O'Connor 2017-04-26 05:34:48 -04:00 committed by Jaco Greeff
parent 3be3b78c90
commit cf904b6b2f
15 changed files with 765 additions and 15 deletions

View File

@ -0,0 +1,177 @@
// 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 { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { newError } from '~/redux/actions';
import { personalAccountsInfo } from '~/redux/providers/personalActions';
import { AccountCard, Button, Portal, SelectionList } from '~/ui';
import { CancelIcon, CheckIcon } from '~/ui/Icons';
import ExportInput from './exportInput';
import ExportStore from './exportStore';
@observer
class ExportAccount extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
};
static propTypes = {
accounts: PropTypes.object.isRequired,
balances: PropTypes.object.isRequired,
newError: PropTypes.func.isRequired,
personalAccountsInfo: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired
};
componentWillMount () {
const { accounts, newError } = this.props;
this.exportStore = new ExportStore(this.context.api, accounts, newError, null);
}
render () {
const { canExport } = this.exportStore;
return (
<Portal
buttons={ [
<Button
icon={ <CancelIcon /> }
key='cancel'
label={
<FormattedMessage
id='export.accounts.button.cancel'
defaultMessage='Cancel'
/>
}
onClick={ this.onClose }
/>,
<Button
disabled={ !canExport }
icon={ <CheckIcon /> }
key='execute'
label={
<FormattedMessage
id='export.accounts.button.export'
defaultMessage='Export'
/>
}
onClick={ this.onExport }
/>
] }
onClose={ this.onClose }
open
title={
<FormattedMessage
id='export.accounts.title'
defaultMessage='Export an Account'
/>
}
>
{ this.renderList() }
</Portal>
);
}
renderList () {
const { accounts } = this.props;
const { selectedAccounts } = this.exportStore;
const accountList = Object.values(accounts)
.filter((account) => account.uuid)
.map((account) => {
account.checked = !!(selectedAccounts[account.address]);
return account;
});
return (
<SelectionList
items={ accountList }
noStretch
onSelectClick={ this.onSelect }
renderItem={ this.renderAccount }
/>
);
}
renderAccount = (account) => {
const { balances } = this.props;
const balance = balances[account.address];
const { changePassword, getPassword } = this.exportStore;
const password = getPassword(account);
return (
<AccountCard
account={ account }
balance={ balance }
>
<div>
<ExportInput
account={ account }
value={ password }
onClick={ this.onClick }
onChange={ changePassword }
/>
</div>
</AccountCard>
);
}
onSelect = (account) => {
this.exportStore.toggleSelectedAccount(account.address);
}
onClick = (address) => {
this.exportStore.onClick(address);
}
onClose = () => {
this.props.onClose();
}
onExport = () => {
this.exportStore.onExport();
}
}
function mapStateToProps (state) {
const { balances } = state;
const { accounts } = state.personal;
return {
accounts,
balances
};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
newError,
personalAccountsInfo
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExportAccount);

View File

@ -0,0 +1,76 @@
// 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 ExportAccount from './';
const ADDRESS = '0x0123456789012345678901234567890123456789';
let component;
let NEWERROR;
let PAI;
let ONCLOSE;
let reduxStore;
function createReduxStore () {
reduxStore = {
dispatch: sinon.stub(),
subscribe: sinon.stub(),
getState: () => {
return {
balances: {
balances: {
[ADDRESS]: {}
}
},
personal: {
accounts: {
[ADDRESS]: {
address: ADDRESS
}
}
}
};
}
};
return reduxStore;
}
function render () {
component = shallow(
<ExportAccount
newError={ NEWERROR }
personalAccountsInfo={ PAI }
onClose={ ONCLOSE }
/>,
{
context: { api: {}, store: createReduxStore() }
}
);
return component;
}
describe('CreateExportModal', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
});

View File

@ -0,0 +1,61 @@
// 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 { Input } from '~/ui/Form';
export default class ExportInput extends Component {
static propTypes = {
account: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string
};
render () {
const { value, onChange } = this.props;
return (
<Input
type='password'
label={
<FormattedMessage
id='export.setPassword.label'
defaultMessage='Password'
/>
}
hint={
<FormattedMessage
id='export.setPassword.hint'
defaultMessage='Enter password Here'
/>
}
value={ value }
onClick={ this.onClick }
onChange={ onChange }
/>
);
}
onClick = (event) => {
const { account, onClick } = this.props;
event.stopPropagation();
onClick && onClick(account.address);
}
}

View 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 './exportInput';

View File

@ -0,0 +1,106 @@
// 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 { action, observable } from 'mobx';
import FileSaver from 'file-saver';
export default class ExportStore {
@observable canExport = false;
@observable selectedAccount = '';
@observable selectedAccounts = {};
@observable passwordInputs = {};
constructor (api, accounts, newError, address) {
this.accounts = accounts;
this._api = api;
this._newError = newError;
if (address) {
this.selectedAccounts[address] = true;
this.selectedAccount = address;
}
}
@action changePassword = (event, password) => {
this.passwordInputs[this.selectedAccount] = password;
}
@action getPassword = (address) => {
return this.passwordInputs[address];
}
@action onClick = (address) => {
this.selectedAccount = address;
}
@action resetAccountValue = () => {
this.passwordInputs[this.selectedAccount] = '';
}
@action setAccounts = (accounts) => {
this.accounts = accounts;
}
@action setSelectedAccount = (addr) => {
this.selectedAccounts[addr] = true;
this.canExport = true;
}
@action toggleSelectedAccount = (addr) => {
if (this.selectedAccounts[addr]) {
delete this.selectedAccounts[addr];
} else {
this.selectedAccounts[addr] = true;
}
this.canExport = false;
Object
.keys(this.selectedAccounts)
.forEach((address) => {
if (this.selectedAccounts[address]) {
this.canExport = true;
}
});
}
onExport = (event) => {
const { parity } = this._api;
const accounts = Object.keys(this.selectedAccounts);
accounts.forEach((address) => {
let password = this.passwordInputs[address];
parity
.exportAccount(address, password)
.then((content) => {
const text = JSON.stringify(content, null, 4);
const blob = new Blob([ text ], { type: 'application/json' });
const filename = this.accounts[address].uuid;
FileSaver.saveAs(blob, `${filename}.json`);
this.accountValue = '';
if (event) { event(); }
})
.catch((err) => {
const { name, meta } = this.accounts[address];
const { passwordHint } = meta;
this._newError({
message: `[${err.code}] Account "${name}" - Incorrect password. (Password Hint: ${passwordHint})`
});
});
});
}
}

View File

@ -0,0 +1,133 @@
// 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 sinon from 'sinon';
import ExportStore from './exportStore';
const ADDRESS = '0x00000123456789abcdef123456789abcdef123456789abcdef';
const ADDRESS_2 = '0x123456789abcdef123456789abcdef123456789abcdef00000';
const ACCOUNTS = { ADDRESS: {}, ADDRESS_2: {} };
let api;
let AccountStore;
function createApi () {
return {
eth: {
},
parity: {
exportAccount: sinon.stub().resolves({})
}
};
}
function createMultiAccountStore (loadGeth) {
api = createApi();
AccountStore = new ExportStore(api, ACCOUNTS, null, null);
return AccountStore;
}
describe('modals/exportAccount/Store', () => {
beforeEach(() => {
createMultiAccountStore();
});
describe('constructor', () => {
it('insert api', () => {
expect(AccountStore._api).to.deep.equal(api);
});
it('insert accounts', () => {
expect(AccountStore.accounts).to.deep.equal(ACCOUNTS);
});
it('newError created', () => {
expect(AccountStore._newError).to.deep.equal(null);
});
});
describe('@action', () => {
describe('toggleSelectedAccount', () => {
it('Updates the selected accounts', () => {
// First set selectedAccounts
AccountStore.selectedAccounts = {
[ADDRESS]: true,
[ADDRESS_2]: false
};
// Toggle
AccountStore.toggleSelectedAccount(ADDRESS_2);
// Prep eqality
const eq = {
[ADDRESS]: true,
[ADDRESS_2]: true
};
// Check equality
expect(JSON.stringify(AccountStore.selectedAccounts)).to.deep.equal(JSON.stringify(eq));
});
});
describe('getPassword', () => {
it('Grab from the selected accounts input', () => {
// First set passwordInputs
AccountStore.passwordInputs = {
[ADDRESS]: 'abc'
};
// getPassword
const pass = AccountStore.getPassword(ADDRESS);
// Check equality
expect(AccountStore.passwordInputs[ADDRESS]).to.deep.equal(pass);
});
});
describe('setPassword & getPassword', () => {
it('First save the input of the selected account, than get the input.', () => {
// Set password
AccountStore.selectedAccount = ADDRESS;
// Set new pass
AccountStore.changePassword(null, 'abc');
// getPassword
const pass = AccountStore.getPassword(ADDRESS);
// Check equality
expect(AccountStore.passwordInputs[ADDRESS]).to.deep.equal(pass);
});
});
describe('changePassword', () => {
it('Change the stored value with the new input.', () => {
// First set selectedAccounts
AccountStore.selectedAccounts = {
[ADDRESS]: true,
[ADDRESS_2]: false
};
// First set passwordInputs
AccountStore.passwordInputs = {
[ADDRESS]: 'abc'
};
// 'Click' on the address:
AccountStore.onClick(ADDRESS);
// Change password
AccountStore.changePassword(null, '123');
// Check equality
expect(AccountStore.passwordInputs[ADDRESS]).to.deep.equal('123');
});
});
});
});

View 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 './exportAccount';

View File

@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
export DeployContract from './DeployContract';
export EditMeta from './EditMeta';
export ExecuteContract from './ExecuteContract';
export ExportAccount from './ExportAccount';
export Faucet from './Faucet';
export FirstRun from './FirstRun';
export LoadContract from './LoadContract';

View File

@ -27,6 +27,7 @@ import styles from './accountCard.css';
export default class AccountCard extends Component {
static propTypes = {
children: PropTypes.node,
account: PropTypes.object.isRequired,
balance: PropTypes.object,
className: PropTypes.string,
@ -44,7 +45,7 @@ export default class AccountCard extends Component {
};
render () {
const { account, balance, className, onFocus } = this.props;
const { account, balance, className, onFocus, children } = this.props;
const { copied } = this.state;
const { address, description, meta = {}, name } = account;
const { tags = [] } = meta;
@ -89,6 +90,7 @@ export default class AccountCard extends Component {
className={ styles.balance }
showOnlyEth
/>
{ children }
</div>
{

View File

@ -29,7 +29,7 @@ export default class SelectionList extends Component {
items: arrayOrObjectProptype().isRequired,
noStretch: PropTypes.bool,
onDefaultClick: PropTypes.func,
onSelectClick: PropTypes.func.isRequired,
onSelectClick: PropTypes.func,
onSelectDoubleClick: PropTypes.func,
renderItem: PropTypes.func.isRequired
};
@ -57,8 +57,10 @@ export default class SelectionList extends Component {
: item.checked;
const handleClick = () => {
if (onSelectClick) {
onSelectClick(item);
return false;
}
};
const handleDoubleClick = () => {
onSelectDoubleClick(item);

View File

@ -19,3 +19,8 @@
width: 24px;
height: 24px;
}
.textbox {
line-height: 1.5em;
margin-bottom: 1.5em;
}

View File

@ -20,13 +20,15 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { newError } from '~/redux/actions';
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
import HardwareStore from '~/mobx/hardwareStore';
import ExportStore from '~/modals/ExportAccount/exportStore';
import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals';
import { setVisibleAccounts } from '~/redux/providers/personalActions';
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
import { Actionbar, Button, Page } from '~/ui';
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
import { Actionbar, Button, ConfirmDialog, Input, Page, Portal } from '~/ui';
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon, FileDownloadIcon } from '~/ui/Icons';
import DeleteAddress from '../Address/Delete';
@ -42,6 +44,7 @@ class Account extends Component {
};
static propTypes = {
accounts: PropTypes.object.isRequired,
fetchCertifiers: PropTypes.func.isRequired,
fetchCertifications: PropTypes.func.isRequired,
setVisibleAccounts: PropTypes.func.isRequired,
@ -49,12 +52,20 @@ class Account extends Component {
account: PropTypes.object,
certifications: PropTypes.object,
netVersion: PropTypes.string.isRequired,
newError: PropTypes.func,
params: PropTypes.object
}
store = new Store();
hwstore = HardwareStore.get(this.context.api);
componentWillMount () {
const { accounts, newError, params } = this.props;
const { address } = params;
this.exportStore = new ExportStore(this.context.api, accounts, newError, address);
}
componentDidMount () {
this.props.fetchCertifiers();
this.setVisibleAccounts();
@ -63,10 +74,15 @@ class Account extends Component {
componentWillReceiveProps (nextProps) {
const prevAddress = this.props.params.address;
const nextAddress = nextProps.params.address;
const { accounts } = nextProps;
if (prevAddress !== nextAddress) {
this.setVisibleAccounts(nextProps);
}
if (!Object.keys(this.exportStore.accounts).length) {
this.exportStore.setAccounts(accounts);
}
}
componentWillUnmount () {
@ -95,6 +111,7 @@ class Account extends Component {
<div>
{ this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) }
{ this.renderExportDialog() }
{ this.renderFaucetDialog() }
{ this.renderFundDialog() }
{ this.renderPasswordDialog(account) }
@ -212,6 +229,17 @@ class Account extends Component {
}
onClick={ this.store.toggleEditDialog }
/>,
<Button
icon={ <FileDownloadIcon /> }
key='exportmeta'
label={
<FormattedMessage
id='account.button.export'
defaultMessage='export'
/>
}
onClick={ this.store.toggleExportDialog }
/>,
!(account.external || account.hardware) && (
<Button
icon={ <LockedIcon /> }
@ -320,6 +348,64 @@ class Account extends Component {
);
}
renderExportDialog () {
const { changePassword, accountValue } = this.exportStore;
if (!this.store.isExportVisible) {
return null;
}
return (
<Portal
open
isSmallModal
onClose={ this.exportClose }
>
<ConfirmDialog
open
disabledConfirm={ false }
labelConfirm='Export'
labelDeny='Cancel'
onConfirm={ this.onExport }
onDeny={ this.exportClose }
title={
<FormattedMessage
id='export.account.title'
defaultMessage='Export Account'
/>
}
>
<div className={ styles.textbox }>
<FormattedMessage
id='export.account.info'
defaultMessage='Export your account as a JSON file. Please enter the password linked with this account.'
/>
</div>
<Input
className={ styles.textbox }
onKeyDown={ this.onEnter }
autoFocus
type='password'
hint={
<FormattedMessage
id='export.account.password.hint'
defaultMessage='The password specified when creating this account'
/>
}
label={
<FormattedMessage
id='export.account.password.label'
defaultMessage='Account password'
/>
}
onChange={ changePassword }
value={ accountValue }
/>
</ConfirmDialog>
</Portal>
);
}
renderFaucetDialog () {
const { netVersion } = this.props;
@ -393,6 +479,30 @@ class Account extends Component {
/>
);
}
onEnter = (event) => {
if (event.key === 'Enter') {
this.onExport();
}
}
exportClose = () => {
const { toggleExportDialog } = this.store;
const { resetAccountValue } = this.exportStore;
resetAccountValue();
toggleExportDialog();
}
onExport = () => {
const { onExport } = this.exportStore;
onExport(this.hideExport);
}
hideExport = () => {
this.store.toggleExportDialog();
}
}
function mapStateToProps (state, props) {
@ -406,6 +516,7 @@ function mapStateToProps (state, props) {
return {
account,
accounts,
certifications,
netVersion
};
@ -415,6 +526,7 @@ function mapDispatchToProps (dispatch) {
return bindActionCreators({
fetchCertifiers,
fetchCertifications,
newError,
setVisibleAccounts
}, dispatch);
}

View File

@ -19,6 +19,7 @@ import { action, observable } from 'mobx';
export default class Store {
@observable isDeleteVisible = false;
@observable isEditVisible = false;
@observable isExportVisible = false;
@observable isFaucetVisible = false;
@observable isFundVisible = false;
@observable isPasswordVisible = false;
@ -33,6 +34,10 @@ export default class Store {
this.isEditVisible = !this.isEditVisible;
}
@action toggleExportDialog = () => {
this.isExportVisible = !this.isExportVisible;
}
@action toggleFaucetDialog = () => {
this.isFaucetVisible = !this.isFaucetVisible;
}

View File

@ -24,9 +24,9 @@ import { Link } from 'react-router';
import { bindActionCreators } from 'redux';
import HardwareStore from '~/mobx/hardwareStore';
import { CreateAccount, CreateWallet } from '~/modals';
import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '~/ui';
import { AddIcon, KeyIcon } from '~/ui/Icons';
import { CreateAccount, CreateWallet, ExportAccount } from '~/modals';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '~/ui';
import { AddIcon, KeyIcon, FileDownloadIcon } from '~/ui/Icons';
import { setVisibleAccounts } from '~/redux/providers/personalActions';
import List from './List';
@ -52,6 +52,7 @@ class Accounts extends Component {
addressBook: false,
newDialog: false,
newWalletDialog: false,
newExportDialog: false,
sortOrder: '',
searchValues: [],
searchTokens: [],
@ -96,6 +97,7 @@ class Accounts extends Component {
<div>
{ this.renderNewDialog() }
{ this.renderNewWalletDialog() }
{ this.renderNewExportDialog() }
{ this.renderActionbar() }
<Page>
@ -244,8 +246,6 @@ class Accounts extends Component {
}
renderActionbar () {
const { accounts } = this.props;
const buttons = [
<Link
to='/vaults'
@ -284,10 +284,16 @@ class Accounts extends Component {
}
onClick={ this.onNewWalletClick }
/>,
<ActionbarExport
key='exportAccounts'
content={ accounts }
filename='accounts'
<Button
key='newExport'
icon={ <FileDownloadIcon /> }
label={
<FormattedMessage
id='accounts.button.export'
defaultMessage='export'
/>
}
onClick={ this.onNewExportClick }
/>,
this.renderSearchButton(),
this.renderSortButton()
@ -351,6 +357,20 @@ class Accounts extends Component {
);
}
renderNewExportDialog () {
const { newExportDialog } = this.state;
if (!newExportDialog) {
return null;
}
return (
<ExportAccount
onClose={ this.onNewExportClose }
/>
);
}
onAddSearchToken = (token) => {
const { searchTokens } = this.state;
const newSearchTokens = uniq([].concat(searchTokens, token));
@ -370,6 +390,12 @@ class Accounts extends Component {
});
}
onNewExportClick = () => {
this.setState({
newExportDialog: true
});
}
onNewAccountClose = () => {
this.setState({
newDialog: false
@ -382,6 +408,12 @@ class Accounts extends Component {
});
}
onNewExportClose = () => {
this.setState({
newExportDialog: false
});
}
onNewAccountUpdate = () => {
}

4
yarn.lock Normal file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1