aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components/transaction-list-item
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2018-09-28 14:45:16 +0800
committerGitHub <noreply@github.com>2018-09-28 14:45:16 +0800
commit59ab595b5ea6a4e24f2048d2ed43c1181bb5aa3e (patch)
tree36a9c0701cd80942c48e97cab0c4190d134980c8 /ui/app/components/transaction-list-item
parentcb0af67f743d242afa3bdb518847f77d3c2cc260 (diff)
parentbd489d358383b7556af323db78f30013459febf6 (diff)
downloadtangerine-wallet-browser-59ab595b5ea6a4e24f2048d2ed43c1181bb5aa3e.tar.gz
tangerine-wallet-browser-59ab595b5ea6a4e24f2048d2ed43c1181bb5aa3e.tar.zst
tangerine-wallet-browser-59ab595b5ea6a4e24f2048d2ed43c1181bb5aa3e.zip
Merge branch 'develop' into account-tracker-network-change
Diffstat (limited to 'ui/app/components/transaction-list-item')
-rw-r--r--ui/app/components/transaction-list-item/index.js1
-rw-r--r--ui/app/components/transaction-list-item/index.scss131
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.component.js200
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js48
4 files changed, 380 insertions, 0 deletions
diff --git a/ui/app/components/transaction-list-item/index.js b/ui/app/components/transaction-list-item/index.js
new file mode 100644
index 000000000..697cc55e9
--- /dev/null
+++ b/ui/app/components/transaction-list-item/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-list-item.container'
diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss
new file mode 100644
index 000000000..9d694546b
--- /dev/null
+++ b/ui/app/components/transaction-list-item/index.scss
@@ -0,0 +1,131 @@
+.transaction-list-item {
+ box-sizing: border-box;
+ min-height: 74px;
+ border-bottom: 1px solid $geyser;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ background: $white;
+
+ &__grid {
+ cursor: pointer;
+ width: 100%;
+ padding: 16px 20px;
+ display: grid;
+ grid-template-columns: 45px 1fr 1fr 1fr;
+ grid-template-areas:
+ "identicon action status primary-amount"
+ "identicon nonce status secondary-amount";
+
+ @media screen and (max-width: $break-small) {
+ padding: 8px 20px 12px;
+ grid-template-columns: 45px 5fr 3fr;
+ grid-template-areas:
+ "nonce nonce nonce"
+ "identicon action primary-amount"
+ "identicon status secondary-amount";
+ }
+
+ &:hover {
+ background: rgba($alto, .2);
+ }
+ }
+
+ &__identicon {
+ grid-area: identicon;
+ grid-row: 1 / span 2;
+ align-self: center;
+
+ @media screen and (max-width: $break-small) {
+ grid-row: 2 / span 2;
+ }
+ }
+
+ &__action {
+ text-transform: capitalize;
+ padding: 0 8px 2px 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ grid-area: action;
+ align-self: end;
+ }
+
+ &__status {
+ grid-area: status;
+ grid-row: 1 / span 2;
+ align-self: center;
+
+ @media screen and (max-width: $break-small) {
+ grid-row: 3;
+ }
+ }
+
+ &__nonce {
+ font-size: .75rem;
+ color: #5e6064;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ grid-area: nonce;
+ align-self: start;
+
+ @media screen and (max-width: $break-small) {
+ padding-bottom: 4px;
+ }
+ }
+
+ &__amount {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &--primary {
+ text-align: end;
+ grid-area: primary-amount;
+ align-self: end;
+
+ @media screen and (max-width: $break-small) {
+ padding-bottom: 2px;
+ }
+ }
+
+ &--secondary {
+ text-align: end;
+ font-size: .75rem;
+ color: #5e6064;
+ grid-area: secondary-amount;
+ align-self: start;
+ }
+ }
+
+ &__retry {
+ background: #d1edff;
+ border-radius: 12px;
+ font-size: .75rem;
+ padding: 4px 12px;
+ cursor: pointer;
+ margin-top: 8px;
+
+ @media screen and (max-width: $break-small) {
+ font-size: .5rem;
+ }
+ }
+
+ &__details-container {
+ padding: 8px 16px 16px;
+ background: #f3f4f7;
+ width: 100%;
+ }
+
+ &__expander {
+ max-height: 0px;
+ width: 100%;
+
+ &--show {
+ max-height: 1000px;
+ transition: max-height 700ms ease-out;
+ }
+ }
+}
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
new file mode 100644
index 000000000..c1c69f59b
--- /dev/null
+++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js
@@ -0,0 +1,200 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Identicon from '../identicon'
+import TransactionStatus from '../transaction-status'
+import TransactionAction from '../transaction-action'
+import CurrencyDisplay from '../currency-display'
+import TokenCurrencyDisplay from '../token-currency-display'
+import TransactionListItemDetails from '../transaction-list-item-details'
+import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
+import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
+import { ETH } from '../../constants/common'
+import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
+
+export default class TransactionListItem extends PureComponent {
+ static propTypes = {
+ assetImages: PropTypes.object,
+ history: PropTypes.object,
+ methodData: PropTypes.object,
+ nonceAndDate: PropTypes.string,
+ retryTransaction: PropTypes.func,
+ setSelectedToken: PropTypes.func,
+ showCancelModal: PropTypes.func,
+ showCancel: PropTypes.bool,
+ showRetry: PropTypes.bool,
+ showTransactionDetailsModal: PropTypes.func,
+ token: PropTypes.object,
+ tokenData: PropTypes.object,
+ transaction: PropTypes.object,
+ value: PropTypes.string,
+ }
+
+ state = {
+ showTransactionDetails: false,
+ }
+
+ handleClick = () => {
+ const {
+ transaction,
+ history,
+ showTransactionDetailsModal,
+ methodData,
+ showCancel,
+ showRetry,
+ } = this.props
+ const { id, status } = transaction
+ const { showTransactionDetails } = this.state
+ const windowType = window.METAMASK_UI_TYPE
+
+ if (status === UNAPPROVED_STATUS) {
+ history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)
+ return
+ }
+
+ if (windowType === ENVIRONMENT_TYPE_FULLSCREEN) {
+ this.setState({ showTransactionDetails: !showTransactionDetails })
+ } else {
+ showTransactionDetailsModal({
+ transaction,
+ onRetry: this.handleRetry,
+ showRetry: showRetry && methodData.done,
+ onCancel: this.handleCancel,
+ showCancel,
+ })
+ }
+ }
+
+ handleCancel = () => {
+ const { transaction: { id, txParams: { gasPrice } } = {}, showCancelModal } = this.props
+ showCancelModal(id, gasPrice)
+ }
+
+ handleRetry = () => {
+ const {
+ transaction: { txParams: { to } = {} },
+ methodData: { name } = {},
+ setSelectedToken,
+ } = this.props
+
+ if (name === TOKEN_METHOD_TRANSFER) {
+ setSelectedToken(to)
+ }
+
+ return this.resubmit()
+ }
+
+ resubmit () {
+ const { transaction: { id }, retryTransaction, history } = this.props
+ return retryTransaction(id)
+ .then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
+ }
+
+ renderPrimaryCurrency () {
+ const { token, transaction: { txParams: { data } = {} } = {}, value } = this.props
+
+ return token
+ ? (
+ <TokenCurrencyDisplay
+ className="transaction-list-item__amount transaction-list-item__amount--primary"
+ token={token}
+ transactionData={data}
+ prefix="-"
+ />
+ ) : (
+ <CurrencyDisplay
+ className="transaction-list-item__amount transaction-list-item__amount--primary"
+ value={value}
+ prefix="-"
+ numberOfDecimals={2}
+ currency={ETH}
+ />
+ )
+ }
+
+ renderSecondaryCurrency () {
+ const { token, value } = this.props
+
+ return token
+ ? null
+ : (
+ <CurrencyDisplay
+ className="transaction-list-item__amount transaction-list-item__amount--secondary"
+ prefix="-"
+ value={value}
+ />
+ )
+ }
+
+ render () {
+ const {
+ assetImages,
+ methodData,
+ nonceAndDate,
+ showCancel,
+ showRetry,
+ tokenData,
+ transaction,
+ } = this.props
+ const { txParams = {} } = transaction
+ const { showTransactionDetails } = this.state
+ const toAddress = tokenData
+ ? tokenData.params && tokenData.params[0] && tokenData.params[0].value || txParams.to
+ : txParams.to
+
+ return (
+ <div className="transaction-list-item">
+ <div
+ className="transaction-list-item__grid"
+ onClick={this.handleClick}
+ >
+ <Identicon
+ className="transaction-list-item__identicon"
+ address={toAddress}
+ diameter={34}
+ image={assetImages[toAddress]}
+ />
+ <TransactionAction
+ transaction={transaction}
+ methodData={methodData}
+ className="transaction-list-item__action"
+ />
+ <div
+ className="transaction-list-item__nonce"
+ title={nonceAndDate}
+ >
+ { nonceAndDate }
+ </div>
+ <TransactionStatus
+ className="transaction-list-item__status"
+ statusKey={transaction.status}
+ title={(
+ (transaction.err && transaction.err.rpc)
+ ? transaction.err.rpc.message
+ : transaction.err && transaction.err.message
+ )}
+ />
+ { this.renderPrimaryCurrency() }
+ { this.renderSecondaryCurrency() }
+ </div>
+ <div className={classnames('transaction-list-item__expander', {
+ 'transaction-list-item__expander--show': showTransactionDetails,
+ })}>
+ {
+ showTransactionDetails && (
+ <div className="transaction-list-item__details-container">
+ <TransactionListItemDetails
+ transaction={transaction}
+ onRetry={this.handleRetry}
+ showRetry={showRetry && methodData.done}
+ onCancel={this.handleCancel}
+ showCancel={showCancel}
+ />
+ </div>
+ )
+ }
+ </div>
+ </div>
+ )
+ }
+}
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
new file mode 100644
index 000000000..72f5f5d61
--- /dev/null
+++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js
@@ -0,0 +1,48 @@
+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, showModal } from '../../actions'
+import { hexToDecimal } from '../../helpers/conversions.util'
+import { getTokenData } from '../../helpers/transactions.util'
+import { formatDate } from '../../util'
+
+const mapStateToProps = (state, ownProps) => {
+ const { transaction: { txParams: { value, nonce, data } = {}, time } = {} } = ownProps
+
+ const tokenData = data && getTokenData(data)
+ const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
+
+ return {
+ value,
+ nonceAndDate,
+ tokenData,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)),
+ retryTransaction: transactionId => dispatch(retryTransaction(transactionId)),
+ showCancelModal: (transactionId, originalGasPrice) => {
+ return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice }))
+ },
+ showTransactionDetailsModal: ({ transaction, onRetry, showRetry, onCancel, showCancel }) => {
+ return dispatch(showModal({
+ name: 'TRANSACTION_DETAILS',
+ transaction,
+ onRetry,
+ showRetry,
+ onCancel,
+ showCancel,
+ }))
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps),
+ withMethodData,
+)(TransactionListItem)