openethereum/js/packages/dapp-account/account.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

555 lines
14 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 { observer } from 'mobx-react';
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 HistoryStore from '@parity/shared/mobx/historyStore';
import { newError } from '@parity/shared/redux/actions';
import { setVisibleAccounts } from '@parity/shared/redux/providers/personalActions';
import { fetchCertifiers, fetchCertifications } from '@parity/shared/redux/providers/certifications/actions';
import { Actionbar, Button, ConfirmDialog, Input, Page, Portal } from '@parity/ui';
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon, FileDownloadIcon } from '@parity/ui/Icons';
import shapeshiftBtn from '@parity/shared/assets/images/shapeshift-btn.png';
import DeleteAccount from './DeleteAccount';
import EditMeta from './EditMeta';
import DeleteAddress from '@parity/dapp-address/Delete';
import ExportStore from '@parity/dapp-accounts/ExportAccount/exportStore';
import Faucet from './Faucet';
import PasswordManager from './PasswordManager';
import Shapeshift from './Shapeshift';
import Transfer from './Transfer';
import Verification from './Verification';
import Header from './Header';
import Store from './store';
import Transactions from './Transactions';
import styles from './account.css';
const accountsHistory = HistoryStore.get('accounts');
@observer
class Account extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
};
static propTypes = {
accounts: PropTypes.object.isRequired,
fetchCertifiers: PropTypes.func.isRequired,
fetchCertifications: PropTypes.func.isRequired,
setVisibleAccounts: PropTypes.func.isRequired,
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 () {
const { params } = this.props;
if (params.address) {
accountsHistory.add(params.address, 'wallet');
}
this.props.fetchCertifiers();
this.setVisibleAccounts();
}
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 () {
this.props.setVisibleAccounts([]);
}
setVisibleAccounts (props = this.props) {
const { params, setVisibleAccounts, fetchCertifications } = props;
const addresses = [params.address];
setVisibleAccounts(addresses);
fetchCertifications(params.address);
}
render () {
const { account } = this.props;
const { address } = this.props.params;
if (!account) {
return null;
}
const isAvailable = !account.hardware || this.hwstore.isConnected(address);
return (
<div>
{ this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) }
{ this.renderExportDialog() }
{ this.renderFaucetDialog() }
{ this.renderFundDialog() }
{ this.renderPasswordDialog(account) }
{ this.renderTransferDialog(account) }
{ this.renderVerificationDialog() }
{ this.renderActionbar(account) }
<Page padded>
<Header
account={ account }
disabled={ !isAvailable }
/>
<Transactions
address={ address }
/>
</Page>
</div>
);
}
isKovan = (netVersion) => {
return netVersion === '42';
}
isMainnet = (netVersion) => {
return netVersion === '1';
}
isFaucettable = (netVersion, certifications, address) => {
return this.isKovan(netVersion) || (
this.isMainnet(netVersion) &&
this.isSmsCertified(certifications, address)
);
}
isSmsCertified = (_certifications, address) => {
const certifications = _certifications && _certifications[address]
? _certifications[address].filter((cert) => cert.name.indexOf('smsverification') === 0)
: [];
return certifications.length !== 0;
}
renderActionbar (account) {
const { certifications, netVersion } = this.props;
const { address } = this.props.params;
const isVerifiable = this.isMainnet(netVersion);
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
const buttons = [
<Button
icon={ <SendIcon /> }
key='transferFunds'
label={
<FormattedMessage
id='account.button.transfer'
defaultMessage='transfer'
/>
}
onClick={ this.store.toggleTransferDialog }
/>,
<Button
icon={
<img
className='icon'
src={ shapeshiftBtn }
/>
}
key='shapeshift'
label={
<FormattedMessage
id='account.button.shapeshift'
defaultMessage='shapeshift'
/>
}
onClick={ this.store.toggleFundDialog }
/>,
isVerifiable
? (
<Button
icon={ <VerifyIcon /> }
key='verification'
label={
<FormattedMessage
id='account.button.verify'
defaultMessage='verify'
/>
}
onClick={ this.store.toggleVerificationDialog }
/>
)
: null,
isFaucettable
? (
<Button
icon={ <DialIcon /> }
key='faucet'
label={
<FormattedMessage
id='account.button.faucet'
defaultMessage='Kovan ETH'
/>
}
onClick={ this.store.toggleFaucetDialog }
/>
)
: null,
<Button
icon={ <EditIcon /> }
key='editmeta'
label={
<FormattedMessage
id='account.button.edit'
defaultMessage='edit'
/>
}
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 /> }
key='passwordManager'
label={
<FormattedMessage
id='account.button.password'
defaultMessage='password'
/>
}
onClick={ this.store.togglePasswordDialog }
/>
),
<Button
icon={ <DeleteIcon /> }
key='delete'
label={
account.external || account.hardware
? (
<FormattedMessage
id='account.button.forget'
defaultMessage='forget'
/>
)
: (
<FormattedMessage
id='account.button.delete'
defaultMessage='delete'
/>
)
}
onClick={ this.store.toggleDeleteDialog }
/>
];
return (
<Actionbar
buttons={ buttons }
title={
<FormattedMessage
id='account.title'
defaultMessage='Account Management'
/>
}
/>
);
}
renderDeleteDialog (account) {
if (!this.store.isDeleteVisible) {
return null;
}
if (account.hardware) {
return (
<DeleteAddress
account={ account }
confirmMessage={
<FormattedMessage
id='account.hardware.confirmDelete'
defaultMessage='Are you sure you want to remove the following hardware address from your account list?'
/>
}
visible
route='/accounts'
onClose={ this.store.toggleDeleteDialog }
/>
);
}
if (account.external) {
return (
<DeleteAddress
account={ account }
confirmMessage={
<FormattedMessage
id='account.external.confirmDelete'
defaultMessage='Are you sure you want to remove the following external address from your account list?'
/>
}
visible
route='/accounts'
onClose={ this.store.toggleDeleteDialog }
/>
);
}
return (
<DeleteAccount
account={ account }
onClose={ this.store.toggleDeleteDialog }
/>
);
}
renderEditDialog (account) {
if (!this.store.isEditVisible) {
return null;
}
return (
<EditMeta
account={ account }
onClose={ this.store.toggleEditDialog }
/>
);
}
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;
if (!this.store.isFaucetVisible) {
return null;
}
const { address } = this.props.params;
return (
<Faucet
address={ address }
netVersion={ netVersion }
onClose={ this.store.toggleFaucetDialog }
/>
);
}
renderFundDialog () {
if (!this.store.isFundVisible) {
return null;
}
const { address } = this.props.params;
return (
<Shapeshift
address={ address }
onClose={ this.store.toggleFundDialog }
/>
);
}
renderPasswordDialog (account) {
if (!this.store.isPasswordVisible) {
return null;
}
return (
<PasswordManager
account={ account }
onClose={ this.store.togglePasswordDialog }
/>
);
}
renderTransferDialog (account) {
if (!this.store.isTransferVisible) {
return null;
}
return (
<Transfer
account={ account }
onClose={ this.store.toggleTransferDialog }
/>
);
}
renderVerificationDialog () {
if (!this.store.isVerificationVisible) {
return null;
}
const { address } = this.props.params;
return (
<Verification
account={ address }
onClose={ this.store.toggleVerificationDialog }
/>
);
}
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) {
const { address } = props.params;
const { accounts } = state.personal;
const certifications = state.certifications;
const { netVersion } = state.nodeStatus;
const account = (accounts || {})[address];
return {
account,
accounts,
certifications,
netVersion
};
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
fetchCertifiers,
fetchCertifications,
newError,
setVisibleAccounts
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Account);