Several fixes to the Wallet in general (#4504)

* Fix address non-ellipsis on Wallet view

* Add warning if daily limit has been reached

* Add OOG warnings to TxHash

* Fixes to the Warning

* Added stringified version of the changes

* Add wallet link for notifications

* Fix tests

* Use Formatted Messages

* React Intl

* s/ui.walletSettings/walletSettings in React Intl
This commit is contained in:
Nicolas Gotchac
2017-02-10 18:27:18 +01:00
committed by Jaco Greeff
parent da2e28dad1
commit ace5c27211
10 changed files with 384 additions and 48 deletions

View File

@@ -61,3 +61,8 @@
margin-left: 0.125em;
}
.modifications {
color: white;
margin: 0;
}

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { observer } from 'mobx-react';
import { pick } from 'lodash';
@@ -23,7 +24,7 @@ import ActionDone from 'material-ui/svg-icons/action/done';
import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { Button, Modal, TxHash, BusyStep, Form, TypedInput, InputAddress, AddressSelect } from '~/ui';
import { Button, Modal, TxHash, BusyStep, Form, TypedInput, Input, InputAddress, AddressSelect } from '~/ui';
import { fromWei } from '~/api/util/wei';
import WalletSettingsStore from './walletSettingsStore.js';
@@ -51,12 +52,27 @@ class WalletSettings extends Component {
return (
<Modal
visible
title='rejected'
title={
<FormattedMessage
id='walletSettings.rejected.title'
defaultMessage='rejected'
/>
}
actions={ this.renderDialogActions() }
>
<BusyStep
title='The modifications have been rejected'
state='The wallet settings will not be modified. You can safely close this window.'
title={
<FormattedMessage
id='walletSettings.rejected.busyStep.title'
defaultMessage='The modifications have been rejected'
/>
}
state={
<FormattedMessage
id='walletSettings.rejected.busyStep.state'
defaultMessage='The wallet settings will not be modified. You can safely close this window.'
/>
}
/>
</Modal>
);
@@ -112,57 +128,124 @@ class WalletSettings extends Component {
default:
case 'EDIT':
const { wallet, errors } = this.store;
const { errors, fromString, wallet } = this.store;
const { accountsInfo, senders } = this.props;
return (
<Form>
<p>
In order to edit this contract's settings, at
least { this.store.initialWallet.require.toNumber() } owners have to
send the very same modifications.
Otherwise, no modification will be taken into account...
<FormattedMessage
id='walletSettings.edit.message'
defaultMessage={
`In order to edit this contract's settings, at
least { owners, number } { owners, plural,
one { owner }
other { owners }
} have to
send the very same modifications. You can paste a stringified version
of the modifications here.`
}
values={ {
owners: this.store.initialWallet.require.toNumber()
} }
/>
</p>
<Input
hint='[ ... ]'
label={
<FormattedMessage
id='walletSettings.modifications.fromString.label'
defaultMessage='modifications'
/>
}
onChange={ this.store.onModificationsStringChange }
/>
<AddressSelect
label='from account (wallet owner)'
hint='send modifications as this owner'
label={
<FormattedMessage
id='walletSettings.modifications.sender.label'
defaultMessage='from account (wallet owner)'
/>
}
hint={
<FormattedMessage
id='walletSettings.modifications.sender.hint'
defaultMessage='send modifications as this owner'
/>
}
value={ wallet.sender }
error={ errors.sender }
onChange={ this.store.onSenderChange }
accounts={ senders }
/>
<TypedInput
label='other wallet owners'
value={ wallet.owners.slice() }
onChange={ this.store.onOwnersChange }
accounts={ accountsInfo }
param='address[]'
/>
<br />
<div className={ styles.splitInput }>
<TypedInput
label='required owners'
hint='number of required owners to accept a transaction'
error={ errors.require }
min={ 1 }
onChange={ this.store.onRequireChange }
max={ wallet.owners.length }
param='uint'
value={ wallet.require }
/>
{
fromString
? null
: (
<div>
<TypedInput
label={
<FormattedMessage
id='walletSettings.modifications.owners.label'
defaultMessage='other wallet owners'
/>
}
value={ wallet.owners.slice() }
onChange={ this.store.onOwnersChange }
accounts={ accountsInfo }
param='address[]'
/>
<TypedInput
label='wallet day limit'
hint='amount of ETH spendable without confirmations'
value={ wallet.dailylimit }
error={ errors.dailylimit }
onChange={ this.store.onDailylimitChange }
param='uint'
isEth
/>
</div>
<div className={ styles.splitInput }>
<TypedInput
label={
<FormattedMessage
id='walletSettings.modifications.required.label'
defaultMessage='required owners'
/>
}
hint={
<FormattedMessage
id='walletSettings.modifications.required.hint'
defaultMessage='number of required owners to accept a transaction'
/>
}
error={ errors.require }
min={ 1 }
onChange={ this.store.onRequireChange }
max={ wallet.owners.length }
param='uint'
value={ wallet.require }
/>
<TypedInput
label={
<FormattedMessage
id='walletSettings.modifications.daylimit.label'
defaultMessage='wallet day limit'
/>
}
hint={
<FormattedMessage
id='walletSettings.modifications.daylimit.hint'
defaultMessage='amount of ETH spendable without confirmations'
/>
}
value={ wallet.dailylimit }
error={ errors.dailylimit }
onChange={ this.store.onDailylimitChange }
param='uint'
isEth
/>
</div>
</div>
)
}
</Form>
);
}
@@ -171,7 +254,12 @@ class WalletSettings extends Component {
renderChanges (changes) {
if (changes.length === 0) {
return (
<p>No modifications have been made to the Wallet settings.</p>
<p>
<FormattedMessage
id='walletSettings.changes.none'
defaultMessage='No modifications have been made to the Wallet settings.'
/>
</p>
);
}
@@ -183,7 +271,31 @@ class WalletSettings extends Component {
return (
<div>
<p>You are about to make the following modifications</p>
<p className={ styles.modifications }>
<FormattedMessage
id='walletSettings.changes.modificationString'
defaultMessage={
`For your modifications to be taken into account,
other owners have to send the same modifications. They can paste
this string to make it easier:`
}
/>
</p>
<Input
allowCopy
label='modifications'
readOnly
value={ this.store.changesToString() }
/>
<p>
<FormattedMessage
id='walletSettings.changes.overview'
defaultMessage={
`You are about to make the following modifications`
}
/>
</p>
{ modifications }
</div>
);

View File

@@ -30,10 +30,11 @@ const STEPS = {
export default class WalletSettingsStore {
accounts = {};
@observable step = null;
@observable requests = [];
@observable deployState = '';
@observable done = false;
@observable fromString = false;
@observable requests = [];
@observable step = null;
@observable wallet = {
owners: null,
@@ -79,6 +80,51 @@ export default class WalletSettingsStore {
.map((s) => s.idx);
}
@action
changesFromString (json) {
try {
const data = JSON.parse(json);
const changes = data.map((datum) => {
const [ type, valueStr ] = datum.split(';');
let value = valueStr;
// Only addresses start with `0x`, the others
// are BigNumbers
if (!/^0x/.test(valueStr)) {
value = new BigNumber(valueStr, 16);
}
return { type, value };
});
this.changes = changes;
} catch (error) {
if (!(error instanceof SyntaxError)) {
console.error('changes from string', error);
}
this.changes = [];
}
}
changesToString () {
const changes = this.changes.map((change) => {
const { type, value } = change;
const valueStr = (value && typeof value.plus === 'function')
? value.toString(16)
: value;
return [
type,
valueStr
].join(';');
});
return JSON.stringify(changes);
}
get changes () {
const changes = [];
@@ -127,6 +173,36 @@ export default class WalletSettingsStore {
return changes;
}
set changes (changes) {
transaction(() => {
this.wallet.dailylimit = this.initialWallet.dailylimit;
this.wallet.require = this.initialWallet.require;
this.wallet.owners = this.initialWallet.owners.slice();
changes.forEach((change) => {
const { type, value } = change;
switch (type) {
case 'dailylimit':
this.wallet.dailylimit = value;
break;
case 'require':
this.wallet.require = value;
break;
case 'remove_owner':
this.wallet.owners = this.wallet.owners.filter((owner) => owner !== value);
break;
case 'add_owner':
this.wallet.owners.push(value);
break;
}
});
});
}
constructor (api, wallet) {
this.api = api;
this.step = this.stepsKeys[0];
@@ -177,6 +253,16 @@ export default class WalletSettingsStore {
this.onChange({ dailylimit });
}
@action onModificationsStringChange = (event, value) => {
this.changesFromString(value);
if (this.changes && this.changes.length > 0) {
this.fromString = true;
} else {
this.fromString = false;
}
}
@action send = () => {
const changes = this.changes;
const walletInstance = this.walletInstance;