1+ "use client" ;
2+
13import Image from "next/image" ;
4+ import { useState , useEffect , useMemo } from "react" ;
25import {
36 getAllWalletsList ,
47 getWalletInfo ,
58 type WalletId ,
69} from "thirdweb/wallets" ;
710import { DocLink , InlineCode } from "../Document" ;
811import { 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
1016const 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