From b7732d3a755ec832aa6c0c74653ae8fc4892d51b Mon Sep 17 00:00:00 2001 From: MananTank Date: Sat, 20 Sep 2025 00:54:37 +0000 Subject: [PATCH] Dashboard: Add search in tokens page (#8088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR enhances the `Token` and `TokenPage` components by adding search functionality for tokens and improving the bridge network selection process. ### Detailed summary - Added `query` parameter in `tokens` function for searching tokens by name or symbol. - Updated `Page` component to fetch supported chains asynchronously. - Modified `TokenPage` to accept `chains` as a prop and integrated search functionality. - Enhanced `BridgeNetworkSelector` to utilize passed `chains` prop instead of querying. - Added search input for token filtering in the `TokenPage`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit * **New Features** * Added token search on the Tokens page; search by name or symbol with a new inline search field beside sort controls. * Search intelligently interacts with sorting: choosing Popular or Trending clears the search. * **Improvements** * Faster, more reliable page load by fetching supported networks server‑side and passing them to the page. * Refined network selector UI with a consistent placeholder and smoother option rendering. * **Refactor** * Streamlined components to receive pre-fetched chain data, reducing in-component data fetching and simplifying state. --- .../@/components/blocks/NetworkSelectors.tsx | 38 ++++++----------- .../tokens/components/token-page.tsx | 41 ++++++++++++++----- .../src/app/(app)/(dashboard)/tokens/page.tsx | 20 ++++++++- packages/thirdweb/src/bridge/Token.ts | 7 ++++ 4 files changed, 68 insertions(+), 38 deletions(-) diff --git a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx index 81637c09f54..709d391d05d 100644 --- a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx +++ b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx @@ -1,8 +1,7 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; import { useCallback, useMemo } from "react"; -import { Bridge, type ThirdwebClient } from "thirdweb"; +import type { Bridge, ThirdwebClient } from "thirdweb"; import { MultiSelect } from "@/components/blocks/multi-select"; import { SelectWithSearch } from "@/components/blocks/select-with-search"; import { Badge } from "@/components/ui/badge"; @@ -298,7 +297,7 @@ export function SingleNetworkSelector(props: { ); } -export function BridgeNetworkSelector(props: { +type BridgeNetworkSelectorProps = { chainId: number | undefined; onChange: (chainId: number) => void; className?: string; @@ -307,28 +306,22 @@ export function BridgeNetworkSelector(props: { align?: "center" | "start" | "end"; placeholder?: string; client: ThirdwebClient; -}) { - const chainsQuery = useQuery({ - queryKey: ["bridge-chains"], - queryFn: () => { - return Bridge.chains({ client: props.client }); - }, - refetchOnMount: false, - refetchOnWindowFocus: false, - }); + chains: Bridge.chains.Result; +}; +export function BridgeNetworkSelector(props: BridgeNetworkSelectorProps) { const options = useMemo(() => { - return (chainsQuery.data || [])?.map((chain) => { + return props.chains.map((chain) => { return { label: cleanChainName(chain.name), value: String(chain.chainId), }; }); - }, [chainsQuery.data]); + }, [props.chains]); const searchFn = useCallback( (option: Option, searchValue: string) => { - const chain = chainsQuery.data?.find( + const chain = props.chains.find( (chain) => chain.chainId === Number(option.value), ); if (!chain) { @@ -340,12 +333,12 @@ export function BridgeNetworkSelector(props: { } return chain.name.toLowerCase().includes(searchValue.toLowerCase()); }, - [chainsQuery.data], + [props.chains], ); const renderOption = useCallback( (option: Option) => { - const chain = chainsQuery.data?.find( + const chain = props.chains.find( (chain) => chain.chainId === Number(option.value), ); if (!chain) { @@ -366,27 +359,20 @@ export function BridgeNetworkSelector(props: { ); }, - [chainsQuery.data, props.client], + [props.chains, props.client], ); - const isLoadingChains = chainsQuery.isPending; - return ( { props.onChange(Number(chainId)); }} options={options} overrideSearchFn={searchFn} - placeholder={ - isLoadingChains - ? "Loading Chains..." - : props.placeholder || "Select Chain" - } + placeholder={props.placeholder || "Select Chain"} popoverContentClassName={props.popoverContentClassName} renderOption={renderOption} searchPlaceholder="Search by Name or Chain ID" diff --git a/apps/dashboard/src/app/(app)/(dashboard)/tokens/components/token-page.tsx b/apps/dashboard/src/app/(app)/(dashboard)/tokens/components/token-page.tsx index ebde5ed448a..f440a0bb673 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/tokens/components/token-page.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/tokens/components/token-page.tsx @@ -1,8 +1,9 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import { Input } from "@workspace/ui/components/input"; import { cn } from "@workspace/ui/lib/utils"; -import { ActivityIcon, TrendingUpIcon } from "lucide-react"; +import { ActivityIcon, SearchIcon, TrendingUpIcon } from "lucide-react"; import { useState } from "react"; import { Bridge } from "thirdweb"; import { BridgeNetworkSelector } from "@/components/blocks/NetworkSelectors"; @@ -14,9 +15,10 @@ const client = getClientThirdwebClient(); const pageSize = 20; -export function TokenPage() { +export function TokenPage(props: { chains: Bridge.chains.Result }) { const [page, setPage] = useState(1); const [chainId, setChainId] = useState(1); + const [search, setSearch] = useState(""); const [sortBy, setSortBy] = useState<"volume" | "market_cap">("volume"); const tokensQuery = useQuery({ @@ -26,6 +28,7 @@ export function TokenPage() { page, chainId, sortBy, + search, }, ], queryFn: () => { @@ -34,7 +37,8 @@ export function TokenPage() { chainId: chainId, limit: pageSize, offset: (page - 1) * pageSize, - sortBy, + sortBy: search ? undefined : sortBy, + query: search ? search : undefined, }); }, refetchOnMount: false, @@ -44,29 +48,46 @@ export function TokenPage() { return (
-
+
setSortBy("market_cap")} - isSelected={sortBy === "market_cap"} + onClick={() => { + setSortBy("market_cap"); + setSearch(""); + }} + isSelected={sortBy === "market_cap" && !search} icon={ActivityIcon} /> setSortBy("volume")} - isSelected={sortBy === "volume"} + onClick={() => { + setSortBy("volume"); + setSearch(""); + }} + isSelected={sortBy === "volume" && !search} icon={TrendingUpIcon} />
+ +
+ + setSearch(e.target.value)} + /> +
@@ -39,7 +44,18 @@ export default function Page() {
- +
); } + +const getBridgeSupportedChains = unstable_cache( + async () => { + const chains = await Bridge.chains({ client: serverThirdwebClient }); + return chains; + }, + ["bridge-supported-chains"], + { + revalidate: 60 * 60, // 1 hour + }, +); diff --git a/packages/thirdweb/src/bridge/Token.ts b/packages/thirdweb/src/bridge/Token.ts index 518dc1ae463..c21f70ba7a6 100644 --- a/packages/thirdweb/src/bridge/Token.ts +++ b/packages/thirdweb/src/bridge/Token.ts @@ -142,6 +142,7 @@ export async function tokens< offset, includePrices, sortBy, + query, } = options; const clientFetch = getClientFetch(client); @@ -172,6 +173,10 @@ export async function tokens< url.searchParams.set("sortBy", sortBy); } + if (query !== undefined) { + url.searchParams.set("query", query); + } + const response = await clientFetch(url.toString()); if (!response.ok) { const errorJson = await response.json(); @@ -210,6 +215,8 @@ export declare namespace tokens { includePrices?: IncludePrices; /** Sort by a specific field. */ sortBy?: "newest" | "oldest" | "volume" | "market_cap"; + /** search for tokens by token name or symbol */ + query?: string; }; /**