Skip to content

Commit

Permalink
feat: block + tx list
Browse files Browse the repository at this point in the history
  • Loading branch information
jagnani73 committed May 16, 2024
1 parent 7eddb43 commit d1a47ed
Show file tree
Hide file tree
Showing 9 changed files with 522 additions and 190 deletions.
2 changes: 1 addition & 1 deletion src/components/Atoms/Timestamp/Timestamp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const Timestamp: React.FC<TimestampProps> = ({
);

return (
<span className="inline-flex items-center gap-x-1 text-foreground-light dark:text-foreground-dark">
<span className="inline-flex items-center gap-x-1">
{timestampParser(
timestamp,
relativeTime ? "relative" : "descriptive"
Expand Down
17 changes: 17 additions & 0 deletions src/components/Molecules/Block/BlocksList/BlocksList.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type Meta, type StoryObj } from "@storybook/react";
import { BlocksList as BlocksListComponent } from "./BlocksList";

type Story = StoryObj<typeof BlocksListComponent>;

const meta: Meta<typeof BlocksListComponent> = {
title: "Molecules/Block/Blocks List",
component: BlocksListComponent,
};

export default meta;

export const BlocksList: Story = {
args: {
chain_name: "eth-mainnet",
},
};
140 changes: 140 additions & 0 deletions src/components/Molecules/Block/BlocksList/BlocksList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Address } from "@/components/Atoms";
import { Timestamp } from "@/components/Atoms";
import { TableHeaderSorting, TableList } from "@/components/Shared";
import { defaultErrorMessage } from "@/utils/constants/shared.constants";
import { timestampParser } from "@/utils/functions";
import { None, Some, type Option } from "@/utils/option";
import { useGoldRush } from "@/utils/store";
import { type BlocksListProps } from "@/utils/types/molecules.types";
import { type CovalentAPIError } from "@/utils/types/shared.types";
import { type Pagination, type Block } from "@covalenthq/client-sdk";
import { ColumnDef } from "@tanstack/react-table";
import { useCallback, useEffect, useState } from "react";

export const BlocksList: React.FC<BlocksListProps> = ({
chain_name,
page_size = 10,
}) => {
const { covalentClient } = useGoldRush();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [maybeResult, setMaybeResult] =
useState<Option<Block[] | null>>(None);
const [pagination, setPagination] = useState<Pagination | null>(null);

useEffect(() => {
updateResult(null);
}, [chain_name, page_size]);

const updateResult = useCallback(
async (_pagination: Pagination | null) => {
try {
setMaybeResult(None);
setErrorMessage(null);
const { data, ...error } =
await covalentClient.BaseService.getBlockHeightsByPage(
chain_name,
timestampParser(new Date(), "YYYY MM DD"),
"2100-01-01",
{
pageNumber: _pagination?.page_number ?? 0,
pageSize: _pagination?.page_size ?? page_size,
}
);
if (error.error) {
throw error;
}
setPagination(data.pagination);
setMaybeResult(new Some(data.items));
} catch (error: CovalentAPIError | any) {
setErrorMessage(error?.error_message ?? defaultErrorMessage);
setMaybeResult(new Some(null));
console.error(error);
}
},
[chain_name]
);

const handleOnChangePagination = (updatedPagination: Pagination) => {
setPagination(updatedPagination);
updateResult(updatedPagination);
};

const columns: ColumnDef<Block>[] = [
{
accessorKey: "height",
id: "height",
header: ({ column }) => (
<TableHeaderSorting<Block>
align="left"
header="Height"
column={column}
/>
),
cell: ({ row }) => row.original.height.toLocaleString(),
},
{
accessorKey: "signed_at",
id: "signed_at",
header: ({ column }) => (
<TableHeaderSorting<Block>
align="left"
header="Signed At"
column={column}
/>
),
cell: ({ row }) => (
<Timestamp
timestamp={row.original.signed_at}
defaultType="relative"
/>
),
},
{
accessorKey: "block_hash",
id: "block_hash",
header: ({ column }) => (
<TableHeaderSorting<Block>
align="left"
header="Block Hash"
column={column}
/>
),
cell: ({ row }) => <Address address={row.original.block_hash} />,
},
{
accessorKey: "gas_used",
id: "gas_used",
header: ({ column }) => (
<TableHeaderSorting<Block>
align="left"
header="Gas Used"
column={column}
/>
),
cell: ({ row }) =>
`${((row.original.gas_used / row.original.gas_limit) * 100).toFixed(2)}%`,
},
{
accessorKey: "gas_limit",
id: "gas_limit",
header: ({ column }) => (
<TableHeaderSorting<Block>
align="left"
header="Gas Limit"
column={column}
/>
),
cell: ({ row }) => row.original.gas_limit.toLocaleString(),
},
];

return (
<TableList<Block>
columns={columns}
errorMessage={errorMessage}
maybeData={maybeResult}
pagination={pagination}
onChangePaginationHandler={handleOnChangePagination}
/>
);
};
160 changes: 66 additions & 94 deletions src/components/Molecules/Block/LatestBlocks/LatestBlocks.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Address } from "@/components/Atoms";
import { Timestamp } from "@/components/Atoms";
import { CardDetail } from "@/components/Shared";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import {
Expand All @@ -12,36 +10,28 @@ import { timestampParser } from "@/utils/functions";
import { None, Some, type Option } from "@/utils/option";
import { useGoldRush } from "@/utils/store";
import { type LatestBlocksProps } from "@/utils/types/molecules.types";
import {
type CovalentAPIError,
type CardDetailProps,
} from "@/utils/types/shared.types";
import { type CovalentAPIError } from "@/utils/types/shared.types";
import { type Block } from "@covalenthq/client-sdk";
import { ExternalLinkIcon } from "@radix-ui/react-icons";
import { useEffect, useState } from "react";

export const LatestBlocks: React.FC<LatestBlocksProps> = ({
chain_name,
limit = 5,
on_view_details,
}) => {
export const LatestBlocks: React.FC<LatestBlocksProps> = ({ chain_name }) => {
const { covalentClient } = useGoldRush();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [maybeResult, setMaybeResult] =
useState<Option<Block[] | null>>(None);

useEffect(() => {
(async () => {
setMaybeResult(None);
setErrorMessage(null);
try {
setMaybeResult(None);
setErrorMessage(null);
const { data, ...error } =
await covalentClient.BaseService.getBlockHeightsByPage(
chain_name,
timestampParser(new Date(), "YYYY MM DD"),
"2100-01-01",
{
pageSize: limit,
pageSize: 5,
}
);
if (error.error) {
Expand All @@ -54,85 +44,67 @@ export const LatestBlocks: React.FC<LatestBlocksProps> = ({
console.error(error);
}
})();
}, [chain_name, limit]);
}, [chain_name]);

return maybeResult.match({
None: () => (
<>
{new Array(limit).fill(null).map(() => (
<Skeleton key={Math.random()} size={GRK_SIZES.LARGE} />
))}
</>
),
Some: (blocks) =>
errorMessage ? (
<p className="col-span-5">{errorMessage}</p>
) : blocks ? (
blocks.map((block) => (
<Card
key={block.height}
className="flex w-full flex-col rounded border border-secondary-light p-2 dark:border-secondary-dark dark:bg-background-dark dark:text-white"
>
{(
[
{
heading: "BLOCK HEIGHT",
content: block.height.toLocaleString(),
},
{
heading: "SIGNED AT",
content: (
<Timestamp
timestamp={block.signed_at}
defaultType="relative"
/>
),
},
{
heading: "BLOCK HASH",
content: (
<Address
address={block.block_hash}
show_copy_icon={false}
/>
),
},
{
heading: "GAS USED",
content: `${(
(block.gas_used / block.gas_limit) *
100
).toFixed(2)}%`,
},
{
heading: "GAS LIMIT",
content: block.gas_limit.toLocaleString(),
},
] as CardDetailProps[]
).map((props) => (
<CardDetail
key={props.heading?.toString()}
wrapperClassName="flex justify-between"
{...props}
/>
))}
return (
<Card className="flex w-full flex-col rounded border border-secondary-light px-4 dark:border-secondary-dark dark:bg-background-dark dark:text-white">
{maybeResult.match({
None: () =>
new Array(5).fill(null).map(() => (
<div
key={Math.random()}
className="grid grid-cols-2 border-b border-secondary-light py-4 last:border-b-0 dark:border-secondary-dark"
>
{Array(2)
.fill(null)
.map(() => (
<Skeleton
key={Math.random()}
size={GRK_SIZES.LARGE}
/>
))}
</div>
)),
Some: (blocks) =>
errorMessage ? (
<p className="col-span-5">{errorMessage}</p>
) : blocks ? (
blocks.map(
({ gas_limit, gas_used, height, signed_at }) => (
<div
key={height}
className="grid grid-cols-2 items-center border-b border-secondary-light py-4 last:border-b-0 dark:border-secondary-dark"
>
<CardDetail
heading={
<Timestamp
timestamp={signed_at}
defaultType="relative"
/>
}
content={
<p className="text-base">
{height.toLocaleString()}
</p>
}
wrapperClassName="flex flex-col-reverse"
/>

{on_view_details ? (
<Button
variant="ghost"
className="mx-auto mb-2 mt-4 flex items-center justify-center gap-x-2 text-sm"
onClick={() => on_view_details(block)}
>
View Block Details
<ExternalLinkIcon />
</Button>
) : (
<></>
)}
</Card>
))
) : (
<></>
),
});
<CardDetail
heading={"GAS USED"}
content={`${(
(gas_used / gas_limit) *
100
).toFixed(2)}%`}
subtext={`of ${gas_limit.toLocaleString()}`}
/>
</div>
)
)
) : (
<></>
),
})}
</Card>
);
};
Loading

0 comments on commit d1a47ed

Please sign in to comment.