diff --git a/src/api/BlocksApi.ts b/src/api/BlocksApi.ts index 03279eb1..f5094f8c 100644 --- a/src/api/BlocksApi.ts +++ b/src/api/BlocksApi.ts @@ -5,7 +5,11 @@ import { MAIN_BLOCKS_URL, wrapResponse, } from "./index"; -import { BlockProps, RawTxnWithPaginationProps } from "./types"; +import { + BlockProps, + BlockWithPaginationProps, + RawTxnWithPaginationProps, +} from "./types"; export default { getBlocks: async ( @@ -55,15 +59,6 @@ export default { }, }; -interface BlockWithPaginationProps { - items: BlockProps[]; - next_page_params?: { - block_number?: string; - items_count?: string; - index?: string; - }; -} - export interface BlockNextPageParamsProps { block_number: string; items_count: string; diff --git a/src/api/SmartContractApi.ts b/src/api/SmartContractApi.ts index 245ec515..e0e9d5fd 100644 --- a/src/api/SmartContractApi.ts +++ b/src/api/SmartContractApi.ts @@ -1,31 +1,12 @@ import { - SMART_CONTRACT_URL, VERIFY_SMART_CONTRACT_URL, - filterParams, getBaseUrl, wrapResponse, } from "@api/index"; -import { - SmartContractPageParamsProps, - SmartContractWithPaginationProps, - CompilerType, -} from "@api/types"; +import { SmartContractPageParamsProps, CompilerType } from "@api/types"; import { NetworkConnection } from "@contexts/Environment"; export default { - getSmartContracts: async ( - network: NetworkConnection, - smartContractId?: string, - itemsCount?: string - ): Promise => { - const baseUrl = getBaseUrl(network); - const params = filterParams([ - { key: "smart_contract_id", value: smartContractId }, - { key: "items_count", value: itemsCount }, - ]); - const res = await fetch(`${baseUrl}/${SMART_CONTRACT_URL}${params}`); - return wrapResponse(res); - }, verifySmartContract: async ( network: NetworkConnection, data, diff --git a/src/api/types.ts b/src/api/types.ts index b2461cb9..42899728 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,5 +1,15 @@ import { TokenProps } from "./TokenApi"; +export interface BlocksPageParamsProps { + block_number: string; + items_count: string; +} + +export interface BlockWithPaginationProps { + items: BlockProps[]; + next_page_params: BlocksPageParamsProps; +} + export interface WalletAddressToken { address: string; type: string; diff --git a/src/components/LatestDataTable.tsx b/src/components/LatestDataTable.tsx index afe15371..bb2ecc9e 100644 --- a/src/components/LatestDataTable.tsx +++ b/src/components/LatestDataTable.tsx @@ -14,7 +14,7 @@ type DataType = "blocks" | "transactions"; interface Props { type: DataType; title: string; - data: RowData[]; + data: RowData[] | []; listPageUrl: string; detailsPageBaseUrl: string; containerClass?: string; @@ -47,7 +47,11 @@ export default function LatestDataTable({
{isLoading ? ( ) : ( diff --git a/src/components/skeletonLoaders/MainTableBlocksLoader.tsx b/src/components/skeletonLoaders/MainTableBlocksLoader.tsx new file mode 100644 index 00000000..f06769e9 --- /dev/null +++ b/src/components/skeletonLoaders/MainTableBlocksLoader.tsx @@ -0,0 +1,50 @@ +import clsx from "clsx"; + +export default function MainTableTxs() { + const valueStyle = "h-3 md:h-4 lg:h-6 bg-dark-200 rounded"; + return ( +
+
+
+
+ + {/* desktop */} +
+
+
+
+
+
+ + {/* tablet */} +
+
+
+
+
+
+
+
+
+ + {/* mobile */} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/skeletonLoaders/MainTableTxsLoader.tsx b/src/components/skeletonLoaders/MainTableTxsLoader.tsx new file mode 100644 index 00000000..5f5dcac4 --- /dev/null +++ b/src/components/skeletonLoaders/MainTableTxsLoader.tsx @@ -0,0 +1,51 @@ +import clsx from "clsx"; + +export default function MainTableTxs() { + const valueStyle = "h-3 md:h-4 lg:h-6 bg-dark-200 rounded"; + return ( +
+
+
+
+
+
+ + {/* from & to */} +
+
+
+
+
+ + {/* amount & timeago */} +
+
+
+
+ + {/* timeago in mobile */} +
+
+
+
+
+
+ ); +} diff --git a/src/components/skeletonLoaders/RowItemLoader.tsx b/src/components/skeletonLoaders/RowItemLoader.tsx deleted file mode 100644 index f4d44f19..00000000 --- a/src/components/skeletonLoaders/RowItemLoader.tsx +++ /dev/null @@ -1,38 +0,0 @@ -export default function RowItemLoader(): JSX.Element { - return ( -
-
-
- -
- -
-
- - {/* last col */} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); -} diff --git a/src/components/skeletonLoaders/SkeletonLoader.tsx b/src/components/skeletonLoaders/SkeletonLoader.tsx index d82f007f..646b0810 100644 --- a/src/components/skeletonLoaders/SkeletonLoader.tsx +++ b/src/components/skeletonLoaders/SkeletonLoader.tsx @@ -1,10 +1,11 @@ import BlockRowLoader from "pages/blocks/_components/BlockRowLoader"; import TransactionRowLoader from "./TransactionRowLoader"; -import RowItemLoader from "./RowItemLoader"; +import MainTableBlocksLoader from "./MainTableBlocksLoader"; import SmartContractLoader from "./SmartContractLoader"; import AddressTokenLoader from "./AddressTokenLoader"; import TokenHoldersLoader from "./TokenHoldersLoader"; import AddressLogsLoader from "./AddressLogsLoader"; +import MainTableTxsLoader from "./MainTableTxsLoader"; interface SkeletonLoaderProp { rows: number; @@ -13,7 +14,8 @@ interface SkeletonLoaderProp { export enum SkeletonLoaderScreen { // Main page - "MainTable" = "MainTable", + "MainTableBlocks" = "MainTableBlocks", + "MainTableTxs" = "MainTableTxs", // Child pages "Tx" = "Tx", @@ -30,11 +32,19 @@ export function SkeletonLoader(props: SkeletonLoaderProp): JSX.Element { // eslint-disable-next-line default-case switch (screen) { - case SkeletonLoaderScreen.MainTable: + case SkeletonLoaderScreen.MainTableBlocks: return ( <> {skeletonRow.map((row) => ( - + + ))} + + ); + case SkeletonLoaderScreen.MainTableTxs: + return ( + <> + {skeletonRow.map((row) => ( + ))} ); diff --git a/src/pages/blocks/index.tsx b/src/pages/blocks/index.tsx index 8fe809e9..1911b575 100644 --- a/src/pages/blocks/index.tsx +++ b/src/pages/blocks/index.tsx @@ -1,55 +1,51 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; import Container from "@components/commons/Container"; import GradientCardContainer from "@components/commons/GradientCardContainer"; import Pagination from "@components/commons/Pagination"; import { SearchBar } from "layouts/components/searchbar/SearchBar"; import { - GetServerSidePropsContext, - GetServerSidePropsResult, - InferGetServerSidePropsType, -} from "next"; -import { isNumeric } from "shared/textHelper"; -import { NetworkConnection } from "@contexts/Environment"; -import BlocksApi, { BlockNextPageParamsProps, BlockQueryParamsProps, } from "@api/BlocksApi"; -import { BlockProps } from "@api/types"; +import { BlockProps, BlocksPageParamsProps } from "@api/types"; import PaginationLoader from "@components/skeletonLoaders/PaginationLoader"; import { SkeletonLoader, SkeletonLoaderScreen, } from "@components/skeletonLoaders/SkeletonLoader"; +import { useNetwork } from "@contexts/NetworkContext"; +import { useGetBlocksMutation } from "@store/blocks"; +import { sleep } from "shared/sleep"; import BlockRow from "./_components/BlockRow"; -interface PageProps { - blocks: BlockProps[]; - next_page_params: BlockNextPageParamsProps; -} +export default function Blocks() { + const [blocks, setBlocks] = useState([]); + const [nextPage, setNextPage] = useState(); -function BlockPagination({ - nextPageParams, -}: { - nextPageParams: BlockNextPageParamsProps; -}) { - return ( - - nextPageParams={ - nextPageParams - ? { - block_number: nextPageParams.block_number, - items_count: nextPageParams.items_count, - type: "block", - } - : undefined - } - /> - ); -} + const [isLoading, setIsLoading] = useState(true); + const { connection } = useNetwork(); + const [trigger] = useGetBlocksMutation(); + const router = useRouter(); + + const params = router.query; + const fetchBlocks = async () => { + setIsLoading(true); + const data = await trigger({ + network: connection, + blockNumber: params.block_number as string, + itemsCount: params.items_count as string, + }).unwrap(); + setBlocks(data.items); + setNextPage(data.next_page_params); + await sleep(150); // added timeout to prevent flicker + setIsLoading(false); + }; + + useEffect(() => { + fetchBlocks(); + }, [params.page_number]); -export default function Blocks({ - data, - isLoading, -}: InferGetServerSidePropsType) { return ( @@ -60,21 +56,19 @@ export default function Blocks({ {isLoading && ( )} - +
{isLoading ? ( ) : ( - data.blocks.map((item) => ( - - )) + blocks.map((item) => ) )}
{isLoading && ( )} - +
@@ -82,34 +76,22 @@ export default function Blocks({ ); } -export async function getServerSideProps( - context: GetServerSidePropsContext -): Promise> { - const { network, ...params } = context.query; - // Avoid fetching if some params are not valid - const hasInvalidParams = - !isNumeric(params?.block_number as string) || - !isNumeric(params?.items_count as string) || - !isNumeric(params?.page_number as string); - - try { - // Fetch data from external API - const blocks = hasInvalidParams - ? await BlocksApi.getBlocks(network as NetworkConnection) - : await BlocksApi.getBlocks( - network as NetworkConnection, - params?.block_number as string, - params?.items_count as string - ); - - const data = { - blocks: blocks.items as BlockProps[], - next_page_params: blocks.next_page_params as BlockNextPageParamsProps, - }; - - // Pass data to the page via props - return { props: { data } }; - } catch (e) { - return { notFound: true }; - } +function BlockPagination({ + nextPageParams, +}: { + nextPageParams?: BlockNextPageParamsProps; +}) { + return ( + + nextPageParams={ + nextPageParams + ? { + block_number: nextPageParams.block_number, + items_count: nextPageParams.items_count, + type: "block", + } + : undefined + } + /> + ); } diff --git a/src/pages/contracts/index.tsx b/src/pages/contracts/index.tsx index a1749c50..7aed539b 100644 --- a/src/pages/contracts/index.tsx +++ b/src/pages/contracts/index.tsx @@ -1,16 +1,13 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; import GradientCardContainer from "@components/commons/GradientCardContainer"; import { SearchBar } from "layouts/components/searchbar/SearchBar"; -import { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; import { SmartContractPageParamsProps, SmartContractListItemProps, } from "@api/types"; -import SmartContractApi, { - SmartContractQueryParamsProps, -} from "@api/SmartContractApi"; -import { NetworkConnection } from "@contexts/Environment"; +import { SmartContractQueryParamsProps } from "@api/SmartContractApi"; import Pagination from "@components/commons/Pagination"; -import { isNumeric } from "shared/textHelper"; import clsx from "clsx"; import Button from "@components/commons/Button"; import PaginationLoader from "@components/skeletonLoaders/PaginationLoader"; @@ -18,38 +15,38 @@ import { SkeletonLoader, SkeletonLoaderScreen, } from "@components/skeletonLoaders/SkeletonLoader"; +import { useNetwork } from "@contexts/NetworkContext"; +import { useGetContractsMutation } from "@store/contract"; +import { sleep } from "shared/sleep"; import VerifiedContractRow from "./_components/VerifiedContractRow"; -interface PageProps { - items: SmartContractListItemProps[]; - next_page_params: SmartContractPageParamsProps; -} +export default function VerifiedContracts() { + const [contracts, setContracts] = useState([]); + const [nextPage, setNextPage] = useState(); -function SmartContractPagination({ - nextPageParams, - containerClass, -}: { - nextPageParams: SmartContractPageParamsProps; - containerClass?: string; -}) { - return ( - - nextPageParams={ - nextPageParams - ? { - smart_contract_id: nextPageParams.smart_contract_id, - items_count: nextPageParams.items_count, - type: "smartcontracts", - } - : undefined - } - containerClass={clsx("!justify-start md:!justify-end", containerClass)} - pathname="/contracts" - /> - ); -} + const [isLoading, setIsLoading] = useState(true); + const { connection } = useNetwork(); + const [trigger] = useGetContractsMutation(); + const router = useRouter(); + + const params = router.query; + const fetchTransactions = async () => { + setIsLoading(true); + const data = await trigger({ + network: connection, + smartContractId: params.smart_contract_id as string, + itemsCount: params.items_count as string, + }).unwrap(); + setContracts(data.items); + setNextPage(data.next_page_params); + await sleep(150); // added timeout to prevent flicker + setIsLoading(false); + }; + + useEffect(() => { + fetchTransactions(); + }, [params.page_number]); -export default function VerifiedContracts({ data, isLoading }) { return (
@@ -72,7 +69,7 @@ export default function VerifiedContracts({ data, isLoading }) { {isLoading ? ( ) : ( - data.items.map((item) => ( + contracts.map((item) => ( )) )} @@ -80,7 +77,7 @@ export default function VerifiedContracts({ data, isLoading }) { {isLoading && ( )} - +
@@ -88,29 +85,26 @@ export default function VerifiedContracts({ data, isLoading }) { ); } -export async function getServerSideProps( - context: GetServerSidePropsContext -): Promise> { - const { network, ...params } = context.query; - // Avoid fetching if some params are not valid - const hasInvalidParams = - !isNumeric(params?.smart_contract_id as string) || - !isNumeric(params?.items_count as string) || - !isNumeric(params?.page_number as string); - try { - const data = hasInvalidParams - ? await SmartContractApi.getSmartContracts(network as NetworkConnection) - : await SmartContractApi.getSmartContracts( - network as NetworkConnection, - params?.smart_contract_id as string, - params?.items_count as string - ); - return { - props: { - data, - }, - }; - } catch (e) { - return { notFound: true }; - } +function SmartContractPagination({ + nextPageParams, + containerClass, +}: { + nextPageParams?: SmartContractPageParamsProps; + containerClass?: string; +}) { + return ( + + nextPageParams={ + nextPageParams + ? { + smart_contract_id: nextPageParams.smart_contract_id, + items_count: nextPageParams.items_count, + type: "smartcontracts", + } + : undefined + } + containerClass={clsx("!justify-start md:!justify-end", containerClass)} + pathname="/contracts" + /> + ); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 2971bba1..01f96938 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,17 +1,96 @@ // import GroupStatisticCard from "@components/GroupStatisticCard"; // import TokenStatsDisplay from "@components/TokenStatsDisplay"; +import { useEffect, useState } from "react"; +import { utils } from "ethers"; +import { useNetwork } from "@contexts/NetworkContext"; +import { + useGetLatestBlocksMutation, + useGetLatestTransactionsMutation, +} from "@store/latestData"; +import { getRewards } from "shared/getRewards"; +import { getTimeAgo } from "shared/durationHelper"; +import { BURN_ADDRESS_HASH } from "shared/constants"; +import { + getTokenTransfers, + getTransactionType, +} from "shared/transactionDataHelper"; import HomeTitle from "@components/HomeTitle"; import { SearchBar } from "layouts/components/searchbar/SearchBar"; -import { GetServerSidePropsResult, InferGetServerSidePropsType } from "next"; -import { RowData } from "@components/types"; import LatestDataTable from "@components/LatestDataTable"; -import LatestDataApi from "@api/LatestDataApi"; +import { RowData } from "@components/types"; + +const MAX_ROW = 5; + +export default function Home() { + const [isLoadingBlocks, setIsLoadingBlocks] = useState(true); + const [isLoadingTxs, setIsLoadingTxs] = useState(true); + const [latestBlocks, setLatestBlocks] = useState([]); + const [latestTransactions, setLatestTransactions] = useState([]); + + const { connection } = useNetwork(); + const [getLatestBlocks] = useGetLatestBlocksMutation(); + const [getTransactions] = useGetLatestTransactionsMutation(); + + const fetchLatestBlocks = async () => { + const results = await getLatestBlocks({ network: connection }).unwrap(); + const blockRows = Math.min(results.length, MAX_ROW); + const slicedBlocks = results.slice(0, blockRows).map((data) => { + const reward = getRewards(data.rewards); + const time = getTimeAgo(data.timestamp); + return { + transactionId: data.height.toString(), + tokenAmount: reward, + txnOrBlockInfo: { + transactionsPerBlock: data.tx_count?.toString(), + blockTimeInSec: null, + }, + time, + }; + }); + setLatestBlocks(slicedBlocks); + setIsLoadingBlocks(false); + }; + + const fetchLatestTxs = async () => { + const txResults = await getTransactions({ network: connection }).unwrap(); + const txRows = Math.min(txResults.length, MAX_ROW); + const slicedTxs = txResults.slice(0, txRows).map((data) => { + const toHash = data.to?.hash ?? BURN_ADDRESS_HASH; + const isFromContract = data.from.is_contract; + const isToContract = data.to?.is_contract ?? false; + const tokenTransfers = + data.token_transfers?.length > 0 + ? getTokenTransfers(data.token_transfers) + : []; + const transactionType = getTransactionType({ + toHash, + tokenTransfers, + isFromContract, + isToContract, + txTypes: data.tx_types, + }); + const time = getTimeAgo(data.timestamp); + + return { + transactionId: data.hash, + tokenAmount: utils.formatEther(data.value ?? "0"), + txnOrBlockInfo: { + from: data.from.hash ?? BURN_ADDRESS_HASH, + to: data.to?.hash ?? BURN_ADDRESS_HASH, + transactionType, + }, + time, + }; + }); + setLatestTransactions(slicedTxs); + setIsLoadingTxs(false); + }; + + useEffect(() => { + fetchLatestBlocks(); + fetchLatestTxs(); + }, []); -export default function Home({ - latestTransactions, - latestBlocks, - isLoading, -}: InferGetServerSidePropsType) { return ( <> @@ -27,7 +106,7 @@ export default function Home({ listPageUrl="/txs" detailsPageBaseUrl="/tx" containerClass="pt-8 md:pt-16" - isLoading={isLoading} + isLoading={isLoadingTxs} />
); } - -interface LatestDataProps { - latestTransactions: RowData[]; - latestBlocks: RowData[]; - isLoading?: boolean; -} - -export async function getServerSideProps( - context -): Promise> { - try { - const { network } = context.query; - const latestTransactions = await LatestDataApi.getLatestTransactions( - network - ); - const latestBlocks = await LatestDataApi.getLatestBlocks(network); - return { - props: { - latestTransactions, - latestBlocks, - }, - }; - } catch (e) { - return { - props: { - latestTransactions: [], - latestBlocks: [], - }, - }; - } -} diff --git a/src/pages/tokens/index.tsx b/src/pages/tokens/index.tsx index 9c7291df..9baca0a0 100644 --- a/src/pages/tokens/index.tsx +++ b/src/pages/tokens/index.tsx @@ -1,11 +1,8 @@ +import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import GradientCardContainer from "@components/commons/GradientCardContainer"; import { SearchBar } from "layouts/components/searchbar/SearchBar"; -import { - GetServerSidePropsContext, - GetServerSidePropsResult, - InferGetServerSidePropsType, -} from "next"; +import { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; import { NetworkConnection } from "@contexts/Environment"; import Pagination from "@components/commons/Pagination"; import { isAlphanumeric, isNumeric } from "shared/textHelper"; @@ -19,6 +16,9 @@ import TokensApi, { TokenQueryParamsProps, } from "@api/TokensApi"; import { RawTokenI } from "@api/types"; +import { sleep } from "shared/sleep"; +import { useNetwork } from "@contexts/NetworkContext"; +import { useGetTokensMutation } from "@store/token"; import TokenRow from "./_components/TokenRow"; interface PageProps { @@ -26,35 +26,37 @@ interface PageProps { next_page_params: TokenNextPageParamsProps; } -function TokenPagination({ - nextPageParams, -}: { - nextPageParams?: TokenNextPageParamsProps; -}) { - return ( - - pathname="/tokens" - nextPageParams={ - nextPageParams - ? { - items_count: nextPageParams.items_count, - contract_address_hash: nextPageParams.contract_address_hash, - holder_count: nextPageParams.holder_count, - is_name_null: nextPageParams.is_name_null, - market_cap: nextPageParams.market_cap ?? "null", - name: nextPageParams.name, - } - : undefined - } - /> - ); -} +export default function Tokens() { + const [tokens, setTokens] = useState([]); + const [nextPage, setNextPage] = useState(); -export default function Tokens({ - data, - isLoading, -}: InferGetServerSidePropsType) { + const [isLoading, setIsLoading] = useState(true); + const { connection } = useNetwork(); + const [trigger] = useGetTokensMutation(); const router = useRouter(); + + const params = router.query; + const fetchTransactions = async () => { + setIsLoading(true); + const data = await trigger({ + network: connection, + itemsCount: params.items_count as string, + contractAddressHash: params.contract_address_hash as string, + holderCount: params.holder_count as string, + isNameNull: params.is_name_null as string, + marketCap: params.market_cap as string, + name: params.name as string, + }).unwrap(); + setTokens(data.items); + setNextPage(data.next_page_params); + await sleep(150); // added timeout to prevent flicker + setIsLoading(false); + }; + + useEffect(() => { + fetchTransactions(); + }, [params.page_number]); + const pageNumber = Number(router.query.page_number ?? 0); const numberOfItems = 50; // Page number > 1, then add numberOfItems * pageNumber @@ -71,12 +73,12 @@ export default function Tokens({ {isLoading && ( )} - +
{isLoading ? ( - + ) : ( - data.tokens.map((token, index) => ( + tokens.map((token, index) => ( )} - +
@@ -133,3 +135,27 @@ export async function getServerSideProps( return { notFound: true }; } } + +function TokenPagination({ + nextPageParams, +}: { + nextPageParams?: TokenNextPageParamsProps; +}) { + return ( + + pathname="/tokens" + nextPageParams={ + nextPageParams + ? { + items_count: nextPageParams.items_count, + contract_address_hash: nextPageParams.contract_address_hash, + holder_count: nextPageParams.holder_count, + is_name_null: nextPageParams.is_name_null, + market_cap: nextPageParams.market_cap ?? "null", + name: nextPageParams.name, + } + : undefined + } + /> + ); +} diff --git a/src/pages/txs/index.tsx b/src/pages/txs/index.tsx index e0dfc156..e8434c42 100644 --- a/src/pages/txs/index.tsx +++ b/src/pages/txs/index.tsx @@ -1,15 +1,10 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; import GradientCardContainer from "@components/commons/GradientCardContainer"; import { SearchBar } from "layouts/components/searchbar/SearchBar"; -import TransactionsApi, { TxnQueryParamsProps } from "@api/TransactionsApi"; -import { - GetServerSidePropsContext, - GetServerSidePropsResult, - InferGetServerSidePropsType, -} from "next"; -import { NetworkConnection } from "@contexts/Environment"; +import { TxnQueryParamsProps } from "@api/TransactionsApi"; import Pagination from "@components/commons/Pagination"; import { RawTransactionI, TxnNextPageParamsProps } from "@api/types"; -import { isNumeric } from "shared/textHelper"; import { SkeletonLoader, SkeletonLoaderScreen, @@ -17,36 +12,38 @@ import { import PaginationLoader from "@components/skeletonLoaders/PaginationLoader"; import TransactionRow from "@components/commons/TransactionRow"; import { transformTransactionData } from "shared/transactionDataHelper"; +import { useNetwork } from "@contexts/NetworkContext"; +import { useGetTransactionsMutation } from "@store/transactions"; +import { sleep } from "shared/sleep"; -interface PageProps { - transactions: RawTransactionI[]; - next_page_params: TxnNextPageParamsProps; -} +export default function Transactions() { + const [transactions, setTransactions] = useState([]); + const [nextPage, setNextPage] = useState(); -function TxnPagination({ - nextPageParams, -}: { - nextPageParams: TxnNextPageParamsProps; -}) { - return ( - - nextPageParams={ - nextPageParams - ? { - block_number: nextPageParams.block_number, - items_count: nextPageParams.items_count, - index: nextPageParams.index, - } - : undefined - } - /> - ); -} + const [isLoading, setIsLoading] = useState(true); + const { connection } = useNetwork(); + const [trigger] = useGetTransactionsMutation(); + const router = useRouter(); + + const params = router.query; + const fetchTransactions = async () => { + setIsLoading(true); + const data = await trigger({ + network: connection, + blockNumber: params.block_number as string, + itemsCount: params.items_count as string, + index: params.index as string, + }).unwrap(); + setTransactions(data.items); + setNextPage(data.next_page_params); + await sleep(150); // added timeout to prevent flicker + setIsLoading(false); + }; + + useEffect(() => { + fetchTransactions(); + }, [params.page_number]); -export default function Transactions({ - data, - isLoading, -}: InferGetServerSidePropsType) { return (
@@ -59,12 +56,12 @@ export default function Transactions({ {isLoading && ( )} - +
{isLoading ? ( - + ) : ( - data.transactions.map((tx) => ( + transactions.map((tx) => ( )} - +
@@ -84,35 +81,22 @@ export default function Transactions({ ); } -export async function getServerSideProps( - context: GetServerSidePropsContext -): Promise> { - const { network, ...params } = context.query; - // Avoid fetching if some params are not valid - const hasInvalidParams = - !isNumeric(params?.block_number as string) || - !isNumeric(params?.items_count as string) || - !isNumeric(params?.page_number as string) || - !isNumeric(params?.index as string); - - try { - // Fetch data from external API - const txs = hasInvalidParams - ? await TransactionsApi.getTransactions(network as NetworkConnection) - : await TransactionsApi.getTransactions( - network as NetworkConnection, - params?.block_number as string, - params?.items_count as string, - params?.index as string - ); - const data = { - transactions: txs.items, - next_page_params: txs.next_page_params as TxnNextPageParamsProps, - }; - - // Pass data to the page via props - return { props: { data } }; - } catch (e) { - return { notFound: true }; - } +function TxnPagination({ + nextPageParams, +}: { + nextPageParams?: TxnNextPageParamsProps; +}) { + return ( + + nextPageParams={ + nextPageParams + ? { + block_number: nextPageParams.block_number, + items_count: nextPageParams.items_count, + index: nextPageParams.index, + } + : undefined + } + /> + ); } diff --git a/src/store/blocks.ts b/src/store/blocks.ts new file mode 100644 index 00000000..bd929133 --- /dev/null +++ b/src/store/blocks.ts @@ -0,0 +1,34 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { filterParams, getBaseUrl, MAIN_BLOCKS_URL } from "@api/index"; +import { NetworkConnection } from "@contexts/Environment"; +import { BlockWithPaginationProps } from "@api/types"; + +export const blocksApi = createApi({ + reducerPath: "blocks", + baseQuery: fetchBaseQuery({ baseUrl: "/" }), + endpoints: (builder) => ({ + getBlocks: builder.mutation< + BlockWithPaginationProps, + { + network: NetworkConnection; + blockNumber?: string; + itemsCount?: string; + } + >({ + query: ({ network, blockNumber, itemsCount }) => { + const baseUrl = getBaseUrl(network); + const params = filterParams([ + { key: "block_number", value: blockNumber }, + { key: "items_count", value: itemsCount }, + { key: "type", value: "block" }, + ]); + return { + url: `${baseUrl}/${MAIN_BLOCKS_URL}${params}`, + method: "GET", + }; + }, + }), + }), +}); + +export const { useGetBlocksMutation } = blocksApi; diff --git a/src/store/contract.ts b/src/store/contract.ts index 9fbdea7a..40d13c6b 100644 --- a/src/store/contract.ts +++ b/src/store/contract.ts @@ -1,8 +1,9 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; -import { SMART_CONTRACT_URL, getBaseUrl } from "@api/index"; +import { SMART_CONTRACT_URL, filterParams, getBaseUrl } from "@api/index"; import { ContractMethodType, SmartContractMethod, + SmartContractWithPaginationProps, StateMutability, } from "@api/types"; import { NetworkConnection } from "@contexts/Environment"; @@ -67,6 +68,25 @@ export const contractApi = createApi({ baseUrl: "/", // This will be overridden by query url below, need to dynamically get the base url based on network }), endpoints: (builder) => ({ + getContracts: builder.mutation< + SmartContractWithPaginationProps, + { + network: NetworkConnection; + smartContractId: string; + itemsCount: string; + } + >({ + query: ({ network, smartContractId, itemsCount }) => { + const params = filterParams([ + { key: "smart_contract_id", value: smartContractId }, + { key: "items_count", value: itemsCount }, + ]); + return { + url: `${getBaseUrl(network)}/${SMART_CONTRACT_URL}${params}`, + method: "GET", + }; + }, + }), getContract: builder.query< RawContractProps, { network: NetworkConnection; addressHash: string } @@ -117,6 +137,6 @@ export const contractVerificationApi = createApi({ }), }); -export const { useGetContractQuery } = contractApi; +export const { useGetContractsMutation, useGetContractQuery } = contractApi; export const { useGetContractMethodsQuery } = contractMethodsApi; export const { useGetVerificationConfigQuery } = contractVerificationApi; diff --git a/src/store/latestData.ts b/src/store/latestData.ts new file mode 100644 index 00000000..dc24f2d9 --- /dev/null +++ b/src/store/latestData.ts @@ -0,0 +1,38 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { + getBaseUrl, + MAIN_LATEST_BLOCK_URL, + MAIN_LATEST_TRANSACTION_URL, +} from "@api/index"; +import { NetworkConnection } from "@contexts/Environment"; + +export const latestDataApi = createApi({ + reducerPath: "latest", + baseQuery: fetchBaseQuery({ baseUrl: "/" }), + endpoints: (builder) => ({ + getLatestBlocks: builder.mutation({ + query: ({ network }) => { + const baseUrl = getBaseUrl(network); + return { + url: `${baseUrl}/${MAIN_LATEST_BLOCK_URL}`, + method: "GET", + }; + }, + }), + getLatestTransactions: builder.mutation< + any, + { network: NetworkConnection } + >({ + query: ({ network }) => { + const baseUrl = getBaseUrl(network); + return { + url: `${baseUrl}/${MAIN_LATEST_TRANSACTION_URL}`, + method: "GET", + }; + }, + }), + }), +}); + +export const { useGetLatestBlocksMutation, useGetLatestTransactionsMutation } = + latestDataApi; diff --git a/src/store/reducer/rootReducer.ts b/src/store/reducer/rootReducer.ts index ac69503d..3b230f1f 100644 --- a/src/store/reducer/rootReducer.ts +++ b/src/store/reducer/rootReducer.ts @@ -1,5 +1,7 @@ import { configureStore } from "@reduxjs/toolkit"; import { addressApi } from "@store/address"; +import { blocksApi } from "@store/blocks"; +import { transactionsApi } from "@store/transactions"; import { contractApi, contractMethodsApi, @@ -7,25 +9,32 @@ import { } from "@store/contract"; import { searchApi } from "@store/search"; import { tokenApi } from "@store/token"; +import { latestDataApi } from "@store/latestData"; export function initializeStore() { return configureStore({ reducer: { [searchApi.reducerPath]: searchApi.reducer, + [blocksApi.reducerPath]: blocksApi.reducer, + [transactionsApi.reducerPath]: transactionsApi.reducer, [contractApi.reducerPath]: contractApi.reducer, [contractMethodsApi.reducerPath]: contractMethodsApi.reducer, [contractVerificationApi.reducerPath]: contractVerificationApi.reducer, [tokenApi.reducerPath]: tokenApi.reducer, [addressApi.reducerPath]: addressApi.reducer, + [latestDataApi.reducerPath]: latestDataApi.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }).concat( searchApi.middleware, + transactionsApi.middleware, + blocksApi.middleware, contractApi.middleware, contractMethodsApi.middleware, contractVerificationApi.middleware, tokenApi.middleware, - addressApi.middleware + addressApi.middleware, + latestDataApi.middleware ), }); } diff --git a/src/store/token.ts b/src/store/token.ts index 91ff2dcd..d84c586d 100644 --- a/src/store/token.ts +++ b/src/store/token.ts @@ -12,6 +12,7 @@ import { RawTokensWithPaginationProps, TokensListPageParamsProps, } from "@api/types"; +import { RawTokenWithPaginationProps } from "@api/TokensApi"; // Token Holders export interface TokenHolderProps { @@ -60,6 +61,42 @@ export const tokenApi = createApi({ baseUrl: "/", }), endpoints: (builder) => ({ + getTokens: builder.mutation< + RawTokenWithPaginationProps, + { + network: NetworkConnection; + contractAddressHash?: string; + holderCount?: string; + isNameNull?: string; + itemsCount?: string; + marketCap?: string; + name?: string; + } + >({ + query: ({ + network, + contractAddressHash, + holderCount, + isNameNull, + itemsCount, + marketCap, + name, + }) => { + const baseUrl = getBaseUrl(network); + const params = filterParams([ + { key: "contract_address_hash", value: contractAddressHash }, + { key: "holder_count", value: holderCount }, + { key: "is_name_null", value: isNameNull }, + { key: "items_count", value: itemsCount }, + { key: "market_cap", value: marketCap }, + { key: "name", value: name }, + ]); + return { + url: `${baseUrl}/${TOKENS_URL}${params}`, + method: "GET", + }; + }, + }), getContractTokens: builder.mutation< RawTokensWithPaginationProps, { @@ -139,6 +176,7 @@ export const tokenApi = createApi({ }); export const { + useGetTokensMutation, useGetContractTokensMutation, useGetTokenHoldersMutation, useGetTokenTransfersMutation, diff --git a/src/store/transactions.ts b/src/store/transactions.ts new file mode 100644 index 00000000..3627f4a3 --- /dev/null +++ b/src/store/transactions.ts @@ -0,0 +1,35 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { filterParams, getBaseUrl, TRANSACTIONS_URL } from "@api/index"; +import { NetworkConnection } from "@contexts/Environment"; +import { RawTxnWithPaginationProps } from "@api/types"; + +export const transactionsApi = createApi({ + reducerPath: "transactions", + baseQuery: fetchBaseQuery({ baseUrl: "/" }), + endpoints: (builder) => ({ + getTransactions: builder.mutation< + RawTxnWithPaginationProps, + { + network: NetworkConnection; + blockNumber: string; + itemsCount: string; + index: string; + } + >({ + query: ({ network, blockNumber, itemsCount, index }) => { + const baseUrl = getBaseUrl(network); + const params = filterParams([ + { key: "block_number", value: blockNumber }, + { key: "items_count", value: itemsCount }, + { key: "index", value: index }, + ]); + return { + url: `${baseUrl}/${TRANSACTIONS_URL}${params}`, + method: "GET", + }; + }, + }), + }), +}); + +export const { useGetTransactionsMutation } = transactionsApi;