11"use client" ;
22
3- import { Flex , IconButton , Select , Skeleton } from "@chakra-ui/react" ;
4- import { createColumnHelper } from "@tanstack/react-table" ;
5- import { Legacy_CopyButton } from "chakra/button" ;
6- import { Text } from "chakra/text" ;
7- import {
8- ChevronFirstIcon ,
9- ChevronLastIcon ,
10- ChevronLeftIcon ,
11- ChevronRightIcon ,
12- } from "lucide-react" ;
3+ import { ArrowUpRightIcon } from "lucide-react" ;
4+ import Link from "next/link" ;
135import { useMemo , useState } from "react" ;
146import type { ThirdwebContract } from "thirdweb" ;
157import { getAccounts , totalAccounts } from "thirdweb/extensions/erc4337" ;
168import { useReadContract } from "thirdweb/react" ;
17- import { TWTable } from "@/components/blocks/TWTable" ;
9+ import { PaginationButtons } from "@/components/blocks/pagination-buttons" ;
10+ import { CopyTextButton } from "@/components/ui/CopyTextButton" ;
11+ import { Skeleton } from "@/components/ui/skeleton" ;
12+ import {
13+ Table ,
14+ TableBody ,
15+ TableCell ,
16+ TableContainer ,
17+ TableHead ,
18+ TableHeader ,
19+ TableRow ,
20+ } from "@/components/ui/table" ;
1821import { useChainSlug } from "@/hooks/chains/chainSlug" ;
19- import { useDashboardRouter } from "@/lib/DashboardRouter" ;
2022import type { ProjectMeta } from "../../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types" ;
2123import { buildContractPagePath } from "../../_utils/contract-page-path" ;
2224
23- const columnHelper = createColumnHelper < { account : string } > ( ) ;
24-
25- const columns = [
26- columnHelper . accessor ( "account" , {
27- cell : ( info ) => (
28- < Flex align = "center" gap = { 2 } >
29- < Text fontFamily = "mono" > { info . getValue ( ) } </ Text >
30- < Legacy_CopyButton
31- aria-label = "Copy account address"
32- colorScheme = "primary"
33- value = { info . getValue ( ) }
34- />
35- </ Flex >
36- ) ,
37- header : "Account" ,
38- } ) ,
39- ] ;
25+ const pageSize = 10 ;
4026
4127type AccountsTableProps = {
4228 contract : ThirdwebContract ;
4329 projectMeta : ProjectMeta | undefined ;
4430} ;
4531
46- export const AccountsTable : React . FC < AccountsTableProps > = ( {
47- contract,
48- projectMeta,
49- } ) => {
50- const router = useDashboardRouter ( ) ;
32+ export function AccountsTable ( { contract, projectMeta } : AccountsTableProps ) {
5133 const chainSlug = useChainSlug ( contract . chain . id ) ;
5234
5335 const [ currentPage , setCurrentPage ] = useState ( 0 ) ;
54- // default page size of 25
55- const [ pageSize , setPageSize ] = useState ( 25 ) ;
5636
5737 const totalAccountsQuery = useReadContract ( totalAccounts , { contract } ) ;
5838
@@ -72,7 +52,7 @@ export const AccountsTable: React.FC<AccountsTableProps> = ({
7252 ) ;
7353 }
7454 return currentPage * pageSize + pageSize ;
75- } , [ currentPage , pageSize , totalAccountsQuery . data ] ) ;
55+ } , [ currentPage , totalAccountsQuery . data ] ) ;
7656
7757 const accountsQuery = useReadContract ( getAccounts , {
7858 contract,
@@ -82,105 +62,91 @@ export const AccountsTable: React.FC<AccountsTableProps> = ({
8262 } ) ;
8363
8464 const totalPages = Math . ceil ( totalAccountsNum / pageSize ) ;
85-
86- const canNextPage = currentPage < totalPages - 1 ;
87- const canPreviousPage = currentPage > 0 ;
65+ const showPagination = totalPages > 1 ;
8866
8967 const data = accountsQuery . data || [ ] ;
9068
9169 return (
92- < Flex direction = "column" gap = { 4 } >
70+ < div className = "border rounded-lg overflow-hidden" >
9371 { /* TODO add a skeleton when loading*/ }
94- < TWTable
95- columns = { columns }
96- data = { data . map ( ( account ) => ( { account } ) ) }
97- isFetched = { accountsQuery . isFetched }
98- isPending = { accountsQuery . isPending }
99- onRowClick = { ( row ) => {
100- const accountContractPagePath = buildContractPagePath ( {
101- chainIdOrSlug : chainSlug . toString ( ) ,
102- contractAddress : row . account ,
103- projectMeta,
104- } ) ;
105-
106- router . push ( accountContractPagePath ) ;
107- } }
108- title = "account"
109- />
110- { /* pagination */ }
111- < div className = "flex w-full items-center justify-center" >
112- < Flex align = "center" direction = "row" gap = { 2 } >
113- < IconButton
114- aria-label = "first page"
115- icon = { < ChevronFirstIcon className = "size-4" /> }
116- isDisabled = { totalAccountsQuery . isPending }
117- onClick = { ( ) => setCurrentPage ( 0 ) }
118- />
119- < IconButton
120- aria-label = "previous page"
121- icon = { < ChevronLeftIcon className = "size-4" /> }
122- isDisabled = { totalAccountsQuery . isPending || ! canPreviousPage }
123- onClick = { ( ) => {
124- setCurrentPage ( ( curr ) => {
125- if ( curr > 0 ) {
126- return curr - 1 ;
127- }
128- return curr ;
129- } ) ;
130- } }
131- />
132- < Text whiteSpace = "nowrap" >
133- Page < strong > { currentPage + 1 } </ strong > of{ " " }
134- < Skeleton
135- as = "span"
136- display = "inline"
137- isLoaded = { totalAccountsQuery . isSuccess }
138- >
139- < strong > { totalPages } </ strong >
140- </ Skeleton >
141- </ Text >
142- < IconButton
143- aria-label = "next page"
144- icon = { < ChevronRightIcon className = "size-4" /> }
145- isDisabled = { totalAccountsQuery . isPending || ! canNextPage }
146- onClick = { ( ) =>
147- setCurrentPage ( ( curr ) => {
148- if ( curr < totalPages - 1 ) {
149- return curr + 1 ;
150- }
151- return curr ;
152- } )
153- }
154- />
155- < IconButton
156- aria-label = "last page"
157- icon = { < ChevronLastIcon className = "size-4" /> }
158- isDisabled = { totalAccountsQuery . isPending || ! canNextPage }
159- onClick = { ( ) => setCurrentPage ( totalPages - 1 ) }
72+ < TableContainer className = "border-0 rounded-none" >
73+ < Table >
74+ < TableHeader >
75+ < TableRow >
76+ < TableHead > Account</ TableHead >
77+ </ TableRow >
78+ </ TableHeader >
79+ < TableBody >
80+ { accountsQuery . isPending &&
81+ new Array ( pageSize ) . fill ( 0 ) . map ( ( _ , index ) => (
82+ // biome-ignore lint/suspicious/noArrayIndexKey: ok
83+ < SkeletonRow key = { index } />
84+ ) ) }
85+
86+ { ! accountsQuery . isPending &&
87+ data . map ( ( account ) => {
88+ const accountContractPagePath = buildContractPagePath ( {
89+ chainIdOrSlug : chainSlug . toString ( ) ,
90+ contractAddress : account ,
91+ projectMeta,
92+ } ) ;
93+
94+ return (
95+ < TableRow linkBox key = { account } className = "hover:bg-muted/50" >
96+ < TableCell className = "py-6" >
97+ < Link
98+ href = { accountContractPagePath }
99+ target = "_blank"
100+ rel = "noopener noreferrer"
101+ className = "before:absolute before:inset-0"
102+ aria-label = "View account"
103+ />
104+
105+ < div className = "flex items-center justify-between gap-2" >
106+ < CopyTextButton
107+ textToShow = { account }
108+ textToCopy = { account }
109+ variant = "ghost"
110+ tooltip = "Copy account address"
111+ copyIconPosition = "right"
112+ className = "z-10 relative"
113+ />
114+
115+ < ArrowUpRightIcon className = "size-4 text-muted-foreground" />
116+ </ div >
117+ </ TableCell >
118+ </ TableRow >
119+ ) ;
120+ } ) }
121+ </ TableBody >
122+ </ Table >
123+
124+ { ! accountsQuery . isPending && data . length === 0 && (
125+ < div className = "px-4 text-center py-20 flex items-center justify-center text-muted-foreground" >
126+ No accounts
127+ </ div >
128+ ) }
129+ </ TableContainer >
130+
131+ { showPagination && (
132+ < div className = "py-4 border-t bg-card" >
133+ < PaginationButtons
134+ activePage = { currentPage + 1 }
135+ totalPages = { totalPages }
136+ onPageClick = { ( page ) => setCurrentPage ( page - 1 ) }
160137 />
138+ </ div >
139+ ) }
140+ </ div >
141+ ) ;
142+ }
161143
162- < Select
163- isDisabled = { totalAccountsQuery . isPending }
164- onChange = { ( e ) => {
165- const newPageSize = Number . parseInt ( e . target . value as string , 10 ) ;
166- // compute the new page number based on the new page size
167- const newPage = Math . floor (
168- ( currentPage * pageSize ) / newPageSize ,
169- ) ;
170- setCurrentPage ( newPage ) ;
171- setPageSize ( newPageSize ) ;
172- } }
173- value = { pageSize }
174- >
175- < option value = "25" > 25</ option >
176- < option value = "50" > 50</ option >
177- < option value = "100" > 100</ option >
178- < option value = "250" > 250</ option >
179- < option value = "500" > 500</ option >
180- </ Select >
181- </ Flex >
182- </ div >
183- </ Flex >
144+ function SkeletonRow ( ) {
145+ return (
146+ < TableRow >
147+ < TableCell className = "py-6" >
148+ < Skeleton className = "h-6 w-[364px]" />
149+ </ TableCell >
150+ </ TableRow >
184151 ) ;
185- } ;
186- //
152+ }
0 commit comments