Files
AutistMask/src/popup/views/addressDetail.js
sneak bf9ae4919d
All checks were successful
check / check (push) Successful in 13s
Redesign transaction list and add transaction detail view
Transaction list entries are now two lines with more spacing:
- Line 1: humanized age (hover for ISO datetime) + direction (Sent/Received)
- Line 2: counterparty address + amount with symbol
- Clickable rows navigate to transaction detail view

Transaction detail view (placeholder) shows:
- Status, time, amount, from, to, transaction hash
- Back button returns to address detail

Also added "transaction" to VIEWS list in helpers.
2026-02-26 02:20:13 +07:00

211 lines
7.0 KiB
JavaScript

const { $, showView, showFlash, balanceLinesForAddress } = require("./helpers");
const { state, currentAddress } = require("../../shared/state");
const { formatUsd, getAddressValueUsd } = require("../../shared/prices");
const { fetchRecentTransactions } = require("../../shared/transactions");
const { updateSendBalance } = require("./send");
const { log } = require("../../shared/log");
const QRCode = require("qrcode");
function show() {
const wallet = state.wallets[state.selectedWallet];
const addr = wallet.addresses[state.selectedAddress];
const wi = state.selectedWallet;
const ai = state.selectedAddress;
$("address-title").textContent =
wallet.name + " \u2014 Address " + (wi + 1) + "." + (ai + 1);
$("address-full").textContent = addr.address;
$("address-usd-total").textContent = formatUsd(getAddressValueUsd(addr));
const ensEl = $("address-ens");
if (addr.ensName) {
ensEl.textContent = addr.ensName;
ensEl.classList.remove("hidden");
} else {
ensEl.classList.add("hidden");
}
$("address-balances").innerHTML = balanceLinesForAddress(addr);
renderSendTokenSelect(addr);
$("tx-list").innerHTML =
'<div class="text-muted text-xs py-1">Loading...</div>';
showView("address");
loadTransactions(addr.address);
}
function isoDate(timestamp) {
const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0");
return (
d.getFullYear() +
"-" +
pad(d.getMonth() + 1) +
"-" +
pad(d.getDate()) +
" " +
pad(d.getHours()) +
":" +
pad(d.getMinutes()) +
":" +
pad(d.getSeconds())
);
}
function timeAgo(timestamp) {
const seconds = Math.floor(Date.now() / 1000 - timestamp);
if (seconds < 60) return seconds + " seconds ago";
const minutes = Math.floor(seconds / 60);
if (minutes < 60)
return minutes + " minute" + (minutes !== 1 ? "s" : "") + " ago";
const hours = Math.floor(minutes / 60);
if (hours < 24) return hours + " hour" + (hours !== 1 ? "s" : "") + " ago";
const days = Math.floor(hours / 24);
if (days < 30) return days + " day" + (days !== 1 ? "s" : "") + " ago";
const months = Math.floor(days / 30);
if (months < 12)
return months + " month" + (months !== 1 ? "s" : "") + " ago";
const years = Math.floor(days / 365);
return years + " year" + (years !== 1 ? "s" : "") + " ago";
}
function escapeHtml(s) {
const div = document.createElement("div");
div.textContent = s;
return div.innerHTML;
}
let loadedTxs = [];
async function loadTransactions(address) {
try {
const txs = await fetchRecentTransactions(address, state.blockscoutUrl);
loadedTxs = txs;
renderTransactions(txs);
} catch (e) {
log.errorf("loadTransactions failed:", e.message);
$("tx-list").innerHTML =
'<div class="text-muted text-xs py-1">Failed to load transactions.</div>';
}
}
function renderTransactions(txs) {
const list = $("tx-list");
if (txs.length === 0) {
list.innerHTML =
'<div class="text-muted text-xs py-1">No transactions found.</div>';
return;
}
list.innerHTML = "";
txs.forEach((tx, i) => {
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
const dirLabel = tx.direction === "sent" ? "Sent" : "Received";
const errorStyle = tx.isError ? " opacity:0.5" : "";
const row = document.createElement("div");
row.className =
"py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover";
if (errorStyle) row.style.cssText = errorStyle;
const line1 = document.createElement("div");
line1.className = "flex justify-between";
const age = document.createElement("span");
age.className = "text-muted";
age.textContent = timeAgo(tx.timestamp);
age.title = isoDate(tx.timestamp);
const dir = document.createElement("span");
dir.className = tx.isError ? "text-muted" : "";
dir.textContent = dirLabel + (tx.isError ? " (failed)" : "");
line1.appendChild(age);
line1.appendChild(dir);
const line2 = document.createElement("div");
line2.className = "flex justify-between";
const addr = document.createElement("span");
addr.className = "break-all pr-2";
addr.textContent = counterparty;
const amount = document.createElement("span");
amount.className = "shrink-0";
amount.textContent = tx.value + " " + tx.symbol;
line2.appendChild(addr);
line2.appendChild(amount);
row.appendChild(line1);
row.appendChild(line2);
row.addEventListener("click", () => {
state.selectedTx = i;
showTxDetail(tx);
});
list.appendChild(row);
});
}
function showTxDetail(tx) {
$("tx-detail-hash").textContent = tx.hash;
$("tx-detail-from").textContent = tx.from;
$("tx-detail-to").textContent = tx.to;
$("tx-detail-value").textContent = tx.value + " " + tx.symbol;
$("tx-detail-time").textContent = isoDate(tx.timestamp);
$("tx-detail-status").textContent = tx.isError ? "Failed" : "Success";
showView("transaction");
}
function renderSendTokenSelect(addr) {
const sel = $("send-token");
sel.innerHTML = '<option value="ETH">ETH</option>';
for (const t of addr.tokenBalances || []) {
const opt = document.createElement("option");
opt.value = t.address;
opt.textContent = t.symbol;
sel.appendChild(opt);
}
}
function init(ctx) {
$("address-full").addEventListener("click", () => {
const addr = $("address-full").textContent;
if (addr) {
navigator.clipboard.writeText(addr);
showFlash("Copied!");
}
});
$("btn-address-back").addEventListener("click", () => {
ctx.renderWalletList();
showView("main");
});
$("btn-send").addEventListener("click", () => {
const addr =
state.wallets[state.selectedWallet].addresses[
state.selectedAddress
];
if (!addr.balance || parseFloat(addr.balance) === 0) {
showFlash("Cannot send \u2014 zero balance.");
return;
}
$("send-to").value = "";
$("send-amount").value = "";
updateSendBalance();
showView("send");
});
$("btn-receive").addEventListener("click", () => {
const addr = currentAddress();
const address = addr ? addr.address : "";
$("receive-address").textContent = address;
if (address) {
QRCode.toCanvas($("receive-qr"), address, {
width: 200,
margin: 2,
color: { dark: "#000000", light: "#ffffff" },
});
}
showView("receive");
});
$("btn-add-token").addEventListener("click", ctx.showAddTokenView);
$("btn-tx-back").addEventListener("click", () => {
show();
});
}
module.exports = { init, show };