openethereum/js/packages/dapp-accounts/accounts.js
Jaco Greeff 49fdd23d58 Ui 2 move to packages/* (#6113)
* Move secureApi to shell

* Extract isTestnet test

* Use mobx + subscriptions for status

* Re-add status indicator

* Add lerna

* Move intial packages to js/packages

* Move 3rdparty/{email,sms}-verification to correct location

* Move package.json & README to library src

* Move tests for library packages

* Move views & dapps to packages

* Move i18n to root

* Move shell to actual src (main app)

* Remove ~ references

* Change ~ to root (explicit imports)

* Finalise convert of ~

* Move views into dapps as well

* Move dapps to packages/

* Fix references

* Update css

* Update test spec locations

* Update tests

* Case fix

* Skip flakey tests

* Update enzyme

* Skip previously ignored tests

* Allow empty api for hw

* Re-add theme for embed
2017-07-21 15:46:53 +02:00

502 lines
11 KiB
JavaScript

// 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 { observe } from 'mobx';
import { observer } from 'mobx-react';
import { uniq, isEqual, pickBy } from 'lodash';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import HardwareStore from '@parity/shared/mobx/hardwareStore';
import { setVisibleAccounts } from '@parity/shared/redux/providers/personalActions';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, DappLink, Page } from '@parity/ui';
import { AddIcon, KeyIcon, FileDownloadIcon } from '@parity/ui/Icons';
import CreateWallet from './CreateWallet';
import CreateAccount from './CreateAccount';
import ExportAccount from './ExportAccount';
import List from './List';
import styles from './accounts.css';
@observer
class Accounts extends Component {
static contextTypes = {
api: PropTypes.object
}
static propTypes = {
accounts: PropTypes.object.isRequired,
accountsInfo: PropTypes.object.isRequired,
availability: PropTypes.string.isRequired,
hasAccounts: PropTypes.bool.isRequired,
health: PropTypes.object.isRequired,
setVisibleAccounts: PropTypes.func.isRequired
}
hwstore = HardwareStore.get(this.context.api);
state = {
_observeCancel: null,
addressBook: false,
newDialog: false,
newWalletDialog: false,
newExportDialog: false,
restoreDialog: false,
sortOrder: '',
searchValues: [],
searchTokens: [],
show: false
}
componentWillMount () {
// FIXME: Messy, figure out what it fixes and do it elegantly
window.setTimeout(() => {
this.setState({ show: true });
}, 100);
this.setVisibleAccounts();
this.setState({
_observeCancel: observe(this.hwstore, 'wallets', this.onHardwareChange, true)
});
}
componentWillReceiveProps (nextProps) {
const prevAddresses = Object.keys(this.props.accounts);
const nextAddresses = Object.keys(nextProps.accounts);
if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) {
this.setVisibleAccounts(nextProps);
}
}
componentWillUnmount () {
this.props.setVisibleAccounts([]);
this.state._observeCancel();
}
setVisibleAccounts (props = this.props) {
const { accounts, setVisibleAccounts } = props;
setVisibleAccounts(Object.keys(accounts));
}
render () {
return (
<div>
{ this.renderNewDialog() }
{ this.renderRestoreDialog() }
{ this.renderNewWalletDialog() }
{ this.renderNewExportDialog() }
{ this.renderActionbar() }
<Page>
{ this.renderExternalAccounts() }
{ this.renderWallets() }
{ this.renderAccounts() }
</Page>
</div>
);
}
renderLoading (object) {
const loadings = ((object && Object.keys(object)) || []).map((_, idx) => (
<div key={ idx } className={ styles.loading }>
<div />
</div>
));
return (
<div className={ styles.loadings }>
{ loadings }
</div>
);
}
renderAccounts () {
const { accounts } = this.props;
const _accounts = pickBy(accounts, (account) => account.uuid);
const _hasAccounts = Object.keys(_accounts).length > 0;
if (!this.state.show) {
return this.renderLoading(_accounts);
}
const { searchValues, sortOrder } = this.state;
return (
<List
search={ searchValues }
accounts={ _accounts }
empty={ !_hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken }
/>
);
}
renderWallets () {
const { accounts } = this.props;
const wallets = pickBy(accounts, (account) => account.wallet);
const hasWallets = Object.keys(wallets).length > 0;
if (!hasWallets) {
return null;
}
if (!this.state.show) {
return this.renderLoading(wallets);
}
const { searchValues, sortOrder } = this.state;
return (
<List
link='wallet'
search={ searchValues }
accounts={ wallets }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken }
/>
);
}
renderExternalAccounts () {
const { accounts } = this.props;
const { wallets } = this.hwstore;
const hardware = pickBy(accounts, (account) => account.hardware);
const external = pickBy(accounts, (account) => account.external);
const all = Object.assign({}, hardware, external);
if (Object.keys(all).length === 0) {
return null;
}
if (!this.state.show) {
return this.renderLoading(hardware);
}
const { searchValues, sortOrder } = this.state;
const disabled = Object
.keys(hardware)
.filter((address) => !wallets[address])
.reduce((result, address) => {
result[address] = true;
return result;
}, {});
return (
<List
search={ searchValues }
accounts={ all }
disabled={ disabled }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken }
/>
);
}
renderSearchButton () {
const onChange = (searchTokens, searchValues) => {
this.setState({ searchTokens, searchValues });
};
return (
<ActionbarSearch
key='searchAccount'
tokens={ this.state.searchTokens }
onChange={ onChange }
/>
);
}
renderSortButton () {
const onChange = (sortOrder) => {
this.setState({ sortOrder });
};
return (
<ActionbarSort
key='sortAccounts'
id='sortAccounts'
order={ this.state.sortOrder }
onChange={ onChange }
/>
);
}
renderActionbar () {
const buttons = [
this.renderVaultsButton(),
<Button
key='newAccount'
icon={ <AddIcon /> }
label={
<FormattedMessage
id='accounts.button.newAccount'
defaultMessage='account'
/>
}
onClick={ this.onNewAccountClick }
/>,
this.renderNewWalletButton(),
<Button
key='restoreAccount'
icon={ <AddIcon /> }
label={
<FormattedMessage
id='accounts.button.restoreAccount'
defaultMessage='restore'
/>
}
onClick={ this.onRestoreAccountClick }
/>,
<Button
key='newExport'
icon={ <FileDownloadIcon /> }
label={
<FormattedMessage
id='accounts.button.export'
defaultMessage='export'
/>
}
onClick={ this.onNewExportClick }
/>,
this.renderSearchButton(),
this.renderSortButton()
];
return (
<Actionbar
className={ styles.toolbar }
title={
<FormattedMessage
id='accounts.title'
defaultMessage='Accounts Overview'
/>
}
buttons={ buttons }
/>
);
}
renderNewDialog () {
const { accounts } = this.props;
const { newDialog } = this.state;
if (!newDialog) {
return null;
}
return (
<CreateAccount
accounts={ accounts }
onClose={ this.onNewAccountClose }
/>
);
}
renderRestoreDialog () {
const { accounts } = this.props;
const { restoreDialog } = this.state;
if (!restoreDialog) {
return null;
}
return (
<CreateAccount
accounts={ accounts }
onClose={ this.onRestoreAccountClose }
restore
/>
);
}
renderVaultsButton () {
if (this.props.availability !== 'personal') {
return null;
}
return (
<DappLink
to='/vaults'
key='vaults'
>
<Button
icon={ <KeyIcon /> }
label={
<FormattedMessage
id='accounts.button.vaults'
defaultMessage='vaults'
/>
}
onClick={ this.onVaultsClick }
/>
</DappLink>
);
}
renderNewWalletButton () {
if (this.props.availability !== 'personal') {
return null;
}
return (
<Button
key='newWallet'
icon={ <AddIcon /> }
label={
<FormattedMessage
id='accounts.button.newWallet'
defaultMessage='wallet'
/>
}
onClick={ this.onNewWalletClick }
/>
);
}
renderNewWalletDialog () {
const { accounts } = this.props;
const { newWalletDialog } = this.state;
if (!newWalletDialog) {
return null;
}
return (
<CreateWallet
accounts={ accounts }
onClose={ this.onNewWalletClose }
/>
);
}
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));
this.setState({ searchTokens: newSearchTokens });
}
onNewAccountClick = () => {
this.setState({
newDialog: true
});
}
onRestoreAccountClick = () => {
this.setState({
restoreDialog: true
});
}
onNewWalletClick = () => {
this.setState({
newWalletDialog: true
});
}
onNewExportClick = () => {
this.setState({
newExportDialog: true
});
}
onNewAccountClose = () => {
this.setState({
newDialog: false
});
}
onRestoreAccountClose = () => {
this.setState({
restoreDialog: false
});
}
onNewWalletClose = () => {
this.setState({
newWalletDialog: false
});
}
onNewExportClose = () => {
this.setState({
newExportDialog: false
});
}
onHardwareChange = () => {
const { accountsInfo } = this.props;
const { wallets } = this.hwstore;
Object
.keys(wallets)
.filter((address) => {
const account = accountsInfo[address];
return !account || !account.meta || !account.meta.hardware;
})
.forEach((address) => this.hwstore.createAccountInfo(wallets[address], accountsInfo[address]));
this.setVisibleAccounts();
}
}
function mapStateToProps (state) {
const { accounts, accountsInfo, hasAccounts } = state.personal;
const { availability = 'unknown' } = state.nodeStatus.nodeKind || {};
const { health } = state.nodeStatus;
return {
accounts,
accountsInfo,
availability,
hasAccounts,
health
};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
setVisibleAccounts
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Accounts);