import React, { useMemo, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { nonceSortedCompletedTransactionsSelector, nonceSortedPendingTransactionsSelector, } from '../../../selectors/transactions'; import { getCurrentChainId } from '../../../selectors'; import { useI18nContext } from '../../../hooks/useI18nContext'; import TransactionListItem from '../transaction-list-item'; import Button from '../../ui/button'; import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions'; import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP } from '../../../../shared/constants/swaps'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction'; const PAGE_INCREMENT = 10; // When we are on a token page, we only want to show transactions that involve that token. // In the case of token transfers or approvals, these will be transactions sent to the // token contract. In the case of swaps, these will be transactions sent to the swaps contract // and which have the token address in the transaction data. // // getTransactionGroupRecipientAddressFilter is used to determine whether a transaction matches // either of those criteria const getTransactionGroupRecipientAddressFilter = ( recipientAddress, chainId, ) => { return ({ initialTransaction: { txParams } }) => { return ( txParams?.to === recipientAddress || (txParams?.to === SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[chainId] && txParams.data.match(recipientAddress.slice(2))) ); }; }; const tokenTransactionFilter = ({ initialTransaction: { type, destinationTokenSymbol, sourceTokenSymbol }, }) => { if (TOKEN_CATEGORY_HASH[type]) { return false; } else if (type === TRANSACTION_TYPES.SWAP) { return destinationTokenSymbol === 'ETH' || sourceTokenSymbol === 'ETH'; } return true; }; const getFilteredTransactionGroups = ( transactionGroups, hideTokenTransactions, tokenAddress, chainId, ) => { if (hideTokenTransactions) { return transactionGroups.filter(tokenTransactionFilter); } else if (tokenAddress) { return transactionGroups.filter( getTransactionGroupRecipientAddressFilter(tokenAddress, chainId), ); } return transactionGroups; }; export default function TransactionList({ hideTokenTransactions, tokenAddress, }) { const [limit, setLimit] = useState(PAGE_INCREMENT); const t = useI18nContext(); const unfilteredPendingTransactions = useSelector( nonceSortedPendingTransactionsSelector, ); const unfilteredCompletedTransactions = useSelector( nonceSortedCompletedTransactionsSelector, ); const chainId = useSelector(getCurrentChainId); const pendingTransactions = useMemo( () => getFilteredTransactionGroups( unfilteredPendingTransactions, hideTokenTransactions, tokenAddress, chainId, ), [ hideTokenTransactions, tokenAddress, unfilteredPendingTransactions, chainId, ], ); const completedTransactions = useMemo( () => getFilteredTransactionGroups( unfilteredCompletedTransactions, hideTokenTransactions, tokenAddress, chainId, ), [ hideTokenTransactions, tokenAddress, unfilteredCompletedTransactions, chainId, ], ); const viewMore = useCallback( () => setLimit((prev) => prev + PAGE_INCREMENT), [], ); const pendingLength = pendingTransactions.length; return ( <div className="transaction-list"> <div className="transaction-list__transactions"> {pendingLength > 0 && ( <div className="transaction-list__pending-transactions"> <div className="transaction-list__header"> {`${t('queue')} (${pendingTransactions.length})`} </div> {pendingTransactions.map((transactionGroup, index) => ( <TransactionListItem isEarliestNonce={index === 0} transactionGroup={transactionGroup} key={`${transactionGroup.nonce}:${index}`} /> ))} </div> )} <div className="transaction-list__completed-transactions"> {pendingLength > 0 ? ( <div className="transaction-list__header">{t('history')}</div> ) : null} {completedTransactions.length > 0 ? ( completedTransactions .slice(0, limit) .map((transactionGroup, index) => ( <TransactionListItem transactionGroup={transactionGroup} key={`${transactionGroup.nonce}:${limit + index - 10}`} /> )) ) : ( <div className="transaction-list__empty"> <div className="transaction-list__empty-text"> {t('noTransactions')} </div> </div> )} {completedTransactions.length > limit && ( <Button className="transaction-list__view-more" type="secondary" rounded onClick={viewMore} > {t('viewMore')} </Button> )} </div> </div> </div> ); } TransactionList.propTypes = { hideTokenTransactions: PropTypes.bool, tokenAddress: PropTypes.string, }; TransactionList.defaultProps = { hideTokenTransactions: false, tokenAddress: undefined, };