diff --git a/package-lock.json b/package-lock.json index 5029598..19aa32e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.0", + "react-timeago": "^8.3.0", "react-truncate-inside": "^1.0.3", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", @@ -6015,6 +6016,15 @@ } } }, + "node_modules/react-timeago": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/react-timeago/-/react-timeago-8.3.0.tgz", + "integrity": "sha512-BeR0hj/5qqTc2+zxzBSQZMky6MmqwOtKseU3CSmcjKR5uXerej2QY34v2d+cdz11PoeVfAdWLX+qjM/UdZkUUg==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-truncate-inside": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/react-truncate-inside/-/react-truncate-inside-1.0.3.tgz", diff --git a/package.json b/package.json index f8237df..8a2e932 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.0", + "react-timeago": "^8.3.0", "react-truncate-inside": "^1.0.3", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", diff --git a/src/app/batches/[height]/page.tsx b/src/app/batches/[height]/page.tsx index d777b2d..33a5ec6 100644 --- a/src/app/batches/[height]/page.tsx +++ b/src/app/batches/[height]/page.tsx @@ -4,6 +4,7 @@ import { useParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; +import TimeAgo from "react-timeago"; import { DetailsLayout } from "@/components/details/layout"; import DataTable from "@/components/ui/DataTable"; @@ -19,6 +20,7 @@ export interface GetBatchQueryResponse { blocks: { height: string; hash: string; + createdAt: string; result: { stateRoot: string; }; @@ -28,6 +30,7 @@ export interface GetBatchQueryResponse { }[]; settlementTransactionHash: string; height: string; + createdAt: string; } | undefined; }; @@ -44,9 +47,11 @@ export default function BatchDetail() { batch(where: { height: $height }) { height settlementTransactionHash + createdAt blocks { height hash + createdAt result { stateRoot } _count { transactions } } @@ -95,6 +100,12 @@ export default function BatchDetail() { label: "Blocks", value: `${data?.batch?.blocks?.length ?? "—"}`, }, + { + label: "Created", + value: data?.batch?.createdAt + ? + : "—", + }, ]; const blocks: TableItem[] = (data?.batch?.blocks || []).map((item) => ({ @@ -102,6 +113,7 @@ export default function BatchDetail() { hash: item.hash, transactions: item._count?.transactions?.toString(), stateRoot: item.result?.stateRoot, + createdAt: item.createdAt, })); return ( @@ -123,6 +135,7 @@ export default function BatchDetail() { loading={loading} navigationPath="/blocks/{hash}" copyKeys={["hash", "stateRoot"]} + columnRenderers={{ createdAt: (item) => }} /> ); diff --git a/src/app/blocks/[hash]/page.tsx b/src/app/blocks/[hash]/page.tsx index 43839b8..6fade7c 100644 --- a/src/app/blocks/[hash]/page.tsx +++ b/src/app/blocks/[hash]/page.tsx @@ -2,6 +2,8 @@ import { useParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; +import TimeAgo from "react-timeago"; +import { CircleCheck, CircleX } from "lucide-react"; import { DetailsLayout } from "@/components/details/layout"; import DataTable from "@/components/ui/DataTable"; @@ -19,6 +21,7 @@ export interface GetBlockQueryResponse { | { hash: string; height: string; + createdAt: string; result: { stateRoot: string; }; @@ -49,6 +52,7 @@ export default function BlockDetail() { block(where: { hash: $hash }) { height hash + createdAt result { stateRoot } transactions { tx { hash, methodId, sender, nonce } @@ -100,11 +104,20 @@ export default function BlockDetail() { label: "StateRoot", value: data?.block?.result.stateRoot ?? "—", }, + { + label: "Created", + value: data?.block?.createdAt ? ( + + ) : ( + "—" + ), + }, ]; const transactions: TableItem[] = (data?.block?.transactions || []).map( (tx) => ({ ...tx.tx, + createdAt: data?.block?.createdAt || "", status: { isSuccess: tx.status === true, message: tx.statusMessage, @@ -112,6 +125,27 @@ export default function BlockDetail() { }), ); + const statusRenderer = (item: TableItem) => { + const { isSuccess, message } = item.status; + + return ( +
+ {isSuccess === true ? ( + + ) : ( + <> + + {message !== undefined && ( + + {message} + + )} + + )} +
+ ); + }; + return ( , + status: statusRenderer, + }} /> ); diff --git a/src/app/settlements/[transactionHash]/page.tsx b/src/app/settlements/[transactionHash]/page.tsx index 8d4981d..527c89a 100644 --- a/src/app/settlements/[transactionHash]/page.tsx +++ b/src/app/settlements/[transactionHash]/page.tsx @@ -4,6 +4,7 @@ import { useParams } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; +import TimeAgo from "react-timeago"; import Truncate from "react-truncate-inside/es"; import { DetailsLayout } from "@/components/details/layout"; @@ -20,12 +21,14 @@ export interface GetSettlementQueryResponse { batches: { height: string; settlementTransactionHash: string; + createdAt: string; _count: { blocks: number; }; }[]; transactionHash: string; promisedMessagesHash: string; + createdAt: string; } | undefined; }; @@ -43,10 +46,12 @@ export default function SettlementDetail() { batches { height settlementTransactionHash + createdAt _count { blocks } } transactionHash promisedMessagesHash + createdAt } }`; @@ -92,6 +97,14 @@ export default function SettlementDetail() { label: "Batches", value: `${data?.settlement?.batches?.length ?? "—"}`, }, + { + label: "Created", + value: data?.settlement?.createdAt ? ( + + ) : ( + "—" + ), + }, ]; const batches: TableItem[] = (data?.settlement?.batches || []).map( @@ -99,6 +112,7 @@ export default function SettlementDetail() { height: item.height, settlementTransactionHash: item.settlementTransactionHash, blocks: item._count?.blocks?.toString(), + createdAt: item.createdAt, }), ); @@ -128,6 +142,7 @@ export default function SettlementDetail() { navigationPath="/batches/{height}" copyKeys={["settlementTransactionHash"]} columnRenderers={{ + createdAt: (item) => , settlementTransactionHash: (item) => ( (); const [data, setData] = useState(); const [loading, setLoading] = useState(true); @@ -47,7 +49,7 @@ export default function BlockDetail() { executionResult { status statusMessage - block { batch { proof settlementTransactionHash } } + block { createdAt batch { proof settlementTransactionHash } } } } }`; @@ -131,6 +133,12 @@ export default function BlockDetail() { label: "Sender", value: data?.transaction?.sender ?? "—", }, + { + label: "Created", + value: data?.transaction?.executionResult?.block?.createdAt + ? + : "—", + }, ]; return ( diff --git a/src/components/batches/BatchesPageClient.tsx b/src/components/batches/BatchesPageClient.tsx index 0ce5d48..96d208b 100644 --- a/src/components/batches/BatchesPageClient.tsx +++ b/src/components/batches/BatchesPageClient.tsx @@ -3,6 +3,7 @@ /* eslint-disable no-underscore-dangle */ import { useCallback, useEffect, useState } from "react"; +import TimeAgo from "react-timeago"; import { z } from "zod"; import useQueryParams from "@/hooks/use-query-params"; @@ -17,6 +18,7 @@ export interface TableItem { height: string; blocks: string; settlementTransactionHash: string; + createdAt: string; } export interface GetBatchesQueryResponse { @@ -24,6 +26,7 @@ export interface GetBatchesQueryResponse { batches: { height: string; settlementTransactionHash: string; + createdAt: string; _count: { blocks: number; }; @@ -40,6 +43,7 @@ export const columns: Record = { height: "Height", blocks: "Blocks", settlementTransactionHash: "Settlement Transaction Hash", + createdAt: "Created", }; const formSchema = z.object({ @@ -71,6 +75,7 @@ const graphqlQuery = `query GetBatches($take: Int!, $skip: Int!, $where: BatchWh batches(take: $take, skip: $skip, orderBy: {height: desc}, where: $where) { settlementTransactionHash height + createdAt _count { blocks } } aggregateBatch(where: $where) { _count { _all } } @@ -111,6 +116,7 @@ export default function BatchesPageClient() { height: item.height, settlementTransactionHash: item.settlementTransactionHash, blocks: item._count?.blocks?.toString() || "0", + createdAt: item.createdAt, })); setData(mappedItems); @@ -148,6 +154,7 @@ export default function BatchesPageClient() { navigationPath="/batches/{height}" copyKeys={["settlementTransactionHash"]} columnRenderers={{ + createdAt: (item) => , settlementTransactionHash: (item) => ( = { hash: "Hash", transactions: "Transactions", stateRoot: "State Root", + createdAt: "Created", }; const formSchema = z.object({ @@ -83,6 +87,7 @@ const graphqlQuery = `query GetBlocks($take: Int!, $skip: Int!, $where: BlockWhe blocks(take: $take, skip: $skip, orderBy: {height: desc}, where: $where) { height hash + createdAt result { stateRoot } _count { transactions } } @@ -91,7 +96,7 @@ const graphqlQuery = `query GetBlocks($take: Int!, $skip: Int!, $where: BlockWhe const queryTransformer = ( filters: Record, - schema: Record + schema: Record, ) => { const where: Record = {}; Object.entries(filters).forEach(([key, value]) => { @@ -123,7 +128,7 @@ const queryTransformer = ( export default function BlocksPageClient() { const [page, view, filters, setPage, setView, setFilters] = useQueryParams( columns, - querySchema + querySchema, ); const [data, setData] = useState([]); const [totalCount, setTotalCount] = useState("0"); @@ -156,11 +161,12 @@ export default function BlocksPageClient() { hash: item.hash, transactions: item._count?.transactions?.toString(), stateRoot: item.result.stateRoot, + createdAt: item.createdAt, })); setData(mappedItems); setTotalCount( - result.data?.aggregateBlock?._count?._all?.toString() || "0" + result.data?.aggregateBlock?._count?._all?.toString() || "0", ); setLoading(false); } catch (error) { @@ -191,6 +197,9 @@ export default function BlocksPageClient() { onViewChange={setView} navigationPath="/blocks/{hash}" copyKeys={["hash", "stateRoot"]} + columnRenderers={{ + createdAt: (item) => , + }} /> ); } diff --git a/src/components/dashboard/DashboardStats.tsx b/src/components/dashboard/DashboardStats.tsx index 4be9887..caaf2bb 100644 --- a/src/components/dashboard/DashboardStats.tsx +++ b/src/components/dashboard/DashboardStats.tsx @@ -141,7 +141,7 @@ export default function DashboardStats() { height hash } - settlements(take: 1, orderBy: { transactionHash: desc }) { + settlements(take: 1, orderBy: { createdAt: desc }) { transactionHash promisedMessagesHash } diff --git a/src/components/settlements/settlementsPageClient.tsx b/src/components/settlements/settlementsPageClient.tsx index 978669a..3f8c1da 100644 --- a/src/components/settlements/settlementsPageClient.tsx +++ b/src/components/settlements/settlementsPageClient.tsx @@ -3,6 +3,7 @@ /* eslint-disable no-underscore-dangle */ import { useCallback, useEffect, useState } from "react"; +import TimeAgo from "react-timeago"; import { z } from "zod"; import DataTable from "@/components/ui/DataTable"; @@ -18,6 +19,7 @@ export interface TableItem { transactionHash: string; promisedMessagesHash: string; batches: string; + createdAt: string; } export interface GetSettlementsQueryResponse { @@ -25,6 +27,7 @@ export interface GetSettlementsQueryResponse { settlements: { transactionHash: string; promisedMessagesHash: string; + createdAt: string; _count: { batches: number; }; @@ -41,6 +44,7 @@ export const columns: Record = { transactionHash: "Transaction Hash", promisedMessagesHash: "Promised Messages Hash", batches: "Batches", + createdAt: "Created", }; export function TransactionHash(props: { transactionHash?: string | null }) { @@ -80,9 +84,10 @@ const fields: FilterFieldDef[] = [ ]; const graphqlQuery = `query GetSettlements($take: Int!, $skip: Int!, $where: SettlementWhereInput) { - settlements(take: $take, skip: $skip, where: $where) { + settlements(take: $take, skip: $skip, orderBy: { createdAt: desc }, where: $where) { transactionHash promisedMessagesHash + createdAt _count { batches } } aggregateSettlement(where: $where) { _count { _all } } @@ -125,6 +130,7 @@ export default function SettlementsPageClient() { transactionHash: item.transactionHash, promisedMessagesHash: item.promisedMessagesHash, batches: item._count?.batches?.toString() || "0", + createdAt: item.createdAt, })); setData(mappedItems); @@ -167,6 +173,7 @@ export default function SettlementsPageClient() { transactionHash={String(item.transactionHash ?? "")} /> ), + createdAt: (item) => , }} /> ); diff --git a/src/components/transactions/TransactionsPageClient.tsx b/src/components/transactions/TransactionsPageClient.tsx index 631d89f..aa7ed42 100644 --- a/src/components/transactions/TransactionsPageClient.tsx +++ b/src/components/transactions/TransactionsPageClient.tsx @@ -3,6 +3,7 @@ /* eslint-disable no-underscore-dangle */ import { useCallback, useEffect, useState } from "react"; +import TimeAgo from "react-timeago"; import { z } from "zod"; import { CircleCheck, CircleX } from "lucide-react"; @@ -23,6 +24,9 @@ export interface GetTransactionsQueryResponse { executionResult: { status: boolean; statusMessage?: string; + block: { + createdAt: string; + }; }; }[]; aggregateTransaction: { @@ -38,6 +42,7 @@ export interface TableItem { methodId: string; sender: string; nonce: string; + createdAt: string; status: { isSuccess: boolean; message?: string }; } @@ -46,6 +51,7 @@ export const columns: Record = { methodId: "Method ID", sender: "Sender", nonce: "Nonce", + createdAt: "Created", status: "Status", }; @@ -83,7 +89,7 @@ const fields: FilterFieldDef[] = [ ]; const graphqlQuery = `query GetTransactions($take: Int!, $skip: Int!, $where: TransactionWhereInput) { - transactions(take: $take, skip: $skip, where: $where) { + transactions(take: $take, skip: $skip, orderBy: { executionResult: { block: { createdAt: desc } } }, where: $where) { methodId hash nonce @@ -91,6 +97,9 @@ const graphqlQuery = `query GetTransactions($take: Int!, $skip: Int!, $where: Tr executionResult { status statusMessage + block { + createdAt + } } } aggregateTransaction(where: $where) { @@ -116,6 +125,7 @@ export const statusRenderer = (item: TableItem) => { ); }; + export default function TransactionsPageClient() { const [page, view, filters, setPage, setView, setFilters] = useQueryParams( columns, @@ -160,6 +170,7 @@ export default function TransactionsPageClient() { methodId: item.methodId, sender: item.sender, nonce: item.nonce, + createdAt: item.executionResult?.block?.createdAt || "-", status: statusDisplay, }; }); @@ -196,7 +207,7 @@ export default function TransactionsPageClient() { onViewChange={setView} navigationPath="/transactions/{hash}" copyKeys={["hash", "sender", "methodId"]} - columnRenderers={{ status: statusRenderer }} + columnRenderers={{ status: statusRenderer, createdAt: (item) => }} /> ); }