diff --git a/apps/web/src/app/applications/[address]/page.tsx b/apps/web/src/app/applications/[address]/page.tsx index c3a4dfc7..7d272c5d 100644 --- a/apps/web/src/app/applications/[address]/page.tsx +++ b/apps/web/src/app/applications/[address]/page.tsx @@ -1,120 +1,47 @@ -"use client"; -import { - Anchor, - Breadcrumbs, - Group, - Pagination, - Select, - Stack, - Text, - Title, -} from "@mantine/core"; -import { useScrollIntoView } from "@mantine/hooks"; -import Link from "next/link"; -import { pathOr } from "ramda"; -import { FC, useEffect, useState } from "react"; +import { Group, Stack, Title } from "@mantine/core"; +import { FC } from "react"; +import { Metadata } from "next"; import { TbInbox } from "react-icons/tb"; import Address from "../../../components/address"; -import { InputOrderByInput, useInputsQuery } from "../../../graphql"; -import { - limitBounds, - usePaginationParams, -} from "../../../hooks/usePaginationParams"; -import InputsTable from "../../../components/inputsTable"; +import Inputs from "../../../components/inputs"; +import Breadcrumbs from "../../../components/breadcrumbs"; export type ApplicationPageProps = { params: { address: string }; }; -const ApplicationPage: FC = ({ params }) => { - const [{ limit, page }, updateParams] = usePaginationParams(); - const after = page === 1 ? undefined : ((page - 1) * limit).toString(); - const [{ data, fetching }] = useInputsQuery({ - variables: { - orderBy: InputOrderByInput.TimestampDesc, - applicationId: params.address.toLowerCase(), - limit, - after, - }, - }); - const totalInputs = data?.inputsConnection.totalCount ?? 1; - const totalPages = Math.ceil(totalInputs / limit); - const [activePage, setActivePage] = useState( - page > totalPages ? totalPages : page, - ); - const inputs = data?.inputsConnection.edges.map((edge) => edge.node) ?? []; - const { scrollIntoView, targetRef } = useScrollIntoView({ - duration: 700, - offset: 150, - cancelable: true, - }); - - if (!fetching && page > totalPages) { - updateParams(totalPages, limit); - } - useEffect(() => { - setActivePage((n) => { - return n !== page ? page : n; - }); - }, [page]); +export async function generateMetadata({ + params, +}: ApplicationPageProps): Promise { + return { + title: `Application ${params.address}`, + }; +} +const ApplicationPage: FC = ({ params }) => { return ( - - - Home - - - Applications - +
- + + Inputs - - { - updateParams(pageN, limit); - }} - /> - - - - - Show: - { - const entry = val ?? limit; - const l = pathOr(limit, [entry], limitBounds); - updateParams(page, l); - }} - data={[ - limitBounds[10].toString(), - limitBounds[20].toString(), - limitBounds[30].toString(), - ]} - /> - Applications - - { - updateParams(pageN, limit); - scrollIntoView({ alignment: "center" }); - }} - /> - - + ); -}; - -export default ApplicationsPage; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index a7918891..427ecbf6 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,20 +1,32 @@ import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; -import React, { FC } from "react"; +import type { FC, ReactNode } from "react"; +import { Metadata } from "next"; import { ColorSchemeScript } from "@mantine/core"; import { Providers } from "../providers/providers"; import Shell from "../components/shell"; +export const metadata: Metadata = { + title: { + template: "%s | CartesiScan", + default: "Blockchain Explorer | CartesiScan", + }, + description: + "CartesiScan is a tool for inspecting and analyzing Cartesi rollups applications. Blockchain explorer for Ethereum Networks.", + icons: { + icon: "/favicon.ico", + }, +}; + interface LayoutProps { - children: React.ReactNode; + children: ReactNode; } -const Layout: FC<{ children: React.ReactNode }> = ({ children }) => { +const Layout: FC = ({ children }) => { return ( - diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 18c738d7..4fed1843 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,117 +1,29 @@ -"use client"; -import { Summary } from "@cartesi/rollups-explorer-ui"; -import { - Anchor, - Breadcrumbs, - Group, - Pagination, - Select, - Stack, - Text, - Title, -} from "@mantine/core"; -import { FC, useEffect, useState } from "react"; +import { Group, Stack, Title } from "@mantine/core"; import { TbInbox } from "react-icons/tb"; -import { useScrollIntoView } from "@mantine/hooks"; -import { pathOr } from "ramda"; -import { InputOrderByInput, useInputsQuery, useStatsQuery } from "../graphql"; -import { limitBounds, usePaginationParams } from "../hooks/usePaginationParams"; -import InputsTable from "../components/inputsTable"; - -const Explorer: FC = (props) => { - const [{ limit, page }, updateParams] = usePaginationParams(); - const after = page === 1 ? undefined : ((page - 1) * limit).toString(); - const [{ data: stats }] = useStatsQuery(); - const [{ data, fetching }] = useInputsQuery({ - variables: { - orderBy: InputOrderByInput.TimestampDesc, - limit, - after, - }, - }); - const totalInputs = data?.inputsConnection.totalCount ?? 1; - const totalPages = Math.ceil(totalInputs / limit); - const [activePage, setActivePage] = useState( - page > totalPages ? totalPages : page, - ); - const inputs = data?.inputsConnection.edges.map((edge) => edge.node) ?? []; - - const { scrollIntoView, targetRef } = useScrollIntoView({ - duration: 700, - offset: 150, - cancelable: true, - }); - - if (!fetching && page > totalPages) { - updateParams(totalPages, limit); - } - - useEffect(() => { - setActivePage((n) => { - return n !== page ? page : n; - }); - }, [page]); +import Inputs from "../components/inputs"; +import EntriesSummary from "../components/entriesSummary"; +import Breadcrumbs from "../components/breadcrumbs"; +export default function HomePage() { return ( - - Home - - - - + + + Inputs - - { - updateParams(pageN, limit); - }} - /> - - - - - - Show: - { + const entry = val ?? limit; + const l = pathOr(limit, [entry], limitBounds); + updateParams(page, l); + }} + data={[ + limitBounds[10].toString(), + limitBounds[20].toString(), + limitBounds[30].toString(), + ]} + /> + Applications + + { + updateParams(pageN, limit); + scrollIntoView({ alignment: "center" }); + }} + /> + + + ); +}; + +export default Applications; diff --git a/apps/web/src/components/breadcrumbs.tsx b/apps/web/src/components/breadcrumbs.tsx new file mode 100644 index 00000000..fecab104 --- /dev/null +++ b/apps/web/src/components/breadcrumbs.tsx @@ -0,0 +1,32 @@ +import { Anchor, Breadcrumbs as MantineBreadcrumbs } from "@mantine/core"; +import Link from "next/link"; +import type { FC, ReactNode } from "react"; + +export interface BreadcrumbModel { + href: string; + label: string; +} + +interface BreadcrumbsProps { + breadcrumbs: BreadcrumbModel[]; + children?: ReactNode; +} + +const Breadcrumbs: FC = ({ breadcrumbs, children }) => { + return ( + + {breadcrumbs.map((breadcrumb) => ( + + {breadcrumb.label} + + ))} + {children} + + ); +}; + +export default Breadcrumbs; diff --git a/apps/web/src/components/entriesSummary.tsx b/apps/web/src/components/entriesSummary.tsx new file mode 100644 index 00000000..5b9ac640 --- /dev/null +++ b/apps/web/src/components/entriesSummary.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { Summary } from "@cartesi/rollups-explorer-ui"; +import { FC } from "react"; +import { useStatsQuery } from "../graphql"; + +const EntriesSummary: FC = () => { + const [{ data: stats }] = useStatsQuery(); + + return ( + + ); +}; + +export default EntriesSummary; diff --git a/apps/web/src/components/inputs.tsx b/apps/web/src/components/inputs.tsx new file mode 100644 index 00000000..113379ff --- /dev/null +++ b/apps/web/src/components/inputs.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { Group, Pagination, Select, Stack, Text } from "@mantine/core"; +import { useScrollIntoView } from "@mantine/hooks"; +import { pathOr } from "ramda"; +import { FC, useEffect, useState } from "react"; +import { InputOrderByInput, useInputsQuery } from "../graphql"; +import { limitBounds, usePaginationParams } from "../hooks/usePaginationParams"; +import InputsTable from "../components/inputsTable"; + +export type InputsProps = { + orderBy?: InputOrderByInput; + applicationId?: string; +}; + +const Inputs: FC = ({ + orderBy = InputOrderByInput.TimestampDesc, + applicationId, +}) => { + const [{ limit, page }, updateParams] = usePaginationParams(); + const after = page === 1 ? undefined : ((page - 1) * limit).toString(); + const [{ data, fetching }] = useInputsQuery({ + variables: { + orderBy, + applicationId: applicationId?.toLowerCase(), + limit, + after, + }, + }); + const totalInputs = data?.inputsConnection.totalCount ?? 1; + const totalPages = Math.ceil(totalInputs / limit); + const [activePage, setActivePage] = useState( + page > totalPages ? totalPages : page, + ); + const inputs = data?.inputsConnection.edges.map((edge) => edge.node) ?? []; + const { scrollIntoView } = useScrollIntoView({ + duration: 700, + offset: 150, + cancelable: true, + }); + + if (!fetching && page > totalPages) { + updateParams(totalPages, limit); + } + + useEffect(() => { + setActivePage((n) => { + return n !== page ? page : n; + }); + }, [page]); + + return ( + + { + updateParams(pageN, limit); + }} + /> + + + + + + Show: +