mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add retry button to TransactionListItem
This commit is contained in:
parent
fa8313f903
commit
5ddd9b55be
@ -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 {
|
&__primary-balance {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export default class TokenViewBalance extends PureComponent {
|
|||||||
<TokenBalance
|
<TokenBalance
|
||||||
token={selectedToken}
|
token={selectedToken}
|
||||||
withSymbol
|
withSymbol
|
||||||
className="token-view-balance__primary-balance"
|
className="token-view-balance__token-balance"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="token-view-balance__balance">
|
<div className="token-view-balance__balance">
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default } from './transaction-action.container'
|
export { default } from './transaction-action.component'
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
import withMethodData from '../../higher-order-components/with-method-data'
|
|
||||||
import TransactionAction from './transaction-action.component'
|
|
||||||
|
|
||||||
export default withMethodData(TransactionAction)
|
|
@ -2,21 +2,36 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-height: 74px;
|
min-height: 74px;
|
||||||
padding: 8px 20px;
|
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;
|
border-bottom: 1px solid $geyser;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
@media screen and (max-width: $break-small) {
|
@media screen and (max-width: $break-small) {
|
||||||
padding: 8px 20px 12px;
|
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:
|
grid-template-areas:
|
||||||
"nonce nonce nonce"
|
"identicon action status primary-amount"
|
||||||
"identicon action primary-amount"
|
"identicon nonce status secondary-amount";
|
||||||
"identicon 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 {
|
&__identicon {
|
||||||
@ -87,8 +102,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__retry {
|
||||||
|
background: #d1edff;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: .75rem;
|
||||||
|
padding: 4px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
&:hover {
|
@media screen and (max-width: $break-small) {
|
||||||
background: rgba($alto, .2);
|
font-size: .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import TransactionAction from '../transaction-action'
|
|||||||
import { formatDate } from '../../util'
|
import { formatDate } from '../../util'
|
||||||
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
|
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
|
||||||
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
|
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'
|
import { hexToDecimal } from '../../helpers/conversions.util'
|
||||||
|
|
||||||
export default class TransactionListItem extends PureComponent {
|
export default class TransactionListItem extends PureComponent {
|
||||||
@ -15,6 +15,10 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
transaction: PropTypes.object,
|
transaction: PropTypes.object,
|
||||||
ethTransactionAmount: PropTypes.string,
|
ethTransactionAmount: PropTypes.string,
|
||||||
fiatDisplayValue: PropTypes.string,
|
fiatDisplayValue: PropTypes.string,
|
||||||
|
methodData: PropTypes.object,
|
||||||
|
showRetry: PropTypes.bool,
|
||||||
|
retryTransaction: PropTypes.func,
|
||||||
|
setSelectedToken: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = () => {
|
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 () {
|
render () {
|
||||||
const { transaction, ethTransactionAmount, fiatDisplayValue } = this.props
|
const {
|
||||||
|
transaction,
|
||||||
|
ethTransactionAmount,
|
||||||
|
fiatDisplayValue,
|
||||||
|
methodData,
|
||||||
|
showRetry,
|
||||||
|
} = this.props
|
||||||
const { txParams = {} } = transaction
|
const { txParams = {} } = transaction
|
||||||
const nonce = hexToDecimal(txParams.nonce)
|
const nonce = hexToDecimal(txParams.nonce)
|
||||||
|
|
||||||
|
const nonceAndDateText = `#${nonce} - ${formatDate(transaction.time)}`
|
||||||
|
const fiatDisplayText = `-${fiatDisplayValue}`
|
||||||
|
const ethDisplayText = `-${ethTransactionAmount} ETH`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="transaction-list-item"
|
className="transaction-list-item"
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
>
|
>
|
||||||
<Identicon
|
<div className="transaction-list-item__grid">
|
||||||
className="transaction-list-item__identicon"
|
<Identicon
|
||||||
address={txParams.to}
|
className="transaction-list-item__identicon"
|
||||||
diameter={34}
|
address={txParams.to}
|
||||||
/>
|
diameter={34}
|
||||||
<TransactionAction
|
/>
|
||||||
transaction={transaction}
|
<TransactionAction
|
||||||
className="transaction-list-item__action"
|
transaction={transaction}
|
||||||
/>
|
methodData={methodData}
|
||||||
<div className="transaction-list-item__nonce">
|
className="transaction-list-item__action"
|
||||||
{ `#${nonce} - ${formatDate(transaction.time)}` }
|
/>
|
||||||
</div>
|
<div
|
||||||
<TransactionStatus
|
className="transaction-list-item__nonce"
|
||||||
className="transaction-list-item__status"
|
title={nonceAndDateText}
|
||||||
status={transaction.status}
|
>
|
||||||
/>
|
{ nonceAndDateText }
|
||||||
<div
|
</div>
|
||||||
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
<TransactionStatus
|
||||||
title={`-${fiatDisplayValue}`}
|
className="transaction-list-item__status"
|
||||||
>
|
status={transaction.status}
|
||||||
{ `-${fiatDisplayValue}` }
|
/>
|
||||||
</div>
|
<div
|
||||||
<div
|
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
||||||
className="transaction-list-item__amount transaction-list-item__amount--secondary"
|
title={fiatDisplayText}
|
||||||
title={`-${ethTransactionAmount} ETH`}
|
>
|
||||||
>
|
{ fiatDisplayText }
|
||||||
{ `-${ethTransactionAmount} ETH` }
|
</div>
|
||||||
|
<div
|
||||||
|
className="transaction-list-item__amount transaction-list-item__amount--secondary"
|
||||||
|
title={ethDisplayText}
|
||||||
|
>
|
||||||
|
{ ethDisplayText }
|
||||||
|
</div>
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { withRouter } from 'react-router-dom'
|
import { withRouter } from 'react-router-dom'
|
||||||
import { compose } from 'recompose'
|
import { compose } from 'recompose'
|
||||||
|
import withMethodData from '../../higher-order-components/with-method-data'
|
||||||
import TransactionListItem from './transaction-list-item.component'
|
import TransactionListItem from './transaction-list-item.component'
|
||||||
|
import { setSelectedToken, retryTransaction } from '../../actions'
|
||||||
import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
|
import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
|
||||||
import { formatCurrency } from '../../helpers/confirm-transaction/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(
|
export default compose(
|
||||||
withRouter,
|
withRouter,
|
||||||
connect(mapStateToProps),
|
connect(mapStateToProps, mapDispatchToProps),
|
||||||
|
withMethodData,
|
||||||
)(TransactionListItem)
|
)(TransactionListItem)
|
||||||
|
@ -10,16 +10,24 @@ export default class TransactionList extends PureComponent {
|
|||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
pendingTransactions: [],
|
pendingTransactions: [],
|
||||||
completedTransactions: [],
|
completedTransactions: [],
|
||||||
|
transactionToRetry: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
pendingTransactions: PropTypes.array,
|
pendingTransactions: PropTypes.array,
|
||||||
completedTransactions: 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 () {
|
renderTransactions () {
|
||||||
const { t } = this.context
|
const { t } = this.context
|
||||||
const { pendingTransactions, completedTransactions } = this.props
|
const { pendingTransactions = [], completedTransactions = [] } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="transaction-list__transactions">
|
<div className="transaction-list__transactions">
|
||||||
@ -34,6 +42,7 @@ export default class TransactionList extends PureComponent {
|
|||||||
<TransactionListItem
|
<TransactionListItem
|
||||||
transaction={transaction}
|
transaction={transaction}
|
||||||
key={transaction.id}
|
key={transaction.id}
|
||||||
|
showRetry={this.shouldShowRetry(transaction)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,15 @@ import {
|
|||||||
pendingTransactionsSelector,
|
pendingTransactionsSelector,
|
||||||
completedTransactionsSelector,
|
completedTransactionsSelector,
|
||||||
} from '../../selectors/transactions'
|
} from '../../selectors/transactions'
|
||||||
|
import { getLatestSubmittedTxWithEarliestNonce } from '../../helpers/transactions.util'
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
|
const pendingTransactions = pendingTransactionsSelector(state)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pendingTransactions: pendingTransactionsSelector(state),
|
|
||||||
completedTransactions: completedTransactionsSelector(state),
|
completedTransactions: completedTransactionsSelector(state),
|
||||||
|
pendingTransactions,
|
||||||
|
transactionToRetry: getLatestSubmittedTxWithEarliestNonce(pendingTransactions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import ethUtil from 'ethereumjs-util'
|
|||||||
import MethodRegistry from 'eth-method-registry'
|
import MethodRegistry from 'eth-method-registry'
|
||||||
const registry = new MethodRegistry({ provider: global.ethereumProvider })
|
const registry = new MethodRegistry({ provider: global.ethereumProvider })
|
||||||
|
|
||||||
|
import { hexToDecimal } from './conversions.util'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TOKEN_METHOD_TRANSFER,
|
TOKEN_METHOD_TRANSFER,
|
||||||
TOKEN_METHOD_APPROVE,
|
TOKEN_METHOD_APPROVE,
|
||||||
@ -55,3 +57,22 @@ export async function getMethodData (data = {}) {
|
|||||||
params: parsedResult.args,
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR)
|
|||||||
|
|
||||||
// formatData :: ( date: <Unix Timestamp> ) -> String
|
// formatData :: ( date: <Unix Timestamp> ) -> String
|
||||||
function formatDate (date) {
|
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 = {
|
var valueTable = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user