183 lines
7.5 KiB
JavaScript
Raw Normal View History

2025-10-17 14:46:44 +02:00
import { h, Fragment } from 'preact';
import { useEffect, useState } from 'preact/hooks';
import { shortenHex, formatTimestamp } from '../lib/utils.js?dev=1';
import { BLOCK_DETAIL } from '../lib/api.js?dev=1';
export default function BlockDetail({ params: routeParams }) {
const blockId = routeParams[0];
const [block, setBlock] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
(async () => {
try {
const res = await fetch(BLOCK_DETAIL(blockId), {
signal: controller.signal,
cache: 'no-cache',
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setBlock(data);
} catch (err) {
if (!controller.signal.aborted) setError(err.message || 'Request failed');
}
})();
return () => controller.abort();
}, [blockId]);
const header = block?.header ?? {};
const transactions = block?.transactions ?? [];
return h(
'main',
{ class: 'wrap' },
h(
'header',
{ style: 'display:flex; gap:12px; align-items:center; margin:12px 0;' },
h('a', { class: 'linkish', href: '/' }, '← Back'),
h('h1', { style: 'margin:0' }, `Block ${shortenHex(blockId, 12, 12)}`),
),
error && h('p', { style: 'color:#ff8a8a' }, `Error: ${error}`),
!block && !error && h('p', null, 'Loading…'),
block &&
h(
Fragment,
null,
// Header card
h(
'div',
{ class: 'card', style: 'margin-top:12px;' },
h('div', { class: 'card-header' }, h('strong', null, 'Header')),
h(
'div',
{ style: 'padding:12px 14px' },
h('div', null, h('b', null, 'Slot: '), h('span', { class: 'mono' }, header.slot ?? '')),
h(
'div',
null,
h('b', null, 'Root: '),
h(
'span',
{ class: 'mono', title: header.block_root ?? '' },
shortenHex(header.block_root ?? ''),
),
),
h(
'div',
null,
h('b', null, 'Parent: '),
h(
'a',
{
class: 'linkish mono',
href: `/block/${header.parent_block ?? ''}`,
title: header.parent_block ?? '',
},
shortenHex(header.parent_block ?? ''),
),
),
h(
'div',
null,
h('b', null, 'Created: '),
h('span', { class: 'mono' }, formatTimestamp(block.created_at)),
),
),
),
// Transactions card
h(
'div',
{ class: 'card', style: 'margin-top:16px;' },
h(
'div',
{ class: 'card-header' },
h('strong', null, 'Transactions '),
h('span', { class: 'pill' }, String(transactions.length)),
),
h(
'div',
{ class: 'table-wrapper' },
h(
'table',
{ class: 'table--transactions' },
h(
'colgroup',
null,
h('col', { style: 'width:260px' }),
h('col', null),
h('col', { style: 'width:120px' }),
h('col', { style: 'width:180px' }),
),
h(
'thead',
null,
h(
'tr',
null,
h('th', null, 'Hash'),
h('th', null, 'From → To'),
h('th', null, 'Amount'),
h('th', null, 'Time'),
),
),
h(
'tbody',
null,
...transactions.map((tx) =>
h(
'tr',
null,
h(
'td',
null,
h(
'a',
{
class: 'linkish mono',
href: `/transaction/${tx.hash}`,
title: tx.hash,
},
shortenHex(tx.hash),
),
),
h(
'td',
null,
h(
'span',
{ class: 'mono', title: tx.sender ?? '' },
shortenHex(tx.sender ?? ''),
),
' \u2192 ',
h(
'span',
{ class: 'mono', title: tx.recipient ?? '' },
shortenHex(tx.recipient ?? ''),
),
),
h(
'td',
{ class: 'amount' },
Number(tx.amount ?? 0).toLocaleString(undefined, {
maximumFractionDigits: 8,
}),
),
h('td', { class: 'mono' }, formatTimestamp(tx.timestamp)),
),
),
),
),
),
),
),
);
}