diff --git a/ui/app/components/token-view-balance/index.scss b/ui/app/components/token-view-balance/index.scss index 6a89e125b..b522a10f9 100644 --- a/ui/app/components/token-view-balance/index.scss +++ b/ui/app/components/token-view-balance/index.scss @@ -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; diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js index 6b8140a22..f74cc4926 100644 --- a/ui/app/components/token-view-balance/token-view-balance.component.js +++ b/ui/app/components/token-view-balance/token-view-balance.component.js @@ -30,7 +30,7 @@ export default class TokenViewBalance extends PureComponent { ) : (
diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/transaction-action/index.js index 5882443b6..a6e9097f1 100644 --- a/ui/app/components/transaction-action/index.js +++ b/ui/app/components/transaction-action/index.js @@ -1 +1 @@ -export { default } from './transaction-action.container' +export { default } from './transaction-action.component' diff --git a/ui/app/components/transaction-action/transaction-action.container.js b/ui/app/components/transaction-action/transaction-action.container.js deleted file mode 100644 index 56efbdc26..000000000 --- a/ui/app/components/transaction-action/transaction-action.container.js +++ /dev/null @@ -1,4 +0,0 @@ -import withMethodData from '../../higher-order-components/with-method-data' -import TransactionAction from './transaction-action.component' - -export default withMethodData(TransactionAction) diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss index b93edebcc..9c53c8960 100644 --- a/ui/app/components/transaction-list-item/index.scss +++ b/ui/app/components/transaction-list-item/index.scss @@ -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; + } } } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 928d531f0..bf3f09d28 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -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 (
- - -
- { `#${nonce} - ${formatDate(transaction.time)}` } -
- -
- { `-${fiatDisplayValue}` } -
-
- { `-${ethTransactionAmount} ETH` } +
+ + +
+ { nonceAndDateText } +
+ +
+ { fiatDisplayText } +
+
+ { ethDisplayText } +
+ { + showRetry && !methodData.isFetching && ( +
+ Taking too long? Increase the gas price on your transaction +
+ ) + }
) } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js index bc47f20aa..d6e57028e 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -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) diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js index d9b8e3cf8..fb23ece7a 100644 --- a/ui/app/components/transaction-list/transaction-list.component.js +++ b/ui/app/components/transaction-list/transaction-list.component.js @@ -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 (
@@ -34,6 +42,7 @@ export default class TransactionList extends PureComponent { )) } diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js index b1c2c04c9..97a94b981 100644 --- a/ui/app/components/transaction-list/transaction-list.container.js +++ b/ui/app/components/transaction-list/transaction-list.container.js @@ -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), } } diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index 04cef150f..68e935702 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -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 + } + }) +} diff --git a/ui/app/util.js b/ui/app/util.js index d5558c04e..37c0fb698 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -9,7 +9,7 @@ const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) // formatData :: ( date: ) -> 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 = {