Skip to content
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
38 changes: 12 additions & 26 deletions apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -298,7 +297,7 @@ export function SingleNetworkSelector(props: {
);
}

export function BridgeNetworkSelector(props: {
type BridgeNetworkSelectorProps = {
chainId: number | undefined;
onChange: (chainId: number) => void;
className?: string;
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -366,27 +359,20 @@ export function BridgeNetworkSelector(props: {
</div>
);
},
[chainsQuery.data, props.client],
[props.chains, props.client],
);

const isLoadingChains = chainsQuery.isPending;

return (
<SelectWithSearch
align={props.align}
className={props.className}
closeOnSelect={true}
disabled={isLoadingChains}
onValueChange={(chainId) => {
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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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({
Expand All @@ -26,6 +28,7 @@ export function TokenPage() {
page,
chainId,
sortBy,
search,
},
],
queryFn: () => {
Expand All @@ -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,
Expand All @@ -44,29 +48,46 @@ export function TokenPage() {
return (
<div className="pb-20 pt-8">
<div className="container max-w-7xl">
<div className="mb-4 flex gap-3 flex-col lg:flex-row">
<div className="mb-4 flex gap-4 lg:gap-3 flex-col lg:flex-row">
<BridgeNetworkSelector
client={client}
chainId={chainId}
onChange={setChainId}
className="rounded-full bg-card lg:w-fit min-w-[320px]"
popoverContentClassName="!w-[350px] rounded-xl overflow-hidden"
className="rounded-xl bg-card lg:w-fit min-w-[280px]"
popoverContentClassName="!w-[300px] rounded-xl overflow-hidden"
chains={props.chains}
/>

<div className="flex gap-3">
<SortButton
label="Popular"
onClick={() => setSortBy("market_cap")}
isSelected={sortBy === "market_cap"}
onClick={() => {
setSortBy("market_cap");
setSearch("");
}}
isSelected={sortBy === "market_cap" && !search}
icon={ActivityIcon}
/>
<SortButton
label="Trending"
onClick={() => setSortBy("volume")}
isSelected={sortBy === "volume"}
onClick={() => {
setSortBy("volume");
setSearch("");
}}
isSelected={sortBy === "volume" && !search}
icon={TrendingUpIcon}
/>
</div>

<div className="relative w-full lg:w-[420px] ml-auto">
<SearchIcon className="size-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search Tokens"
value={search}
className="rounded-xl bg-card flex-1 pl-9"
onChange={(e) => setSearch(e.target.value)}
/>
</div>
</div>

<TokensTable
Expand Down
20 changes: 18 additions & 2 deletions apps/dashboard/src/app/(app)/(dashboard)/tokens/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { BringToFrontIcon } from "lucide-react";
import type { Metadata } from "next";
import { unstable_cache } from "next/cache";
import Link from "next/link";
import { Bridge } from "thirdweb";
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
import { PageHeader } from "./components/header";
import { TokenPage } from "./components/token-page";

Expand All @@ -16,7 +19,9 @@ export const metadata: Metadata = {
title,
};

export default function Page() {
export default async function Page() {
const chains = await getBridgeSupportedChains();

return (
<div>
<PageHeader />
Expand All @@ -39,7 +44,18 @@ export default function Page() {
</Link>
</div>
</div>
<TokenPage />
<TokenPage chains={chains} />
</div>
);
}

const getBridgeSupportedChains = unstable_cache(
async () => {
const chains = await Bridge.chains({ client: serverThirdwebClient });
return chains;
},
["bridge-supported-chains"],
{
revalidate: 60 * 60, // 1 hour
},
);
7 changes: 7 additions & 0 deletions packages/thirdweb/src/bridge/Token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
offset,
includePrices,
sortBy,
query,
} = options;

const clientFetch = getClientFetch(client);
Expand Down Expand Up @@ -172,6 +173,10 @@
url.searchParams.set("sortBy", sortBy);
}

if (query !== undefined) {
url.searchParams.set("query", query);
}

Check warning on line 178 in packages/thirdweb/src/bridge/Token.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/bridge/Token.ts#L177-L178

Added lines #L177 - L178 were not covered by tests

const response = await clientFetch(url.toString());
if (!response.ok) {
const errorJson = await response.json();
Expand Down Expand Up @@ -210,6 +215,8 @@
includePrices?: IncludePrices;
/** Sort by a specific field. */
sortBy?: "newest" | "oldest" | "volume" | "market_cap";
/** search for tokens by token name or symbol */
query?: string;
};

/**
Expand Down
Loading