Allow editing of gasPrice & gas in Signer (#3777)

* Rework gas display (maintainable)

* Move GasPriceSelector to ui

* Allow opening of gas component (WIP)

* Merge

* Consistency

* Adjust for Signer display

* Set maximum height based on screen size

* Gas editor displays in-place

* Cleanups

* Merge

* Style fixes

* Fixup stash mishap (again)

* Add store test

* Allow edited values to refrect on the display

* Fix properties

* Adjust styling to show different rows

* git mv

* git mv

* Style fixes

* Style updates

* Pass gas & gasPrice with confirmation

* Fix build (case)

* Style fixes

* Basic GasPriceEditor smoketest

* manual move 1

* manual move 2

* manual move 1

* manual move 2

* NODE_ENV=test ace fix

* UI smoketests

* Style

* Format options via formatter

* Initial version

* Re-add even/odd class

* re-add gasLimit to embedded passing

* style

* Updated for passing gas & price to store

* Allow gas/price overrides when none available

* Fix slider value, pass as number
This commit is contained in:
Jaco Greeff
2016-12-11 17:43:51 +01:00
committed by GitHub
parent 885d6eaa4d
commit 929b6ee0f7
59 changed files with 1423 additions and 856 deletions

View File

@@ -46,7 +46,7 @@
border-radius: 4px 4px 0 0;
display: flex;
flex-direction: column;
max-height: 19em;
max-height: 50vh;
}
.expanded .content {

View File

@@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react';
import { addressLink } from '~/3rdparty/etherscan/links';
import styles from './AccountLink.css';
import styles from './accountLink.css';
export default class AccountLink extends Component {
static propTypes = {

View File

@@ -14,4 +14,4 @@
// 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 './AccountLink';
export default from './accountLink';

View File

@@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { IdentityIcon, IdentityName } from '~/ui';
import AccountLink from './AccountLink';
import styles from './Account.css';
import styles from './account.css';
export default class Account extends Component {
static propTypes = {

View File

@@ -14,4 +14,4 @@
// 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 './Account';
export default from './account';

View File

@@ -21,21 +21,26 @@ import SignRequest from '../SignRequest';
export default class RequestPending extends Component {
static propTypes = {
className: PropTypes.string,
date: PropTypes.instanceOf(Date).isRequired,
gasLimit: PropTypes.object.isRequired,
id: PropTypes.object.isRequired,
isSending: PropTypes.bool.isRequired,
isTest: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
isSending: PropTypes.bool.isRequired,
date: PropTypes.instanceOf(Date).isRequired,
payload: PropTypes.oneOfType([
PropTypes.shape({ signTransaction: PropTypes.object.isRequired }),
PropTypes.shape({ sendTransaction: PropTypes.object.isRequired }),
PropTypes.shape({ sign: PropTypes.object.isRequired })
PropTypes.shape({ sign: PropTypes.object.isRequired }),
PropTypes.shape({ signTransaction: PropTypes.object.isRequired })
]).isRequired,
className: PropTypes.string,
isTest: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired
};
static defaultProps = {
isSending: false
};
onConfirm = data => {
const { onConfirm, payload } = this.props;
@@ -44,24 +49,23 @@ export default class RequestPending extends Component {
};
render () {
const { payload, id, className, isSending, date, onReject, isTest, store } = this.props;
const { className, date, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
if (payload.sign) {
const { sign } = payload;
return (
<SignRequest
address={ sign.address }
className={ className }
hash={ sign.hash }
id={ id }
isFinished={ false }
isSending={ isSending }
isTest={ isTest }
onConfirm={ this.onConfirm }
onReject={ onReject }
isSending={ isSending }
isFinished={ false }
id={ id }
address={ sign.address }
hash={ sign.hash }
isTest={ isTest }
store={ store }
/>
store={ store } />
);
}
@@ -70,19 +74,19 @@ export default class RequestPending extends Component {
return (
<TransactionPending
className={ className }
date={ date }
gasLimit={ gasLimit }
id={ id }
isSending={ isSending }
isTest={ isTest }
onConfirm={ this.onConfirm }
onReject={ onReject }
isSending={ isSending }
id={ id }
transaction={ transaction }
date={ date }
isTest={ isTest }
store={ store }
/>
transaction={ transaction } />
);
}
// Unknown payload
console.error('RequestPending: Unknown payload', payload);
return null;
}
}

View File

@@ -14,4 +14,4 @@
// 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 './SignRequest';
export default from './signRequest';

View File

@@ -21,7 +21,7 @@ import Account from '../Account';
import TransactionPendingForm from '../TransactionPendingForm';
import TxHashLink from '../TxHashLink';
import styles from './SignRequest.css';
import styles from './signRequest.css';
@observer
export default class SignRequest extends Component {
@@ -49,7 +49,7 @@ export default class SignRequest extends Component {
const { className } = this.props;
return (
<div className={ `${styles.container} ${className || ''}` }>
<div className={ `${styles.container} ${className}` }>
{ this.renderDetails() }
{ this.renderActions() }
</div>

View File

@@ -0,0 +1,34 @@
// Copyright 2015, 2016 Ethcore (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 SignRequest from './';
const store = {
balances: {},
fetchBalance: sinon.stub()
};
describe('views/Signer/components/SignRequest', () => {
it('renders', () => {
expect(shallow(
<SignRequest store={ store } />,
)).to.be.ok;
});
});

View File

@@ -14,4 +14,4 @@
// 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 './TransactionMainDetails';
export default from './transactionMainDetails';

View File

@@ -17,20 +17,25 @@
@import '../../_layout.css';
.transaction {
flex: 1;
overflow: auto;
}
.transaction > * {
display: inline-block;
}
.account {
text-align: center;
}
.contractIcon {
background: #eee;
width: 50px !important;
height: 50px !important;
box-sizing: border-box;
border-radius: 50%;
padding: 13px;
}
.editButtonRow {
text-align: right;
}
.from {
display: inline-block;
width: 40%;
vertical-align: top;
@@ -47,6 +52,7 @@
}
.method {
display: inline-block;
width: 60%;
vertical-align: top;
line-height: 1em;
@@ -66,11 +72,7 @@
opacity: .5;
}
.contractIcon {
background: #eee;
width: 50px !important;
height: 50px !important;
box-sizing: border-box;
border-radius: 50%;
padding: 13px;
.transaction {
flex: 1;
overflow: auto;
}

View File

@@ -14,52 +14,43 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import MapsLocalGasStation from 'material-ui/svg-icons/maps/local-gas-station';
import React, { Component, PropTypes } from 'react';
import ReactTooltip from 'react-tooltip';
import { MethodDecoding } from '~/ui';
import { Button, MethodDecoding } from '~/ui';
import * as tUtil from '../util/transaction';
import Account from '../Account';
import styles from './TransactionMainDetails.css';
import styles from './transactionMainDetails.css';
export default class TransactionMainDetails extends Component {
static propTypes = {
id: PropTypes.object.isRequired,
children: PropTypes.node,
from: PropTypes.string.isRequired,
fromBalance: PropTypes.object, // eth BigNumber, not required since it might take time to fetch
value: PropTypes.object.isRequired, // wei hex
totalValue: PropTypes.object.isRequired, // wei BigNumber
fromBalance: PropTypes.object,
gasStore: PropTypes.object,
id: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired,
totalValue: PropTypes.object.isRequired,
transaction: PropTypes.object.isRequired,
children: PropTypes.node
value: PropTypes.object.isRequired
};
componentWillMount () {
const { value, totalValue } = this.props;
const { totalValue, value } = this.props;
this.updateDisplayValues(value, totalValue);
}
componentWillReceiveProps (nextProps) {
const { value, totalValue } = nextProps;
const { totalValue, value } = nextProps;
this.updateDisplayValues(value, totalValue);
}
updateDisplayValues (value, totalValue) {
this.setState({
feeEth: tUtil.calcFeeInEth(totalValue, value),
valueDisplay: tUtil.getValueDisplay(value),
valueDisplayWei: tUtil.getValueDisplayWei(value),
totalValueDisplay: tUtil.getTotalValueDisplay(totalValue),
totalValueDisplayWei: tUtil.getTotalValueDisplayWei(totalValue)
});
}
render () {
const { children, from, fromBalance, transaction, isTest } = this.props;
const { children, from, fromBalance, gasStore, isTest, transaction } = this.props;
return (
<div className={ styles.transaction }>
@@ -74,29 +65,74 @@ export default class TransactionMainDetails extends Component {
<div className={ styles.method }>
<MethodDecoding
address={ from }
transaction={ transaction }
historic={ false } />
historic={ false }
transaction={
gasStore
? gasStore.overrideTransaction(transaction)
: transaction
} />
{ this.renderEditGas() }
</div>
{ children }
</div>
);
}
renderEditGas () {
const { gasStore } = this.props;
if (!gasStore) {
return null;
}
return (
<div className={ styles.editButtonRow }>
<Button
icon={ <MapsLocalGasStation /> }
label='Edit gas/gasPrice'
onClick={ this.toggleGasEditor } />
</div>
);
}
renderTotalValue () {
const { id } = this.props;
const { feeEth, totalValueDisplay, totalValueDisplayWei } = this.state;
const labelId = `totalValue${id}`;
return (
<div>
<div
className={ styles.total }
data-effect='solid'
data-for={ labelId }
data-place='bottom'
data-tip>
{ totalValueDisplay } <small>ETH</small>
</div>
<ReactTooltip id={ labelId }>
The value of the transaction including the mining fee is <strong>{ totalValueDisplayWei }</strong> <small>WEI</small>. <br />
(This includes a mining fee of <strong>{ feeEth }</strong> <small>ETH</small>)
</ReactTooltip>
</div>
);
}
renderValue () {
const { id } = this.props;
const { valueDisplay, valueDisplayWei } = this.state;
const labelId = `value${id}`;
return (
<div>
<div
data-tip
data-for={ 'value' + id }
data-effect='solid'
>
data-for={ labelId }
data-tip>
<strong>{ valueDisplay } </strong>
<small>ETH</small>
</div>
<ReactTooltip id={ 'value' + id }>
<ReactTooltip id={ labelId }>
The value of the transaction.<br />
<strong>{ valueDisplayWei }</strong> <small>WEI</small>
</ReactTooltip>
@@ -104,25 +140,17 @@ export default class TransactionMainDetails extends Component {
);
}
renderTotalValue () {
const { id } = this.props;
const { totalValueDisplay, totalValueDisplayWei, feeEth } = this.state;
updateDisplayValues (value, totalValue) {
this.setState({
feeEth: tUtil.calcFeeInEth(totalValue, value),
totalValueDisplay: tUtil.getTotalValueDisplay(totalValue),
totalValueDisplayWei: tUtil.getTotalValueDisplayWei(totalValue),
valueDisplay: tUtil.getValueDisplay(value),
valueDisplayWei: tUtil.getValueDisplayWei(value)
});
}
return (
<div>
<div
data-tip
data-for={ 'totalValue' + id }
data-effect='solid'
data-place='bottom'
className={ styles.total }>
{ totalValueDisplay } <small>ETH</small>
</div>
<ReactTooltip id={ 'totalValue' + id }>
The value of the transaction including the mining fee is <strong>{ totalValueDisplayWei }</strong> <small>WEI</small>. <br />
(This includes a mining fee of <strong>{ feeEth }</strong> <small>ETH</small>)
</ReactTooltip>
</div>
);
toggleGasEditor = () => {
this.props.gasStore.setEditing(true);
}
}

View File

@@ -14,4 +14,4 @@
// 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 './TransactionPending';
export default from './transactionPending';

View File

@@ -19,13 +19,9 @@
.container {
display: flex;
padding: 1em 0 1em;
padding: 1.5em 1em 1.5em 0;
& > * {
vertical-align: middle;
}
}
.container+.container {
padding-top: 2em;
}

View File

@@ -17,89 +17,130 @@
import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react';
import { Button, GasPriceEditor } from '~/ui';
import TransactionMainDetails from '../TransactionMainDetails';
import TransactionPendingForm from '../TransactionPendingForm';
import styles from './TransactionPending.css';
import styles from './transactionPending.css';
import * as tUtil from '../util/transaction';
@observer
export default class TransactionPending extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
};
static propTypes = {
id: PropTypes.object.isRequired,
transaction: PropTypes.shape({
from: PropTypes.string.isRequired,
value: PropTypes.object.isRequired, // wei hex
gasPrice: PropTypes.object.isRequired, // wei hex
gas: PropTypes.object.isRequired, // hex
data: PropTypes.string, // hex
to: PropTypes.string // undefined if it's a contract
}).isRequired,
className: PropTypes.string,
date: PropTypes.instanceOf(Date).isRequired,
gasLimit: PropTypes.object,
id: PropTypes.object.isRequired,
isSending: PropTypes.bool.isRequired,
isTest: PropTypes.bool.isRequired,
nonce: PropTypes.number,
onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
isSending: PropTypes.bool.isRequired,
className: PropTypes.string,
isTest: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired
store: PropTypes.object.isRequired,
transaction: PropTypes.shape({
data: PropTypes.string,
from: PropTypes.string.isRequired,
gas: PropTypes.object.isRequired,
gasPrice: PropTypes.object.isRequired,
to: PropTypes.string,
value: PropTypes.object.isRequired
}).isRequired
};
static defaultProps = {
isSending: false
};
gasStore = new GasPriceEditor.Store(this.context.api, {
gas: this.props.transaction.gas.toFixed(),
gasLimit: this.props.gasLimit,
gasPrice: this.props.transaction.gasPrice.toFixed()
});
componentWillMount () {
const { transaction, store } = this.props;
const { gas, gasPrice, value, from, to } = transaction;
const { store, transaction } = this.props;
const { from, gas, gasPrice, to, value } = transaction;
const fee = tUtil.getFee(gas, gasPrice); // BigNumber object
const totalValue = tUtil.getTotalValue(fee, value);
const gasPriceEthmDisplay = tUtil.getEthmFromWeiDisplay(gasPrice);
const gasToDisplay = tUtil.getGasDisplay(gas);
const totalValue = tUtil.getTotalValue(fee, value);
this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay });
this.gasStore.setEthValue(value);
store.fetchBalances([from, to]);
}
render () {
const { className, id, transaction, store, isTest } = this.props;
const { from, value } = transaction;
return this.gasStore.isEditing
? this.renderGasEditor()
: this.renderTransaction();
}
renderTransaction () {
const { className, id, isSending, isTest, store, transaction } = this.props;
const { totalValue } = this.state;
const { from, value } = transaction;
const fromBalance = store.balances[from];
return (
<div className={ `${styles.container} ${className || ''}` }>
<div className={ `${styles.container} ${className}` }>
<TransactionMainDetails
id={ id }
value={ value }
from={ from }
isTest={ isTest }
fromBalance={ fromBalance }
className={ styles.transactionDetails }
from={ from }
fromBalance={ fromBalance }
gasStore={ this.gasStore }
id={ id }
isTest={ isTest }
totalValue={ totalValue }
transaction={ transaction }
totalValue={ totalValue } />
value={ value } />
<TransactionPendingForm
address={ from }
isSending={ this.props.isSending }
isSending={ isSending }
onConfirm={ this.onConfirm }
onReject={ this.onReject }
/>
onReject={ this.onReject } />
</div>
);
}
onConfirm = data => {
const { id, transaction } = this.props;
const { gasPrice } = transaction;
const { password, wallet } = data;
renderGasEditor () {
const { className } = this.props;
this.props.onConfirm({ id, password, wallet, gasPrice });
return (
<div className={ `${styles.container} ${className}` }>
<GasPriceEditor
store={ this.gasStore }>
<Button
label='view transaction'
onClick={ this.toggleGasEditor } />
</GasPriceEditor>
</div>
);
}
onConfirm = (data) => {
const { id, transaction } = this.props;
const { password, wallet } = data;
const { gas, gasPrice } = this.gasStore.overrideTransaction(transaction);
this.props.onConfirm({
gas,
gasPrice,
id,
password,
wallet
});
}
onReject = () => {
this.props.onReject(this.props.id);
}
toggleGasEditor = () => {
this.gasStore.setEditing(false);
}
}

View File

@@ -14,4 +14,4 @@
// 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 './TransactionPendingFormConfirm';
export default from './transactionPendingFormConfirm';

View File

@@ -22,7 +22,7 @@ import ReactTooltip from 'react-tooltip';
import { Form, Input, IdentityIcon } from '~/ui';
import styles from './TransactionPendingFormConfirm.css';
import styles from './transactionPendingFormConfirm.css';
class TransactionPendingFormConfirm extends Component {
static propTypes = {
@@ -35,14 +35,14 @@ class TransactionPendingFormConfirm extends Component {
id = Math.random(); // for tooltip
state = {
walletError: null,
password: '',
wallet: null,
password: ''
walletError: null
}
render () {
const { accounts, address, isSending } = this.props;
const { password, walletError, wallet } = this.state;
const { password, wallet, walletError } = this.state;
const account = accounts[address] || {};
const isExternal = !account.uuid;
@@ -51,37 +51,54 @@ class TransactionPendingFormConfirm extends Component {
: null;
const isWalletOk = !isExternal || (walletError === null && wallet !== null);
const keyInput = isExternal ? this.renderKeyInput() : null;
const keyInput = isExternal
? this.renderKeyInput()
: null;
return (
<div className={ styles.confirmForm }>
<Form>
{ keyInput }
<Input
hint={
isExternal
? 'decrypt the key'
: 'unlock the account'
}
label={
isExternal
? 'Key Password'
: 'Account Password'
}
onChange={ this.onModifyPassword }
onKeyDown={ this.onKeyDown }
label={ isExternal ? 'Key Password' : 'Account Password' }
hint={ isExternal ? 'decrypt the key' : 'unlock the account' }
type='password'
value={ password } />
<div className={ styles.passwordHint }>
{ passwordHint }
</div>
<div
data-tip
data-place='bottom'
data-for={ 'transactionConfirmForm' + this.id }
data-effect='solid'
>
data-for={ `transactionConfirmForm${this.id}` }
data-place='bottom'
data-tip>
<RaisedButton
onTouchTap={ this.onConfirm }
className={ styles.confirmButton }
fullWidth
primary
disabled={ isSending || !isWalletOk }
icon={ <IdentityIcon address={ address } button className={ styles.signerIcon } /> }
label={ isSending ? 'Confirming...' : 'Confirm Transaction' }
/>
fullWidth
icon={
<IdentityIcon
address={ address }
button
className={ styles.signerIcon } />
}
label={
isSending
? 'Confirming...'
: 'Confirm Transaction'
}
onTouchTap={ this.onConfirm }
primary />
</div>
{ this.renderTooltip() }
</Form>
@@ -95,11 +112,10 @@ class TransactionPendingFormConfirm extends Component {
return (
<Input
className={ styles.fileInput }
onChange={ this.onKeySelect }
error={ walletError }
label='Select Local Key'
type='file'
/>
onChange={ this.onKeySelect }
type='file' />
);
}
@@ -109,34 +125,37 @@ class TransactionPendingFormConfirm extends Component {
}
return (
<ReactTooltip id={ 'transactionConfirmForm' + this.id }>
<ReactTooltip id={ `transactionConfirmForm${this.id}` }>
Please provide a password for this account
</ReactTooltip>
);
}
onKeySelect = evt => {
onKeySelect = (event) => {
const fileReader = new FileReader();
fileReader.onload = e => {
fileReader.onload = (e) => {
try {
const wallet = JSON.parse(e.target.result);
this.setState({
walletError: null,
wallet: wallet
wallet,
walletError: null
});
} catch (e) {
} catch (error) {
this.setState({
walletError: 'Given wallet file is invalid.',
wallet: null
wallet: null,
walletError: 'Given wallet file is invalid.'
});
}
};
fileReader.readAsText(evt.target.files[0]);
fileReader.readAsText(event.target.files[0]);
}
onModifyPassword = evt => {
const password = evt.target.value;
onModifyPassword = (event) => {
const password = event.target.value;
this.setState({
password
});
@@ -150,8 +169,8 @@ class TransactionPendingFormConfirm extends Component {
});
}
onKeyDown = evt => {
if (evt.which !== 13) {
onKeyDown = (event) => {
if (event.which !== 13) {
return;
}

View File

@@ -14,4 +14,4 @@
// 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 './TransactionPendingFormReject';
export default from './transactionPendingFormReject';

View File

@@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import styles from './TransactionPendingFormReject.css';
import styles from './transactionPendingFormReject.css';
export default class TransactionPendingFormReject extends Component {

View File

@@ -14,4 +14,4 @@
// 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 './TransactionPendingForm';
export default from './transactionPendingForm';

View File

@@ -20,10 +20,9 @@ import BackIcon from 'material-ui/svg-icons/navigation/arrow-back';
import TransactionPendingFormConfirm from './TransactionPendingFormConfirm';
import TransactionPendingFormReject from './TransactionPendingFormReject';
import styles from './TransactionPendingForm.css';
import styles from './transactionPendingForm.css';
export default class TransactionPendingForm extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
isSending: PropTypes.bool.isRequired,
@@ -60,8 +59,8 @@ export default class TransactionPendingForm extends Component {
return (
<TransactionPendingFormConfirm
address={ address }
onConfirm={ onConfirm }
isSending={ isSending } />
isSending={ isSending }
onConfirm={ onConfirm } />
);
}
@@ -72,14 +71,13 @@ export default class TransactionPendingForm extends Component {
if (!isRejectOpen) {
html = <span>reject transaction</span>;
} else {
html = <span><BackIcon />I've changed my mind</span>;
html = <span><BackIcon />{ "I've changed my mind" }</span>;
}
return (
<a
onClick={ this.onToggleReject }
className={ styles.rejectToggle }
>
onClick={ this.onToggleReject }>
{ html }
</a>
);
@@ -87,7 +85,9 @@ export default class TransactionPendingForm extends Component {
onToggleReject = () => {
const { isRejectOpen } = this.state;
this.setState({ isRejectOpen: !isRejectOpen });
}
this.setState({
isRejectOpen: !isRejectOpen
});
}
}

View File

@@ -14,4 +14,4 @@
// 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 './TxHashLink';
export default from './txHashLink';

View File

@@ -19,22 +19,21 @@ import React, { Component, PropTypes } from 'react';
import { txLink } from '~/3rdparty/etherscan/links';
export default class TxHashLink extends Component {
static propTypes = {
txHash: PropTypes.string.isRequired,
isTest: PropTypes.bool.isRequired,
children: PropTypes.node,
className: PropTypes.string
className: PropTypes.string,
isTest: PropTypes.bool.isRequired,
txHash: PropTypes.string.isRequired
}
render () {
const { children, txHash, className, isTest } = this.props;
const { children, className, isTest, txHash } = this.props;
return (
<a
className={ className }
href={ txLink(txHash, isTest) }
target='_blank'
className={ className }>
target='_blank'>
{ children || txHash }
</a>
);

View File

@@ -17,16 +17,22 @@
@import '../../_layout.css';
.signer {
box-sizing: border-box;
padding: 0;
width: $embedWidth;
.info {
padding: 1em 0;
}
.none {
color: #aaa;
}
.info {
padding: 1em 0;
.request {
&:nth-child(even) {
background: rgba(255, 255, 255, 0.04);
}
}
.signer {
box-sizing: border-box;
padding: 0;
width: $embedWidth;
}

View File

@@ -33,15 +33,16 @@ class Embedded extends Component {
};
static propTypes = {
signer: PropTypes.shape({
pending: PropTypes.array.isRequired,
finished: PropTypes.array.isRequired
}).isRequired,
actions: PropTypes.shape({
startConfirmRequest: PropTypes.func.isRequired,
startRejectRequest: PropTypes.func.isRequired
}).isRequired,
isTest: PropTypes.bool.isRequired
gasLimit: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired,
signer: PropTypes.shape({
finished: PropTypes.array.isRequired,
pending: PropTypes.array.isRequired
}).isRequired
};
store = new Store(this.context.api);
@@ -78,20 +79,21 @@ class Embedded extends Component {
}
renderPending = (data) => {
const { actions, isTest } = this.props;
const { payload, id, isSending, date } = data;
const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data;
return (
<RequestPending
className={ styles.request }
date={ date }
gasLimit={ gasLimit }
id={ id }
isSending={ isSending }
isTest={ isTest }
key={ id }
onConfirm={ actions.startConfirmRequest }
onReject={ actions.startRejectRequest }
isSending={ isSending || false }
key={ id }
id={ id }
payload={ payload }
date={ date }
isTest={ isTest }
store={ this.store }
/>
);
@@ -103,13 +105,14 @@ class Embedded extends Component {
}
function mapStateToProps (state) {
const { isTest } = state.nodeStatus;
const { gasLimit, isTest } = state.nodeStatus;
const { actions, signer } = state;
return {
actions,
signer,
isTest
gasLimit,
isTest,
signer
};
}

View File

@@ -14,4 +14,4 @@
// 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 './RequestsPage';
export default from './requestsPage';

View File

@@ -18,3 +18,9 @@
.noRequestsMsg {
color: #aaa;
}
.request {
&:nth-child(odd) {
background: rgba(255, 255, 255, 0.04);
}
}

View File

@@ -26,7 +26,7 @@ import { Container, Page, TxList } from '~/ui';
import RequestPending from '../../components/RequestPending';
import styles from './RequestsPage.css';
import styles from './requestsPage.css';
@observer
class RequestsPage extends Component {
@@ -35,15 +35,16 @@ class RequestsPage extends Component {
};
static propTypes = {
signer: PropTypes.shape({
pending: PropTypes.array.isRequired,
finished: PropTypes.array.isRequired
}).isRequired,
actions: PropTypes.shape({
startConfirmRequest: PropTypes.func.isRequired,
startRejectRequest: PropTypes.func.isRequired
}).isRequired,
isTest: PropTypes.bool.isRequired
gasLimit: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired,
signer: PropTypes.shape({
pending: PropTypes.array.isRequired,
finished: PropTypes.array.isRequired
}).isRequired
};
store = new Store(this.context.api, true);
@@ -104,19 +105,21 @@ class RequestsPage extends Component {
}
renderPending = (data) => {
const { actions, isTest } = this.props;
const { payload, id, isSending, date } = data;
const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data;
return (
<RequestPending
className={ styles.request }
date={ date }
gasLimit={ gasLimit }
id={ id }
isSending={ isSending }
isTest={ isTest }
key={ id }
onConfirm={ actions.startConfirmRequest }
onReject={ actions.startRejectRequest }
isSending={ isSending || false }
key={ id }
id={ id }
payload={ payload }
date={ date }
isTest={ isTest }
store={ this.store }
/>
);
@@ -124,13 +127,14 @@ class RequestsPage extends Component {
}
function mapStateToProps (state) {
const { isTest } = state.nodeStatus;
const { gasLimit, isTest } = state.nodeStatus;
const { actions, signer } = state;
return {
actions,
signer,
isTest
gasLimit,
isTest,
signer
};
}