User page: hash-routed view, full block hash, aligned rewards
The per-user view was an overlay modal, but users asked for an actual page — focused, scrollable, addressable. Replace the modal with a hash-routed page: selectUser(addr) sets window.location.hash to #/user/<addr> and a hashchange listener syncs the selection state back, so browser back/forward and direct-link refresh all work without a routing library. App renders the dashboard or the UserDetailPage based on selection.user. Make the clickable BTC addresses in the Miners table obviously interactive: accent colour, always-on dashed underline, brighter on hover/focus. The previous styling rendered them as dim static text with a hover underline, so it wasn't obvious they were links. BlocksTable: show the full 64-char block hash as a link to mempool.space (auto-picks testnet4/signet based on snap.chain). word-break: break-all keeps the hash from blowing out the column width. Reward column is split into a tabular-num number span and a dim 0.75em "BTC" unit span, matching the Block reward tile in the overview, so numbers line up cleanly across rows.
This commit is contained in:
@@ -1,13 +1,49 @@
|
||||
// Cross-component selection state. Currently just tracks the user
|
||||
// whose detail modal is open; App renders the modal off this store
|
||||
// so any component in the tree can open it by setting `user`.
|
||||
// Simple hash-based routing for user pages. The URL hash is the
|
||||
// source of truth:
|
||||
// #/ -> dashboard
|
||||
// #/user/<address> -> per-user page
|
||||
//
|
||||
// selectUser / clearSelection just mutate the hash; a hashchange
|
||||
// listener reads it back into reactive state so any component in the
|
||||
// tree re-renders when the route changes.
|
||||
|
||||
export const selection = $state<{ user: string | null }>({ user: null });
|
||||
const USER_PREFIX = "#/user/";
|
||||
|
||||
export const selection = $state<{ user: string | null }>({
|
||||
user: readHash(),
|
||||
});
|
||||
|
||||
function readHash(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
const h = window.location.hash;
|
||||
if (h.startsWith(USER_PREFIX)) {
|
||||
const addr = decodeURIComponent(h.slice(USER_PREFIX.length));
|
||||
return addr || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function syncFromHash(): void {
|
||||
selection.user = readHash();
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("hashchange", syncFromHash);
|
||||
}
|
||||
|
||||
export function selectUser(address: string): void {
|
||||
selection.user = address;
|
||||
// Set the hash; the hashchange listener updates selection.user so
|
||||
// routing-style state stays in one place.
|
||||
window.location.hash = USER_PREFIX + encodeURIComponent(address);
|
||||
}
|
||||
|
||||
export function clearSelection(): void {
|
||||
selection.user = null;
|
||||
// Prefer history.back when we arrived here via selectUser so the
|
||||
// browser back arrow does the obvious thing; otherwise just drop
|
||||
// the hash so a fresh page load starts on the dashboard.
|
||||
if (window.history.length > 1 && window.location.hash.startsWith(USER_PREFIX)) {
|
||||
window.history.back();
|
||||
} else {
|
||||
window.location.hash = "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user