Add retry button to TransactionListItem

This commit is contained in:
Alexander Tseung 2018-08-02 20:20:15 -07:00
parent fa8313f903
commit 5ddd9b55be
11 changed files with 175 additions and 50 deletions

View File

@ -16,6 +16,16 @@
}
}
&__token-balance {
margin-left: 12px;
font-size: 1.5rem;
@media screen and (max-width: $break-small) {
margin-bottom: 12px;
font-size: 1.75rem;
}
}
&__primary-balance {
font-size: 1.5rem;

View File

@ -30,7 +30,7 @@ export default class TokenViewBalance extends PureComponent {
<TokenBalance
token={selectedToken}
withSymbol
className="token-view-balance__primary-balance"
className="token-view-balance__token-balance"
/>
) : (
<div className="token-view-balance__balance">

View File

@ -1 +1 @@
export { default } from './transaction-action.container'
export { default } from './transaction-action.component'

View File

@ -1,4 +0,0 @@
import withMethodData from '../../higher-order-components/with-method-data'
import TransactionAction from './transaction-action.component'
export default withMethodData(TransactionAction)

View File

@ -2,21 +2,36 @@
box-sizing: border-box;
min-height: 74px;
padding: 8px 20px;
display: grid;
grid-template-columns: 45px 1fr 1fr 1fr;
grid-template-areas:
"identicon action status primary-amount"
"identicon nonce status secondary-amount";
border-bottom: 1px solid $geyser;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
@media screen and (max-width: $break-small) {
padding: 8px 20px 12px;
grid-template-columns: 45px 5fr 3fr;
}
&:hover {
background: rgba($alto, .2);
}
&__grid {
width: 100%;
display: grid;
grid-template-columns: 45px 1fr 1fr 1fr;
grid-template-areas:
"nonce nonce nonce"
"identicon action primary-amount"
"identicon status secondary-amount";
"identicon action status primary-amount"
"identicon nonce status secondary-amount";
@media screen and (max-width: $break-small) {
grid-template-columns: 45px 5fr 3fr;
grid-template-areas:
"nonce nonce nonce"
"identicon action primary-amount"
"identicon status secondary-amount";
}
}
&__identicon {
@ -87,8 +102,16 @@
}
}
&__retry {
background: #d1edff;
border-radius: 12px;
font-size: .75rem;
padding: 4px 12px;
cursor: pointer;
margin-top: 8px;
&:hover {
background: rgba($alto, .2);
@media screen and (max-width: $break-small) {
font-size: .5rem;
}
}
}

View File

@ -6,7 +6,7 @@ import TransactionAction from '../transaction-action'
import { formatDate } from '../../util'
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
import { UNAPPROVED_STATUS } from '../../constants/transactions'
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
import { hexToDecimal } from '../../helpers/conversions.util'
export default class TransactionListItem extends PureComponent {
@ -15,6 +15,10 @@ export default class TransactionListItem extends PureComponent {
transaction: PropTypes.object,
ethTransactionAmount: PropTypes.string,
fiatDisplayValue: PropTypes.string,
methodData: PropTypes.object,
showRetry: PropTypes.bool,
retryTransaction: PropTypes.func,
setSelectedToken: PropTypes.func,
}
handleClick = () => {
@ -30,44 +34,92 @@ export default class TransactionListItem extends PureComponent {
}
}
handleRetryClick = event => {
event.stopPropagation()
const {
transaction: { txParams: { to } = {} },
methodData: { name } = {},
setSelectedToken,
} = this.props
if (name === TOKEN_METHOD_TRANSFER) {
setSelectedToken(to)
}
this.resubmit()
}
resubmit () {
const { transaction: { id }, retryTransaction, history } = this.props
retryTransaction(id)
.then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
}
render () {
const { transaction, ethTransactionAmount, fiatDisplayValue } = this.props
const {
transaction,
ethTransactionAmount,
fiatDisplayValue,
methodData,
showRetry,
} = this.props
const { txParams = {} } = transaction
const nonce = hexToDecimal(txParams.nonce)
const nonceAndDateText = `#${nonce} - ${formatDate(transaction.time)}`
const fiatDisplayText = `-${fiatDisplayValue}`
const ethDisplayText = `-${ethTransactionAmount} ETH`
return (
<div
className="transaction-list-item"
onClick={this.handleClick}
>
<Identicon
className="transaction-list-item__identicon"
address={txParams.to}
diameter={34}
/>
<TransactionAction
transaction={transaction}
className="transaction-list-item__action"
/>
<div className="transaction-list-item__nonce">
{ `#${nonce} - ${formatDate(transaction.time)}` }
</div>
<TransactionStatus
className="transaction-list-item__status"
status={transaction.status}
/>
<div
className="transaction-list-item__amount transaction-list-item__amount--primary"
title={`-${fiatDisplayValue}`}
>
{ `-${fiatDisplayValue}` }
</div>
<div
className="transaction-list-item__amount transaction-list-item__amount--secondary"
title={`-${ethTransactionAmount} ETH`}
>
{ `-${ethTransactionAmount} ETH` }
<div className="transaction-list-item__grid">
<Identicon
className="transaction-list-item__identicon"
address={txParams.to}
diameter={34}
/>
<TransactionAction
transaction={transaction}
methodData={methodData}
className="transaction-list-item__action"
/>
<div
className="transaction-list-item__nonce"
title={nonceAndDateText}
>
{ nonceAndDateText }
</div>
<TransactionStatus
className="transaction-list-item__status"
status={transaction.status}
/>
<div
className="transaction-list-item__amount transaction-list-item__amount--primary"
title={fiatDisplayText}
>
{ fiatDisplayText }
</div>
<div
className="transaction-list-item__amount transaction-list-item__amount--secondary"
title={ethDisplayText}
>
{ ethDisplayText }
</div>
</div>
{
showRetry && !methodData.isFetching && (
<div
className="transaction-list-item__retry"
onClick={this.handleRetryClick}
>
<span>Taking too long? Increase the gas price on your transaction</span>
</div>
)
}
</div>
)
}

View File

@ -1,7 +1,9 @@
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
import withMethodData from '../../higher-order-components/with-method-data'
import TransactionListItem from './transaction-list-item.component'
import { setSelectedToken, retryTransaction } from '../../actions'
import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
import { formatCurrency } from '../../helpers/confirm-transaction/util'
@ -22,7 +24,15 @@ const mapStateToProps = (state, ownProps) => {
}
}
const mapDispatchToProps = dispatch => {
return {
setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)),
retryTransaction: transactionId => dispatch(retryTransaction(transactionId)),
}
}
export default compose(
withRouter,
connect(mapStateToProps),
connect(mapStateToProps, mapDispatchToProps),
withMethodData,
)(TransactionListItem)

