diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index c0312311660..2df446288a6 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -45,7 +45,6 @@ "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.2", - "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-tooltip": "1.1.7", "@sentry/nextjs": "8.51.0", "@shazow/whatsabi": "^0.19.0", diff --git a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchNFTs.ts b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchNFTs.ts index dad536c6c14..3419dc730c0 100644 --- a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchNFTs.ts +++ b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchNFTs.ts @@ -84,7 +84,7 @@ interface Collection { telegram_url: string | null; marketplace_pages: MarketplacePage[]; floor_prices: FloorPrice[]; - top_bids: any[]; + top_bids: unknown[]; distinct_owner_count: number; distinct_nft_count: number; total_quantity: number; @@ -148,7 +148,7 @@ interface NFT { queried_wallet_balances: Owner[]; } -export interface SimpleHashResponse { +interface SimpleHashResponse { next_cursor: null | string; next: null | string; previous: null | string; diff --git a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchTxActivity.ts b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchTxActivity.ts new file mode 100644 index 00000000000..7ca2fb23405 --- /dev/null +++ b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchTxActivity.ts @@ -0,0 +1,86 @@ +import { DASHBOARD_THIRDWEB_CLIENT_ID } from "@/constants/env"; + +interface Transaction { + chain_id: number; + hash: string; + nonce: number; + block_hash: string; + block_number: number; + block_timestamp: number; + transaction_index: number; + from_address: string; + to_address: string | null; + value: number; + gas: number; + gas_price: number | null; + data: string | null; + function_selector: string | null; + max_fee_per_gas: number | null; + max_priority_fee_per_gas: number | null; + transaction_type: number | null; + r: string | null; + s: string | null; + v: number | null; + access_list_json: string | null; + contract_address: string | null; + gas_used: number | null; + cumulative_gas_used: number | null; + effective_gas_price: number | null; + blob_gas_used: number | null; + blob_gas_price: number | null; + logs_bloom: string | null; + status: boolean | null; // true for success, false for failure +} + +interface InsightsResponse { + meta: { + address: string; + signature: string; + page: number; + total_items: number; + total_pages: number; + limit_per_chain: number; + chain_ids: number[]; + }; + data: Transaction[]; +} + +export async function fetchTxActivity(args: { + chainId: number; + address: string; + limit_per_type?: number; + page?: number; +}): Promise { + let { chainId, address, limit_per_type, page } = args; + if (!limit_per_type) limit_per_type = 100; + if (!page) page = 0; + + const outgoingTxsResponse = await fetch( + `https://insight.thirdweb-dev.com/v1/transactions?chain=${chainId}&filter_from_address=${address}&page=${page}&limit=${limit_per_type}&sort_by=block_number&sort_order=desc`, + { + headers: { + "x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID, + }, + }, + ); + + const incomingTxsResponse = await fetch( + `https://insight.thirdweb-dev.com/v1/transactions?chain=${chainId}&filter_to_address=${address}&page=${page}&limit=${limit_per_type}&sort_by=block_number&sort_order=desc`, + { + headers: { + "x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID, + }, + }, + ); + + if (!outgoingTxsResponse.ok || !incomingTxsResponse.ok) { + throw new Error("Failed to fetch transaction history"); + } + + const outgoingTxsData: InsightsResponse = await outgoingTxsResponse.json(); + const incomingTxsData: InsightsResponse = await incomingTxsResponse.json(); + + return [...outgoingTxsData.data, ...incomingTxsData.data].sort( + (a, b) => b.block_number - a.block_number, + ); +} diff --git a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/ActivityOverview.tsx b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/ActivityOverview.tsx index 3dd09f83092..9d9cddb059f 100644 --- a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/ActivityOverview.tsx +++ b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/ActivityOverview.tsx @@ -9,12 +9,11 @@ import { TableRow, } from "@/components/ui/table"; import { TabButtons } from "@/components/ui/tabs"; -import {} from "@radix-ui/react-tabs"; import { useState } from "react"; interface Transaction { - id: number; - type: string; + id: string; + type: "out" | "in"; amount: string; to?: string; from?: string; @@ -43,6 +42,17 @@ export function ActivityOverview({ const [activeTab, setActiveTab] = useState<"transactions" | "contracts">( "transactions", ); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + + // Calculate the index of the last transaction on the current page + const lastIndex = currentPage * itemsPerPage; + // Calculate the index of the first transaction on the current page + const firstIndex = lastIndex - itemsPerPage; + // Get the current transactions to display + const currentTransactions = transactions.slice(firstIndex, lastIndex); + // Calculate total pages + const totalPages = Math.ceil(transactions.length / itemsPerPage); return ( @@ -71,31 +81,62 @@ export function ActivityOverview({ {isLoading ? ( ) : activeTab === "transactions" ? ( - - - - Type - Amount - Details - Date - - - - {transactions.map((tx) => ( - - {tx.type} - {tx.amount} - - {tx.to && `To: ${tx.to}`} - {tx.from && `From: ${tx.from}`} - {tx.contract && `Contract: ${tx.contract}`} - {tx.method && ` Method: ${tx.method}`} - - {tx.date} + <> +
+ + + Type + Amount + Details + Date - ))} - -
+ + + {currentTransactions.map((tx) => ( + + {tx.type} + {tx.amount} + + {tx.to && `To: ${tx.to} `} + {tx.from && `From: ${tx.from} `} + {tx.contract && `Contract: ${tx.contract} `} + {tx.method && ` Method: ${tx.method}`} + + {tx.date} + + ))} + + + + {/* Pagination Controls */} +
+ 1, + onClick: () => + setCurrentPage((prev) => Math.max(prev - 1, 1)), + }, + { + name: `Page ${currentPage} of ${totalPages}`, + isActive: true, + isEnabled: false, + onClick: () => {}, // No action needed + }, + { + name: "Next", + isActive: currentPage === totalPages, + isEnabled: currentPage < totalPages, + onClick: () => + setCurrentPage((prev) => Math.min(prev + 1, totalPages)), + }, + ]} + tabClassName="font-medium !text-sm" + /> +
+ ) : activeTab === "contracts" ? ( @@ -107,7 +148,7 @@ export function ActivityOverview({ {contracts.map((contract, index) => ( - + {contract.name} {contract.address} {contract.lastInteraction} diff --git a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/TokenHoldings.tsx b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/TokenHoldings.tsx index eb1e4424164..f5f7b8a1497 100644 --- a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/TokenHoldings.tsx +++ b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/TokenHoldings.tsx @@ -9,7 +9,6 @@ import { TableRow, } from "@/components/ui/table"; import { TabButtons } from "@/components/ui/tabs"; -import {} from "@radix-ui/react-tabs"; import { useState } from "react"; import { toTokens } from "thirdweb"; import type { ChainMetadata } from "thirdweb/chains"; @@ -72,7 +71,7 @@ export function TokenHoldings({ ) : ( tokens.map((token, idx) => ( - + {token.symbol} ({token.name}) @@ -90,7 +89,11 @@ export function TokenHoldings({ ) : activeTab === "nft" ? (
{nfts.map((nft, idx) => ( - + ))}
) : null} diff --git a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/WalletDashboard.tsx b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/WalletDashboard.tsx index 6656bb44bde..8a9a4077374 100644 --- a/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/WalletDashboard.tsx +++ b/apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/WalletDashboard.tsx @@ -3,6 +3,7 @@ import type { ChainMetadata } from "thirdweb/chains"; import { useBalance } from "../hooks/getBalance"; import { useGetERC20Tokens } from "../hooks/useGetERC20Tokens"; import { useGetNFTs } from "../hooks/useGetNFTs"; +import { useGetTxActivity } from "../hooks/useGetTxActivity"; import { mockWalletData } from "../utils/mockData"; import { ActivityOverview } from "./ActivityOverview"; import { BalanceOverview } from "./BalanceOverview"; @@ -23,19 +24,24 @@ export function WalletDashboard(props: { const { tokens, isLoading: isLoadingERC20, - error: errorERC20, + // error: errorERC20, } = useGetERC20Tokens(props.chain.chainId, props.address); - if (errorERC20) { - console.error("Error fetching ERC20 tokens:", errorERC20); - } + // if (errorERC20) { + // console.error("Error fetching ERC20 tokens:", errorERC20); + // } const { nfts, isLoading: isLoadingNFTs, - error: errorNFTs, + // error: errorNFTs, } = useGetNFTs(props.chain.chainId, props.address); - if (errorNFTs) { - console.error("Error fetching NFTs:", errorNFTs); - } + // if (errorNFTs) { + // console.error("Error fetching NFTs:", errorNFTs); + // } + + const { txActivity, isLoading: isLoadingActivity } = useGetTxActivity( + props.chain.chainId, + props.address, + ); return (
@@ -45,7 +51,8 @@ export function WalletDashboard(props: { isLoading={isLoadingBalance} /> ([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + (async () => { + const response = await fetchTxActivity({ chainId, address }); + const activity = response.map((tx): TxActivityItem => { + const type = + tx.to_address?.toLowerCase() === address.toLowerCase() ? "in" : "out"; + return { + id: tx.hash, + type, + amount: `${tx.value / 10 ** 18} ETH`, + to: tx.to_address || undefined, + from: tx.from_address, + method: tx.function_selector || undefined, + date: new Date(tx.block_timestamp * 1000).toLocaleString(), + }; + }); + setTxActivity(activity); + setIsLoading(false); + })(); + }, [address, chainId]); + + return { txActivity, isLoading }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29622ff510f..f8a42a98d61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,9 +130,6 @@ importers: '@radix-ui/react-switch': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-tabs': - specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-tooltip': specifier: 1.1.7 version: 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -213,7 +210,7 @@ importers: version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nextjs-toploader: specifier: ^1.6.12 - version: 1.6.12(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.6.12(next@15.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) openapi-types: specifier: ^12.1.3 version: 12.1.3 @@ -400,7 +397,7 @@ importers: version: 5.43.5(@types/node@22.10.10)(typescript@5.7.3) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 4.2.3(next@15.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) postcss: specifier: 8.5.1 version: 8.5.1 @@ -25944,8 +25941,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.4(eslint@8.57.0) eslint-plugin-react-hooks: 5.1.0(eslint@8.57.0) @@ -25964,7 +25961,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) @@ -25976,7 +25973,7 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -26001,18 +25998,18 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -26023,7 +26020,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -29779,14 +29776,6 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - next-sitemap@4.2.3(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): - dependencies: - '@corex/deepmerge': 4.0.43 - '@next/env': 13.5.8 - fast-glob: 3.3.3 - minimist: 1.2.8 - next: 15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - next-sitemap@4.2.3(next@15.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: '@corex/deepmerge': 4.0.43 @@ -29827,14 +29816,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextjs-toploader@1.6.12(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - next: 15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - nprogress: 0.2.0 - prop-types: 15.8.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - nextjs-toploader@1.6.12(next@15.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: next: 15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)