Skip to content

Commit

Permalink
feat: better history (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
fmorency authored Dec 2, 2024
1 parent 4c2b671 commit 20e0804
Show file tree
Hide file tree
Showing 9 changed files with 422 additions and 141 deletions.
23 changes: 7 additions & 16 deletions components/bank/components/__tests__/historyBox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mock.module('@/hooks', () => ({
],
},
}),
useSendTxIncludingAddressQuery: () => ({
useGetFilteredTxAndSuccessfulProposals: () => ({
sendTxs: mockTransactions,
totalPages: 1,
isLoading: false,
Expand All @@ -31,19 +31,16 @@ mock.module('@/hooks', () => ({
describe('HistoryBox', () => {
afterEach(() => {
cleanup();
mock.restore();
});

test('renders correctly', () => {
renderWithChainProvider(
<HistoryBox isLoading={false} send={mockTransactions} address="address1" />
);
renderWithChainProvider(<HistoryBox isLoading={false} address="address1" />);
expect(screen.getByText('Transaction History')).toBeTruthy();
});

test('displays transactions', () => {
renderWithChainProvider(
<HistoryBox isLoading={false} send={mockTransactions} address="address1" />
);
renderWithChainProvider(<HistoryBox isLoading={false} address="address1" />);

const sentText = screen.getByText('Sent');
const receivedText = screen.getByText('Received');
Expand All @@ -53,9 +50,7 @@ describe('HistoryBox', () => {
});

test('opens modal when clicking on a transaction', () => {
renderWithChainProvider(
<HistoryBox isLoading={false} send={mockTransactions} address="address1" />
);
renderWithChainProvider(<HistoryBox isLoading={false} address="address1" />);

const transactionElement = screen.getByText('Sent').closest('div[role="button"]');

Expand All @@ -66,9 +61,7 @@ describe('HistoryBox', () => {
});

test('formats amount correctly', () => {
renderWithChainProvider(
<HistoryBox isLoading={false} send={mockTransactions} address="address1" />
);
renderWithChainProvider(<HistoryBox isLoading={false} address="address1" />);

const sentAmount = screen.queryByText('-1 TOKEN');
const receivedAmount = screen.queryByText('+2 TOKEN');
Expand All @@ -78,9 +71,7 @@ describe('HistoryBox', () => {
});

test('displays both sent and received transactions', () => {
renderWithChainProvider(
<HistoryBox isLoading={false} send={mockTransactions} address="address1" />
);
renderWithChainProvider(<HistoryBox isLoading={false} address="address1" />);

const sentText = screen.getByText('Sent');
const receivedText = screen.getByText('Received');
Expand Down
121 changes: 97 additions & 24 deletions components/bank/components/historyBox.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React, { useState, useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
import TxInfoModal from '../modals/txInfo';
import { shiftDigits, truncateString } from '@/utils';
import { formatDenom } from '@/components';
import { useTokenFactoryDenomsMetadata } from '@/hooks';
import { SendIcon, ReceiveIcon } from '@/components/icons';

import { DenomImage } from '@/components';
import { useSendTxIncludingAddressQuery } from '@/hooks';
import { BurnIcon, DenomImage, formatDenom, MintIcon } from '@/components';
import {
HistoryTxType,
useGetFilteredTxAndSuccessfulProposals,
useTokenFactoryDenomsMetadata,
} from '@/hooks';
import { ReceiveIcon, SendIcon } from '@/components/icons';
import { useEndpointStore } from '@/store/endpointStore';

interface Transaction {
tx_type: HistoryTxType;
from_address: string;
to_address: string;
amount: Array<{ amount: string; denom: string }>;
Expand Down Expand Up @@ -45,23 +48,24 @@ function formatLargeNumber(num: number): string {

export function HistoryBox({
isLoading: initialLoading,
send,
address,
}: {
isLoading: boolean;
send: TransactionGroup[];
address: string;
}) {
const [selectedTx, setSelectedTx] = useState<TransactionGroup | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;

const { selectedEndpoint } = useEndpointStore();
const indexerUrl = selectedEndpoint?.indexer || '';

const {
sendTxs,
totalPages,
isLoading: txLoading,
isError,
} = useSendTxIncludingAddressQuery(address, undefined, currentPage, pageSize);
} = useGetFilteredTxAndSuccessfulProposals(indexerUrl, address, currentPage, pageSize);

const isLoading = initialLoading || txLoading;

Expand Down Expand Up @@ -91,6 +95,71 @@ export function HistoryBox({
return groups;
}, [sendTxs]);

function getTransactionIcon(tx: TransactionGroup, address: string) {
if (tx.data.tx_type === HistoryTxType.SEND) {
return tx.data.from_address === address ? <SendIcon /> : <ReceiveIcon />;
} else if (tx.data.tx_type === HistoryTxType.MINT || tx.data.tx_type === HistoryTxType.PAYOUT) {
return (
<MintIcon
className={`w-6 h-6 p-1 border-[#00FFAA] border-opacity-[0.12] border-[1.5px] bg-[#00FFAA] bg-opacity-[0.06] rounded-sm text-green-500`}
/>
);
} else if (
tx.data.tx_type === HistoryTxType.BURN ||
tx.data.tx_type === HistoryTxType.BURN_HELD_BALANCE
) {
return (
<BurnIcon className="w-6 h-6 p-1 border-[#F54562] border-[1.5px] border-opacity-[0.12] bg-[#f54562] bg-opacity-[0.06] rounded-sm text-red-500" />
);
}
return null;
}

// Get the history message based on the transaction type
function getTransactionMessage(tx: TransactionGroup, address: string) {
if (tx.data.tx_type === HistoryTxType.SEND) {
return tx.data.from_address === address ? 'Sent' : 'Received';
} else if (tx.data.tx_type === HistoryTxType.MINT || tx.data.tx_type === HistoryTxType.PAYOUT) {
return 'Minted';
} else if (
tx.data.tx_type === HistoryTxType.BURN ||
tx.data.tx_type === HistoryTxType.BURN_HELD_BALANCE
) {
return 'Burned';
}
return 'Unsupported';
}

// Get the transaction direction based on the transaction type
function getTransactionPlusMinus(tx: TransactionGroup, address: string) {
if (tx.data.tx_type === HistoryTxType.SEND) {
return tx.data.from_address === address ? '-' : '+';
} else if (tx.data.tx_type === HistoryTxType.MINT || tx.data.tx_type === HistoryTxType.PAYOUT) {
return '+';
} else if (
tx.data.tx_type === HistoryTxType.BURN ||
tx.data.tx_type === HistoryTxType.BURN_HELD_BALANCE
) {
return '-';
}
return '!!';
}

// Get the transaction color based on the transaction type and direction
function getTransactionColor(tx: TransactionGroup, address: string) {
if (tx.data.tx_type === HistoryTxType.SEND) {
return tx.data.from_address === address ? 'text-red-500' : 'text-green-500';
} else if (tx.data.tx_type === HistoryTxType.MINT || tx.data.tx_type === HistoryTxType.PAYOUT) {
return 'text-green-500';
} else if (
tx.data.tx_type === HistoryTxType.BURN ||
tx.data.tx_type === HistoryTxType.BURN_HELD_BALANCE
) {
return 'text-red-500';
}
return null;
}

return (
<div className="w-full mx-auto rounded-[24px] h-full flex flex-col">
<div className="flex items-center justify-between mb-4">
Expand Down Expand Up @@ -206,7 +275,7 @@ export function HistoryBox({
>
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-full overflow-hidden flex items-center justify-center">
{tx.data.from_address === address ? <SendIcon /> : <ReceiveIcon />}
{getTransactionIcon(tx, address)}
</div>
<div className="w-10 h-10 rounded-full overflow-hidden bg-[#0000000A] dark:bg-[#FFFFFF0F] flex items-center justify-center">
{tx.data.amount.map((amt, index) => {
Expand All @@ -217,7 +286,7 @@ export function HistoryBox({
<div className="">
<div className="flex flex-row items-center gap-2">
<p className="font-semibold text-[#161616] dark:text-white">
{tx.data.from_address === address ? 'Sent' : 'Received'}
{getTransactionMessage(tx, address)}
</p>
<p className="font-semibold text-[#161616] dark:text-white">
{tx.data.amount.map((amt, index) => {
Expand All @@ -234,22 +303,26 @@ export function HistoryBox({
</p>
</div>
<div className="address-copy" onClick={e => e.stopPropagation()}>
<TruncatedAddressWithCopy
address={
tx.data.from_address === address
? tx.data.to_address
: tx.data.from_address
}
slice={6}
/>
{tx.data.from_address.startsWith('manifest1') ? (
<TruncatedAddressWithCopy
address={
tx.data.from_address === address
? tx.data.to_address
: tx.data.from_address
}
slice={6}
/>
) : (
<div className="text-[#00000099] dark:text-[#FFFFFF99]">
{tx.data.from_address}
</div>
)}
</div>
</div>
</div>
<div className="text-right">
<p
className={`font-semibold ${tx.data.from_address === address ? 'text-red-500' : 'text-green-500'}`}
>
{tx.data.from_address === address ? '-' : '+'}
<p className={`font-semibold ${getTransactionColor(tx, address)}`}>
{getTransactionPlusMinus(tx, address)}
{tx.data.amount
.map(amt => {
const metadata = metadatas?.metadatas.find(
Expand Down
41 changes: 35 additions & 6 deletions components/bank/modals/txInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
import { formatDenom, TransactionGroup } from '@/components';
import { FaExternalLinkAlt } from 'react-icons/fa';
import { shiftDigits } from '@/utils';
import { useEndpointStore } from '@/store/endpointStore';

interface TxInfoModalProps {
tx: TransactionGroup;
Expand All @@ -11,6 +12,9 @@ interface TxInfoModalProps {
}

export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
const { selectedEndpoint } = useEndpointStore();
const explorerUrl = selectedEndpoint?.explorer || '';

function formatDate(dateString: string): string {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
Expand Down Expand Up @@ -39,13 +43,36 @@ export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<InfoItem label="TRANSACTION HASH" value={tx?.tx_hash} isAddress={true} />
<InfoItem label="BLOCK" value={tx?.block_number?.toString()} />
<InfoItem label="TIMESTAMP" value={formatDate(tx?.formatted_date)} />
<InfoItem
label="TRANSACTION HASH"
explorerUrl={explorerUrl}
value={tx?.tx_hash}
isAddress={true}
/>
<InfoItem
label="BLOCK"
explorerUrl={explorerUrl}
value={tx?.block_number?.toString()}
/>
<InfoItem
label="TIMESTAMP"
explorerUrl={explorerUrl}
value={formatDate(tx?.formatted_date)}
/>
</div>
<div>
<InfoItem label="FROM" value={tx?.data?.from_address} isAddress={true} />
<InfoItem label="TO" value={tx?.data?.to_address} isAddress={true} />
<InfoItem
label="FROM"
explorerUrl={explorerUrl}
value={tx?.data?.from_address}
isAddress={true}
/>
<InfoItem
label="TO"
explorerUrl={explorerUrl}
value={tx?.data?.to_address}
isAddress={true}
/>
<div>
<p className="text-sm font-semibold text-[#00000099] dark:text-[#FFFFFF99] mb-2">
VALUE
Expand Down Expand Up @@ -74,10 +101,12 @@ export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
function InfoItem({
label,
value,
explorerUrl,
isAddress = false,
}: {
label: string;
value: string;
explorerUrl: string;
isAddress?: boolean;
}) {
return (
Expand All @@ -88,7 +117,7 @@ function InfoItem({
<div className="flex items-center">
<TruncatedAddressWithCopy address={value} slice={8} />
<a
href={`${process.env.NEXT_PUBLIC_TESTNET_EXPLORER_URL}/${label === 'TRANSACTION HASH' ? 'transaction' : 'account'}/${label?.includes('TRANSACTION') ? value?.toUpperCase() : value}`}
href={`${explorerUrl}/${label === 'TRANSACTION HASH' ? 'transaction' : 'account'}/${label?.includes('TRANSACTION') ? value?.toUpperCase() : value}`}
target="_blank"
rel="noopener noreferrer"
className="ml-2 text-primary hover:text-primary/50"
Expand Down
Loading

0 comments on commit 20e0804

Please sign in to comment.