Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/app/batches/[height]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useCallback, useEffect, useState } from "react";
import { DetailsLayout } from "@/components/details/layout";
import DataTable from "@/components/ui/DataTable";
import config from "@/config";
import { TransactionHash } from "@/components/settlements/settlementsPageClient";
import { typed } from "@/lib/utils";
import { columns, TableItem } from "@/components/blocks/BlocksPageClient";

Expand Down Expand Up @@ -84,7 +85,11 @@ export default function BatchDetail() {
},
{
label: "Settlement Transaction Hash",
value: data?.batch?.settlementTransactionHash ?? "—",
value: (
<TransactionHash
transactionHash={data?.batch?.settlementTransactionHash}
/>
),
},
{
label: "Blocks",
Expand Down
16 changes: 13 additions & 3 deletions src/app/settlements/[transactionHash]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Truncate from "react-truncate-inside/es";
import { DetailsLayout } from "@/components/details/layout";
import DataTable from "@/components/ui/DataTable";
import config from "@/config";
import { TransactionHash } from "@/components/settlements/settlementsPageClient";
import { typed } from "@/lib/utils";
import { columns, TableItem } from "@/components/batches/BatchesPageClient";

Expand Down Expand Up @@ -61,7 +62,7 @@ export default function SettlementDetail() {
});
try {
const response = typed<GetSettlementQueryResponse>(
await responseData.json()
await responseData.json(),
);
setData(response.data);
setLoading(false);
Expand All @@ -79,7 +80,9 @@ export default function SettlementDetail() {
const details = [
{
label: "Transaction Hash",
value: data?.settlement?.transactionHash ?? "—",
value: (
<TransactionHash transactionHash={data?.settlement?.transactionHash} />
),
},
{
label: "Promised Messages Hash",
Expand All @@ -96,7 +99,7 @@ export default function SettlementDetail() {
height: item.height,
settlementTransactionHash: item.settlementTransactionHash,
blocks: item._count?.blocks?.toString(),
})
}),
);

