Skip to content
Merged
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
6 changes: 4 additions & 2 deletions packages/explorer/src/app/blocks/[hash]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ export default function BlockDetail() {
const transactions: TableItem[] = (data?.block?.transactions || []).map(
(tx) => ({
...tx.tx,
status: `${tx.status}`,
statusMessage: tx.statusMessage ?? "—",
status: {
isSuccess: tx.status === true,
message: tx.statusMessage,
},
})
);

Expand Down
5 changes: 3 additions & 2 deletions packages/explorer/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Sparkles } from "lucide-react";

import GlobalSearch from "@/components/search/GlobalSearch";
import DashboardStats from "@/components/dashboard/DashboardStats";
import config from "@/config";

export default function LandingPage() {
return (
Expand All @@ -13,12 +14,12 @@ export default function LandingPage() {
<div className="flex items-center justify-center gap-2 mb-4">
<Sparkles className="w-6 h-6 text-primary" />
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold tracking-tight">
Explorer
{config.DASHBOARD_TITLE}
</h1>
<Sparkles className="w-6 h-6 text-primary" />
</div>
<p className="text-lg sm:text-xl text-muted-foreground max-w-2xl mx-auto">
Explore the blockchain. Search in real-time.
{config.DASHBOARD_SLOGAN}
</p>
</div>

Expand Down
108 changes: 103 additions & 5 deletions packages/explorer/src/components/dashboard/DashboardStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
/* eslint-disable no-nested-ternary */

import { useCallback, useEffect, useState } from "react";
import { Blocks, Zap, Link, Clock, Database } from "lucide-react";
import { useRouter } from "next/navigation";
import { Blocks, Zap, Link, Clock, Database, ArrowRight } from "lucide-react";

import {
Card,
Expand All @@ -13,6 +14,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import config from "@/config";
import { cn } from "@/lib/utils";
Expand All @@ -38,6 +40,11 @@ export interface GetDashboardStatsResponse {
stateRoot: string;
};
}>;
recentBlocks: Array<{
height: number;
hash: string;
timestamp?: string;
}>;
settlements: Array<{
transactionHash: string;
promisedMessagesHash: string;
Expand All @@ -57,6 +64,11 @@ interface DashboardStatsData {
promisedMessagesHash: string;
} | null;
currentStateRoot: string;
recentBlocks: Array<{
height: number;
hash: string;
timestamp?: string;
}>;
}

interface StatCardProps {
Expand Down Expand Up @@ -111,6 +123,7 @@ function StatCard({
export default function DashboardStats() {
const [stats, setStats] = useState<DashboardStatsData | null>(null);
const [loading, setLoading] = useState(true);
const router = useRouter();

const fetchStats = useCallback(async () => {
setLoading(true);
Expand All @@ -124,6 +137,10 @@ export default function DashboardStats() {
fromStateRoot
result { stateRoot }
}
recentBlocks: blocks(take: 10, orderBy: { height: desc }) {
height
hash
}
settlements(take: 1, orderBy: { transactionHash: desc }) {
transactionHash
promisedMessagesHash
Expand Down Expand Up @@ -161,6 +178,7 @@ export default function DashboardStats() {
data?.blocks?.[0]?.result?.stateRoot ||
data?.blocks?.[0]?.fromStateRoot ||
"—",
recentBlocks: data.recentBlocks,
});
} catch (error) {
console.error("Failed to fetch dashboard stats:", error);
Expand Down Expand Up @@ -206,7 +224,10 @@ export default function DashboardStats() {
loading={loading}
>
{stats?.latestBlock ? (
<>
<div
className="cursor-pointer hover:opacity-75 transition-opacity"
onClick={() => router.push(`/blocks/${stats.latestBlock?.hash}`)}
>
<div>
<p className="text-xs text-muted-foreground mb-1">Height</p>
<p className="text-2xl font-bold">
Expand All @@ -219,7 +240,7 @@ export default function DashboardStats() {
<CopyToClipboard text={stats.latestBlock.hash} />
</div>
</div>
</>
</div>
) : (
<p className="text-sm text-muted-foreground">No data available</p>
)}
Expand All @@ -230,7 +251,12 @@ export default function DashboardStats() {
loading={loading}
>
{stats?.latestSettlement ? (
<>
<div
className="cursor-pointer hover:opacity-75 transition-opacity"
onClick={() =>
router.push(`/settlements/${stats.latestSettlement?.hash}`)
}
>
<div>
<p className="text-xs text-muted-foreground mb-1">
Transaction Hash
Expand All @@ -249,12 +275,84 @@ export default function DashboardStats() {
/>
</div>
</div>
</>
</div>
) : (
<p className="text-sm text-muted-foreground">No data available</p>
)}
</StatCard>
</div>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
<div>
<CardTitle className="flex items-center gap-2">
<Blocks className="w-5 h-5" />
Latest Blocks
</CardTitle>
<CardDescription>The 10 most recently mined blocks</CardDescription>
</div>
<Button
variant="outline"
size="sm"
onClick={() => router.push("/blocks")}
className="gap-2"
>
See all blocks
<ArrowRight className="w-4 h-4" />
</Button>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b">
<th className="text-left py-3 px-4 font-medium text-muted-foreground">
Height
</th>
<th className="text-left py-3 px-4 font-medium text-muted-foreground">
Hash
</th>
</tr>
</thead>
<tbody>
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
<tr key={i} className="border-b hover:bg-muted/50">
<td className="py-3 px-4">
<Skeleton className="h-4 w-12" />
</td>
<td className="py-3 px-4">
<Skeleton className="h-4 w-48" />
</td>
</tr>
))
) : stats?.recentBlocks && stats.recentBlocks.length > 0 ? (
stats.recentBlocks.map((block) => (
<tr
key={block.hash}
className="border-b hover:bg-muted/50 cursor-pointer transition-colors"
onClick={() => router.push(`/blocks/${block.hash}`)}
>
<td className="py-3 px-4 font-medium">#{block.height}</td>
<td className="py-3 px-4 text-muted-foreground truncate max-w-xs">
<CopyToClipboard text={block.hash} />
</td>
</tr>
))
) : (
<tr>
<td
colSpan={2}
className="py-4 px-4 text-center text-muted-foreground"
>
No blocks available
</td>
</tr>
)}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
);
}
Expand Down
Loading