From 63b5e3c5fb6ff82ecbaa31088c9959a87159ca4d Mon Sep 17 00:00:00 2001 From: TanmayDagur Date: Mon, 27 Jul 2026 16:38:33 +0530 Subject: [PATCH] feat(playground): add Token Price Tracker page with live market data --- .../src/app/data/pages-metadata.ts | 8 + apps/playground-web/src/app/navLinks.ts | 4 + .../src/app/tokens/price-tracker/page.tsx | 34 +++ .../token-price/token-price-tracker.tsx | 209 ++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 apps/playground-web/src/app/tokens/price-tracker/page.tsx create mode 100644 apps/playground-web/src/components/token-price/token-price-tracker.tsx diff --git a/apps/playground-web/src/app/data/pages-metadata.ts b/apps/playground-web/src/app/data/pages-metadata.ts index b3ba3ecbadc..ebf073c34b9 100644 --- a/apps/playground-web/src/app/data/pages-metadata.ts +++ b/apps/playground-web/src/app/data/pages-metadata.ts @@ -16,6 +16,7 @@ import { SquareIcon, SquircleDashedIcon, StampIcon, + TrendingUpIcon, UserIcon, WalletCardsIcon, } from "lucide-react"; @@ -203,6 +204,13 @@ export const tokensFeatureCards: FeatureCardMetadata[] = [ link: "/tokens/nft-components", description: "Headless UI components for rendering NFT Media and metadata", }, + { + icon: TrendingUpIcon, + title: "Price Tracker", + link: "/tokens/price-tracker", + description: + "Live token prices, market cap, and 24h volume across chains", + }, ]; export const aiFeatureCards: FeatureCardMetadata[] = [ diff --git a/apps/playground-web/src/app/navLinks.ts b/apps/playground-web/src/app/navLinks.ts index 864beb4dc60..7ef4732be34 100644 --- a/apps/playground-web/src/app/navLinks.ts +++ b/apps/playground-web/src/app/navLinks.ts @@ -173,6 +173,10 @@ const tokens: ShadcnSidebarLink = { href: "/tokens/nft-components", label: "NFT Components", }, + { + href: "/tokens/price-tracker", + label: "Price Tracker", + }, ], }; diff --git a/apps/playground-web/src/app/tokens/price-tracker/page.tsx b/apps/playground-web/src/app/tokens/price-tracker/page.tsx new file mode 100644 index 00000000000..8c116ea09d0 --- /dev/null +++ b/apps/playground-web/src/app/tokens/price-tracker/page.tsx @@ -0,0 +1,34 @@ +import { TrendingUpIcon } from "lucide-react"; +import { PageLayout } from "@/components/blocks/APIHeader"; +import { TokenPriceTracker } from "@/components/token-price/token-price-tracker"; +import ThirdwebProvider from "@/components/thirdweb-provider"; +import { createMetadata } from "@/lib/metadata"; + +const title = "Price Tracker"; +const description = + "Live token prices, market cap, and 24h volume across chains"; + +export const metadata = createMetadata({ + title, + description, + image: { + icon: "wallets", + title, + }, +}); + +export default function Page() { + return ( + + + + + + ); +} diff --git a/apps/playground-web/src/components/token-price/token-price-tracker.tsx b/apps/playground-web/src/components/token-price/token-price-tracker.tsx new file mode 100644 index 00000000000..196bcbdfd21 --- /dev/null +++ b/apps/playground-web/src/components/token-price/token-price-tracker.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; +import { Bridge } from "thirdweb"; +import { + arbitrum, + base, + ethereum, + optimism, + polygon, +} from "thirdweb/chains"; +import { THIRDWEB_CLIENT } from "@/lib/client"; +import { CodeExample } from "../code/code-example"; + +const CHAINS = [ + { chain: ethereum, label: "Ethereum", id: 1 }, + { chain: base, label: "Base", id: 8453 }, + { chain: polygon, label: "Polygon", id: 137 }, + { chain: arbitrum, label: "Arbitrum", id: 42161 }, + { chain: optimism, label: "Optimism", id: 10 }, +] as const; + +function formatUsd(value: number): string { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +} + +function formatPrice(value: number): string { + if (value >= 1) { + return `$${value.toFixed(2)}`; + } + if (value >= 0.01) { + return `$${value.toFixed(4)}`; + } + return `$${value.toFixed(6)}`; +} + +function TokenPriceTrackerPreview() { + const [selectedChainId, setSelectedChainId] = useState(1); + + const tokensQuery = useQuery({ + queryKey: ["bridge-tokens-price", selectedChainId], + queryFn: () => + Bridge.tokens({ + client: THIRDWEB_CLIENT, + chainId: selectedChainId, + limit: 15, + sortBy: "market_cap", + includePrices: true, + }), + refetchInterval: 30_000, + }); + + return ( +
+ {/* Chain Selector */} +
+ {CHAINS.map((c) => ( + + ))} +
+ + {/* Token List */} +
+ {/* Header */} +
+ Token + Price + Market Cap + 24h Volume +
+ + {/* Loading State */} + {tokensQuery.isLoading && ( +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ )} + + {/* Error State */} + {tokensQuery.isError && ( +
+ Failed to load token data. Please try again. +
+ )} + + {/* Data */} + {tokensQuery.data?.map((token) => ( +
+
+ {token.iconUri ? ( + {token.name} + ) : ( +
+ )} +
+

+ {token.name} +

+

{token.symbol}

+
+
+ + {token.prices?.usd ? formatPrice(token.prices.usd) : "—"} + + + {token.marketCapUsd ? formatUsd(token.marketCapUsd) : "—"} + + + {token.volume24hUsd ? formatUsd(token.volume24hUsd) : "—"} + +
+ ))} +
+ + {/* Auto-refresh indicator */} +

+ Auto-refreshes every 30s + {tokensQuery.isFetching && !tokensQuery.isLoading && ( + + )} +

+
+ ); +} + +export function TokenPriceTracker() { + return ( + + Bridge.tokens({ + client: THIRDWEB_CLIENT, + chainId: 1, // Ethereum + limit: 15, + sortBy: "market_cap", + includePrices: true, + }), + refetchInterval: 30_000, // auto-refresh every 30s + }); + + return tokens?.map((token) => ( +
+ {token.name} + {token.name} ({token.symbol}) + \${token.prices?.usd?.toFixed(2)} + MCap: {token.marketCapUsd} + Vol: {token.volume24hUsd} +
+ )); +}`} + lang="tsx" + preview={} + /> + ); +}