View File

@ -10,16 +10,24 @@ export default class TransactionList extends PureComponent {
static defaultProps = {
pendingTransactions: [],
completedTransactions: [],
transactionToRetry: {},
}
static propTypes = {
pendingTransactions: PropTypes.array,
completedTransactions: PropTypes.array,
transactionToRetry: PropTypes.object,
}
shouldShowRetry = transaction => {
const { transactionToRetry } = this.props
const { id, submittedTime } = transaction
return id === transactionToRetry.id && Date.now() - submittedTime > 30000
}
renderTransactions () {
const { t } = this.context
const { pendingTransactions, completedTransactions } = this.props
const { pendingTransactions = [], completedTransactions = [] } = this.props
return (
<div className="transaction-list__transactions">
@ -34,6 +42,7 @@ export default class TransactionList extends PureComponent {
<TransactionListItem
transaction={transaction}
key={transaction.id}
showRetry={this.shouldShowRetry(transaction)}
/>
))
}

View File

@ -6,11 +6,15 @@ import {
pendingTransactionsSelector,
completedTransactionsSelector,
} from '../../selectors/transactions'
import { getLatestSubmittedTxWithEarliestNonce } from '../../helpers/transactions.util'
const mapStateToProps = state => {
const pendingTransactions = pendingTransactionsSelector(state)
return {
pendingTransactions: pendingTransactionsSelector(state),
completedTransactions: completedTransactionsSelector(state),
pendingTransactions,
transactionToRetry: getLatestSubmittedTxWithEarliestNonce(pendingTransactions),
}
}

View File

@ -2,6 +2,8 @@ import ethUtil from 'ethereumjs-util'
import MethodRegistry from 'eth-method-registry'
const registry = new MethodRegistry({ provider: global.ethereumProvider })
import { hexToDecimal } from './conversions.util'
import {
TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_APPROVE,
@ -55,3 +57,22 @@ export async function getMethodData (data = {}) {
params: parsedResult.args,
}
}
export function getLatestSubmittedTxWithEarliestNonce (transactions = []) {
if (!transactions.length) {
return {}
}
return transactions.reduce((acc, current) => {
const accNonce = hexToDecimal(acc.nonce)
const currentNonce = hexToDecimal(current.nonce)
if (currentNonce < accNonce) {
return current
} else if (currentNonce === accNonce) {
return current.submittedTime > acc.submittedTime ? current : acc
} else {
return acc
}
})
}

View File

@ -9,7 +9,7 @@ const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR)
// formatData :: ( date: <Unix Timestamp> ) -> String
function formatDate (date) {
return vreme.format(new Date(date), 'March 16 2014, at 14:30')
return vreme.format(new Date(date), '3/16/2014 at 14:30')
}
var valueTable = {