Skip to content

Commit 3c424db

Browse files
authored
Merge pull request #416 from proto-kit/feat/enhance-explorer-ux
enhance explorer ux
2 parents 273e185 + f7fe538 commit 3c424db

10 files changed

Lines changed: 314 additions & 83 deletions

File tree

packages/explorer/src/app/blocks/[hash]/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ export default function BlockDetail() {
104104
const transactions: TableItem[] = (data?.block?.transactions || []).map(
105105
(tx) => ({
106106
...tx.tx,
107-
status: `${tx.status}`,
108-
statusMessage: tx.statusMessage ?? "—",
107+
status: {
108+
isSuccess: tx.status === true,
109+
message: tx.statusMessage,
110+
},
109111
})
110112
);
111113

packages/explorer/src/app/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Sparkles } from "lucide-react";
44

55
import GlobalSearch from "@/components/search/GlobalSearch";
66
import DashboardStats from "@/components/dashboard/DashboardStats";
7+
import config from "@/config";
78

89
export default function LandingPage() {
910
return (
@@ -13,12 +14,12 @@ export default function LandingPage() {
1314
<div className="flex items-center justify-center gap-2 mb-4">
1415
<Sparkles className="w-6 h-6 text-primary" />
1516
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold tracking-tight">
16-
Explorer
17+
{config.DASHBOARD_TITLE}
1718
</h1>
1819
<Sparkles className="w-6 h-6 text-primary" />
1920
</div>
2021
<p className="text-lg sm:text-xl text-muted-foreground max-w-2xl mx-auto">
21-
Explore the blockchain. Search in real-time.
22+
{config.DASHBOARD_SLOGAN}
2223
</p>
2324
</div>
2425

packages/explorer/src/components/dashboard/DashboardStats.tsx

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
/* eslint-disable no-nested-ternary */
55

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

910
import {
1011
Card,
@@ -13,6 +14,7 @@ import {
1314
CardHeader,
1415
CardTitle,
1516
} from "@/components/ui/card";
17+
import { Button } from "@/components/ui/button";
1618
import { Skeleton } from "@/components/ui/skeleton";
1719
import config from "@/config";
1820
import { cn } from "@/lib/utils";
@@ -38,6 +40,11 @@ export interface GetDashboardStatsResponse {
3840
stateRoot: string;
3941
};
4042
}>;
43+
recentBlocks: Array<{
44+
height: number;
45+
hash: string;
46+
timestamp?: string;
47+
}>;
4148
settlements: Array<{
4249
transactionHash: string;
4350
promisedMessagesHash: string;
@@ -57,6 +64,11 @@ interface DashboardStatsData {
5764
promisedMessagesHash: string;
5865
} | null;
5966
currentStateRoot: string;
67+
recentBlocks: Array<{
68+
height: number;
69+
hash: string;
70+
timestamp?: string;
71+
}>;
6072
}
6173

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

115128
const fetchStats = useCallback(async () => {
116129
setLoading(true);
@@ -124,6 +137,10 @@ export default function DashboardStats() {
124137
fromStateRoot
125138
result { stateRoot }
126139
}
140+
recentBlocks: blocks(take: 10, orderBy: { height: desc }) {
141+
height
142+
hash
143+
}
127144
settlements(take: 1, orderBy: { transactionHash: desc }) {
128145
transactionHash
129146
promisedMessagesHash
@@ -161,6 +178,7 @@ export default function DashboardStats() {
161178
data?.blocks?.[0]?.result?.stateRoot ||
162179
data?.blocks?.[0]?.fromStateRoot ||
163180
"—",
181+
recentBlocks: data.recentBlocks,
164182
});
165183
} catch (error) {
166184
console.error("Failed to fetch dashboard stats:", error);
@@ -206,7 +224,10 @@ export default function DashboardStats() {
206224
loading={loading}
207225
>
208226
{stats?.latestBlock ? (
209-
<>
227+
<div
228+
className="cursor-pointer hover:opacity-75 transition-opacity"
229+
onClick={() => router.push(`/blocks/${stats.latestBlock?.hash}`)}
230+
>
210231
<div>
211232
<p className="text-xs text-muted-foreground mb-1">Height</p>
212233
<p className="text-2xl font-bold">
@@ -219,7 +240,7 @@ export default function DashboardStats() {
219240
<CopyToClipboard text={stats.latestBlock.hash} />
220241
</div>
221242
</div>
222-
</>
243+
</div>
223244
) : (
224245
<p className="text-sm text-muted-foreground">No data available</p>
225246
)}
@@ -230,7 +251,12 @@ export default function DashboardStats() {
230251
loading={loading}
231252
>
232253
{stats?.latestSettlement ? (
233-
<>
254+
<div
255+
className="cursor-pointer hover:opacity-75 transition-opacity"
256+
onClick={() =>
257+
router.push(`/settlements/${stats.latestSettlement?.hash}`)
258+
}
259+
>
234260
<div>
235261
<p className="text-xs text-muted-foreground mb-1">
236262
Transaction Hash
@@ -249,12 +275,84 @@ export default function DashboardStats() {
249275
/>
250276
</div>
251277
</div>
252-
</>
278+
</div>
253279
) : (
254280
<p className="text-sm text-muted-foreground">No data available</p>
255281
)}
256282
</StatCard>
257283
</div>
284+
<Card>
285+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3">
286+
<div>
287+
<CardTitle className="flex items-center gap-2">
288+
<Blocks className="w-5 h-5" />
289+
Latest Blocks
290+
</CardTitle>
291+
<CardDescription>The 10 most recently mined blocks</CardDescription>
292+
</div>
293+
<Button
294+
variant="outline"
295+
size="sm"
296+
onClick={() => router.push("/blocks")}
297+
className="gap-2"
298+
>
299+
See all blocks
300+
<ArrowRight className="w-4 h-4" />
301+
</Button>
302+
</CardHeader>
303+
<CardContent>
304+
<div className="overflow-x-auto">
305+
<table className="w-full text-sm">
306+
<thead>
307+
<tr className="border-b">
308+
<th className="text-left py-3 px-4 font-medium text-muted-foreground">
309+
Height
310+
</th>
311+
<th className="text-left py-3 px-4 font-medium text-muted-foreground">
312+
Hash
313+
</th>
314+
</tr>
315+
</thead>
316+
<tbody>
317+
{loading ? (
318+
Array.from({ length: 5 }).map((_, i) => (
319+
<tr key={i} className="border-b hover:bg-muted/50">
320+
<td className="py-3 px-4">
321+
<Skeleton className="h-4 w-12" />
322+
</td>
323+
<td className="py-3 px-4">
324+
<Skeleton className="h-4 w-48" />
325+
</td>
326+
</tr>
327+
))
328+
) : stats?.recentBlocks && stats.recentBlocks.length > 0 ? (
329+
stats.recentBlocks.map((block) => (
330+
<tr
331+
key={block.hash}
332+
className="border-b hover:bg-muted/50 cursor-pointer transition-colors"
333+
onClick={() => router.push(`/blocks/${block.hash}`)}
334+
>
335+
<td className="py-3 px-4 font-medium">#{block.height}</td>
336+
<td className="py-3 px-4 text-muted-foreground truncate max-w-xs">
337+
<CopyToClipboard text={block.hash} />
338+
</td>
339+
</tr>
340+
))
341+
) : (
342+
<tr>
343+
<td
344+
colSpan={2}
345+
className="py-4 px-4 text-center text-muted-foreground"
346+
>
347+
No blocks available
348+
</td>
349+
</tr>
350+
)}
351+
</tbody>
352+
</table>
353+
</div>
354+
</CardContent>
355+
</Card>
258356
</div>
259357
);
260358
}

0 commit comments

Comments
 (0)