Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b7732d3

Browse files
committed
Dashboard: Add search in tokens page (#8088)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## 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}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 6f88d38 commit b7732d3

File tree

4 files changed

+68
-38
lines changed

4 files changed

+68
-38
lines changed

apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"use client";
22

3-
import { useQuery } from "@tanstack/react-query";
43
import { useCallback, useMemo } from "react";
5-
import { Bridge, type ThirdwebClient } from "thirdweb";
4+
import type { Bridge, ThirdwebClient } from "thirdweb";
65
import { MultiSelect } from "@/components/blocks/multi-select";
76
import { SelectWithSearch } from "@/components/blocks/select-with-search";
87
import { Badge } from "@/components/ui/badge";
@@ -298,7 +297,7 @@ export function SingleNetworkSelector(props: {
298297
);
299298
}
300299

301-
export function BridgeNetworkSelector(props: {
300+
type BridgeNetworkSelectorProps = {
302301
chainId: number | undefined;
303302
onChange: (chainId: number) => void;
304303
className?: string;
@@ -307,28 +306,22 @@ export function BridgeNetworkSelector(props: {
307306
align?: "center" | "start" | "end";
308307
placeholder?: string;
309308
client: ThirdwebClient;
310-
}) {
311-
const chainsQuery = useQuery({
312-
queryKey: ["bridge-chains"],
313-
queryFn: () => {
314-
return Bridge.chains({ client: props.client });
315-
},
316-
refetchOnMount: false,
317-
refetchOnWindowFocus: false,
318-
});
309+
chains: Bridge.chains.Result;
310+
};
319311

312+
export function BridgeNetworkSelector(props: BridgeNetworkSelectorProps) {
320313
const options = useMemo(() => {
321-
return (chainsQuery.data || [])?.map((chain) => {
314+
return props.chains.map((chain) => {
322315
return {
323316
label: cleanChainName(chain.name),
324317
value: String(chain.chainId),
325318
};
326319
});
327-
}, [chainsQuery.data]);
320+
}, [props.chains]);
328321

329322
const searchFn = useCallback(
330323
(option: Option, searchValue: string) => {
331-
const chain = chainsQuery.data?.find(
324+
const chain = props.chains.find(
332325
(chain) => chain.chainId === Number(option.value),
333326
);
334327
if (!chain) {
@@ -340,12 +333,12 @@ export function BridgeNetworkSelector(props: {
340333
}
341334
return chain.name.toLowerCase().includes(searchValue.toLowerCase());
342335
},
343-
[chainsQuery.data],
336+
[props.chains],
344337
);
345338

346339
const renderOption = useCallback(
347340
(option: Option) => {
348-
const chain = chainsQuery.data?.find(
341+
const chain = props.chains.find(
349342
(chain) => chain.chainId === Number(option.value),
350343
);
351344
if (!chain) {
@@ -366,27 +359,20 @@ export function BridgeNetworkSelector(props: {
366359
</div>
367360
);
368361
},
369-
[chainsQuery.data, props.client],
362+
[props.chains, props.client],
370363
);
371364

372-
const isLoadingChains = chainsQuery.isPending;
373-
374365
return (
375366
<SelectWithSearch
376367
align={props.align}
377368
className={props.className}
378369
closeOnSelect={true}
379-
disabled={isLoadingChains}
380370
onValueChange={(chainId) => {
381371
props.onChange(Number(chainId));
382372
}}
383373
options={options}
384374
overrideSearchFn={searchFn}
385-
placeholder={
386-
isLoadingChains
387-
? "Loading Chains..."
388-
: props.placeholder || "Select Chain"
389-
}
375+
placeholder={props.placeholder || "Select Chain"}
390376
popoverContentClassName={props.popoverContentClassName}
391377
renderOption={renderOption}
392378
searchPlaceholder="Search by Name or Chain ID"

apps/dashboard/src/app/(app)/(dashboard)/tokens/components/token-page.tsx

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"use client";
22

33
import { useQuery } from "@tanstack/react-query";
4+
import { Input } from "@workspace/ui/components/input";
45
import { cn } from "@workspace/ui/lib/utils";
5-
import { ActivityIcon, TrendingUpIcon } from "lucide-react";
6+
import { ActivityIcon, SearchIcon, TrendingUpIcon } from "lucide-react";
67
import { useState } from "react";
78
import { Bridge } from "thirdweb";
89
import { BridgeNetworkSelector } from "@/components/blocks/NetworkSelectors";
@@ -14,9 +15,10 @@ const client = getClientThirdwebClient();
1415

1516
const pageSize = 20;
1617

17-
export function TokenPage() {
18+
export function TokenPage(props: { chains: Bridge.chains.Result }) {
1819
const [page, setPage] = useState(1);
1920
const [chainId, setChainId] = useState(1);
21+
const [search, setSearch] = useState("");
2022
const [sortBy, setSortBy] = useState<"volume" | "market_cap">("volume");
2123

2224
const tokensQuery = useQuery({
@@ -26,6 +28,7 @@ export function TokenPage() {
2628
page,
2729
chainId,
2830
sortBy,
31+
search,
2932
},
3033
],
3134
queryFn: () => {
@@ -34,7 +37,8 @@ export function TokenPage() {
3437
chainId: chainId,
3538
limit: pageSize,
3639
offset: (page - 1) * pageSize,
37-
sortBy,
40+
sortBy: search ? undefined : sortBy,
41+
query: search ? search : undefined,
3842
});
3943
},
4044
refetchOnMount: false,
@@ -44,29 +48,46 @@ export function TokenPage() {
4448
return (
4549
<div className="pb-20 pt-8">
4650
<div className="container max-w-7xl">
47-
<div className="mb-4 flex gap-3 flex-col lg:flex-row">
51+
<div className="mb-4 flex gap-4 lg:gap-3 flex-col lg:flex-row">
4852
<BridgeNetworkSelector
4953
client={client}
5054
chainId={chainId}
5155
onChange={setChainId}
52-
className="rounded-full bg-card lg:w-fit min-w-[320px]"
53-
popoverContentClassName="!w-[350px] rounded-xl overflow-hidden"
56+
className="rounded-xl bg-card lg:w-fit min-w-[280px]"
57+
popoverContentClassName="!w-[300px] rounded-xl overflow-hidden"
58+
chains={props.chains}
5459
/>
5560

5661
<div className="flex gap-3">
5762
<SortButton
5863
label="Popular"
59-
onClick={() => setSortBy("market_cap")}
60-
isSelected={sortBy === "market_cap"}
64+
onClick={() => {
65+
setSortBy("market_cap");
66+
setSearch("");
67+
}}
68+
isSelected={sortBy === "market_cap" && !search}
6169
icon={ActivityIcon}
6270
/>
6371
<SortButton
6472
label="Trending"
65-
onClick={() => setSortBy("volume")}
66-
isSelected={sortBy === "volume"}
73+
onClick={() => {
74+
setSortBy("volume");
75+
setSearch("");
76+
}}
77+
isSelected={sortBy === "volume" && !search}
6778
icon={TrendingUpIcon}
6879
/>
6980
</div>
81+
82+
<div className="relative w-full lg:w-[420px] ml-auto">
83+
<SearchIcon className="size-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
84+
<Input
85+
placeholder="Search Tokens"
86+
value={search}
87+
className="rounded-xl bg-card flex-1 pl-9"
88+
onChange={(e) => setSearch(e.target.value)}
89+
/>
90+
</div>
7091
</div>
7192

7293
<TokensTable

apps/dashboard/src/app/(app)/(dashboard)/tokens/page.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { BringToFrontIcon } from "lucide-react";
22
import type { Metadata } from "next";
3+
import { unstable_cache } from "next/cache";
34
import Link from "next/link";
5+
import { Bridge } from "thirdweb";
6+
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
47
import { PageHeader } from "./components/header";
58
import { TokenPage } from "./components/token-page";
69

@@ -16,7 +19,9 @@ export const metadata: Metadata = {
1619
title,
1720
};
1821

19-
export default function Page() {
22+
export default async function Page() {
23+
const chains = await getBridgeSupportedChains();
24+
2025
return (
2126
<div>
2227
<PageHeader />
@@ -39,7 +44,18 @@ export default function Page() {
3944
</Link>
4045
</div>
4146
</div>
42-
<TokenPage />
47+
<TokenPage chains={chains} />
4348
</div>
4449
);
4550
}
51+
52+
const getBridgeSupportedChains = unstable_cache(
53+
async () => {
54+
const chains = await Bridge.chains({ client: serverThirdwebClient });
55+
return chains;
56+
},
57+
["bridge-supported-chains"],
58+
{
59+
revalidate: 60 * 60, // 1 hour
60+
},
61+
);

packages/thirdweb/src/bridge/Token.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export async function tokens<
142142
offset,
143143
includePrices,
144144
sortBy,
145+
query,
145146
} = options;
146147

147148
const clientFetch = getClientFetch(client);
@@ -172,6 +173,10 @@ export async function tokens<
172173
url.searchParams.set("sortBy", sortBy);
173174
}
174175

176+
if (query !== undefined) {
177+
url.searchParams.set("query", query);
178+
}
179+
175180
const response = await clientFetch(url.toString());
176181
if (!response.ok) {
177182
const errorJson = await response.json();
@@ -210,6 +215,8 @@ export declare namespace tokens {
210215
includePrices?: IncludePrices;
211216
/** Sort by a specific field. */
212217
sortBy?: "newest" | "oldest" | "volume" | "market_cap";
218+
/** search for tokens by token name or symbol */
219+
query?: string;
213220
};
214221

215222
/**

0 commit comments

Comments
 (0)