Fix address and accounts links (#4491)
* Add proper links to TxRows (#4314) * Add proper link to the Signer (#4314) * Fix and add tests
This commit is contained in:
parent
867a593988
commit
48eac51c8a
@ -16,8 +16,10 @@
|
|||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import { txLink, addressLink } from '~/3rdparty/etherscan/links';
|
import { txLink } from '~/3rdparty/etherscan/links';
|
||||||
|
|
||||||
import IdentityIcon from '../../IdentityIcon';
|
import IdentityIcon from '../../IdentityIcon';
|
||||||
import IdentityName from '../../IdentityName';
|
import IdentityName from '../../IdentityName';
|
||||||
@ -25,19 +27,20 @@ import MethodDecoding from '../../MethodDecoding';
|
|||||||
|
|
||||||
import styles from '../txList.css';
|
import styles from '../txList.css';
|
||||||
|
|
||||||
export default class TxRow extends Component {
|
class TxRow extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
tx: PropTypes.object.isRequired,
|
accountAddresses: PropTypes.array.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
|
tx: PropTypes.object.isRequired,
|
||||||
|
|
||||||
block: PropTypes.object,
|
block: PropTypes.object,
|
||||||
historic: PropTypes.bool,
|
className: PropTypes.string,
|
||||||
className: PropTypes.string
|
historic: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -77,22 +80,20 @@ export default class TxRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAddress (address) {
|
renderAddress (address) {
|
||||||
const { isTest } = this.props;
|
|
||||||
|
|
||||||
let esLink = null;
|
let esLink = null;
|
||||||
|
|
||||||
if (address) {
|
if (address) {
|
||||||
esLink = (
|
esLink = (
|
||||||
<a
|
<Link
|
||||||
href={ addressLink(address, isTest) }
|
activeClassName={ styles.currentLink }
|
||||||
target='_blank'
|
|
||||||
className={ styles.link }
|
className={ styles.link }
|
||||||
|
to={ this.addressLink(address) }
|
||||||
>
|
>
|
||||||
<IdentityName
|
<IdentityName
|
||||||
address={ address }
|
address={ address }
|
||||||
shorten
|
shorten
|
||||||
/>
|
/>
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,4 +139,30 @@ export default class TxRow extends Component {
|
|||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addressLink (address) {
|
||||||
|
const { accountAddresses } = this.props;
|
||||||
|
const isAccount = accountAddresses.includes(address);
|
||||||
|
|
||||||
|
if (isAccount) {
|
||||||
|
return `/accounts/${address}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/addresses/${address}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (initState) {
|
||||||
|
const { accounts } = initState.personal;
|
||||||
|
const accountAddresses = Object.keys(accounts);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return { accountAddresses };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(TxRow);
|
||||||
|
|
||||||
|
@ -25,13 +25,28 @@ import TxRow from './txRow';
|
|||||||
|
|
||||||
const api = new Api({ execute: sinon.stub() });
|
const api = new Api({ execute: sinon.stub() });
|
||||||
|
|
||||||
|
const STORE = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
personal: {
|
||||||
|
accounts: {
|
||||||
|
'0x123': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function render (props) {
|
function render (props) {
|
||||||
return shallow(
|
return shallow(
|
||||||
<TxRow
|
<TxRow
|
||||||
|
store={ STORE }
|
||||||
{ ...props }
|
{ ...props }
|
||||||
/>,
|
/>,
|
||||||
{ context: { api } }
|
{ context: { api } }
|
||||||
);
|
).find('TxRow').shallow({ context: { api } });
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ui/TxList/TxRow', () => {
|
describe('ui/TxList/TxRow', () => {
|
||||||
@ -48,5 +63,37 @@ describe('ui/TxList/TxRow', () => {
|
|||||||
|
|
||||||
expect(render({ address: '0x123', block, isTest: true, tx })).to.be.ok;
|
expect(render({ address: '0x123', block, isTest: true, tx })).to.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders an account link', () => {
|
||||||
|
const block = {
|
||||||
|
timestamp: new Date()
|
||||||
|
};
|
||||||
|
const tx = {
|
||||||
|
blockNumber: new BigNumber(123),
|
||||||
|
hash: '0x123456789abcdef0123456789abcdef0123456789abcdef',
|
||||||
|
to: '0x123',
|
||||||
|
value: new BigNumber(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = render({ address: '0x123', block, isTest: true, tx });
|
||||||
|
|
||||||
|
expect(element.find('Link').prop('to')).to.equal('/accounts/0x123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders an address link', () => {
|
||||||
|
const block = {
|
||||||
|
timestamp: new Date()
|
||||||
|
};
|
||||||
|
const tx = {
|
||||||
|
blockNumber: new BigNumber(123),
|
||||||
|
hash: '0x123456789abcdef0123456789abcdef0123456789abcdef',
|
||||||
|
to: '0x456',
|
||||||
|
value: new BigNumber(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = render({ address: '0x123', block, isTest: true, tx });
|
||||||
|
|
||||||
|
expect(element.find('Link').prop('to')).to.equal('/addresses/0x456');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -65,6 +65,11 @@
|
|||||||
|
|
||||||
.link {
|
.link {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
|
&.currentLink {
|
||||||
|
color: white;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
|
@ -285,6 +285,7 @@ class ParityBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderExpanded () {
|
renderExpanded () {
|
||||||
|
const { externalLink } = this.props;
|
||||||
const { displayType } = this.state;
|
const { displayType } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -333,7 +334,7 @@ class ParityBar extends Component {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<Signer />
|
<Signer externalLink={ externalLink } />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,16 +15,19 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import { addressLink } from '~/3rdparty/etherscan/links';
|
|
||||||
import styles from './accountLink.css';
|
import styles from './accountLink.css';
|
||||||
|
|
||||||
export default class AccountLink extends Component {
|
class AccountLink extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isTest: PropTypes.bool.isRequired,
|
accountAddresses: PropTypes.array.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
|
isTest: PropTypes.bool.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -32,36 +35,72 @@ export default class AccountLink extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const { address, isTest } = this.props;
|
const { address, externalLink, isTest } = this.props;
|
||||||
|
|
||||||
this.updateLink(address, isTest);
|
this.updateLink(address, externalLink, isTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
const { address, isTest } = nextProps;
|
const { address, externalLink, isTest } = nextProps;
|
||||||
|
|
||||||
this.updateLink(address, isTest);
|
this.updateLink(address, externalLink, isTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, address, className } = this.props;
|
const { children, address, className, externalLink } = this.props;
|
||||||
|
|
||||||
|
if (externalLink) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={ this.state.link }
|
||||||
|
target='_blank'
|
||||||
|
className={ `${styles.container} ${className}` }
|
||||||
|
>
|
||||||
|
{ children || address }
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<Link
|
||||||
href={ this.state.link }
|
|
||||||
target='_blank'
|
|
||||||
className={ `${styles.container} ${className}` }
|
className={ `${styles.container} ${className}` }
|
||||||
|
to={ this.state.link }
|
||||||
>
|
>
|
||||||
{ children || address }
|
{ children || address }
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLink (address, isTest) {
|
updateLink (address, externalLink, isTest) {
|
||||||
const link = addressLink(address, isTest);
|
const { accountAddresses } = this.props;
|
||||||
|
const isAccount = accountAddresses.includes(address);
|
||||||
|
|
||||||
|
let link = isAccount
|
||||||
|
? `/accounts/${address}`
|
||||||
|
: `/addresses/${address}`;
|
||||||
|
|
||||||
|
if (externalLink) {
|
||||||
|
const path = externalLink.replace(/\/+$/, '');
|
||||||
|
|
||||||
|
link = `${path}/#${link}`;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
link
|
link
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (initState) {
|
||||||
|
const { accounts } = initState.personal;
|
||||||
|
const accountAddresses = Object.keys(accounts);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return { accountAddresses };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
null
|
||||||
|
)(AccountLink);
|
||||||
|
@ -25,6 +25,7 @@ export default class Account extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
balance: PropTypes.object // eth BigNumber, not required since it mght take time to fetch
|
balance: PropTypes.object // eth BigNumber, not required since it mght take time to fetch
|
||||||
};
|
};
|
||||||
@ -51,12 +52,13 @@ export default class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { address, isTest, className } = this.props;
|
const { address, externalLink, isTest, className } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ `${styles.acc} ${className}` }>
|
<div className={ `${styles.acc} ${className}` }>
|
||||||
<AccountLink
|
<AccountLink
|
||||||
address={ address }
|
address={ address }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
>
|
>
|
||||||
<IdentityIcon
|
<IdentityIcon
|
||||||
@ -79,13 +81,14 @@ export default class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderName () {
|
renderName () {
|
||||||
const { address, isTest } = this.props;
|
const { address, externalLink, isTest } = this.props;
|
||||||
const name = <IdentityName address={ address } empty />;
|
const name = <IdentityName address={ address } empty />;
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return (
|
return (
|
||||||
<AccountLink
|
<AccountLink
|
||||||
address={ address }
|
address={ address }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
>
|
>
|
||||||
[{ this.shortAddress(address) }]
|
[{ this.shortAddress(address) }]
|
||||||
@ -96,6 +99,7 @@ export default class Account extends Component {
|
|||||||
return (
|
return (
|
||||||
<AccountLink
|
<AccountLink
|
||||||
address={ address }
|
address={ address }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
@ -93,7 +93,9 @@ export default class SignRequest extends Component {
|
|||||||
renderDetails () {
|
renderDetails () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { address, isTest, store, data } = this.props;
|
const { address, isTest, store, data } = this.props;
|
||||||
const balance = store.balances[address];
|
const { balances, externalLink } = store;
|
||||||
|
|
||||||
|
const balance = balances[address];
|
||||||
|
|
||||||
if (!balance) {
|
if (!balance) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@ -105,6 +107,7 @@ export default class SignRequest extends Component {
|
|||||||
<Account
|
<Account
|
||||||
address={ address }
|
address={ address }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,6 +27,7 @@ import styles from './transactionMainDetails.css';
|
|||||||
export default class TransactionMainDetails extends Component {
|
export default class TransactionMainDetails extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
from: PropTypes.string.isRequired,
|
from: PropTypes.string.isRequired,
|
||||||
fromBalance: PropTypes.object,
|
fromBalance: PropTypes.object,
|
||||||
gasStore: PropTypes.object,
|
gasStore: PropTypes.object,
|
||||||
@ -50,7 +51,7 @@ export default class TransactionMainDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, from, fromBalance, gasStore, isTest, transaction } = this.props;
|
const { children, externalLink, from, fromBalance, gasStore, isTest, transaction } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.transaction }>
|
<div className={ styles.transaction }>
|
||||||
@ -59,6 +60,7 @@ export default class TransactionMainDetails extends Component {
|
|||||||
<Account
|
<Account
|
||||||
address={ from }
|
address={ from }
|
||||||
balance={ fromBalance }
|
balance={ fromBalance }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,14 +89,16 @@ export default class TransactionPending extends Component {
|
|||||||
renderTransaction () {
|
renderTransaction () {
|
||||||
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
|
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
|
||||||
const { totalValue } = this.state;
|
const { totalValue } = this.state;
|
||||||
|
const { balances, externalLink } = store;
|
||||||
const { from, value } = transaction;
|
const { from, value } = transaction;
|
||||||
|
|
||||||
const fromBalance = store.balances[from];
|
const fromBalance = balances[from];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ `${styles.container} ${className}` }>
|
<div className={ `${styles.container} ${className}` }>
|
||||||
<TransactionMainDetails
|
<TransactionMainDetails
|
||||||
className={ styles.transactionDetails }
|
className={ styles.transactionDetails }
|
||||||
|
externalLink={ externalLink }
|
||||||
from={ from }
|
from={ from }
|
||||||
fromBalance={ fromBalance }
|
fromBalance={ fromBalance }
|
||||||
gasStore={ this.gasStore }
|
gasStore={ this.gasStore }
|
||||||
|
@ -37,6 +37,7 @@ class Embedded extends Component {
|
|||||||
startConfirmRequest: PropTypes.func.isRequired,
|
startConfirmRequest: PropTypes.func.isRequired,
|
||||||
startRejectRequest: PropTypes.func.isRequired
|
startRejectRequest: PropTypes.func.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
signer: PropTypes.shape({
|
signer: PropTypes.shape({
|
||||||
@ -45,7 +46,7 @@ class Embedded extends Component {
|
|||||||
}).isRequired
|
}).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
store = new Store(this.context.api, false, this.props.externalLink);
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
|
|
||||||
export default class Store {
|
export default class SignerStore {
|
||||||
@observable balances = {};
|
@observable balances = {};
|
||||||
@observable localHashes = [];
|
@observable localHashes = [];
|
||||||
|
|
||||||
constructor (api, withLocalTransactions = false) {
|
externalLink = '';
|
||||||
|
|
||||||
|
constructor (api, withLocalTransactions = false, externalLink = '') {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._timeoutId = 0;
|
this._timeoutId = 0;
|
||||||
|
this.externalLink = externalLink;
|
||||||
|
|
||||||
if (withLocalTransactions) {
|
if (withLocalTransactions) {
|
||||||
this.fetchLocalTransactions();
|
this.fetchLocalTransactions();
|
||||||
|
Loading…
Reference in New Issue
Block a user