From 2d949d1f8e80df2c8949ead3d77695805819ff0f Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Thu, 22 May 2025 16:27:13 -0700 Subject: [PATCH 1/3] fix: update routes page to use server-side pagination and filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated getRoutes to use server-side parameters for filtering and pagination - Modified routes-table to work with API pagination response format - Changed search placeholder text to be more specific 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../routes/components/client/search.tsx | 2 +- .../routes/components/server/routes-table.tsx | 84 ++++--------------- .../app/(app)/(dashboard)/(bridge)/utils.ts | 56 ++++++++++--- 3 files changed, 61 insertions(+), 81 deletions(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/client/search.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/client/search.tsx index 35a63802679..47c1eb5a281 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/client/search.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/client/search.tsx @@ -47,7 +47,7 @@ export const SearchInput: React.FC = () => {
handleSearch(e.target.value)} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx index 1e8f775edf5..f19dd0d3203 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx @@ -7,7 +7,6 @@ import { TableRow, } from "@/components/ui/table"; import type { Address } from "thirdweb"; -import { checksumAddress } from "thirdweb/utils"; import { getRoutes } from "../../../utils"; import { ChainlistPagination } from "../client/pagination"; import { RouteListCard } from "./routelist-card"; @@ -25,18 +24,15 @@ export type SearchParams = Partial<{ // 120 is divisible by 2, 3, and 4 so card layout looks nice const DEFAULT_PAGE_SIZE = 120; -const DEFAULT_PAGE = 1; async function getRoutesToRender(params: SearchParams) { const filters: Partial<{ - limit: number; - offset: number; + originQuery?: string; + destinationQuery?: string; originChainId?: number; originTokenAddress?: Address; destinationChainId?: number; destinationTokenAddress?: Address; - originTextQuery?: string; - destinationTextQuery?: string; }> = {}; if (params.type === "origin" || typeof params.type === "undefined") { @@ -45,7 +41,7 @@ async function getRoutesToRender(params: SearchParams) { } else if (Number.isInteger(Number(params.query))) { filters.originChainId = Number(params.query); } else if (params.query) { - filters.originTextQuery = params.query; + filters.originQuery = params.query; } } else if (params.type === "destination") { if (params.query?.startsWith("0x")) { @@ -53,69 +49,20 @@ async function getRoutesToRender(params: SearchParams) { } else if (Number.isInteger(Number(params.query))) { filters.destinationChainId = Number(params.query); } else if (params.query) { - filters.destinationTextQuery = params.query; + filters.destinationQuery = params.query; } } - // Temporary, will update this after the /routes endpoint - let routes = await getRoutes({ limit: 500_000 }); - - const totalCount = routes.length; - - if (filters.originChainId) { - routes = routes.filter( - (route) => route.originToken.chainId === filters.originChainId, - ); - } - if (filters.originTokenAddress) { - const originTokenAddress = filters.originTokenAddress; - routes = routes.filter( - (route) => - checksumAddress(route.originToken.address) === - checksumAddress(originTokenAddress), - ); - } - if (filters.destinationChainId) { - routes = routes.filter( - (route) => route.destinationToken.chainId === filters.destinationChainId, - ); - } - if (filters.destinationTokenAddress) { - const destinationTokenAddress = filters.destinationTokenAddress; - routes = routes.filter( - (route) => - checksumAddress(route.destinationToken.address) === - checksumAddress(destinationTokenAddress), - ); - } - - if (filters.originTextQuery) { - const originTextQuery = filters.originTextQuery.toLowerCase(); - routes = routes.filter((route) => { - return ( - route.originToken.name.toLowerCase().includes(originTextQuery) || - route.originToken.symbol.toLowerCase().includes(originTextQuery) - ); - }); - } - - if (filters.destinationTextQuery) { - const destinationTextQuery = filters.destinationTextQuery.toLowerCase(); - routes = routes.filter((route) => { - return ( - route.destinationToken.name - .toLowerCase() - .includes(destinationTextQuery) || - route.destinationToken.symbol - .toLowerCase() - .includes(destinationTextQuery) - ); - }); - } + const routes = await getRoutes({ + limit: DEFAULT_PAGE_SIZE, + offset: DEFAULT_PAGE_SIZE * ((params.page || 1) - 1), + originQuery: filters.originQuery, + destinationQuery: filters.destinationQuery, + }); return { - routesToRender: routes, - totalCount, - filteredCount: routes.length, + routesToRender: routes.data, + totalCount: routes.meta.totalCount, + filteredCount: routes.meta.filteredCount, }; } @@ -128,10 +75,9 @@ export async function RoutesData(props: { props.searchParams, ); - // pagination - const totalPages = Math.ceil(routesToRender.length / DEFAULT_PAGE_SIZE); + const totalPages = Math.ceil(filteredCount / DEFAULT_PAGE_SIZE); - const activePage = Number(props.searchParams.page || DEFAULT_PAGE); + const activePage = Number(props.searchParams.page || 1); const pageSize = DEFAULT_PAGE_SIZE; const startIndex = (activePage - 1) * pageSize; const endIndex = startIndex + pageSize; diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/utils.ts b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/utils.ts index 786e8aebd68..cdeadfba530 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/utils.ts +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/utils.ts @@ -2,35 +2,69 @@ import "server-only"; import { NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST } from "@/constants/public-envs"; import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs"; -import { getAuthToken } from "app/(app)/api/lib/getAuthToken"; +import type { Address } from "thirdweb"; import type { Route } from "./types/route"; export async function getRoutes({ limit, + offset, + originQuery, + destinationQuery, + originChainId, + destinationChainId, + originTokenAddress, + destinationTokenAddress, }: { limit?: number; + offset?: number; + originQuery?: string; + destinationQuery?: string; + originChainId?: number; + destinationChainId?: number; + originTokenAddress?: Address; + destinationTokenAddress?: Address; } = {}) { const url = new URL(`${NEXT_PUBLIC_THIRDWEB_BRIDGE_HOST}/v1/routes`); if (limit) { url.searchParams.set("limit", limit.toString()); } + if (offset) { + url.searchParams.set("offset", offset.toString()); + } + if (originQuery) { + url.searchParams.set("originQuery", originQuery); + } + if (destinationQuery) { + url.searchParams.set("destinationQuery", destinationQuery); + } + if (originChainId) { + url.searchParams.set("originChainId", originChainId.toString()); + } + if (destinationChainId) { + url.searchParams.set("destinationChainId", destinationChainId.toString()); + } + if (originTokenAddress) { + url.searchParams.set("originTokenAddress", originTokenAddress); + } + if (destinationTokenAddress) { + url.searchParams.set("destinationTokenAddress", destinationTokenAddress); + } + url.searchParams.set("sortBy", "popularity"); // It's faster to filter client side, doesn't seem to be a big performance boost to paginate/filter server side - const token = await getAuthToken(); const routesResponse = await fetch(url, { - headers: token - ? { - authorization: `Bearer ${token}`, - } - : { - "x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY, - }, + headers: { + "x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY, + }, next: { revalidate: 60 * 60 }, }); if (!routesResponse.ok) { throw new Error("Failed to fetch routes"); } - const routes: { data: Route[] } = await routesResponse.json(); + const routes: { + data: Route[]; + meta: { totalCount: number; filteredCount: number }; + } = await routesResponse.json(); - return routes.data; + return routes; } From e0b18b88751f74913142dcbebc6becd8ec26d06e Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Thu, 22 May 2025 17:04:15 -0700 Subject: [PATCH 2/3] Enhance UI components and layout for Universal Bridge integration - Updated `CopyTextButton` styles for improved spacing and appearance. - Added a promotional section for Universal Bridge on multiple pages, including `bridge/page.tsx`, `routes/page.tsx`, and `universal-bridge/page.tsx`, featuring a call-to-action button. - Improved layout and styling of route list components for better user experience. This update aims to enhance the visibility and accessibility of the Universal Bridge feature across the dashboard. --- .../src/@/components/ui/CopyTextButton.tsx | 2 +- .../(dashboard)/(bridge)/bridge/page.tsx | 29 ++++++++++++++++ .../components/server/routelist-row.tsx | 30 ++++++++-------- .../routes/components/server/routes-table.tsx | 20 +++++++---- .../(dashboard)/(bridge)/routes/page.tsx | 34 ++++++++++++++++--- .../connect/universal-bridge/page.tsx | 25 ++++++++++++++ 6 files changed, 113 insertions(+), 27 deletions(-) diff --git a/apps/dashboard/src/@/components/ui/CopyTextButton.tsx b/apps/dashboard/src/@/components/ui/CopyTextButton.tsx index b1a9c14d7fb..6ec9722bc51 100644 --- a/apps/dashboard/src/@/components/ui/CopyTextButton.tsx +++ b/apps/dashboard/src/@/components/ui/CopyTextButton.tsx @@ -42,7 +42,7 @@ export function CopyTextButton(props: { variant={props.variant || "outline"} aria-label={props.tooltip} className={cn( - "flex h-auto w-auto gap-2 rounded-lg px-2.5 py-1.5 font-normal text-foreground", + "flex h-auto w-auto gap-2 rounded-lg px-1.5 py-0.5 font-normal text-foreground", props.className, )} onClick={(e) => { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx index 5f226408c72..5652304ed52 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx @@ -1,3 +1,4 @@ +import { ArrowUpRightIcon } from "lucide-react"; import type { Metadata } from "next"; import { getClientThirdwebClient } from "../../../../../@/constants/thirdweb-client.client"; import { UniversalBridgeEmbed } from "./components/client/UniversalBridgeEmbed"; @@ -35,6 +36,34 @@ export default async function RoutesPage({ src="/assets/login/background.svg" className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0" /> + +
+
+
+
+
+
+

+ Get Started with Universal Bridge +

+

+ Simple, instant, and secure payments across any token and + chain. +

+
+ + Learn More + + +
+
+
+
); } diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routelist-row.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routelist-row.tsx index 9626cfafa7f..1775152cc00 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routelist-row.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routelist-row.tsx @@ -52,20 +52,20 @@ export async function RouteListRow({ ]); return ( - + -
-
+
+
{resolvedOriginTokenIconUri ? ( // For now we're using a normal img tag because the domain for these images is unknown // eslint-disable-next-line @next/next/no-img-element {originTokenAddress} ) : ( -
+
)} {originTokenSymbol && ( @@ -85,22 +85,22 @@ export async function RouteListRow({
- - {originChain.name} + + {originChain.name} -
-
+
+
{resolvedDestinationTokenIconUri ? ( // eslint-disable-next-line @next/next/no-img-element {destinationTokenAddress} ) : ( -
+
)} {destinationTokenSymbol && ( @@ -120,8 +120,8 @@ export async function RouteListRow({
- - {destinationChain.name} + + {destinationChain.name} ); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx index f19dd0d3203..0b4232dd51c 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx @@ -92,14 +92,22 @@ export async function RoutesData(props: {

No Results found

) : props.activeView === "table" ? ( - + - - Origin Token - Origin Chain - Destination Token - Destination Chain + + + Origin Token + + + Origin Chain + + + Destination Token + + + Destination Chain + diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/page.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/page.tsx index 30d336d881b..144611fe26d 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/page.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/page.tsx @@ -1,3 +1,4 @@ +import { ArrowUpRightIcon } from "lucide-react"; import type { Metadata } from "next"; import { headers } from "next/headers"; import { getAuthToken } from "../../../api/lib/getAuthToken"; @@ -43,15 +44,15 @@ export default async function RoutesPage(props: { return (
-
-
-
+
+
+

Routes

-
-
+
+
@@ -60,6 +61,29 @@ export default async function RoutesPage(props: {
+
+
+
+
+

+ Get Started with Universal Bridge +

+

+ Simple, instant, and secure payments across any token and chain. +

+
+ + Learn More + + +
+
+
+ +
+
+
+
+
+

+ Get Started with Universal Bridge +

+

+ Simple, instant, and secure payments across any token and chain. +

+
+ + Learn More + + +
+
); From c0ebb26f6b8dbb407204ba09ee7aaffbf325f345 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Thu, 22 May 2025 17:05:35 -0700 Subject: [PATCH 3/3] Refactor layout in bridge page for improved styling - Adjusted the positioning of a div in `bridge/page.tsx` to enhance the overall layout and visual consistency. - Removed unnecessary class from the div to streamline the styling. This change aims to improve the user interface and maintain a clean design across the dashboard. --- .../src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx index 5652304ed52..67dd2b1fa06 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(bridge)/bridge/page.tsx @@ -37,7 +37,7 @@ export default async function RoutesPage({ className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0" /> -
+