Files
js/apps/dashboard/src/contract-ui/tabs/shared-components/marketplace-table.tsx
T
jnsdls 575c3e3141 react-query v4 -> v5 (#4600)
<!-- start pr-codex -->

## PR-Codex overview
This PR updates loading states in various components to use `isPending` instead of `isLoading`.

### Detailed summary
- Replaced `isLoading` with `isPending` in multiple components for loading state handling.

> The following files were skipped due to too many changes: `apps/dashboard/src/contract-ui/tabs/manage/components/InstalledModulesTable.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayCustomers.ts`, `apps/dashboard/src/components/pay/PayConfig.tsx`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/server/chain-header.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/add-partner-form.client.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/components/PaymentHistory.tsx`, `apps/dashboard/src/contract-ui/tabs/split/components/distribute-button.tsx`, `apps/dashboard/src/components/onboarding/LinkWallet.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/components/TotalVolumePieChart.tsx`, `apps/dashboard/package.json`, `apps/dashboard/src/components/pay/PayAnalytics/hooks/usePayPurchases.ts`, `apps/dashboard/src/components/engine/configuration/local-config.tsx.tsx`, `apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/server/partners-table.tsx`, `apps/dashboard/src/contract-ui/components/solidity-inputs/string-input.tsx`, `apps/dashboard/src/components/settings/Account/Billing/index.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/update-partner-form.client.tsx`, `apps/dashboard/src/contract-ui/tabs/nfts/page.tsx`, `apps/dashboard/src/components/configure-networks/Form/IconUpload.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/hooks/use-create-ecosystem.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/create/components/client/create-ecosystem-form.client.tsx`, `apps/dashboard/src/components/engine/engine-instances-table.tsx`, `apps/dashboard/src/components/engine/contract-subscription/contract-subscriptions-table.tsx`, `apps/dashboard/src/components/pay/PayAnalytics/components/PayCustomersTable.tsx`, `apps/dashboard/src/pages/api/revalidate/publish.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-ecosystem.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-add-partner.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-delete-partner.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-partner.ts`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/live-stats.tsx`, `apps/dashboard/src/contract-ui/tabs/shared-components/marketplace-table.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/query/useQueryWithNetwork.ts`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/pay/components/webhooks.client.tsx`, `apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx`, `apps/dashboard/src/contract-ui/tabs/proposals/components/proposal.tsx`, `apps/dashboard/src/components/app-layouts/app.tsx`, `apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/integration-permissions-toggle.client.tsx`, `apps/dashboard/src/app/account/settings/AccountSettingsPageUI.tsx`, `apps/dashboard/src/components/cmd-k-search/index.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useLoggedInUser.ts`, `apps/dashboard/src/components/contract-components/hooks.ts`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useRegistry.ts`, `apps/dashboard/src/contract-ui/tabs/manage/components/ModuleForm.tsx`, `apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.tsx`, `apps/dashboard/src/components/storage/your-files.tsx`, `apps/dashboard/src/components/contract-functions/interactive-abi-function.tsx`, `apps/dashboard/src/components/ipfs-upload/dropzone.tsx`, `apps/dashboard/src/core-ui/nft-drawer/useNftDrawerTabs.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts`, `pnpm-lock.yaml`, `apps/dashboard/src/contract-ui/hooks/useRouteConfig.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2024-09-15 21:32:49 +00:00

335 lines
10 KiB
TypeScript

import { WalletAddress } from "@/components/blocks/wallet-address";
import {
ButtonGroup,
Center,
Flex,
Icon,
IconButton,
Select,
Skeleton,
Spinner,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
usePrevious,
} from "@chakra-ui/react";
import type { UseQueryResult } from "@tanstack/react-query";
import { MediaCell } from "components/contract-pages/table/table-columns/cells/media-cell";
import { ListingDrawer } from "contract-ui/tabs/shared-components/listing-drawer";
import {
type Dispatch,
type SetStateAction,
useEffect,
useMemo,
useState,
} from "react";
import { FiArrowRight } from "react-icons/fi";
import {
MdFirstPage,
MdLastPage,
MdNavigateBefore,
MdNavigateNext,
} from "react-icons/md";
import { type Cell, type Column, usePagination, useTable } from "react-table";
import type { ThirdwebContract } from "thirdweb";
import type {
DirectListing,
EnglishAuction,
} from "thirdweb/extensions/marketplace";
import { min } from "thirdweb/utils";
import { Button, Text } from "tw-components";
import { LISTING_STATUS } from "./types";
const tableColumns: Column<DirectListing | EnglishAuction>[] = [
{
Header: "Listing Id",
accessor: (row) => row.id.toString(),
},
{
Header: "Media",
accessor: (row) => row.asset.metadata,
// biome-ignore lint/suspicious/noExplicitAny: FIXME
Cell: (cell: any) => <MediaCell cell={cell} />,
},
{
Header: "Name",
accessor: (row) => row.asset.metadata.name ?? "N/A",
},
{
Header: "Creator",
accessor: (row) => row.creatorAddress,
// biome-ignore lint/suspicious/noExplicitAny: FIXME
Cell: ({ cell }: { cell: Cell<any, string> }) => (
<WalletAddress address={cell.value} />
),
},
{
Header: "Price",
accessor: (row) =>
(row as DirectListing)?.currencyValuePerToken ||
(row as EnglishAuction)?.buyoutCurrencyValue,
// biome-ignore lint/suspicious/noExplicitAny: FIXME
Cell: ({ cell }: { cell: Cell<any, any> }) => {
return (
<Text size="label.md" whiteSpace="nowrap">
{cell.value.displayValue} {cell.value.symbol}
</Text>
);
},
},
{
Header: "Status",
accessor: (row) => LISTING_STATUS[row.status],
},
];
interface MarketplaceTableProps {
contract: ThirdwebContract;
getAllQueryResult: UseQueryResult<DirectListing[] | EnglishAuction[]>;
getValidQueryResult: UseQueryResult<DirectListing[] | EnglishAuction[]>;
totalCountQuery: UseQueryResult<bigint>;
queryParams: {
count: number;
start: number;
};
setQueryParams: Dispatch<
SetStateAction<{
count: number;
start: number;
}>
>;
type: "direct-listings" | "english-auctions";
}
const DEFAULT_QUERY_STATE = { count: 50, start: 0 };
export const MarketplaceTable: React.FC<MarketplaceTableProps> = ({
contract,
getAllQueryResult,
getValidQueryResult,
totalCountQuery,
queryParams,
setQueryParams,
type,
}) => {
const [listingsToShow, setListingsToShow_] = useState<"all" | "valid">("all");
const setListingsToShow = (value: "all" | "valid") => {
setQueryParams(DEFAULT_QUERY_STATE);
setListingsToShow_(value);
};
const prevData = usePrevious(
listingsToShow === "all"
? getAllQueryResult?.data
: getValidQueryResult?.data,
);
const renderData = useMemo(() => {
if (listingsToShow === "all") {
return getAllQueryResult?.data || prevData;
}
return getValidQueryResult?.data || prevData;
}, [getAllQueryResult, getValidQueryResult, listingsToShow, prevData]);
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns: tableColumns,
// biome-ignore lint/suspicious/noExplicitAny: FIXME
data: (renderData as any) || [],
initialState: {
pageSize: queryParams.count,
pageIndex: 0,
},
manualPagination: true,
pageCount: Math.max(
Math.ceil(
Number(
// To avoid overflow issue
min(totalCountQuery.data || 0n, BigInt(Number.MAX_SAFE_INTEGER)),
) / queryParams.count,
),
1,
),
},
// FIXME: re-work tables and pagination with @tanstack/table@latest - which (I believe) does not need this workaround anymore
// eslint-disable-next-line react-compiler/react-compiler
usePagination,
);
// FIXME: re-work tables and pagination with @tanstack/table@latest - which (I believe) does not need this workaround anymore
// eslint-disable-next-line no-restricted-syntax
useEffect(() => {
setQueryParams({ start: pageIndex * pageSize, count: pageSize });
}, [pageIndex, pageSize, setQueryParams]);
const [tokenRow, setTokenRow] = useState<
DirectListing | EnglishAuction | null
>(null);
return (
<Flex gap={4} direction="column">
<ButtonGroup size="sm" variant="outline" isAttached>
<Button
onClick={() => setListingsToShow("all")}
variant={listingsToShow === "all" ? "solid" : "outline"}
>
All
</Button>
<Button
onClick={() => setListingsToShow("valid")}
variant={listingsToShow === "valid" ? "solid" : "outline"}
>
Valid
</Button>
</ButtonGroup>
<TableContainer maxW="100%" className="relative">
{((listingsToShow === "all" && getAllQueryResult.isFetching) ||
(listingsToShow === "valid" && getValidQueryResult.isFetching)) && (
<Spinner
color="primary"
size="xs"
position="absolute"
top={2}
right={4}
/>
)}
<ListingDrawer
contract={contract}
data={tokenRow}
isOpen={!!tokenRow}
onClose={() => setTokenRow(null)}
type={type}
/>
<Table {...getTableProps()}>
<Thead>
{headerGroups.map((headerGroup, headerGroupIndex) => (
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
<Tr {...headerGroup.getHeaderGroupProps()} key={headerGroupIndex}>
{headerGroup.headers.map((column, columnIndex) => (
<Th
{...column.getHeaderProps()}
border="none"
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
key={columnIndex}
>
<Text as="label" size="label.sm" color="faded">
{column.render("Header")}
</Text>
</Th>
))}
{/* // Need to add an empty header for the drawer button */}
<Th border="none" />
</Tr>
))}
</Thead>
<Tbody {...getTableBodyProps()} position="relative">
{page.map((row, rowIndex) => {
prepareRow(row);
return (
<Tr
{...row.getRowProps()}
role="group"
className="hover:bg-muted/50"
style={{ cursor: "pointer" }}
onClick={() => setTokenRow(row.original)}
borderBottomWidth={1}
_last={{ borderBottomWidth: 0 }}
borderColor="borderColor"
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
key={rowIndex}
>
{row.cells.map((cell, cellIndex) => (
<Td
{...cell.getCellProps()}
borderBottomWidth="inherit"
borderColor="borderColor"
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
key={cellIndex}
>
{cell.render("Cell")}
</Td>
))}
<Td borderBottomWidth="inherit" borderColor="borderColor">
<Icon as={FiArrowRight} />
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<Center w="100%">
<Flex gap={2} direction="row" align="center">
<IconButton
isDisabled={!canPreviousPage || totalCountQuery.isLoading}
aria-label="first page"
icon={<Icon as={MdFirstPage} />}
onClick={() => gotoPage(0)}
/>
<IconButton
isDisabled={!canPreviousPage || totalCountQuery.isLoading}
aria-label="previous page"
icon={<Icon as={MdNavigateBefore} />}
onClick={() => previousPage()}
/>
<Text whiteSpace="nowrap">
Page <strong>{pageIndex + 1}</strong> of{" "}
<Skeleton
as="span"
display="inline"
isLoaded={totalCountQuery.isSuccess}
>
<strong>{pageCount}</strong>
</Skeleton>
</Text>
<IconButton
isDisabled={!canNextPage || totalCountQuery.isLoading}
aria-label="next page"
icon={<Icon as={MdNavigateNext} />}
onClick={() => nextPage()}
/>
<IconButton
isDisabled={!canNextPage || totalCountQuery.isLoading}
aria-label="last page"
icon={<Icon as={MdLastPage} />}
onClick={() => gotoPage(pageCount - 1)}
/>
<Select
onChange={(e) => {
setPageSize(Number.parseInt(e.target.value as string, 10));
}}
value={pageSize}
isDisabled={totalCountQuery.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>
);
};