Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui-ux): api integration for block detail and list #150

Merged
merged 13 commits into from
May 22, 2023
57 changes: 48 additions & 9 deletions src/api/LatestDataApi.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { TransactionType } from "mockdata/TransactionData";
import dayjs from "dayjs";
import { NetworkConnection } from "@contexts/Environment";
import { RowData } from "@components/LatestDataTable";
import { getTimeAgo } from "shared/durationHelper";
import { getRewards } from "shared/getRewards";
import { NetworkConnection } from "@contexts/Environment";
import {
getBaseUrl,
MAIN_BLOCKS_URL,
MAIN_LATEST_BLOCK_URL,
MAIN_LATEST_TRANSACTION_URL,
} from "./index";

const MAX_ROW = 5;

function filterParams (params: { key: string; value }[]): string {
let queryParams = "?";
params.forEach((p) => {
if (p.value !== undefined && p.value.trim() !== "") {
queryParams += `${p.key}=${p.value}&`;
}
});

return queryParams;
}

export default {
getLatestBlocks: async (network: NetworkConnection): Promise<RowData[]> => {
const baseUrl = getBaseUrl(network);
Expand All @@ -18,14 +31,11 @@ export default {
const blockRows = Math.min(responseBlockData.length, MAX_ROW);

return responseBlockData.slice(0, blockRows).map((data) => {
const reward =
data.rewards !== undefined && data.rewards.length > 0
? data.rewards[0].reward
: 0;
const time = dayjs().unix() - dayjs(data.timestamp).unix();
const reward = getRewards(data.rewards);
const time = getTimeAgo(data.timestamp);
return {
transactionId: data.height,
tokenAmount: reward,
tokenAmount: reward.toFixed(),
txnOrBlockInfo: {
transactionsPerBlock: data.tx_count || null,
blockTimeInSec: null,
Expand All @@ -51,7 +61,8 @@ export default {
const transactionType = type?.includes("contract")
? TransactionType.ContractCall
: TransactionType.Transaction;
const time = dayjs().unix() - dayjs(data.timestamp).unix();
const time = getTimeAgo(data.timestamp);

return {
transactionId: data.hash,
tokenAmount: data.value,
Expand All @@ -64,4 +75,32 @@ export default {
};
});
},
getBlocks: async (
network: NetworkConnection,
blockNumber?: string,
itemsCount?: string
): Promise<any> => {
const baseUrl = getBaseUrl(network);

const params = filterParams([
{ key: "block_number", value: blockNumber },
{ key: "items_count", value: itemsCount },
{ key: "type", value: "block" },
]);

const resTxn = await fetch(`${baseUrl}/${MAIN_BLOCKS_URL}${params}`);
const responseBlockData = await resTxn.json();

return responseBlockData;
},
getBlock: async (
network: NetworkConnection,
blockId: string
): Promise<any> => {
const baseUrl = getBaseUrl(network);
const resTxn = await fetch(`${baseUrl}/${MAIN_BLOCKS_URL}/${blockId}`);
const responseBlockData = await resTxn.json();

return responseBlockData;
},
};
3 changes: 2 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { NetworkConnection } from "@contexts/Environment";
const BLOCKSCOUT_ENDPOINT_MAINNET = "https://base-goerli.blockscout.com"; // TODO: Replace with MainNet blockscout URL
const BLOCKSCOUT_ENDPOINT_TESTNET = "https://eth-goerli.blockscout.com"; // TODO: Replace with TestNet blockscout URL

export const MAIN_LATEST_TRANSACTION_URL = `api/v2/main-page/transactions`;
export const MAIN_LATEST_TRANSACTION_URL = "api/v2/main-page/transactions";
export const MAIN_LATEST_BLOCK_URL = "api/v2/main-page/blocks";
export const MAIN_BLOCKS_URL = "api/v2/blocks";

export const getBaseUrl = (network: NetworkConnection) => {
if (network === NetworkConnection.TestNet) {
Expand Down
182 changes: 182 additions & 0 deletions src/components/commons/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useEffect, useMemo, useState, PropsWithChildren } from "react";
import { FiArrowRight, FiArrowLeft } from "react-icons/fi";
import { useRouter } from "next/router";
import { Link } from "./Link";

interface PaginationProps<T> {
nextPageParams?: T & {
items_count: string;
page_number?: string;
};
}

export default function Pagination<T>({
nextPageParams: nextPageParamsProps,
}: PaginationProps<T>): JSX.Element {
const router = useRouter();
const pathName = router.pathname;

const currentPageNumber = Number.isNaN(Number(router.query.page_number))
? 1
: Number(router.query.page_number);
const nextPageParams = {
...nextPageParamsProps,
...{ page_number: currentPageNumber + 1 },
};

const [previousPagesParams, setPreviousPagesParams] = useState<any[]>([]);

const getPageQueryParams = (pageNumber: number) =>
previousPagesParams.find(
(page) => Number(page?.page_number) === pageNumber
);

const previousPageQuery = useMemo(
() => getPageQueryParams(Number(router.query.page_number) - 1),
[router.query]
);

const getPageButtons = (pageNumber) => {
/*
Page numbers are only limited to previous and next pages which will be displayed as such:
[1][2] - first page
[1][2][3] - page 2
[98][99] - last page
*/
const pageButton = {
previous: getPageQueryParams(pageNumber - 1),
current: {
...router.query,
items_count: router.query.items_count as string,
page_number: currentPageNumber,
},
next: nextPageParams,
};

if (nextPageParams === undefined) {
return [pageButton.previous, pageButton.current];
}
if (pageNumber === 1) {
return [pageButton.current, pageButton.next];
}
return [pageButton.previous, pageButton.current, pageButton.next];
};

useEffect(() => {
if (
!previousPagesParams.some(
(page) => page?.page_number === (router.query.page_number as string)
)
) {
// Store page query params to be used for previouPage button
setPreviousPagesParams([
...previousPagesParams,
{
...router.query,
items_count: router.query.items_count as string,
page_number: (router.query.page_number as string) ?? "1",
},
]);
}
}, [router.query]);

useEffect(() => {
// If pageNumber > 1 and previousPagesParams (local state) is cleared, go back to page 1
if (
Number(router.query.page_number) > 1 &&
previousPagesParams.length === 0
) {
setPreviousPagesParams([]);
router.push(pathName);
}
}, [router.query]);

return (
<div>
<div className="flex space-x-1">
{previousPageQuery && (
<NavigateButton
type="Prev"
query={previousPageQuery}
pathName={pathName}
>
<FiArrowLeft className="text-white-700" size={24} />
</NavigateButton>
)}

{getPageButtons(currentPageNumber)
.filter((page) => page)
.map((page) => (
<NumberButton
key={page.page_number ?? 1}
n={page.page_number}
active={currentPageNumber === page.page_number}
query={page}
pathName={pathName}
/>
))}
{nextPageParams && (
<NavigateButton
type="Next"
query={nextPageParams}
pathName={pathName}
>
<FiArrowRight className="text-white-700" size={24} />
</NavigateButton>
)}
</div>
</div>
);
}

interface NumberButtonProps {
n: number;
active: boolean;
pathName: string;
query: any;
}

function NumberButton({
n,
active,
query,
pathName,
}: NumberButtonProps): JSX.Element {
if (active) {
return (
<div className="bg-black-500 rounded h-6 w-6 flex items-center justify-center cursor-not-allowed">
<span className="font-medium text-white-50">{n}</span>
</div>
);
}

return (
<Link href={{ pathname: pathName, query }}>
<div className="rounded cursor-pointer h-6 w-6 flex items-center justify-center">
<span className="font-medium text-white-50">{n}</span>
</div>
</Link>
);
}

function NavigateButton({
children,
type,
query,
pathName,
}: PropsWithChildren<{
type: "Next" | "Prev";
pathName: string;
query: any;
}>): JSX.Element {
return (
<Link href={{ pathname: pathName, query }}>
<div
data-testid={`Pagination.${type}`}
className="text-white-700 cursor-pointer h-6 w-6 flex items-center justify-center"
>
{children}
</div>
</Link>
);
}
Loading