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:
Nicolas Gotchac 2017-02-09 17:41:17 +01:00 committed by Jaco Greeff
parent 867a593988
commit 48eac51c8a
11 changed files with 170 additions and 36 deletions

View File

@ -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);

View File

@ -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');
});
}); });
}); });

View File

@ -65,6 +65,11 @@
.link { .link {
vertical-align: top; vertical-align: top;
&.currentLink {
color: white;
cursor: text;
}
} }
.right { .right {

View File

@ -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>

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 }

View File

@ -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 (

View File

@ -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();