Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NFT Transfers Page #70

Merged
merged 6 commits into from
Aug 10, 2024
Merged
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
13 changes: 12 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
dangerouslyAllowSVG: true,
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
},
};

export default nextConfig;
21 changes: 17 additions & 4 deletions src/app/address/[address]/nft-transfers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { Address } from "viem";
import AddressNftTransfers from "@/components/pages/address/address-nft-transfers";
import { getLatestNftTransferEvents } from "@/lib/fetch-data";

const AddressNftTransfersPage = ({
const AddressNftTransfersPage = async ({
params: { address },
}: {
params: { address: string };
}) => <AddressNftTransfers address={address as Address} />;

params: { address: Address };
}) => {
let nftTransferEvents;
try {
nftTransferEvents = await getLatestNftTransferEvents(address);
} catch (error) {
console.error("Error Transfer:", error);
}
return (
<AddressNftTransfers
address={address as Address}
nftTokenTransfers={nftTransferEvents}
/>
);
};
export default AddressNftTransfersPage;
43 changes: 18 additions & 25 deletions src/components/lib/address-link.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import Link from "next/link";
import { SquareArrowOutUpRight } from "lucide-react";
import { cn } from "@/lib/utils";
Expand All @@ -21,45 +20,39 @@ const AddressLink = ({
address,
href,
isExternal = false,
className
className,
}: AddressLinkProps) => {
const {
state: { hoveredAddress },
setHoveredAddress,
} = useGlobalContext();


return (
<TooltipProvider>
<Tooltip
delayDuration={100}
onOpenChange={(open) => setHoveredAddress((open && address) ? address : "")}
onOpenChange={(open) =>
setHoveredAddress(open && address ? address : "")
}
>
<TooltipTrigger>
<Link
href={href}
className={cn("flex items-center", className)}
<Link href={href} className={cn("flex items-center", className)}>
<div
className={cn(
"w-28 rounded-md border px-2 py-1 text-xs transition-colors hover:border-dashed hover:border-yellow-500 hover:bg-yellow-500/15",
{
"border-dashed border-yellow-500 bg-yellow-500/15":
hoveredAddress === address,
},
)}
>
<div
className={cn(
"w-28 rounded-md border px-2 py-1 text-xs transition-colors hover:border-dashed hover:border-yellow-500 hover:bg-yellow-500/15",
{
"border-dashed border-yellow-500 bg-yellow-500/15":
hoveredAddress === address,
},
)}
>
{address?.slice(0,10).concat("...")}
</div>
{isExternal && (
<SquareArrowOutUpRight className="ml-1 size-4" />
)}
</Link>
{address?.slice(0, 10).concat("...")}
</div>
{isExternal && <SquareArrowOutUpRight className="ml-1 size-4" />}
</Link>
</TooltipTrigger>
<TooltipContent>
<p>
{address}
</p>
<p>{address}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
Expand Down
10 changes: 5 additions & 5 deletions src/components/lib/context/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ const useGlobalContext = () => {
payload: { selector },
});
const setHoveredAddress = (address: string): void =>
dispatch({
type: "SET_HOVERED_ADDRESS",
payload: { address },
});
dispatch({
type: "SET_HOVERED_ADDRESS",
payload: { address },
});
return {
state,
toggleTimestampFormattedAsDate,
toggleTxGasPriceShown,
setHoveredSelector,
setHoveredAddress
setHoveredAddress,
};
};

Expand Down
5 changes: 3 additions & 2 deletions src/components/lib/context/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ export const defaultState = (): State => ({
timestampFormattedAsDate: false,
txGasPriceShown: false,
hoveredSelector: "",
hoveredAddress: ""
hoveredAddress: "",
});

export type Action =
| {
type: "TOGGLE_TIMESTAMP_FORMATTED_AS_DATE";
}
| { type: "TOGGLE_TX_GAS_PRICE_SHOWN" }
| { type: "SET_HOVERED_SELECTOR"; payload: { selector: string } } | { type: "SET_HOVERED_ADDRESS"; payload: { address: string } };
| { type: "SET_HOVERED_SELECTOR"; payload: { selector: string } }
| { type: "SET_HOVERED_ADDRESS"; payload: { address: string } };

