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: Add NavArrows component #479

Merged
merged 12 commits into from
Sep 4, 2024
5 changes: 5 additions & 0 deletions .changeset/sweet-melons-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blobscan/web": minor
---

Added navigation arrow buttons to navigate to previous and next block
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"with-env": "dotenv -e ../../.env -v BLOB_PROPAGATOR_ENABLED=false --"
},
"dependencies": {
"@blobscan/db": "workspace:^0.9.0",
"@blobscan/api": "workspace:^0.12.0",
"@blobscan/blob-decoder": "workspace:^0.1.1",
"@blobscan/dates": "workspace:*",
Expand Down
46 changes: 46 additions & 0 deletions apps/web/src/components/NavArrows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useRouter } from "next/router";
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";

import { IconButton } from "./IconButton";

export function NavArrows({ next, prev }: { next?: string; prev?: string }) {
return (
<div className="flex items-center justify-center gap-1">
<NavArrow type="prev" href={prev} />
<NavArrow type="next" href={next} />
</div>
);
}

function NavArrow({ type, href }: { type: "next" | "prev"; href?: string }) {
const router = useRouter();

return (
<IconButton
onClick={() => {
if (href) {
router.push(href);
}
}}
className={`
dark:bg-neutral-850
bg-border-border-dark
rounded-md
border
border-border-light
bg-white
p-1
dark:border-border-dark
dark:bg-border-dark
${href ? "" : "cursor-default opacity-50"}
`}
disabled={!href}
>
{type === "next" ? (
<ChevronRightIcon className="h-4 w-4" />
) : (
<ChevronLeftIcon className="h-4 w-4" />
)}
</IconButton>
);
}
15 changes: 15 additions & 0 deletions apps/web/src/components/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ function resolveApiUrl(): string {
return `https://api.${env.NEXT_PUBLIC_NETWORK_NAME}.blobscan.com`;
}

type Network = typeof env.NEXT_PUBLIC_NETWORK_NAME;

const NETWORKS_FIRST_BLOB_NUMBER: Record<Network, number> = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can have something like getNetworkDencunForkSlot but for blocks?

export function getNetworkDencunForkSlot(
networkName: Environment["NETWORK_NAME"]
): number {
switch (networkName) {
case "mainnet":
return 8626176;
case "holesky":
return 950272;
case "sepolia":
return 4243456;
case "gnosis":
return 14237696;
case "chiado":
return 8265728;
case "devnet":
return 0;
}
}

mainnet: 19426589,
holesky: 894735,
sepolia: 5187052,
gnosis: 32880709,
chiado: 0,
devnet: 0,
};

export function getFirstBlobNumber(): number {
return NETWORKS_FIRST_BLOB_NUMBER[env.NEXT_PUBLIC_NETWORK_NAME];
}

export type NavigationItem = {
label: string;
href: string;
Expand Down
28 changes: 27 additions & 1 deletion apps/web/src/pages/block/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { EtherUnitDisplay } from "~/components/Displays/EtherUnitDisplay";
import { DetailsLayout } from "~/components/Layouts/DetailsLayout";
import type { DetailsLayoutProps } from "~/components/Layouts/DetailsLayout";
import { Link } from "~/components/Link";
import { NavArrows } from "~/components/NavArrows";
import { getFirstBlobNumber } from "~/components/content";
import { api } from "~/api-client";
import NextError from "~/pages/_error";
import type { BlockWithExpandedBlobsAndTransactions } from "~/types";
Expand Down Expand Up @@ -49,6 +51,9 @@ const Block: NextPage = function () {
return deserializeFullBlock(rawBlockData);
}, [rawBlockData]);

const { data: latestBlock } = api.block.getLatestBlock.useQuery();
luis-herasme marked this conversation as resolved.
Show resolved Hide resolved
const blockNumber = blockData ? blockData.number : undefined;

if (error) {
return (
<NextError
Expand Down Expand Up @@ -78,7 +83,28 @@ const Block: NextPage = function () {
);

detailsFields = [
{ name: "Block Height", value: blockData.number },
{
name: "Block Height",
value: (
<div className="flex items-center justify-start gap-4">
{blockData.number}
{blockNumber !== undefined && (
<NavArrows
prev={
getFirstBlobNumber() < blockNumber
? `/block_neighbor?blockNumber=${blockNumber}&direction=prev`
: undefined
}
next={
latestBlock && blockNumber < latestBlock.number
? `/block_neighbor?blockNumber=${blockNumber}&direction=next`
: undefined
}
/>
)}
</div>
),
},
{
name: "Hash",
value: <Copyable value={blockData.hash} label="Copy Hash" />,
Expand Down
72 changes: 72 additions & 0 deletions apps/web/src/pages/block_neighbor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { GetServerSideProps } from "next";
import z from "zod";

import { prisma } from "@blobscan/db";

const QuerySchema = z.object({
blockNumber: z.coerce.number().nonnegative().int(),
direction: z.enum(["next", "prev"]),
});

type Query = z.infer<typeof QuerySchema>;

export const getServerSideProps: GetServerSideProps = async (ctx) => {
const result = QuerySchema.safeParse(ctx.query);

if (!result.success) {
return {
notFound: true,
};
}

const neighbor = await findAdjacentBlockNumber(result.data);

if (neighbor) {
return {
redirect: {
permanent: true,
destination: `/block/${neighbor.number}`,
},
};
}

return {
notFound: true,
};
};

async function findAdjacentBlockNumber({ blockNumber, direction }: Query) {
if (direction === "next") {
return prisma.block.findFirst({
where: {
number: {
gt: blockNumber,
},
},
orderBy: {
number: "asc",
},
select: {
number: true,
},
});
}

return prisma.block.findFirst({
where: {
number: {
lt: blockNumber,
},
},
orderBy: {
number: "desc",
},
select: {
number: true,
},
});
}

export default function BlockNeighbor() {
return null;
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading