Skip to content

Commit ac95554

Browse files
feat: add transactions tab
1 parent 3d51101 commit ac95554

File tree

6 files changed

+508
-0
lines changed

6 files changed

+508
-0
lines changed

apps/evm/src/libs/translations/translations/en.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,47 @@
7777
"tabs": {
7878
"pools": "Pools",
7979
"settings": "Settings",
80+
"transactions": "Transactions",
8081
"vaults": "Vaults"
8182
},
83+
"transactions": {
84+
"today": "Today",
85+
"yesterday": "Yesterday",
86+
"txText": {
87+
"approve": "Approved spending {{tokenAmount}}",
88+
"enterMarket": "<Styled>Enabled</Styled> {{vTokenSymbol}} as collateral",
89+
"exitMarket": "<Styled>Disabled</Styled> {{vTokenSymbol}} as collateral"
90+
},
91+
"txType": {
92+
"mint": "Supply",
93+
"borrow": "Borrow",
94+
"redeem": "Withdraw",
95+
"repay": "Repayment",
96+
"approve": "Approval",
97+
"enterMarket": "Enabled collateral",
98+
"exitMarket": "Disabled collateral"
99+
},
100+
"selects": {
101+
"txType": {
102+
"all": "All transactions",
103+
"mint": "Supplies",
104+
"borrow": "Borrows",
105+
"redeem": "Withdrawals",
106+
"repay": "Borrow repayments",
107+
"approve": "Approvals",
108+
"enterMarket": "Enabled collateral",
109+
"exitMarket": "Disabled collateral"
110+
},
111+
"source": {
112+
"all": "All markets"
113+
}
114+
},
115+
"placeholder": {
116+
"description": "Your transactions will appear here",
117+
"title": "No transactions yet"
118+
},
119+
"view": "View"
120+
},
82121
"vaults": {
83122
"placeholder": {
84123
"description": "Your vault positions will appear here",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { cn } from '@venusprotocol/ui';
2+
import { type AmountTransaction, TxType } from 'clients/api';
3+
import { useTranslation } from 'libs/translations';
4+
import { useMemo } from 'react';
5+
import { formatCentsToReadableValue, formatTokensToReadableValue } from 'utilities';
6+
7+
export interface TransactionDetailsProps {
8+
transactionData: AmountTransaction;
9+
className?: string;
10+
}
11+
12+
export const TransactionDetails: React.FC<TransactionDetailsProps> = ({
13+
transactionData,
14+
className,
15+
}) => {
16+
const { amountTokens, amountCents, token, txType, poolName } = transactionData;
17+
const vTokenSymbol = `v${token.symbol}`;
18+
const { Trans } = useTranslation();
19+
20+
const transactionText = useMemo(() => {
21+
switch (txType) {
22+
case TxType.Approve:
23+
return (
24+
<Trans
25+
i18nKey="account.transactions.txText.approve"
26+
values={{
27+
tokenAmount: formatTokensToReadableValue({
28+
token,
29+
value: amountTokens,
30+
}),
31+
}}
32+
/>
33+
);
34+
case TxType.EnterMarket:
35+
return (
36+
<Trans
37+
i18nKey="account.transactions.txText.enterMarket"
38+
components={{ Styled: <span className="text-green" /> }}
39+
values={{ vTokenSymbol }}
40+
/>
41+
);
42+
case TxType.ExitMarket:
43+
return (
44+
<Trans
45+
i18nKey="account.transactions.txText.exitMarket"
46+
components={{ Styled: <span className="text-red" /> }}
47+
values={{ vTokenSymbol }}
48+
/>
49+
);
50+
default:
51+
return formatTokensToReadableValue({
52+
token,
53+
value: amountTokens,
54+
});
55+
}
56+
}, [Trans, txType, token]);
57+
58+
const transactionSecondaryText = useMemo(() => {
59+
const text =
60+
txType !== TxType.Approve && amountCents
61+
? `${formatCentsToReadableValue({ value: amountCents })}${vTokenSymbol}${poolName}`
62+
: `${vTokenSymbol}${poolName}`;
63+
switch (txType) {
64+
case TxType.EnterMarket:
65+
case TxType.ExitMarket:
66+
return poolName;
67+
default:
68+
return text;
69+
}
70+
}, [txType, token, poolName]);
71+
72+
return (
73+
<div className={cn('flex flex-col self-start', className)}>
74+
<span className="text-sm">{transactionText}</span>
75+
<span className="text-grey text-xs">{transactionSecondaryText}</span>
76+
</div>
77+
);
78+
};
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { cn } from '@venusprotocol/ui';
2+
import { type AmountTransaction, TxType } from 'clients/api';
3+
import { Delimiter, Icon, type IconName, TokenIcon } from 'components';
4+
import { format } from 'date-fns';
5+
import { useTranslation } from 'libs/translations';
6+
import { useMemo } from 'react';
7+
import { generateExplorerUrl } from 'utilities';
8+
import { TransactionDetails } from '../TransactionDetails';
9+
10+
export interface TransactionRowProps {
11+
transactionData: AmountTransaction;
12+
className?: string;
13+
}
14+
15+
const getTransactionIcon = (txType: TxType): IconName => {
16+
switch (txType) {
17+
case TxType.Mint:
18+
case TxType.Repay:
19+
return 'transactionIn';
20+
case TxType.Borrow:
21+
case TxType.Redeem:
22+
return 'transactionOut';
23+
default:
24+
return 'transactionCollateral';
25+
}
26+
};
27+
28+
export const TransactionRow: React.FC<TransactionRowProps> = ({ transactionData, className }) => {
29+
const { chainId, txType, hash, blockTimestamp, token } = transactionData;
30+
const { t } = useTranslation();
31+
32+
const transactionTitle = useMemo(() => {
33+
switch (txType) {
34+
case TxType.Mint:
35+
return t('account.transactions.txType.mint');
36+
case TxType.Repay:
37+
return t('account.transactions.txType.repay');
38+
case TxType.Borrow:
39+
return t('account.transactions.txType.borrow');
40+
case TxType.Redeem:
41+
return t('account.transactions.txType.redeem');
42+
case TxType.Approve:
43+
return t('account.transactions.txType.approve');
44+
case TxType.ExitMarket:
45+
return t('account.transactions.txType.exitMarket');
46+
default:
47+
return t('account.transactions.txType.enterMarket');
48+
}
49+
}, [t, txType]);
50+
51+
return (
52+
<div
53+
className={cn(
54+
'flex flex-col px-4 py-3 bg-cards rounded-xl w-full items-center justify-evenly group md:flex-row md:px-6 md:py-0 md:h-16 md:hover:bg-lightGrey md:rounded-none',
55+
className,
56+
)}
57+
>
58+
<a
59+
className="flex flex-col md:flex-row w-full"
60+
href={generateExplorerUrl({
61+
hash,
62+
urlType: 'tx',
63+
chainId,
64+
})}
65+
target="_blank"
66+
rel="noreferrer"
67+
>
68+
<div className="flex w-full md:w-70 items-center">
69+
<div className="p-[6px] md:p-2 mr-2 md:mr-3 flex items-center justify-center bg-lightGrey rounded-full">
70+
<Icon className="h-3 w-3 md:h-4 md:w-4" name={getTransactionIcon(txType)} />
71+
</div>
72+
<div className="flex flex-row w-full md:w-auto items-center md:items-start justify-between md:flex-col">
73+
<span className="text-sm">{transactionTitle}</span>
74+
<span className="text-grey text-xs">{format(blockTimestamp, 'hh:mm aa')}</span>
75+
</div>
76+
</div>
77+
78+
<Delimiter className="my-3 md:hidden" />
79+
80+
<div className="flex w-full items-center">
81+
<TokenIcon className="mr-2 self-start" token={token} />
82+
<TransactionDetails className="flex-1" transactionData={transactionData} />
83+
<a
84+
className="hidden md:flex items-center self-start text-grey md:group-hover:text-offWhite space-x-1 mt-1"
85+
href={generateExplorerUrl({
86+
hash,
87+
urlType: 'tx',
88+
chainId,
89+
})}
90+
target="_blank"
91+
rel="noreferrer"
92+
>
93+
<span className="hidden lg:block text-sm underline">
94+
{t('account.transactions.view')}
95+
</span>
96+
<Icon className="md:group-hover:text-offWhite" name="transactionLink" />
97+
</a>
98+
</div>
99+
</a>
100+
</div>
101+
);
102+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Spinner, cn } from '@venusprotocol/ui';
2+
import type { AmountTransaction, GetAccountTransactionHistoryOutput } from 'clients/api';
3+
import { Pagination } from 'components';
4+
import { format, isToday, isYesterday } from 'date-fns';
5+
import { useTranslation } from 'libs/translations';
6+
import { Placeholder } from 'pages/Account/Placeholder';
7+
import { useMemo } from 'react';
8+
import { TransactionRow } from '../TransactionRow';
9+
10+
const INITIAL_PAGE_INDEX = 1;
11+
const ITEMS_PER_PAGE_COUNT = 20;
12+
13+
export interface TransactionsListProps {
14+
transactions: GetAccountTransactionHistoryOutput['transactions'];
15+
transactionsCount: number;
16+
onPageChange: (newPage: number) => void;
17+
isLoading: boolean;
18+
className?: string;
19+
}
20+
21+
export const TransactionsList: React.FC<TransactionsListProps> = ({
22+
transactions,
23+
transactionsCount,
24+
isLoading,
25+
onPageChange,
26+
className,
27+
}) => {
28+
const { t } = useTranslation();
29+
30+
const transactionsGroupedByDate = useMemo(() => {
31+
const groupedByDate = transactions.reduce<Record<string, AmountTransaction[]>>(
32+
(acc, txData) => {
33+
const isTodayGroup = isToday(txData.blockTimestamp);
34+
const isYesterdayGroup = isYesterday(txData.blockTimestamp);
35+
36+
let dayGroup = format(txData.blockTimestamp, 'MMM dd, yyyy');
37+
38+
if (isTodayGroup) {
39+
dayGroup = t('account.transactions.today');
40+
}
41+
42+
if (isYesterdayGroup) {
43+
dayGroup = t('account.transactions.yesterday');
44+
}
45+
46+
const dayTxs = acc[dayGroup] ? [...acc[dayGroup], txData] : [txData];
47+
48+
return {
49+
...acc,
50+
[dayGroup]: dayTxs,
51+
};
52+
},
53+
{},
54+
);
55+
56+
return groupedByDate;
57+
}, [transactions, t]);
58+
59+
if (isLoading) {
60+
return <Spinner />;
61+
}
62+
63+
if (transactions.length === 0) {
64+
return (
65+
<Placeholder
66+
iconName="transactionFile"
67+
title={t('account.transactions.placeholder.title')}
68+
description={t('account.transactions.placeholder.description')}
69+
/>
70+
);
71+
}
72+
73+
return (
74+
<>
75+
<ul
76+
className={cn(
77+
'flex flex-col w-full items-center justify-evenly space-y-6 md:bg-cards md:py-4 md:rounded-xl md:space-y-4',
78+
className,
79+
)}
80+
>
81+
{Object.keys(transactionsGroupedByDate).map(day => (
82+
<li className="flex flex-col w-full space-y-3">
83+
<span className="text-base font-semibold md:px-6">{day}</span>
84+
{transactionsGroupedByDate[day].map(transactionData => (
85+
<TransactionRow transactionData={transactionData} />
86+
))}
87+
</li>
88+
))}
89+
</ul>
90+
<Pagination
91+
initialPageIndex={INITIAL_PAGE_INDEX}
92+
itemsCount={transactionsCount}
93+
itemsPerPageCount={ITEMS_PER_PAGE_COUNT}
94+
onChange={onPageChange}
95+
/>
96+
</>
97+
);
98+
};

0 commit comments

Comments
 (0)