Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/website/public/_redirects
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
/packages/:name/:tag/:variant/cannonfile /packages/[name]/[tag]/[variant]/cannonfile.html 200
/deploy/txn/:chainId/:safeAddress/:nonce/:sigHash /deploy/txn/[chainId]/[safeAddress]/[nonce]/[sigHash].html 200
/address/:chainId/:address /address/[chainId]/[address].html 200
/tx/:chainId/:txHash /tx/[chainId]/[txHash].html 200
/tx/:chainId/:txHash /tx/[chainId]/[txHash].html 200
/txs /txs.html 200
1 change: 1 addition & 0 deletions packages/website/src/constants/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_PAGE_SIZE = 100;
27 changes: 25 additions & 2 deletions packages/website/src/features/Address/AddressDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { flexRender } from '@tanstack/react-table';
import { Inbox } from 'lucide-react';
import { Inbox, MoveRight } from 'lucide-react';
import Link from 'next/link';

type AddressDataTableProps = {
table: any;
url?: string;
};

const AddressDataTable: React.FC<AddressDataTableProps> = ({ table }) => {
const AddressDataTable: React.FC<AddressDataTableProps> = ({
table,
url = '',
}) => {
return (
<>
<Table>
Expand Down Expand Up @@ -87,6 +93,23 @@ const AddressDataTable: React.FC<AddressDataTableProps> = ({ table }) => {
</TableRow>
)}
</TableBody>
{url && (
<TableFooter>
<TableRow>
<TableCell colSpan={table.getAllColumns().length}>
<div className="flex items-center justify-center py-3">
<Link
href={url}
className="flex text-sm font-mono border-b border-dotted border-muted-foreground hover:border-solid"
>
<span>VIEW ALL TRANSACTIONS</span>
<MoveRight className="h-4 w-4 ml-3" />
</Link>
</div>
</TableCell>
</TableRow>
</TableFooter>
)}
</Table>
</>
);
Expand Down
8 changes: 6 additions & 2 deletions packages/website/src/features/Address/AddressPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ const AddressPage = () => {
const { getChainById } = useCannonChains();
const chain = getChainById(Number(chainId));
const displayAddress = Array.isArray(address) ? address[0] : address;

const {
data: transactionData,
isLoading,
isError,
} = useAddressTransactions(
parseInt(chainId as string) || 0,
displayAddress ?? ''
displayAddress ?? '',
'0',
true
);

if (isLoading) {
Expand All @@ -54,7 +57,7 @@ const AddressPage = () => {
);
}

const { txs, receipts, oldReceipts } = transactionData;
const { txs, receipts, oldReceipts, isLastPage } = transactionData;

const renderContent = () => {
if (displayAddress) {
Expand All @@ -70,6 +73,7 @@ const AddressPage = () => {
chain={chain}
txs={txs}
receipts={receipts}
isLastPage={isLastPage}
/>
);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/website/src/features/Address/AddressTxLists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ type AddressListsProps = {
chain: Chain;
txs: OtterscanTransaction[];
receipts: OtterscanReceipt[];
isLastPage: boolean;
};

const AddressLists: React.FC<AddressListsProps> = ({
address,
chain,
txs,
receipts,
isLastPage,
}) => {
const [isDate, setIsDate] = useState<boolean>(false);
const [isGasPrice, setIsGasPrice] = useState<boolean>(false);
Expand All @@ -52,6 +54,7 @@ const AddressLists: React.FC<AddressListsProps> = ({
const columnHelper = createColumnHelper<TransactionRow>();
const [openToolTipIndex, setOpenTooltipIndex] = useState<number | null>();

const url = isLastPage ? '' : `/txs?a=${address}&c=${chain?.id}`;
const columns = [
columnHelper.accessor('detail', {
cell: (info: any) => (
Expand Down Expand Up @@ -164,7 +167,7 @@ const AddressLists: React.FC<AddressListsProps> = ({
</CardHeader>
<CardContent>
<div className="w-full rounded-md border border-border overflow-x-auto">
<AddressDataTable table={table} />
<AddressDataTable table={table} url={url} />
</div>
</CardContent>
</Card>
Expand Down
42 changes: 42 additions & 0 deletions packages/website/src/features/Txs/AddressTransactionPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { useRouter } from 'next/router';
import { useCannonChains } from '@/providers/CannonProvidersProvider';
import TransactionsSection from '@/features/Txs/TransactionsSection';
import Link from 'next/link';

const AddressTransactionPage = () => {
const router = useRouter();
const { a, c, p } = router.query;
const chainId = Array.isArray(c) ? c[0] : c;
const { getChainById, getExplorerUrl } = useCannonChains();
const chain = getChainById(Number(chainId));
const displayAddress = Array.isArray(a) ? a[0] : a;

const pageIndex = Array.isArray(p) ? p[0] : p;
const explorerUrl = getExplorerUrl(chain?.id || 0, displayAddress ?? '');

return (
<div className="w-full max-w-screen-xl mx-auto px-4">
<div className="ml-3">
<h1 className="text-2xl font-bold mt-4">Transactions</h1>
</div>
<div className="ml-3 flex items-baseline space-x-2">
<span>For</span>
<Link
href={explorerUrl}
className="flex text-sm font-mono border-b border-dotted border-muted-foreground hover:border-solid"
>
{displayAddress}
</Link>
</div>
<hr className="opacity-75 mt-3" />
<TransactionsSection
address={displayAddress ?? ''}
chain={chain}
pageIndex={pageIndex}
/>
</div>
);
};

export default AddressTransactionPage;
207 changes: 207 additions & 0 deletions packages/website/src/features/Txs/TransactionsPaginatedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import React, { useState } from 'react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { CircleHelp } from 'lucide-react';
import {
createColumnHelper,
useReactTable,
getCoreRowModel,
} from '@tanstack/react-table';
import AddressAdditionalInfo from '@/features/Address/AddressAdditionalDialog';
import { Chain } from '@/types/Chain';
import { mapToTransactionList } from '@/lib/address';
import {
TransactionRow,
OtterscanTransaction,
OtterscanReceipt,
} from '@/types/AddressList';
import AmountColumn from '@/features/Address/column/AmountColumn';
import FromColumn from '@/features/Address/column/FromColumn';
import ToColumn from '@/features/Address/column/ToColumn';
import HashColumn from '@/features/Address/column/HashColumn';
import MethodColumn from '@/features/Address/column/MethodColumn';
import MethodHeader from '@/features/Address/column/MethodHeader';
import AgeColumn from '@/features/Address/column/AgeColumn';
import AgeHeader from '@/features/Address/column/AgeHeader';
import TxFeeHeader from '@/features/Address/column/TxFeeHeader';
import TxFeeColumn from '@/features/Address/column/TxFeeColumn';
import BlockColumn from '@/features/Address/column/BlockColumn';
import AddressDataTable from '@/features/Address/AddressDataTable';
import DownloadListButton from '@/features/Address/DownloadListButton';
import TransactionsPagination from '@/features/Txs/TransactionsPagination';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { MAX_PAGE_SIZE } from '@/constants/pagination';

type TransactionsPaginatedListProps = {
address: string;
chain: Chain;
txs: OtterscanTransaction[];
receipts: OtterscanReceipt[];
isLastPage: boolean;
isFirstPage: boolean;
blockNumber: string;
pages: string[];
totalTxs: number;
};

const TransactionsPaginatedList: React.FC<TransactionsPaginatedListProps> = ({
address,
chain,
txs,
receipts,
isLastPage,
isFirstPage,
blockNumber,
pages,
totalTxs,
}) => {
const [isDate, setIsDate] = useState<boolean>(false);
const [isGasPrice, setIsGasPrice] = useState<boolean>(false);
const [hoverId, setHoverId] = useState<string>('');
const chainId = chain?.id ?? 0;
const data = React.useMemo(() => {
return mapToTransactionList(txs, receipts);
}, [txs, receipts]);

const columnHelper = createColumnHelper<TransactionRow>();
const [openToolTipIndex, setOpenTooltipIndex] = useState<number | null>();

const columns = [
columnHelper.accessor('detail', {
cell: (info: any) => (
<AddressAdditionalInfo
rowIndex={info.row.index}
openToolTipIndex={openToolTipIndex!}
setOpenTooltipIndex={setOpenTooltipIndex}
chain={chain}
txHash={info.row.getValue('hash')}
method={info.row.getValue('method')}
/>
),
header: () => <CircleHelp className="h-4 w-4" />,
}),
columnHelper.accessor('hash', {
cell: (info: any) => <HashColumn info={info} chainId={chainId!} />,
header: 'Transaction Hash',
}),
columnHelper.accessor('method', {
cell: (info: any) => <MethodColumn info={info} />,
header: () => <MethodHeader />,
}),
columnHelper.accessor('blockNumber', {
cell: (info: any) => <BlockColumn info={info} />,
header: 'Block',
}),
columnHelper.accessor('age', {
cell: (info: any) => <AgeColumn info={info} isDate={isDate} />,
header: () => <AgeHeader isDate={isDate} setIsDate={setIsDate} />,
}),
columnHelper.accessor('from', {
cell: (info: any) => (
<FromColumn
info={info}
hoverId={hoverId}
setHoverId={setHoverId}
address={address}
chainId={chainId!}
/>
),
header: 'From',
}),
columnHelper.accessor('to', {
cell: (info: any) => (
<ToColumn
info={info}
hoverId={hoverId}
setHoverId={setHoverId}
address={address}
chainId={chainId!}
/>
),
header: 'To',
}),
columnHelper.accessor('amount', {
cell: (info: any) => (
<AmountColumn info={info} symbol={chain?.nativeCurrency.symbol!} />
),
header: 'Amount',
}),
columnHelper.accessor('txnFee', {
cell: (info: any) => <TxFeeColumn info={info} isGasPrice={isGasPrice} />,
header: () => (
<TxFeeHeader isGasPrice={isGasPrice} setIsGasPrice={setIsGasPrice} />
),
}),
columnHelper.accessor('gasPrice', {
enableHiding: true,
cell: () => null,
header: () => null,
}),
columnHelper.accessor('contractAddress', {
enableHiding: true,
cell: () => null,
header: () => null,
}),
];

const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
});

return (
<>
<Card className="rounded-sm w-full">
<CardHeader>
{table.getRowModel().rows.length > 0 && (
<>
<CardTitle>
<div className="flex sm:flex-row flex-col justify-between w-full sm:space-y-0 space-y-2">
<div className="flex flex-wrap items-center space-x-1">
<span className="text-sm">
A total of {totalTxs.toLocaleString()} transactions found
</span>
</div>
<div className="flex items-center gap-2">
<DownloadListButton
txs={txs}
receipts={receipts}
chain={chain}
fileName={`export-${address}.csv`}
/>
{pages.length > 0 && (
<TransactionsPagination
address={address}
chainId={chainId}
isLastPage={isLastPage}
isFirstPage={isFirstPage}
blockNumber={blockNumber}
pages={pages}
/>
)}
</div>
</div>
{!isLastPage &&
pages.indexOf(blockNumber) + 1 === MAX_PAGE_SIZE && (
<Alert variant="info" className="mt-2">
<AlertDescription>
This is the maximum number of pages currently supported
by this website.
</AlertDescription>
</Alert>
)}
</CardTitle>
</>
)}
</CardHeader>
<CardContent>
<div className="w-full rounded-md border border-border overflow-x-auto">
<AddressDataTable table={table} />
</div>
</CardContent>
</Card>
</>
);
};

export default TransactionsPaginatedList;
Loading
Loading