-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBitcoinMetrics.tsx
More file actions
118 lines (111 loc) · 3.74 KB
/
Copy pathBitcoinMetrics.tsx
File metadata and controls
118 lines (111 loc) · 3.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import {
describeError,
formatBlockHeight,
formatCountdown,
formatRelativeTime,
} from "./api";
import { useBitcoinMetrics, useNow } from "./hooks";
import { RetryButton } from "./RetryButton";
export function BitcoinMetrics() {
const query = useBitcoinMetrics();
const now = useNow(30_000);
if (query.isPending) return <BitcoinMetricsSkeleton />;
if (query.isError && !query.data)
return (
<BitcoinMetricsError
error={query.error}
onRetry={() => query.refetch()}
fetching={query.isFetching}
/>
);
const { data, isFetching } = query;
const halvingMsLeft = data.halving.estimatedDate.getTime() - now.getTime();
return (
<section className="card metrics-card" aria-label="Bitcoin network metrics">
<header className="card__head">
<span className="eyebrow">Bitcoin Network · mempool.space</span>
<div className="card__meta" aria-live="off">
<span
className={`dot ${isFetching ? "dot--live" : ""}`}
aria-hidden="true"
/>
<span className="eyebrow">
block {formatBlockHeight(data.blockHeight)}
</span>
</div>
</header>
<dl className="card__stats metrics-card__stats">
<div className="stat">
<dt className="stat__label eyebrow">Block Height</dt>
<dd className="stat__value">{formatBlockHeight(data.blockHeight)}</dd>
</div>
<div className="stat">
<dt className="stat__label eyebrow">Last Block</dt>
<dd className="stat__value">
{formatRelativeTime(data.lastBlockTime, now)}
</dd>
</div>
<div className="stat">
<dt className="stat__label eyebrow">Next Halving</dt>
<dd className="stat__value">{formatCountdown(halvingMsLeft)}</dd>
<dd className="stat__sub eyebrow">
block {formatBlockHeight(data.halving.nextHalvingHeight)}
</dd>
</div>
<div className="stat">
<dt className="stat__label eyebrow">Fees (sat/vB)</dt>
<dd className="stat__value stat__value--fees">
<span>{data.fees.fastest}</span>
<span className="fee-divider" aria-hidden="true">·</span>
<span>{data.fees.halfHour}</span>
<span className="fee-divider" aria-hidden="true">·</span>
<span>{data.fees.hour}</span>
</dd>
<dd className="stat__sub eyebrow">next · 30m · 1h</dd>
</div>
</dl>
</section>
);
}
function BitcoinMetricsSkeleton() {
return (
<section
className="card metrics-card card--skeleton"
aria-busy="true"
aria-label="Loading Bitcoin network metrics"
>
<header className="card__head">
<span className="skeleton skeleton--sm" style={{ width: 180 }} />
<span className="skeleton skeleton--sm" style={{ width: 96 }} />
</header>
<div className="card__stats metrics-card__stats">
{[0, 1, 2, 3].map((i) => (
<div key={i} className="stat">
<span className="skeleton skeleton--sm" style={{ width: 78 }} />
<span className="skeleton skeleton--md" style={{ width: 110, marginTop: 8 }} />
</div>
))}
</div>
</section>
);
}
function BitcoinMetricsError({
error,
onRetry,
fetching,
}: {
error: unknown;
onRetry: () => void;
fetching: boolean;
}) {
const { heading, message } = describeError(error);
return (
<section className="card metrics-card card--error" role="alert">
<header className="card__head">
<span className="eyebrow eyebrow--error">{heading}</span>
</header>
<p className="card__error-msg">{message}</p>
<RetryButton onClick={onRetry} fetching={fetching} />
</section>
);
}