return (
Expand Down Expand Up @@ -124,6 +127,13 @@ export default function SettlementDetail() {
loading={loading}
navigationPath="/batches/{height}"
copyKeys={["settlementTransactionHash"]}
columnRenderers={{
settlementTransactionHash: (item) => (
<TransactionHash
transactionHash={String(item.settlementTransactionHash ?? "")}
/>
),
}}
/>
</DetailsLayout>
);
Expand Down
12 changes: 10 additions & 2 deletions src/components/batches/BatchesPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useQueryParams from "@/hooks/use-query-params";
import DataTable from "@/components/ui/DataTable";
import { FilterFieldDef } from "@/components/ui/FilterBuilder";
import configs from "@/config";
import { TransactionHash } from "@/components/settlements/settlementsPageClient";
import { showPerPage } from "@/components/pagination";
import { buildWhere } from "@/lib/utils";

Expand Down Expand Up @@ -78,7 +79,7 @@ const graphqlQuery = `query GetBatches($take: Int!, $skip: Int!, $where: BatchWh
export default function BatchesPageClient() {
const [page, view, filters, setPage, setView, setFilters] = useQueryParams(
columns,
querySchema
querySchema,
);

const [data, setData] = useState<TableItem[]>([]);
Expand Down Expand Up @@ -114,7 +115,7 @@ export default function BatchesPageClient() {

setData(mappedItems);
setTotalCount(
result.data?.aggregateBatch?._count?._all?.toString() || "0"
result.data?.aggregateBatch?._count?._all?.toString() || "0",
);
setLoading(false);
} catch (error) {
Expand Down Expand Up @@ -146,6 +147,13 @@ export default function BatchesPageClient() {
onViewChange={setView}
navigationPath="/batches/{height}"
copyKeys={["settlementTransactionHash"]}
columnRenderers={{
settlementTransactionHash: (item) => (
<TransactionHash
transactionHash={String(item.settlementTransactionHash ?? "")}
/>
),
}}
/>
);
}
Expand Down
26 changes: 23 additions & 3 deletions src/components/settlements/settlementsPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { z } from "zod";

import DataTable from "@/components/ui/DataTable";
import { FilterFieldDef } from "@/components/ui/FilterBuilder";
import Copy from "@/components/ui/copy-to-clipboard";
import MinaExplorerLink from "@/components/ui/minaExplorerLink";
import useQueryParams from "@/hooks/use-query-params";
import configs from "@/config";
import { showPerPage } from "@/components/pagination";
Expand Down Expand Up @@ -41,6 +43,17 @@ export const columns: Record<keyof TableItem, string> = {
batches: "Batches",
};

export function TransactionHash(props: { transactionHash?: string | null }) {
const hash = String(props.transactionHash ?? "");

return (
<div className="flex items-center gap-2">
<Copy text={hash} />
<MinaExplorerLink hash={hash} />
</div>
);
}

const formSchema = z.object({
transactionHash: z.string().optional(),
promisedMessagesHash: z.string().optional(),
Expand Down Expand Up @@ -78,7 +91,7 @@ const graphqlQuery = `query GetSettlements($take: Int!, $skip: Int!, $where: Set
export default function SettlementsPageClient() {
const [page, view, filters, setPage, setView, setFilters] = useQueryParams(
columns,
querySchema
querySchema,
);

const [data, setData] = useState<TableItem[]>([]);
Expand Down Expand Up @@ -116,7 +129,7 @@ export default function SettlementsPageClient() {

setData(mappedItems);
setTotalCount(
result.data?.aggregateSettlement?._count?._all?.toString() || "0"
result.data?.aggregateSettlement?._count?._all?.toString() || "0",
);
setLoading(false);
} catch (error) {
Expand Down Expand Up @@ -147,7 +160,14 @@ export default function SettlementsPageClient() {
onPageChange={setPage}
onViewChange={setView}
navigationPath="/settlements/{transactionHash}"
copyKeys={["transactionHash", "promisedMessagesHash"]}
copyKeys={["promisedMessagesHash"]}
columnRenderers={{
transactionHash: (item) => (
<TransactionHash
transactionHash={String(item.transactionHash ?? "")}
/>
),
}}
/>
);
}
Expand Down
63 changes: 63 additions & 0 deletions src/components/ui/minaExplorerLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

import React from "react";
import { ExternalLink } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import configs from "@/config";

interface MinaExplorerLinkProps {
hash?: string | null;
className?: string;
}

export default function MinaExplorerLink({
hash,
className,
}: MinaExplorerLinkProps) {
if (!hash) return null;

const open = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();

const network = (configs.MINA_NETWORK ?? "lightnet").toLowerCase();

let base: string;
if (network === "devnet") {
base = "https://minascan.io/devnet/tx/";
} else if (network === "mainnet") {
base = "https://minascan.io/mainnet/tx/";
} else {
const host = configs.MINA_EXPLORER_HOST ?? "localhost";
const port = configs.MINA_EXPLORER_PORT ?? "8083";
base = `http://${host}:${port}/?target=transaction&hash=`;
}
const href = `${base}${hash}`;
window.open(href, "_blank", "noopener,noreferrer");
};

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<button
type="button"
onClick={open}
className={`p-1 rounded hover:bg-muted transition ml-2 ${className ?? ""}`}
aria-label="Open in Mina explorer"
>
<ExternalLink className="w-4 h-4 text-muted-foreground" />
</button>
</TooltipTrigger>
<TooltipContent side="top" align="center">
Open in Mina explorer
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const config = {
DASHBOARD_SLOGAN:
env("NEXT_PUBLIC_DASHBOARD_SLOGAN") ??
"Explore the blockchain. Search in real-time.",
MINA_NETWORK: env("NEXT_PUBLIC_MINA_NETWORK") ?? "lightnet",
MINA_EXPLORER_HOST: env("NEXT_PUBLIC_MINA_EXPLORER_HOST") ?? "localhost",
MINA_EXPLORER_PORT: env("NEXT_PUBLIC_MINA_EXPLORER_PORT") ?? "8083",
};

export default config;