export const reducer = (state: State, action: Action): State => {
switch (action.type) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/lib/tx-method-badge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"use client"
"use client";
import { startCase } from "lodash";
import { cn } from "@/lib/utils";
import {
Expand Down
126 changes: 66 additions & 60 deletions src/components/pages/address/address-contract-events-table.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,77 @@
import Link from "next/link";

import { DecodedLogDisplay } from "./decodedLog-display";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
import {
Table,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from "@/components/ui/table";

import { FormattedLog } from "@/interfaces";
import { formatTimestamp } from "@/lib/utils";
import TxMethodBadge from "@/components/lib/tx-method-badge";

interface Props{
decodedLogs: FormattedLog[]
interface Props {
decodedLogs: FormattedLog[];
}

export const ContractEventsTable = ({ decodedLogs }: Props) => {
return(
<section className="flex flex-1 justify-center items-center flex-col gap-4 p-4 md:gap-4 md:p-4">
{
decodedLogs.length !== 0
? (
<Table className="table-auto">
<TableHeader>
<TableRow>
<TableHead>Transaction Hash</TableHead>
<TableHead>Block</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Method</TableHead>
<TableHead>Decoded logs</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{decodedLogs.map((item, index) => (
<TableRow key={index}>
<TableCell className="max-w-40 relative truncate align-top text-primary hover:brightness-150">
<Link
href={`/tx/${item.transactionHash}`}
className="text-sm font-medium leading-none"
>
{item.transactionHash}
</Link>
</TableCell>
<TableCell className="max-w-28 truncate text-primary align-top hover:brightness-150">
<Link
href={`/block/${item.blockNumber}`}
className="text-sm font-medium leading-none"
>
{item.blockNumber.toString()}
</Link>
</TableCell>
<TableCell className="align-top">
<div>
{`${formatTimestamp(item.timestamp).distance}`}
</div>
</TableCell>
<TableCell className="align-top">
<TxMethodBadge selector={item.method} signature={item.eventName} />
</TableCell>
<TableCell className="align-top">
<DecodedLogDisplay args={ item.args } />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
: (
<span className="text-xl font-semibold">Oops! No events found. Please reload the page</span>
)
}
</section>
)
}
return (
<section className="flex flex-1 flex-col items-center justify-center gap-4 p-4 md:gap-4 md:p-4">
{decodedLogs.length !== 0 ? (
<Table className="table-auto">
<TableHeader>
<TableRow>
<TableHead>Transaction Hash</TableHead>
<TableHead>Block</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Method</TableHead>
<TableHead>Decoded logs</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{decodedLogs.map((item, index) => (
<TableRow key={index}>
<TableCell className="relative max-w-40 truncate align-top text-primary hover:brightness-150">
<Link
href={`/tx/${item.transactionHash}`}
className="text-sm font-medium leading-none"
>
{item.transactionHash}
</Link>
</TableCell>
<TableCell className="max-w-28 truncate align-top text-primary hover:brightness-150">
<Link
href={`/block/${item.blockNumber}`}
className="text-sm font-medium leading-none"
>
{item.blockNumber.toString()}
</Link>
</TableCell>
<TableCell className="align-top">
<div>{`${formatTimestamp(item.timestamp).distance}`}</div>
</TableCell>
<TableCell className="align-top">
<TxMethodBadge
selector={item.method}
signature={item.eventName}
/>
</TableCell>
<TableCell className="align-top">
<DecodedLogDisplay args={item.args} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<span className="text-xl font-semibold">
Oops! No events found. Please reload the page
</span>
)}
</section>
);
};
44 changes: 28 additions & 16 deletions src/components/pages/address/address-events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,35 @@ const AddressEvents = async ({ address }: { address: Address }) => {
const fromBlock: bigint = latestBlockNumber - BigInt(10);
const [bytecode, logs] = await Promise.all([
l2PublicClient.getCode({ address }) as Promise<string>,
l2PublicClient.getLogs({ address, fromBlock, toBlock: 'latest' }),
l2PublicClient.getLogs({ address, fromBlock, toBlock: "latest" }),
]);

const abi = abiFromBytecode(bytecode) as ABIEventExtended[];

const blockTimestamps = await Promise.all(logs.map(async log => {
const block: Block = await l2PublicClient.getBlock({ blockNumber: log.blockNumber });
return block.timestamp;
}));
const blockTimestamps = await Promise.all(
logs.map(async (log) => {
const block: Block = await l2PublicClient.getBlock({
blockNumber: log.blockNumber,
});
return block.timestamp;
}),
);

const decodedLogs: FormattedLog[] = await Promise.all(logs.map(async (log, index) => {
const formattedLog = await formatEventLog(log, abi);
return {
...formattedLog,
transactionHash: log.transactionHash,
blockNumber: log.blockNumber,
timestamp: blockTimestamps[index],
};
}));
const decodedLogs: FormattedLog[] = await Promise.all(
logs.map(async (log, index) => {
const formattedLog = await formatEventLog(log, abi);
return {
...formattedLog,
transactionHash: log.transactionHash,
blockNumber: log.blockNumber,
timestamp: blockTimestamps[index],
};
}),
);

decodedLogs.sort((a, b) => Number(BigInt(b.timestamp) - BigInt(a.timestamp)));
decodedLogs.sort((a, b) =>
Number(BigInt(b.timestamp) - BigInt(a.timestamp)),
);

return (
<Card>
Expand All @@ -45,7 +53,11 @@ const AddressEvents = async ({ address }: { address: Address }) => {
);
} catch (error) {
console.error("Error fetching contract details:", error);
return <div className="w-full flex justify-center items-center">Error loading contract details. Please try again later.</div>;
return (
<div className="flex w-full items-center justify-center">
Error loading contract details. Please try again later.
</div>
);
}
};

Expand Down
Loading