2021-02-04 19:15:23 +01:00
|
|
|
import React, { useMemo, useState, useCallback } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import { useSelector } from 'react-redux';
|
2020-05-26 22:49:11 +02:00
|
|
|
import {
|
|
|
|
nonceSortedCompletedTransactionsSelector,
|
|
|
|
nonceSortedPendingTransactionsSelector,
|
2021-02-04 19:15:23 +01:00
|
|
|
} from '../../../selectors/transactions';
|
2021-03-18 11:20:06 +01:00
|
|
|
import { getCurrentChainId } from '../../../selectors';
|
2021-02-04 19:15:23 +01:00
|
|
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
|
|
|
import TransactionListItem from '../transaction-list-item';
|
2022-02-18 17:48:38 +01:00
|
|
|
import SmartTransactionListItem from '../transaction-list-item/smart-transaction-list-item.component';
|
2021-02-04 19:15:23 +01:00
|
|
|
import Button from '../../ui/button';
|
|
|
|
import { TOKEN_CATEGORY_HASH } from '../../../helpers/constants/transactions';
|
2021-04-28 21:53:59 +02:00
|
|
|
import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP } from '../../../../shared/constants/swaps';
|
2023-01-18 15:47:29 +01:00
|
|
|
import { TransactionType } from '../../../../shared/constants/transaction';
|
2022-03-07 19:54:36 +01:00
|
|
|
import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils';
|
2023-07-17 19:48:15 +02:00
|
|
|
import { Box, Text } from '../../component-library';
|
|
|
|
import {
|
|
|
|
TextColor,
|
|
|
|
TextVariant,
|
|
|
|
} from '../../../helpers/constants/design-system';
|
|
|
|
import { formatDateWithYearContext } from '../../../helpers/utils/util';
|
2018-07-31 07:03:20 +02:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
const PAGE_INCREMENT = 10;
|
2018-07-31 07:03:20 +02:00
|
|
|
|
2021-03-18 11:20:06 +01:00
|
|
|
// 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,
|
|
|
|
) => {
|
2020-10-06 20:28:38 +02:00
|
|
|
return ({ initialTransaction: { txParams } }) => {
|
2020-11-03 00:41:28 +01:00
|
|
|
return (
|
2021-09-10 19:37:19 +02:00
|
|
|
isEqualCaseInsensitive(txParams?.to, recipientAddress) ||
|
2021-03-18 11:20:06 +01:00
|
|
|
(txParams?.to === SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[chainId] &&
|
2020-11-03 00:41:28 +01:00
|
|
|
txParams.data.match(recipientAddress.slice(2)))
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
};
|
2020-05-28 05:11:15 +02:00
|
|
|
|
2020-06-13 04:06:33 +02:00
|
|
|
const tokenTransactionFilter = ({
|
2021-03-10 21:16:44 +01:00
|
|
|
initialTransaction: { type, destinationTokenSymbol, sourceTokenSymbol },
|
2020-10-06 20:28:38 +02:00
|
|
|
}) => {
|
2021-03-10 21:16:44 +01:00
|
|
|
if (TOKEN_CATEGORY_HASH[type]) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return false;
|
2023-01-18 15:47:29 +01:00
|
|
|
} else if (type === TransactionType.swap) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return destinationTokenSymbol === 'ETH' || sourceTokenSymbol === 'ETH';
|
2020-10-06 20:28:38 +02:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
return true;
|
|
|
|
};
|
2020-06-13 04:06:33 +02:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
const getFilteredTransactionGroups = (
|
|
|
|
transactionGroups,
|
|
|
|
hideTokenTransactions,
|
|
|
|
tokenAddress,
|
2021-03-18 11:20:06 +01:00
|
|
|
chainId,
|
2020-11-03 00:41:28 +01:00
|
|
|
) => {
|
2020-06-13 04:06:33 +02:00
|
|
|
if (hideTokenTransactions) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return transactionGroups.filter(tokenTransactionFilter);
|
2020-06-13 04:06:33 +02:00
|
|
|
} else if (tokenAddress) {
|
2020-11-03 00:41:28 +01:00
|
|
|
return transactionGroups.filter(
|
2021-03-18 11:20:06 +01:00
|
|
|
getTransactionGroupRecipientAddressFilter(tokenAddress, chainId),
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-06-13 04:06:33 +02:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
return transactionGroups;
|
|
|
|
};
|
2020-06-13 04:06:33 +02:00
|
|
|
|
2023-07-17 19:48:15 +02:00
|
|
|
const groupTransactionsByDate = (transactionGroups) => {
|
|
|
|
const groupedTransactions = [];
|
|
|
|
|
|
|
|
transactionGroups.forEach((transactionGroup) => {
|
|
|
|
const date = formatDateWithYearContext(
|
|
|
|
transactionGroup.primaryTransaction.time,
|
|
|
|
'MMM d, y',
|
|
|
|
'MMM d',
|
|
|
|
);
|
|
|
|
|
|
|
|
const existingGroup = groupedTransactions.find(
|
|
|
|
(group) => group.date === date,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (existingGroup) {
|
|
|
|
existingGroup.transactionGroups.push(transactionGroup);
|
|
|
|
} else {
|
|
|
|
groupedTransactions.push({
|
|
|
|
date,
|
|
|
|
dateMillis: transactionGroup.primaryTransaction.time,
|
|
|
|
transactionGroups: [transactionGroup],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
groupedTransactions.sort((a, b) => b.dateMillis - a.dateMillis);
|
|
|
|
});
|
|
|
|
|
|
|
|
return groupedTransactions;
|
|
|
|
};
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
export default function TransactionList({
|
|
|
|
hideTokenTransactions,
|
|
|
|
tokenAddress,
|
|
|
|
}) {
|
2021-02-04 19:15:23 +01:00
|
|
|
const [limit, setLimit] = useState(PAGE_INCREMENT);
|
|
|
|
const t = useI18nContext();
|
2018-07-31 07:03:20 +02:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
const unfilteredPendingTransactions = useSelector(
|
|
|
|
nonceSortedPendingTransactionsSelector,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-11-03 00:41:28 +01:00
|
|
|
const unfilteredCompletedTransactions = useSelector(
|
|
|
|
nonceSortedCompletedTransactionsSelector,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2021-03-18 11:20:06 +01:00
|
|
|
const chainId = useSelector(getCurrentChainId);
|
2023-07-17 19:48:15 +02:00
|
|
|
const renderDateStamp = (index, dateGroup) => {
|
|
|
|
return index === 0 ? (
|
|
|
|
<Text
|
|
|
|
paddingTop={4}
|
|
|
|
paddingInline={4}
|
|
|
|
variant={TextVariant.bodyMd}
|
|
|
|
color={TextColor.textDefault}
|
|
|
|
key={dateGroup.dateMillis}
|
|
|
|
>
|
|
|
|
{dateGroup.date}
|
|
|
|
</Text>
|
|
|
|
) : null;
|
|
|
|
};
|
2018-08-12 06:12:30 +02:00
|
|
|
|
2020-05-28 05:11:15 +02:00
|
|
|
const pendingTransactions = useMemo(
|
2020-11-03 00:41:28 +01:00
|
|
|
() =>
|
2023-07-17 19:48:15 +02:00
|
|
|
groupTransactionsByDate(
|
|
|
|
getFilteredTransactionGroups(
|
|
|
|
unfilteredPendingTransactions,
|
|
|
|
hideTokenTransactions,
|
|
|
|
tokenAddress,
|
|
|
|
chainId,
|
|
|
|
),
|
2020-11-03 00:41:28 +01:00
|
|
|
),
|
2021-03-18 11:20:06 +01:00
|
|
|
[
|
|
|
|
hideTokenTransactions,
|
|
|
|
tokenAddress,
|
|
|
|
unfilteredPendingTransactions,
|
|
|
|
chainId,
|
|
|
|
],
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2023-07-17 19:48:15 +02:00
|
|
|
|
2020-05-28 05:11:15 +02:00
|
|
|
const completedTransactions = useMemo(
|
2020-11-03 00:41:28 +01:00
|
|
|
() =>
|
2023-07-17 19:48:15 +02:00
|
|
|
groupTransactionsByDate(
|
|
|
|
getFilteredTransactionGroups(
|
|
|
|
unfilteredCompletedTransactions,
|
|
|
|
hideTokenTransactions,
|
|
|
|
tokenAddress,
|
|
|
|
chainId,
|
|
|
|
),
|
2020-11-03 00:41:28 +01:00
|
|
|
),
|
2021-03-18 11:20:06 +01:00
|
|
|
[
|
|
|
|
hideTokenTransactions,
|
|
|
|
tokenAddress,
|
|
|
|
unfilteredCompletedTransactions,
|
|
|
|
chainId,
|
|
|
|
],
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-05-28 05:11:15 +02:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
const viewMore = useCallback(
|
|
|
|
() => setLimit((prev) => prev + PAGE_INCREMENT),
|
|
|
|
[],
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2018-12-09 21:48:06 +01:00
|
|
|
|
2020-05-26 22:49:11 +02:00
|
|
|
return (
|
2023-07-17 19:48:15 +02:00
|
|
|
<Box className="transaction-list" paddingTop={4}>
|
|
|
|
<Box className="transaction-list__transactions">
|
2022-02-18 17:48:38 +01:00
|
|
|
{pendingTransactions.length > 0 && (
|
2023-07-17 19:48:15 +02:00
|
|
|
<Box className="transaction-list__pending-transactions">
|
2023-05-24 13:40:58 +02:00
|
|
|
{pendingTransactions
|
|
|
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
|
|
|
.sort(
|
|
|
|
(a, b) => b.primaryTransaction.time - a.primaryTransaction.time,
|
|
|
|
)
|
|
|
|
///: END:ONLY_INCLUDE_IN
|
2023-07-17 19:48:15 +02:00
|
|
|
.map((dateGroup) => {
|
|
|
|
return dateGroup.transactionGroups.map(
|
|
|
|
(transactionGroup, index) => {
|
|
|
|
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
|
|
|
|
if (
|
|
|
|
transactionGroup.initialTransaction.transactionType ===
|
|
|
|
TransactionType.smart
|
|
|
|
) {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{renderDateStamp(index, dateGroup)}
|
|
|
|
<SmartTransactionListItem
|
|
|
|
isEarliestNonce={index === 0}
|
|
|
|
smartTransaction={
|
|
|
|
transactionGroup.initialTransaction
|
|
|
|
}
|
|
|
|
transactionGroup={transactionGroup}
|
|
|
|
key={`${transactionGroup.nonce}:${index}`}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
///: END:ONLY_INCLUDE_IN
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{renderDateStamp(index, dateGroup)}
|
|
|
|
<TransactionListItem
|
|
|
|
isEarliestNonce={index === 0}
|
|
|
|
transactionGroup={transactionGroup}
|
|
|
|
key={`${transactionGroup.nonce}:${index}`}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
},
|
2023-05-24 13:40:58 +02:00
|
|
|
);
|
|
|
|
})}
|
2023-07-17 19:48:15 +02:00
|
|
|
</Box>
|
2020-11-03 00:41:28 +01:00
|
|
|
)}
|
2023-07-17 19:48:15 +02:00
|
|
|
<Box className="transaction-list__completed-transactions">
|
2020-11-03 00:41:28 +01:00
|
|
|
{completedTransactions.length > 0 ? (
|
|
|
|
completedTransactions
|
2023-05-24 13:40:58 +02:00
|
|
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
|
|
|
.sort(
|
|
|
|
(a, b) => b.primaryTransaction.time - a.primaryTransaction.time,
|
|
|
|
)
|
|
|
|
///: END:ONLY_INCLUDE_IN
|
2020-11-03 00:41:28 +01:00
|
|
|
.slice(0, limit)
|
2023-07-17 19:48:15 +02:00
|
|
|
.map((dateGroup) => {
|
|
|
|
return dateGroup.transactionGroups.map(
|
|
|
|
(transactionGroup, index) => {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{renderDateStamp(index, dateGroup)}
|
|
|
|
{transactionGroup.initialTransaction
|
|
|
|
?.transactionType === 'smart' ? (
|
|
|
|
<SmartTransactionListItem
|
|
|
|
transactionGroup={transactionGroup}
|
|
|
|
smartTransaction={
|
|
|
|
transactionGroup.initialTransaction
|
|
|
|
}
|
|
|
|
key={`${transactionGroup.nonce}:${index}`}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<TransactionListItem
|
|
|
|
transactionGroup={transactionGroup}
|
|
|
|
key={`${transactionGroup.nonce}:${
|
|
|
|
limit + index - 10
|
|
|
|
}`}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
})
|
2020-11-03 00:41:28 +01:00
|
|
|
) : (
|
2023-07-17 19:48:15 +02:00
|
|
|
<Box className="transaction-list__empty">
|
|
|
|
<Box className="transaction-list__empty-text">
|
2020-11-03 00:41:28 +01:00
|
|
|
{t('noTransactions')}
|
2023-07-17 19:48:15 +02:00
|
|
|
</Box>
|
|
|
|
</Box>
|
2020-11-03 00:41:28 +01:00
|
|
|
)}
|
2020-05-28 20:00:51 +02:00
|
|
|
{completedTransactions.length > limit && (
|
2020-11-03 00:41:28 +01:00
|
|
|
<Button
|
|
|
|
className="transaction-list__view-more"
|
|
|
|
type="secondary"
|
|
|
|
onClick={viewMore}
|
|
|
|
>
|
2021-05-15 02:29:26 +02:00
|
|
|
{t('viewMore')}
|
2020-11-03 00:41:28 +01:00
|
|
|
</Button>
|
2020-05-26 22:49:11 +02:00
|
|
|
)}
|
2023-07-17 19:48:15 +02:00
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-05-26 22:49:11 +02:00
|
|
|
}
|
2020-05-28 05:11:15 +02:00
|
|
|
|
|
|
|
TransactionList.propTypes = {
|
2020-06-13 04:06:33 +02:00
|
|
|
hideTokenTransactions: PropTypes.bool,
|
2020-05-28 05:11:15 +02:00
|
|
|
tokenAddress: PropTypes.string,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2020-05-28 05:11:15 +02:00
|
|
|
|
|
|
|
TransactionList.defaultProps = {
|
2020-06-13 04:06:33 +02:00
|
|
|
hideTokenTransactions: false,
|
2020-05-28 05:11:15 +02:00
|
|
|
tokenAddress: undefined,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|