1+ "use client" ;
2+
3+ import {
4+ QueryClient ,
5+ QueryClientProvider ,
6+ useQuery ,
7+ } from "@tanstack/react-query" ;
8+ import { ChevronLeftIcon , ChevronRightIcon , SearchIcon } from "lucide-react" ;
19import Image from "next/image" ;
10+ import { useMemo , useState } from "react" ;
211import {
312 getAllWalletsList ,
413 getWalletInfo ,
514 type WalletId ,
615} from "thirdweb/wallets" ;
7- import { DocLink , InlineCode } from "../Document" ;
16+ import { DocLink } from "../Document/DocLink" ;
17+ import { InlineCode } from "../Document/InlineCode" ;
818import { Table , TBody , Td , Th , Tr } from "../Document/Table" ;
19+ import { Button } from "../ui/button" ;
20+ import { Input } from "../ui/input" ;
921
1022const specialWallets : {
1123 [ key in WalletId ] ?: boolean ;
@@ -14,44 +26,215 @@ const specialWallets: {
1426 smart : true ,
1527} ;
1628
17- export async function AllSupportedWallets ( ) {
18- const wallets = await getAllWalletsList ( ) ;
29+ const ITEMS_PER_PAGE = 20 ;
30+
31+ const queryClient = new QueryClient ( ) ;
32+
33+ export function AllSupportedWallets ( ) {
34+ return (
35+ < QueryClientProvider client = { queryClient } >
36+ < AllSupportedWalletsContent />
37+ </ QueryClientProvider >
38+ ) ;
39+ }
40+
41+ function AllSupportedWalletsContent ( ) {
42+ const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
43+ const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
44+
45+ const { data : wallets , isLoading : loading } = useQuery ( {
46+ queryKey : [ "allWalletsList" ] ,
47+ queryFn : async ( ) => {
48+ const allWallets = await getAllWalletsList ( ) ;
49+ return allWallets
50+ . filter ( ( w ) => ! ( w . id in specialWallets ) )
51+ . map ( ( w ) => ( {
52+ id : w . id ,
53+ name : w . name ,
54+ } ) ) ;
55+ } ,
56+ staleTime : 1000 * 60 * 5 , // 5 minutes
57+ } ) ;
58+
59+ const filteredWallets = useMemo ( ( ) => {
60+ if ( ! searchQuery ) return wallets || [ ] ;
61+ if ( ! wallets ) return [ ] ;
62+
63+ setCurrentPage ( 1 ) ;
64+ const query = searchQuery . toLowerCase ( ) ;
65+ return wallets . filter (
66+ ( wallet ) =>
67+ wallet . name . toLowerCase ( ) . includes ( query ) ||
68+ wallet . id . toLowerCase ( ) . includes ( query ) ,
69+ ) ;
70+ } , [ wallets , searchQuery ] ) ;
71+
72+ const totalPages = Math . ceil ( filteredWallets . length / ITEMS_PER_PAGE ) ;
73+ const startIndex = ( currentPage - 1 ) * ITEMS_PER_PAGE ;
74+ const endIndex = startIndex + ITEMS_PER_PAGE ;
75+ const currentWallets = filteredWallets . slice ( startIndex , endIndex ) ;
76+
77+ const handlePreviousPage = ( ) => {
78+ setCurrentPage ( ( prev ) => Math . max ( prev - 1 , 1 ) ) ;
79+ } ;
80+
81+ const handleNextPage = ( ) => {
82+ setCurrentPage ( ( prev ) => Math . min ( prev + 1 , totalPages ) ) ;
83+ } ;
84+
85+ const handlePageClick = ( page : number ) => {
86+ setCurrentPage ( page ) ;
87+ } ;
88+
89+ if ( loading ) {
90+ return (
91+ < div className = "flex items-center justify-center py-8" >
92+ < div className = "text-muted-foreground" > Loading wallets...</ div >
93+ </ div >
94+ ) ;
95+ }
1996
2097 return (
21- < Table >
22- < TBody >
23- < Tr >
24- < Th > Wallet </ Th >
25- < Th > ID </ Th >
26- </ Tr >
27-
28- { wallets
29- . filter ( ( w ) => ! ( w . id in specialWallets ) )
30- . map ( ( w ) => {
31- return (
32- < Tr key = { w . id } >
98+ < div className = "space-y-6" >
99+ { /* Search Input */ }
100+ < div className = "relative" >
101+ < SearchIcon className = "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
102+ < Input
103+ type = "text"
104+ placeholder = "Search wallets by name or ID..."
105+ value = { searchQuery }
106+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
107+ className = "pl-10"
108+ />
109+ </ div >
110+
111+ { /* Results count */ }
112+ < div className = "text-sm text-muted-foreground" >
113+ { filteredWallets . length === wallets ?. length
114+ ? `Showing ${ filteredWallets . length } wallets`
115+ : `Found ${ filteredWallets . length } of ${ wallets ?. length } wallets` }
116+ </ div >
117+
118+ { /* Table */ }
119+ < Table >
120+ < TBody >
121+ < Tr >
122+ < Th > Wallet</ Th >
123+ < Th > ID</ Th >
124+ </ Tr >
125+
126+ { currentWallets . length === 0 ? (
127+ < Tr >
128+ < Td >
129+ { searchQuery
130+ ? "No wallets found matching your search."
131+ : "No wallets available." }
132+ </ Td >
133+ </ Tr >
134+ ) : (
135+ currentWallets . map ( ( wallet ) => (
136+ < Tr key = { wallet . id } >
33137 < Td >
34138 < DocLink
35139 className = "flex flex-nowrap items-center gap-4 whitespace-nowrap"
36- href = { `/wallets/external-wallets/${ w . id } ` }
140+ href = { `/wallets/external-wallets/${ wallet . id } ` }
37141 >
38- < WalletImage id = { w . id } />
39- { w . name }
142+ < WalletImage id = { wallet . id as WalletId } />
143+ { wallet . name }
40144 </ DocLink >
41145 </ Td >
42146 < Td >
43- < InlineCode code = { `"${ w . id } "` } />
147+ < InlineCode code = { `"${ wallet . id } "` } />
44148 </ Td >
45149 </ Tr >
46- ) ;
47- } ) }
48- </ TBody >
49- </ Table >
150+ ) )
151+ ) }
152+ </ TBody >
153+ </ Table >
154+
155+ { /* Pagination */ }
156+ { totalPages > 1 && (
157+ < div className = "flex items-center justify-between" >
158+ < div className = "text-sm text-muted-foreground" >
159+ Page { currentPage } of { totalPages }
160+ { filteredWallets . length > 0 && (
161+ < span className = "ml-2" >
162+ (showing { startIndex + 1 } -
163+ { Math . min ( endIndex , filteredWallets . length ) } of{ " " }
164+ { filteredWallets . length } )
165+ </ span >
166+ ) }
167+ </ div >
168+
169+ < div className = "flex items-center space-x-2" >
170+ < Button
171+ variant = "outline"
172+ size = "sm"
173+ onClick = { handlePreviousPage }
174+ disabled = { currentPage === 1 }
175+ >
176+ < ChevronLeftIcon className = "size-4" />
177+ Previous
178+ </ Button >
179+
180+ { /* Page numbers */ }
181+ < div className = "flex items-center space-x-1" >
182+ { Array . from ( { length : Math . min ( 5 , totalPages ) } , ( _ , i ) => {
183+ let pageNumber : number ;
184+
185+ if ( totalPages <= 5 ) {
186+ pageNumber = i + 1 ;
187+ } else if ( currentPage <= 3 ) {
188+ pageNumber = i + 1 ;
189+ } else if ( currentPage >= totalPages - 2 ) {
190+ pageNumber = totalPages - 4 + i ;
191+ } else {
192+ pageNumber = currentPage - 2 + i ;
193+ }
194+
195+ return (
196+ < Button
197+ key = { pageNumber }
198+ variant = { currentPage === pageNumber ? "default" : "outline" }
199+ size = "sm"
200+ onClick = { ( ) => handlePageClick ( pageNumber ) }
201+ className = "min-w-[32px]"
202+ >
203+ { pageNumber }
204+ </ Button >
205+ ) ;
206+ } ) }
207+ </ div >
208+
209+ < Button
210+ variant = "outline"
211+ size = "sm"
212+ onClick = { handleNextPage }
213+ disabled = { currentPage === totalPages }
214+ >
215+ Next
216+ < ChevronRightIcon className = "size-4" />
217+ </ Button >
218+ </ div >
219+ </ div >
220+ ) }
221+ </ div >
50222 ) ;
51223}
52224
53- async function WalletImage ( props : { id : WalletId } ) {
54- const img = await getWalletInfo ( props . id , true ) ;
225+ function WalletImage ( props : { id : WalletId } ) {
226+ const { data : img } = useQuery ( {
227+ queryKey : [ "wallet-image" , props . id ] ,
228+ queryFn : ( ) => getWalletInfo ( props . id , true ) ,
229+ staleTime : 1000 * 60 * 60 * 24 , // 24 hours
230+ } ) ;
231+
232+ if ( ! img ) {
233+ return (
234+ < div className = "rounded-lg bg-muted" style = { { width : 44 , height : 44 } } />
235+ ) ;
236+ }
237+
55238 return (
56239 < Image alt = "" className = "rounded-lg" height = { 44 } src = { img } width = { 44 } />
57240 ) ;
0 commit comments