Skip to content

Commit 678c00e

Browse files
authored
Fix: Update routes page to use server-side pagination and filtering (#7133)
1 parent 5ab59ff commit 678c00e

File tree

8 files changed

+174
-108
lines changed

8 files changed

+174
-108
lines changed

apps/dashboard/src/@/components/ui/CopyTextButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function CopyTextButton(props: {
4242
variant={props.variant || "outline"}
4343
aria-label={props.tooltip}
4444
className={cn(
45-
"flex h-auto w-auto gap-2 rounded-lg px-2.5 py-1.5 font-normal text-foreground",
45+
"flex h-auto w-auto gap-2 rounded-lg px-1.5 py-0.5 font-normal text-foreground",
4646
props.className,
4747
)}
4848
onClick={(e) => {

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ArrowUpRightIcon } from "lucide-react";
12
import type { Metadata } from "next";
23
import { getClientThirdwebClient } from "../../../../../@/constants/thirdweb-client.client";
34
import { UniversalBridgeEmbed } from "./components/client/UniversalBridgeEmbed";
@@ -35,6 +36,34 @@ export default async function RoutesPage({
3536
src="/assets/login/background.svg"
3637
className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0"
3738
/>
39+
40+
<div className="absolute inset-x-0 bottom-24 z-20">
41+
<div className="container mx-auto px-4">
42+
<div className="relative overflow-hidden rounded-lg border-2 border-green-500/20 bg-gradient-to-br from-card/80 to-card/50 p-4 shadow-[inset_0_1px_2px_0_rgba(0,0,0,0.02)]">
43+
<div className="absolute inset-0 bg-gradient-to-br from-green-500/5 to-transparent" />
44+
<div className="relative flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
45+
<div className="flex flex-col gap-1">
46+
<h3 className="font-medium text-lg">
47+
Get Started with Universal Bridge
48+
</h3>
49+
<p className="text-muted-foreground text-sm">
50+
Simple, instant, and secure payments across any token and
51+
chain.
52+
</p>
53+
</div>
54+
<a
55+
href="https://portal.thirdweb.com/pay"
56+
target="_blank"
57+
rel="noopener noreferrer"
58+
className="inline-flex items-center gap-2 rounded-md bg-green-600 px-4 py-2 font-medium text-sm text-white transition-all hover:bg-green-600/90 hover:shadow-sm"
59+
>
60+
Learn More
61+
<ArrowUpRightIcon className="size-4" />
62+
</a>
63+
</div>
64+
</div>
65+
</div>
66+
</div>
3867
</div>
3968
);
4069
}

apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/client/search.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const SearchInput: React.FC = () => {
4747
<div className="group relative w-full">
4848
<SearchIcon className="-translate-y-1/2 absolute top-[50%] left-3 size-4 text-muted-foreground" />
4949
<Input
50-
placeholder="Search with anything!"
50+
placeholder="Search by token name or symbol"
5151
className="h-10 rounded-lg bg-card py-2 pl-9 lg:min-w-[300px]"
5252
defaultValue={searchParams?.get("query") || ""}
5353
onChange={(e) => handleSearch(e.target.value)}

apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routelist-row.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,20 @@ export async function RouteListRow({
5252
]);
5353

5454
return (
55-
<TableRow linkBox className="hover:bg-accent/50">
55+
<TableRow linkBox className="group transition-colors hover:bg-accent/50">
5656
<TableCell>
57-
<div className="flex flex-row items-center gap-4">
58-
<div className="flex items-center gap-1">
57+
<div className="flex flex-row items-center gap-3">
58+
<div className="flex items-center gap-2">
5959
{resolvedOriginTokenIconUri ? (
6060
// For now we're using a normal img tag because the domain for these images is unknown
6161
// eslint-disable-next-line @next/next/no-img-element
6262
<img
6363
src={resolvedOriginTokenIconUri}
6464
alt={originTokenAddress}
65-
className="size-6 rounded-full border border-muted-foreground"
65+
className="size-7 rounded-full border border-border/50 shadow-sm transition-transform group-hover:scale-105"
6666
/>
6767
) : (
68-
<div className="size-6 rounded-full bg-muted-foreground" />
68+
<div className="size-7 rounded-full bg-muted-foreground/20" />
6969
)}
7070
{originTokenSymbol && (
7171
<CopyTextButton
@@ -76,7 +76,7 @@ export async function RouteListRow({
7676
: originTokenSymbol
7777
}
7878
tooltip="Copy Token Address"
79-
className="relative z-10 text-base"
79+
className="relative z-10 font-medium text-base"
8080
variant="ghost"
8181
copyIconPosition="right"
8282
/>
@@ -85,22 +85,22 @@ export async function RouteListRow({
8585
</div>
8686
</TableCell>
8787

88-
<TableCell className="text-muted-foreground">
89-
{originChain.name}
88+
<TableCell className="text-muted-foreground/90">
89+
<span className="font-medium">{originChain.name}</span>
9090
</TableCell>
9191

9292
<TableCell>
93-
<div className="flex flex-row items-center gap-4">
94-
<div className="flex items-center gap-1">
93+
<div className="flex flex-row items-center gap-3">
94+
<div className="flex items-center gap-2">
9595
{resolvedDestinationTokenIconUri ? (
9696
// eslint-disable-next-line @next/next/no-img-element
9797
<img
9898
src={resolvedDestinationTokenIconUri}
9999
alt={destinationTokenAddress}
100-
className="size-6 rounded-full border border-muted-foreground"
100+
className="size-7 rounded-full border border-border/50 shadow-sm transition-transform group-hover:scale-105"
101101
/>
102102
) : (
103-
<div className="size-6 rounded-full bg-muted-foreground" />
103+
<div className="size-7 rounded-full bg-muted-foreground/20" />
104104
)}
105105
{destinationTokenSymbol && (
106106
<CopyTextButton
@@ -111,7 +111,7 @@ export async function RouteListRow({
111111
: destinationTokenSymbol
112112
}
113113
tooltip="Copy Token Address"
114-
className="relative z-10 text-base"
114+
className="relative z-10 font-medium text-base"
115115
variant="ghost"
116116
copyIconPosition="right"
117117
/>
@@ -120,8 +120,8 @@ export async function RouteListRow({
120120
</div>
121121
</TableCell>
122122

123-
<TableCell className="text-muted-foreground">
124-
{destinationChain.name}
123+
<TableCell className="text-muted-foreground/90">
124+
<span className="font-medium">{destinationChain.name}</span>
125125
</TableCell>
126126
</TableRow>
127127
);

apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/components/server/routes-table.tsx

Lines changed: 29 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
TableRow,
88
} from "@/components/ui/table";
99
import type { Address } from "thirdweb";
10-
import { checksumAddress } from "thirdweb/utils";
1110
import { getRoutes } from "../../../utils";
1211
import { ChainlistPagination } from "../client/pagination";
1312
import { RouteListCard } from "./routelist-card";
@@ -25,18 +24,15 @@ export type SearchParams = Partial<{
2524

2625
// 120 is divisible by 2, 3, and 4 so card layout looks nice
2726
const DEFAULT_PAGE_SIZE = 120;
28-
const DEFAULT_PAGE = 1;
2927

3028
async function getRoutesToRender(params: SearchParams) {
3129
const filters: Partial<{
32-
limit: number;
33-
offset: number;
30+
originQuery?: string;
31+
destinationQuery?: string;
3432
originChainId?: number;
3533
originTokenAddress?: Address;
3634
destinationChainId?: number;
3735
destinationTokenAddress?: Address;
38-
originTextQuery?: string;
39-
destinationTextQuery?: string;
4036
}> = {};
4137

4238
if (params.type === "origin" || typeof params.type === "undefined") {
@@ -45,77 +41,28 @@ async function getRoutesToRender(params: SearchParams) {
4541
} else if (Number.isInteger(Number(params.query))) {
4642
filters.originChainId = Number(params.query);
4743
} else if (params.query) {
48-
filters.originTextQuery = params.query;
44+
filters.originQuery = params.query;
4945
}
5046
} else if (params.type === "destination") {
5147
if (params.query?.startsWith("0x")) {
5248
filters.destinationTokenAddress = params.query as Address;
5349
} else if (Number.isInteger(Number(params.query))) {
5450
filters.destinationChainId = Number(params.query);
5551
} else if (params.query) {
56-
filters.destinationTextQuery = params.query;
52+
filters.destinationQuery = params.query;
5753
}
5854
}
59-
// Temporary, will update this after the /routes endpoint
60-
let routes = await getRoutes({ limit: 500_000 });
61-
62-
const totalCount = routes.length;
63-
64-
if (filters.originChainId) {
65-
routes = routes.filter(
66-
(route) => route.originToken.chainId === filters.originChainId,
67-
);
68-
}
69-
if (filters.originTokenAddress) {
70-
const originTokenAddress = filters.originTokenAddress;
71-
routes = routes.filter(
72-
(route) =>
73-
checksumAddress(route.originToken.address) ===
74-
checksumAddress(originTokenAddress),
75-
);
76-
}
77-
if (filters.destinationChainId) {
78-
routes = routes.filter(
79-
(route) => route.destinationToken.chainId === filters.destinationChainId,
80-
);
81-
}
82-
if (filters.destinationTokenAddress) {
83-
const destinationTokenAddress = filters.destinationTokenAddress;
84-
routes = routes.filter(
85-
(route) =>
86-
checksumAddress(route.destinationToken.address) ===
87-
checksumAddress(destinationTokenAddress),
88-
);
89-
}
90-
91-
if (filters.originTextQuery) {
92-
const originTextQuery = filters.originTextQuery.toLowerCase();
93-
routes = routes.filter((route) => {
94-
return (
95-
route.originToken.name.toLowerCase().includes(originTextQuery) ||
96-
route.originToken.symbol.toLowerCase().includes(originTextQuery)
97-
);
98-
});
99-
}
100-
101-
if (filters.destinationTextQuery) {
102-
const destinationTextQuery = filters.destinationTextQuery.toLowerCase();
103-
routes = routes.filter((route) => {
104-
return (
105-
route.destinationToken.name
106-
.toLowerCase()
107-
.includes(destinationTextQuery) ||
108-
route.destinationToken.symbol
109-
.toLowerCase()
110-
.includes(destinationTextQuery)
111-
);
112-
});
113-
}
55+
const routes = await getRoutes({
56+
limit: DEFAULT_PAGE_SIZE,
57+
offset: DEFAULT_PAGE_SIZE * ((params.page || 1) - 1),
58+
originQuery: filters.originQuery,
59+
destinationQuery: filters.destinationQuery,
60+
});
11461

11562
return {
116-
routesToRender: routes,
117-
totalCount,
118-
filteredCount: routes.length,
63+
routesToRender: routes.data,
64+
totalCount: routes.meta.totalCount,
65+
filteredCount: routes.meta.filteredCount,
11966
};
12067
}
12168

@@ -128,10 +75,9 @@ export async function RoutesData(props: {
12875
props.searchParams,
12976
);
13077

131-
// pagination
132-
const totalPages = Math.ceil(routesToRender.length / DEFAULT_PAGE_SIZE);
78+
const totalPages = Math.ceil(filteredCount / DEFAULT_PAGE_SIZE);
13379

134-
const activePage = Number(props.searchParams.page || DEFAULT_PAGE);
80+
const activePage = Number(props.searchParams.page || 1);
13581
const pageSize = DEFAULT_PAGE_SIZE;
13682
const startIndex = (activePage - 1) * pageSize;
13783
const endIndex = startIndex + pageSize;
@@ -146,14 +92,22 @@ export async function RoutesData(props: {
14692
<p className="text-2xl">No Results found</p>
14793
</div>
14894
) : props.activeView === "table" ? (
149-
<TableContainer>
95+
<TableContainer className="overflow-hidden rounded-xl border border-border/50 bg-card/50 shadow-sm transition-all">
15096
<Table>
15197
<TableHeader className="z-0">
152-
<TableRow>
153-
<TableHead>Origin Token</TableHead>
154-
<TableHead>Origin Chain</TableHead>
155-
<TableHead>Destination Token</TableHead>
156-
<TableHead>Destination Chain</TableHead>
98+
<TableRow className="border-border/50 border-b bg-muted/50">
99+
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
100+
Origin Token
101+
</TableHead>
102+
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
103+
Origin Chain
104+
</TableHead>
105+
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
106+
Destination Token
107+
</TableHead>
108+
<TableHead className="py-4 font-medium text-muted-foreground/80 text-xs uppercase tracking-wider">
109+
Destination Chain
110+
</TableHead>
157111
</TableRow>
158112
</TableHeader>
159113
<TableBody>

apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/page.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ArrowUpRightIcon } from "lucide-react";
12
import type { Metadata } from "next";
23
import { headers } from "next/headers";
34
import { getAuthToken } from "../../../api/lib/getAuthToken";
@@ -43,15 +44,15 @@ export default async function RoutesPage(props: {
4344

4445
return (
4546
<section className="container mx-auto flex h-full flex-col px-4 py-10">
46-
<header className="flex flex-col gap-4">
47-
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
48-
<div className="flex flex-row items-center justify-between gap-4 lg:flex-col lg:justify-start">
47+
<header className="flex flex-col gap-6">
48+
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
49+
<div className="flex flex-col gap-2">
4950
<h1 className="font-semibold text-4xl tracking-tighter lg:text-5xl">
5051
Routes
5152
</h1>
5253
</div>
53-
<div className="flex flex-row items-end gap-4 lg:flex-col ">
54-
<div className="flex w-full flex-row gap-4">
54+
<div className="flex flex-row items-end gap-4 lg:flex-col">
55+
<div className="flex w-full flex-row items-center gap-4">
5556
<SearchInput />
5657
<QueryType activeType={activeType} />
5758
<RouteListView activeView={activeView} />
@@ -60,6 +61,29 @@ export default async function RoutesPage(props: {
6061
</div>
6162
</header>
6263
<div className="h-10" />
64+
<div className="relative overflow-hidden rounded-lg border-2 border-green-500/20 bg-gradient-to-br from-card/80 to-card/50 p-4 shadow-[inset_0_1px_2px_0_rgba(0,0,0,0.02)]">
65+
<div className="absolute inset-0 bg-gradient-to-br from-green-500/5 to-transparent" />
66+
<div className="relative flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
67+
<div className="flex flex-col gap-1">
68+
<h3 className="font-medium text-lg">
69+
Get Started with Universal Bridge
70+
</h3>
71+
<p className="text-muted-foreground text-sm">
72+
Simple, instant, and secure payments across any token and chain.
73+
</p>
74+
</div>
75+
<a
76+
href="https://portal.thirdweb.com/pay"
77+
target="_blank"
78+
rel="noopener noreferrer"
79+
className="inline-flex items-center gap-2 rounded-md bg-green-600 px-4 py-2 font-medium text-sm text-white transition-all hover:bg-green-600/90 hover:shadow-sm"
80+
>
81+
Learn More
82+
<ArrowUpRightIcon className="size-4" />
83+
</a>
84+
</div>
85+
</div>
86+
<div className="h-10" />
6387
<RoutesData
6488
searchParams={searchParams}
6589
activeView={activeView}

0 commit comments

Comments
 (0)