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:
parent
3be3b78c90
commit
cf904b6b2f
177
js/src/modals/ExportAccount/exportAccount.js
Normal file
177
js/src/modals/ExportAccount/exportAccount.js
Normal 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);
|
76
js/src/modals/ExportAccount/exportAccount.spec.js
Normal file
76
js/src/modals/ExportAccount/exportAccount.spec.js
Normal 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;
|
||||
});
|
||||
});
|
61
js/src/modals/ExportAccount/exportInput/exportInput.js
Normal file
61
js/src/modals/ExportAccount/exportInput/exportInput.js
Normal 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);
|
||||
}
|
||||
}
|
17
js/src/modals/ExportAccount/exportInput/index.js
Normal file
17
js/src/modals/ExportAccount/exportInput/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 './exportInput';
|
106
js/src/modals/ExportAccount/exportStore.js
Normal file
106
js/src/modals/ExportAccount/exportStore.js
Normal 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})`
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
133
js/src/modals/ExportAccount/exportStore.spec.js
Normal file
133
js/src/modals/ExportAccount/exportStore.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
17
js/src/modals/ExportAccount/index.js
Normal file
17
js/src/modals/ExportAccount/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 './exportAccount';
|
@ -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';
|
||||
|
@ -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>
|
||||
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -19,3 +19,8 @@
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = () => {
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user