Skip to content

Commit 2c7ae5c

Browse files
Add pagination, search, and loading state to AllSupportedWallets component
Co-authored-by: joaquim.verges <[email protected]>
1 parent 48361f9 commit 2c7ae5c

File tree

1 file changed

+191
-22
lines changed

1 file changed

+191
-22
lines changed

apps/portal/src/components/others/AllSupportedWallets.tsx

Lines changed: 191 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
"use client";
2+
13
import Image from "next/image";
4+
import { useState, useEffect, useMemo } from "react";
25
import {
36
getAllWalletsList,
47
getWalletInfo,
58
type WalletId,
69
} from "thirdweb/wallets";
710
import { DocLink, InlineCode } from "../Document";
811
import { Table, TBody, Td, Th, Tr } from "../Document/Table";
12+
import { Input } from "../ui/input";
13+
import { Button } from "../ui/button";
14+
import { ChevronLeftIcon, ChevronRightIcon, SearchIcon } from "lucide-react";
915

1016
const specialWallets: {
1117
[key in WalletId]?: boolean;
@@ -14,39 +20,202 @@ const specialWallets: {
1420
smart: true,
1521
};
1622

17-
export async function AllSupportedWallets() {
18-
const wallets = await getAllWalletsList();
23+
const ITEMS_PER_PAGE = 20;
1924

20-
return (
21-
<Table>
22-
<TBody>
23-
<Tr>
24-
<Th> Wallet </Th>
25-
<Th> ID </Th>
26-
</Tr>
27-
28-
{wallets
25+
interface WalletInfo {
26+
id: string;
27+
name: string;
28+
}
29+
30+
export function AllSupportedWallets() {
31+
const [wallets, setWallets] = useState<WalletInfo[]>([]);
32+
const [loading, setLoading] = useState(true);
33+
const [searchQuery, setSearchQuery] = useState("");
34+
const [currentPage, setCurrentPage] = useState(1);
35+
36+
useEffect(() => {
37+
async function loadWallets() {
38+
try {
39+
const allWallets = await getAllWalletsList();
40+
const filteredWallets = allWallets
2941
.filter((w) => !(w.id in specialWallets))
30-
.map((w) => {
31-
return (
32-
<Tr key={w.id}>
42+
.map((w) => ({
43+
id: w.id,
44+
name: w.name,
45+
}));
46+
setWallets(filteredWallets);
47+
} catch (error) {
48+
console.error("Failed to load wallets:", error);
49+
} finally {
50+
setLoading(false);
51+
}
52+
}
53+
54+
loadWallets();
55+
}, []);
56+
57+
const filteredWallets = useMemo(() => {
58+
if (!searchQuery) return wallets;
59+
60+
const query = searchQuery.toLowerCase();
61+
return wallets.filter(
62+
(wallet) =>
63+
wallet.name.toLowerCase().includes(query) ||
64+
wallet.id.toLowerCase().includes(query)
65+
);
66+
}, [wallets, searchQuery]);
67+
68+
const totalPages = Math.ceil(filteredWallets.length / ITEMS_PER_PAGE);
69+
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
70+
const endIndex = startIndex + ITEMS_PER_PAGE;
71+
const currentWallets = filteredWallets.slice(startIndex, endIndex);
72+
73+
// Reset to first page when search changes
74+
useEffect(() => {
75+
setCurrentPage(1);
76+
}, [searchQuery]);
77+
78+
const handlePreviousPage = () => {
79+
setCurrentPage((prev) => Math.max(prev - 1, 1));
80+
};
81+
82+
const handleNextPage = () => {
83+
setCurrentPage((prev) => Math.min(prev + 1, totalPages));
84+
};
85+
86+
const handlePageClick = (page: number) => {
87+
setCurrentPage(page);
88+
};
89+
90+
if (loading) {
91+
return (
92+
<div className="flex items-center justify-center py-8">
93+
<div className="text-muted-foreground">Loading wallets...</div>
94+
</div>
95+
);
96+
}
97+
98+
return (
99+
<div className="space-y-6">
100+
{/* Search Input */}
101+
<div className="relative">
102+
<SearchIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
103+
<Input
104+
type="text"
105+
placeholder="Search wallets by name or ID..."
106+
value={searchQuery}
107+
onChange={(e) => setSearchQuery(e.target.value)}
108+
className="pl-10"
109+
/>
110+
</div>
111+
112+
{/* Results count */}
113+
<div className="text-sm text-muted-foreground">
114+
{filteredWallets.length === wallets.length
115+
? `Showing ${filteredWallets.length} wallets`
116+
: `Found ${filteredWallets.length} of ${wallets.length} wallets`}
117+
</div>
118+
119+
{/* Table */}
120+
<Table>
121+
<TBody>
122+
<Tr>
123+
<Th>Wallet</Th>
124+
<Th>ID</Th>
125+
</Tr>
126+
127+
{currentWallets.length === 0 ? (
128+
<Tr>
129+
<Td colSpan={2} className="text-center text-muted-foreground py-8">
130+
{searchQuery ? "No wallets found matching your search." : "No wallets available."}
131+
</Td>
132+
</Tr>
133+
) : (
134+
currentWallets.map((wallet) => (
135+
<Tr key={wallet.id}>
33136
<Td>
34137
<DocLink
35138
className="flex flex-nowrap items-center gap-4 whitespace-nowrap"
36-
href={`/wallets/external-wallets/${w.id}`}
139+
href={`/wallets/external-wallets/${wallet.id}`}
37140
>
38-
<WalletImage id={w.id} />
39-
{w.name}
141+
<WalletImage id={wallet.id as WalletId} />
142+
{wallet.name}
40143
</DocLink>
41144
</Td>
42145
<Td>
43-
<InlineCode code={`"${w.id}"`} />
146+
<InlineCode code={`"${wallet.id}"`} />
44147
</Td>
45148
</Tr>
46-
);
47-
})}
48-
</TBody>
49-
</Table>
149+
))
150+
)}
151+
</TBody>
152+
</Table>
153+
154+
{/* Pagination */}
155+
{totalPages > 1 && (
156+
<div className="flex items-center justify-between">
157+
<div className="text-sm text-muted-foreground">
158+
Page {currentPage} of {totalPages}
159+
{filteredWallets.length > 0 && (
160+
<span className="ml-2">
161+
(showing {startIndex + 1}-{Math.min(endIndex, filteredWallets.length)} of {filteredWallets.length})
162+
</span>
163+
)}
164+
</div>
165+
166+
<div className="flex items-center space-x-2">
167+
<Button
168+
variant="outline"
169+
size="sm"
170+
onClick={handlePreviousPage}
171+
disabled={currentPage === 1}
172+
>
173+
<ChevronLeftIcon className="h-4 w-4" />
174+
Previous
175+
</Button>
176+
177+
{/* Page numbers */}
178+
<div className="flex items-center space-x-1">
179+
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
180+
let pageNumber;
181+
182+
if (totalPages <= 5) {
183+
pageNumber = i + 1;
184+
} else if (currentPage <= 3) {
185+
pageNumber = i + 1;
186+
} else if (currentPage >= totalPages - 2) {
187+
pageNumber = totalPages - 4 + i;
188+
} else {
189+
pageNumber = currentPage - 2 + i;
190+
}
191+
192+
return (
193+
<Button
194+
key={pageNumber}
195+
variant={currentPage === pageNumber ? "default" : "outline"}
196+
size="sm"
197+
onClick={() => handlePageClick(pageNumber)}
198+
className="min-w-[32px]"
199+
>
200+
{pageNumber}
201+
</Button>
202+
);
203+
})}
204+
</div>
205+
206+
<Button
207+
variant="outline"
208+
size="sm"
209+
onClick={handleNextPage}
210+
disabled={currentPage === totalPages}
211+
>
212+
Next
213+
<ChevronRightIcon className="h-4 w-4" />
214+
</Button>
215+
</div>
216+
</div>
217+
)}
218+
</div>
50219
);
51220
}
52221

0 commit comments

Comments
 (0)