Skip to content

Commit

Permalink
#68 Add summaries of the last 6 inputs and the last 6 applications on…
Browse files Browse the repository at this point in the history
… home page (#81)

Co-authored-by: Neven Diulgerov <[email protected]>
Co-authored-by: Bruno Menezes <[email protected]>
  • Loading branch information
nevendyulgerov and brunomenezes authored Dec 20, 2023
1 parent 25cdd72 commit a59614e
Show file tree
Hide file tree
Showing 17 changed files with 731 additions and 15 deletions.
1 change: 1 addition & 0 deletions apps/web/graphql/queries.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ query tokens($limit: Int, $where: TokenWhereInput) {
fragment ApplicationItem on Application {
id
owner
timestamp
factory {
id
}
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/app/inputs/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import type { FC } from "react";
import PageError from "../../components/pageError";

interface PageErrorProps {
reset: () => void;
}

const Error: FC<PageErrorProps> = ({ reset }) => <PageError reset={reset} />;

export default Error;
5 changes: 5 additions & 0 deletions apps/web/src/app/inputs/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import PageLoader from "../../components/pageLoader";

export default function Loading() {
return <PageLoader />;
}
31 changes: 31 additions & 0 deletions apps/web/src/app/inputs/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Group, Stack, Title } from "@mantine/core";
import { TbInbox } from "react-icons/tb";
import { Metadata } from "next";
import Inputs from "../../components/inputs";
import Breadcrumbs from "../../components/breadcrumbs";

export const metadata: Metadata = {
title: "Inputs",
};

export default function InputsPage() {
return (
<Stack>
<Breadcrumbs
breadcrumbs={[
{
href: "/",
label: "Home",
},
]}
/>

<Group>
<TbInbox size={40} />
<Title order={2}>Inputs</Title>
</Group>

<Inputs />
</Stack>
);
}
15 changes: 4 additions & 11 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Group, Stack, Title } from "@mantine/core";
import { TbInbox } from "react-icons/tb";
import Inputs from "../components/inputs";
import EntriesSummary from "../components/entriesSummary";
import { Stack } from "@mantine/core";
import Breadcrumbs from "../components/breadcrumbs";
import EntriesSummary from "../components/entriesSummary";
import LatestEntries from "../components/latestEntries";

export default function HomePage() {
return (
Expand All @@ -17,13 +16,7 @@ export default function HomePage() {
/>

<EntriesSummary />

<Group>
<TbInbox size={40} />
<Title order={2}>Inputs</Title>
</Group>

<Inputs />
<LatestEntries />
</Stack>
);
}
6 changes: 5 additions & 1 deletion apps/web/src/components/inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ const Inputs: FC<InputsProps> = ({
}}
/>

<InputsTable inputs={inputs} />
<InputsTable
inputs={inputs}
fetching={fetching}
totalCount={data?.inputsConnection.totalCount ?? 0}
/>

<Group justify="space-between" align="center">
<Group>
Expand Down
25 changes: 23 additions & 2 deletions apps/web/src/components/inputsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
"use client";
import { Button, Table } from "@mantine/core";
import { Button, Loader, Table } from "@mantine/core";
import { FC, useCallback, useState } from "react";
import InputRow from "../components/inputRow";
import type { InputItemFragment } from "../graphql";

export interface InputsTableProps {
inputs: InputItemFragment[];
fetching: boolean;
totalCount: number;
}

const InputsTable: FC<InputsTableProps> = ({ inputs }) => {
const InputsTable: FC<InputsTableProps> = ({
inputs,
fetching,
totalCount,
}) => {
const [timeType, setTimeType] = useState("age");

const onChangeTimeColumnType = useCallback(() => {
Expand Down Expand Up @@ -37,6 +43,21 @@ const InputsTable: FC<InputsTableProps> = ({ inputs }) => {
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{fetching ? (
<Table.Tr>
<Table.Td align="center" colSpan={7}>
<Loader data-testid="inputs-table-spinner" />
</Table.Td>
</Table.Tr>
) : (
totalCount === 0 && (
<Table.Tr>
<Table.Td colSpan={3} align="center">
No inputs
</Table.Td>
</Table.Tr>
)
)}
{inputs.map((input) => (
<InputRow
key={input.id}
Expand Down
134 changes: 134 additions & 0 deletions apps/web/src/components/latestEntries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"use client";
import { Card } from "@cartesi/rollups-explorer-ui";
import { Button, Grid, Group, Text, useMantineTheme } from "@mantine/core";
import type { FC } from "react";
import Link from "next/link";
import { TbApps, TbInbox } from "react-icons/tb";
import LatestEntriesTable, { Entry } from "./latestEntriesTable";
import {
ApplicationOrderByInput,
InputOrderByInput,
useApplicationsConnectionQuery,
useInputsQuery,
} from "../graphql";
import type { Address as AddressType } from "abitype/dist/types/abi";
import { IconType } from "react-icons";
import { useMediaQuery } from "@mantine/hooks";

interface LatestEntriesCard {
title: string;
Icon: IconType;
entries: Entry[];
fetching: boolean;
totalCount: number;
viewAllText: string;
viewAllHref: string;
}

export const LatestEntriesCard: FC<LatestEntriesCard> = (props) => {
const {
title,
Icon,
entries,
fetching,
totalCount,
viewAllText,
viewAllHref,
} = props;
const theme = useMantineTheme();
const isSmallDevice = useMediaQuery(`(max-width:${theme.breakpoints.sm})`);

return (
<Card h="100%">
<Group gap={5} align="center">
<Icon size={20} />
<Text c="dimmed" lh={1}>
{title}
</Text>
</Group>

<Group gap={5}>
<LatestEntriesTable
entries={entries}
fetching={fetching}
totalCount={totalCount}
/>
</Group>

<Group gap={5} mt="auto">
<Button
component={Link}
href={viewAllHref}
variant="light"
fullWidth={isSmallDevice}
mt="md"
mx="auto"
radius="md"
tt="uppercase"
>
{viewAllText}
</Button>
</Group>
</Card>
);
};

const LatestEntries: FC = () => {
const [{ data: inputsData, fetching: isFetchingInputs }] = useInputsQuery({
variables: {
orderBy: InputOrderByInput.TimestampDesc,
limit: 6,
},
});
const [{ data: applicationsData, fetching: isFetchingApplications }] =
useApplicationsConnectionQuery({
variables: {
orderBy: ApplicationOrderByInput.TimestampDesc,
limit: 6,
},
});
const inputs =
inputsData?.inputsConnection.edges.map((edge) => ({
appId: edge.node.application.id as AddressType,
timestamp: Number(edge.node.timestamp),
href: `/applications/${edge.node.application.id}`,
})) ?? [];
const applications =
applicationsData?.applicationsConnection.edges.map((edge) => ({
appId: edge.node.id as AddressType,
timestamp: Number(edge.node.timestamp),
href: `/applications/${edge.node.id}`,
})) ?? [];

return (
<Grid gutter="md">
<Grid.Col span={{ base: 12, md: 6 }} my="md">
<LatestEntriesCard
title="Latest inputs"
Icon={TbInbox}
entries={inputs}
fetching={isFetchingInputs}
totalCount={inputsData?.inputsConnection.totalCount ?? 0}
viewAllText="View all inputs"
viewAllHref="/inputs"
/>
</Grid.Col>

<Grid.Col span={{ base: 12, md: 6 }} my="md">
<LatestEntriesCard
title="Latest applications"
Icon={TbApps}
entries={applications}
fetching={isFetchingApplications}
totalCount={
applicationsData?.applicationsConnection.totalCount ?? 0
}
viewAllText="View all applications"
viewAllHref="/applications"
/>
</Grid.Col>
</Grid>
);
};

export default LatestEntries;
96 changes: 96 additions & 0 deletions apps/web/src/components/latestEntriesTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"use client";
import { Button, Loader, Table, Text } from "@mantine/core";
import { FC, useCallback, useState } from "react";
import Address from "./address";
import type { Address as AddressType } from "abitype/dist/types/abi";
import prettyMilliseconds from "pretty-ms";

export interface Entry {
appId: AddressType;
timestamp: number;
href: string;
}

export interface LatestEntriesTableProps {
entries: Entry[];
fetching: boolean;
totalCount: number;
}

const LatestEntriesTable: FC<LatestEntriesTableProps> = ({
entries,
fetching,
totalCount,
}) => {
const [timeType, setTimeType] = useState("age");

const onChangeTimeColumnType = useCallback(() => {
setTimeType((timeType) => (timeType === "age" ? "timestamp" : "age"));
}, []);

return (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Address</Table.Th>
<Table.Th>
<Button
variant="transparent"
px={0}
onClick={onChangeTimeColumnType}
>
{timeType === "age" ? "Age" : "Timestamp (UTC)"}
</Button>
</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{fetching ? (
<Table.Tr>
<Table.Td align="center" colSpan={2}>
<Loader data-testid="inputs-table-spinner" />
</Table.Td>
</Table.Tr>
) : (
totalCount === 0 && (
<Table.Tr>
<Table.Td colSpan={3} align="center">
No inputs
</Table.Td>
</Table.Tr>
)
)}
{entries.map((entry) => (
<Table.Tr key={`${entry.appId}-${entry.timestamp}`}>
<Table.Td>
<Address
value={entry.appId}
icon
shorten
href={entry.href}
/>
</Table.Td>
<Table.Td>
<Text>
{timeType === "age"
? `${prettyMilliseconds(
Date.now() - entry.timestamp * 1000,
{
unitCount: 2,
secondsDecimalDigits: 0,
verbose: true,
},
)} ago`
: new Date(
entry.timestamp * 1000,
).toISOString()}
</Text>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
);
};

export default LatestEntriesTable;
Loading

2 comments on commit a59614e

@vercel
Copy link

@vercel vercel bot commented on a59614e Dec 20, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

rollups-explorer-workshop – ./apps/workshop

rollups-explorer-workshop.vercel.app
rollups-explorer-workshop-cartesi.vercel.app
rollups-explorer-workshop-git-main-cartesi.vercel.app

@vercel
Copy link

@vercel vercel bot commented on a59614e Dec 20, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.