aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-16 00:37:34 -0700
committerAdam Soltys <[email protected]>2026-06-16 00:37:34 -0700
commit1cb481d2c906b983b325c97cd2629b253ebe7db4 (patch)
treebffeb46e0c4406d8b09775f4aaae4085ef5f21d0 /src
parent51d07a894c9fcf57951930b2659426220c4cacd1 (diff)
downloadcoinosv3-1cb481d2c906b983b325c97cd2629b253ebe7db4.tar.gz
coinosv3-1cb481d2c906b983b325c97cd2629b253ebe7db4.zip
Add local transaction details page
Clicking a history item now opens an in-wallet details view (status, block, time, amount, fee, full copyable txid) instead of jumping straight to mempool.space — works offline from cached data, with mempool as an optional outbound link. Back returns to the list; switching tabs/logout resets it. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/app.js47
1 files changed, 45 insertions, 2 deletions
diff --git a/src/app.js b/src/app.js
index 27b3ab3..c5a9836 100644
--- a/src/app.js
+++ b/src/app.js
@@ -34,6 +34,7 @@ const ui = {
tab: 'receive', // receive | send | history | settings
receiveSeenIndex: null, // fresh receive index the user has acknowledged
+ txDetail: null, // txid being viewed in the history detail view
send: blankSend(),
draft: null, // built tx summary awaiting review
sendError: '',
@@ -489,6 +490,7 @@ function lock() {
ui.confirm = [];
ui.revealShown = false;
ui.receiveSeenIndex = null;
+ ui.txDetail = null;
render();
}
@@ -633,6 +635,7 @@ function tabsBar() {
tabBtn(label, ui.tab === id, () => {
ui.tab = id;
ui.revealShown = false; // re-mask the recovery phrase whenever tabs change
+ ui.txDetail = null; // back to the history list when leaving/returning
render();
})
)
@@ -1004,6 +1007,11 @@ function sendResultView() {
// ---------------------------------------------------------------- History
function historyTab() {
+ if (ui.txDetail) {
+ const t = wallet.txs.find((x) => x.txid === ui.txDetail);
+ if (t) return txDetailView(t);
+ ui.txDetail = null;
+ }
if (wallet.offline)
return h('div', { class: 'card' }, h('p', { class: 'muted center', style: 'margin:0' }, 'Transaction history is unavailable in offline mode.'));
if (wallet.scanning && !wallet.loaded) return h('div', { class: 'card center' }, h('span', { class: 'spinner' }));
@@ -1018,8 +1026,8 @@ function historyTab() {
wallet.txs.map((t) => {
const incoming = t.net >= 0;
return h(
- 'a',
- { class: 'item', href: wallet.api.explorerTx(t.txid), target: '_blank', rel: 'noopener', style: 'color:inherit' },
+ 'div',
+ { class: 'item', style: 'cursor:pointer', onClick: () => { ui.txDetail = t.txid; render(); } },
h('div', { class: `ico ${incoming ? 'in' : 'out'}` }, incoming ? '↓' : '↑'),
h('div', { class: 'grow' },
h('div', { class: 'row gap6' },
@@ -1038,6 +1046,40 @@ function historyTab() {
);
}
+function txDetailView(t) {
+ const incoming = t.net >= 0;
+ const when = t.confirmed && t.blockTime
+ ? new Date(t.blockTime * 1000).toLocaleString() + ' · ' + timeAgo(t.blockTime)
+ : 'Unconfirmed — waiting to be mined';
+ const line = (k, v) => h('div', { class: 'line' }, h('span', { class: 'k' }, k), h('span', { class: 'v' }, v));
+ return h(
+ 'div',
+ { class: 'card col' },
+ h('div', { class: 'row between' },
+ h('h3', {}, incoming ? 'Received' : 'Sent'),
+ h('span', { class: `tag ${t.confirmed ? 'conf' : 'pending'}` }, t.confirmed ? 'confirmed' : '⏳ pending')
+ ),
+ h('div', { class: 'amt', style: 'font-size:30px' },
+ h('span', { class: incoming ? 'amount-pos' : 'amount-neg' }, (incoming ? '+' : '') + fmtAmount(t.net)),
+ ' ', unitTag('unit')
+ ),
+ h('div', { class: 'summary col', style: 'gap:0' },
+ line('Status', t.confirmed ? `Confirmed (block ${t.blockHeight || '—'})` : 'In mempool'),
+ line('When', when),
+ !incoming && t.fee ? line('Network fee', fmtAmount(t.fee) + ' ' + unitLabel()) : null
+ ),
+ h('div', { class: 'col gap6' },
+ h('span', { class: 'lab' }, 'Transaction ID'),
+ h('div', { class: 'addr-box', style: 'font-size:13px' }, t.txid)
+ ),
+ h('div', { class: 'row gap6 wrap' },
+ copyBtn(t.txid, 'Copy ID'),
+ h('a', { class: 'btn btn-sm', href: wallet.api.explorerTx(t.txid), target: '_blank', rel: 'noopener' }, 'View on mempool.space ↗')
+ ),
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.txDetail = null; render(); } }, '← Back to history')
+ );
+}
+
// Offline snapshot exchange: export coins on an online device, import on an
// offline (air-gapped) one to sign without internet.
function snapshotActions() {
@@ -1085,3 +1127,4 @@ if (!restoreSession()) render();
+