Skip to content
This repository has been archived by the owner on Jun 19, 2024. It is now read-only.

Commit

Permalink
refactor: update Accounts components to use new contract hooks and ad…
Browse files Browse the repository at this point in the history
…d pagination (#2669)

### TL;DR
Implement pagination and count querying in the accounts page.

### What changed?
1. Modified `accounts-count.tsx` to use `totalAccounts` from `thirdweb/extensions/erc4337` instead of fetching all accounts.
2. Updated `accounts-table.tsx` to incorporate pagination with the help of `totalAccounts` and `getAccounts` functions from `thirdweb/extensions/erc4337`.
3. Adjusted `page.tsx` to pass the contract to `AccountsCount` and `AccountsTable` components instead of accountsQuery.
4. Introduced pagination controls and page size selection in `accounts-table.tsx`.

### How to test?
1. Navigate to the accounts page.
2. Verify that the total number of accounts is displayed correctly.
3. Check the pagination functionality by navigating through pages and adjusting the page size.

### Why make this change?
To improve the user experience on the accounts page by enabling efficient data fetching and display through pagination.

---
  • Loading branch information
jnsdls committed Jun 14, 2024
1 parent 7f17d24 commit 9ae2f58
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 35 deletions.
19 changes: 10 additions & 9 deletions src/contract-ui/tabs/accounts/components/accounts-count.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ import {
StatLabel,
StatNumber,
} from "@chakra-ui/react";
import { useAccounts } from "@thirdweb-dev/react";
import { Card } from "tw-components";
import { totalAccounts } from "thirdweb/extensions/erc4337";
import type { ThirdwebContract } from "thirdweb";
import { useReadContract } from "thirdweb/react";

interface AccountsCountProps {
accountsQuery: ReturnType<typeof useAccounts>;
}
type AccountsCountProps = {
contract: ThirdwebContract;
};

export const AccountsCount: React.FC<AccountsCountProps> = ({
accountsQuery,
}) => {
export const AccountsCount: React.FC<AccountsCountProps> = ({ contract }) => {
const totalAccountsQuery = useReadContract(totalAccounts, { contract });
return (
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={{ base: 3, md: 6 }}>
<Card as={Stat}>
<StatLabel mb={{ base: 1, md: 0 }}>Total Accounts</StatLabel>
<Skeleton isLoaded={accountsQuery.isSuccess}>
<StatNumber>{accountsQuery?.data?.length || 0}</StatNumber>
<Skeleton isLoaded={totalAccountsQuery.isSuccess}>
<StatNumber>{totalAccountsQuery.data?.toString()}</StatNumber>
</Skeleton>
</Card>
</SimpleGrid>
Expand Down
148 changes: 127 additions & 21 deletions src/contract-ui/tabs/accounts/components/accounts-table.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
import { Flex } from "@chakra-ui/react";
import {
Center,
Flex,
Icon,
IconButton,
Select,
Skeleton,
} from "@chakra-ui/react";
import { createColumnHelper } from "@tanstack/react-table";
import { useAccounts } from "@thirdweb-dev/react";
import { TWTable } from "components/shared/TWTable";
import { useRouter } from "next/router";
import { useState } from "react";
import {
MdFirstPage,
MdLastPage,
MdNavigateBefore,
MdNavigateNext,
} from "react-icons/md";
import { ThirdwebContract } from "thirdweb";
import { totalAccounts, getAccounts } from "thirdweb/extensions/erc4337";
import { useReadContract } from "thirdweb/react";
import { Text, TrackedCopyButton } from "tw-components";

const columnHelper = createColumnHelper<{ account: string }>();
Expand All @@ -25,31 +41,121 @@ const columns = [
}),
];

interface AccountsTableProps {
accountsQuery: ReturnType<typeof useAccounts>;
}
type AccountsTableProps = {
contract: ThirdwebContract;
};

export const AccountsTable: React.FC<AccountsTableProps> = ({
accountsQuery,
}) => {
export const AccountsTable: React.FC<AccountsTableProps> = ({ contract }) => {
const router = useRouter();
const network = useDashboardEVMChainId();

const [currentPage, setCurrentPage] = useState(0);
// default page size of 25
const [pageSize, setPageSize] = useState(25);

const totalAccountsQuery = useReadContract(totalAccounts, { contract });
const accountsQuery = useReadContract(getAccounts, {
contract,
start: BigInt(currentPage * pageSize),
end: BigInt(currentPage * pageSize + pageSize),
});

// the total size should never be more than max int size (that would be hella wallets!)
// so converting the totalAccounts to a nunber should be safe here
const totalPages = Math.ceil(Number(totalAccountsQuery.data || 0) / pageSize);

const canNextPage = currentPage < totalPages - 1;
const canPreviousPage = currentPage > 0;

const data = accountsQuery.data || [];

return (
<TWTable
title="account"
columns={columns}
data={data.map((account) => ({ account }))}
showMore={{
pageSize: 50,
}}
isLoading={accountsQuery.isLoading}
isFetched={accountsQuery.isFetched}
onRowClick={(row) => {
router.push(`/${network}/${row.account}`);
}}
/>
<Flex direction="column" gap={4}>
{/* TODO add a skeleton when loading*/}
<TWTable
title="account"
columns={columns}
data={data.map((account) => ({ account }))}
isLoading={accountsQuery.isLoading}
isFetched={accountsQuery.isFetched}
onRowClick={(row) => {
router.push(`/${network}/${row.account}`);
}}
/>
{/* pagination */}
<Center w="100%">
<Flex gap={2} direction="row" align="center">
<IconButton
isDisabled={totalAccountsQuery.isLoading}
aria-label="first page"
icon={<Icon as={MdFirstPage} />}
onClick={() => setCurrentPage(0)}
/>
<IconButton
isDisabled={totalAccountsQuery.isLoading || !canPreviousPage}
aria-label="previous page"
icon={<Icon as={MdNavigateBefore} />}
onClick={() => {
setCurrentPage((curr) => {
if (curr > 0) {
return curr - 1;
}
return curr;
});
}}
/>
<Text whiteSpace="nowrap">
Page <strong>{currentPage + 1}</strong> of{" "}
<Skeleton
as="span"
display="inline"
isLoaded={totalAccountsQuery.isSuccess}
>
<strong>{totalPages}</strong>
</Skeleton>
</Text>
<IconButton
isDisabled={totalAccountsQuery.isLoading || !canNextPage}
aria-label="next page"
icon={<Icon as={MdNavigateNext} />}
onClick={() =>
setCurrentPage((curr) => {
if (curr < totalPages - 1) {
return curr + 1;
}
return curr;
})
}
/>
<IconButton
isDisabled={totalAccountsQuery.isLoading || !canNextPage}
aria-label="last page"
icon={<Icon as={MdLastPage} />}
onClick={() => setCurrentPage(totalPages - 1)}
/>

<Select
onChange={(e) => {
const newPageSize = parseInt(e.target.value as string, 10);
// compute the new page number based on the new page size
const newPage = Math.floor(
(currentPage * pageSize) / newPageSize,
);
setCurrentPage(newPage);
setPageSize(newPageSize);
}}
value={pageSize}
isDisabled={totalAccountsQuery.isLoading}
>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="250">250</option>
<option value="500">500</option>
</Select>
</Flex>
</Center>
</Flex>
);
};
//
24 changes: 19 additions & 5 deletions src/contract-ui/tabs/accounts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AccountsTable } from "./components/accounts-table";
import { CreateAccountButton } from "./components/create-account-button";
import { Box, ButtonGroup, Flex } from "@chakra-ui/react";
import { useAccounts, useContract } from "@thirdweb-dev/react";
import { useContract } from "@thirdweb-dev/react";
import { extensionDetectedState } from "components/buttons/ExtensionDetectButton";
import {
Card,
Expand All @@ -11,6 +11,10 @@ import {
TrackedLinkButton,
} from "tw-components";
import { AccountsCount } from "./components/accounts-count";
import { useMemo } from "react";
import { getContract } from "thirdweb";
import { defineDashboardChain } from "lib/v5-adapter";
import { thirdwebClient } from "@/constants/client";

interface AccountsPageProps {
contractAddress?: string;
Expand All @@ -20,14 +24,24 @@ export const AccountsPage: React.FC<AccountsPageProps> = ({
contractAddress,
}) => {
const contractQuery = useContract(contractAddress);
const accountsQuery = useAccounts(contractQuery?.contract);

const v5Contract = useMemo(() => {
if (!contractQuery.contract) {
return null;
}
return getContract({
address: contractQuery.contract.getAddress(),
chain: defineDashboardChain(contractQuery.contract.chainId),
client: thirdwebClient,
});
}, [contractQuery.contract]);

const detectedFeature = extensionDetectedState({
contractQuery,
feature: ["AccountFactory"],
});

if (contractQuery.isLoading) {
if (contractQuery.isLoading || !v5Contract) {
return null;
}

Expand Down Expand Up @@ -79,8 +93,8 @@ export const AccountsPage: React.FC<AccountsPageProps> = ({
<CreateAccountButton contractQuery={contractQuery} />
</ButtonGroup>
</Flex>
<AccountsCount accountsQuery={accountsQuery} />
<AccountsTable accountsQuery={accountsQuery} />
<AccountsCount contract={v5Contract} />
<AccountsTable contract={v5Contract} />
</Flex>
);
};

0 comments on commit 9ae2f58

Please sign in to comment.