diff --git a/src/popup/index.html b/src/popup/index.html
index 22246f6..24447bc 100644
--- a/src/popup/index.html
+++ b/src/popup/index.html
@@ -519,6 +519,41 @@
+
+
A website is requesting access
diff --git a/src/popup/views/addressDetail.js b/src/popup/views/addressDetail.js
index 39d7be7..945f6ea 100644
--- a/src/popup/views/addressDetail.js
+++ b/src/popup/views/addressDetail.js
@@ -30,7 +30,7 @@ function show() {
loadTransactions(addr.address);
}
-function formatDate(timestamp) {
+function isoDate(timestamp) {
const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0");
return (
@@ -42,20 +42,42 @@ function formatDate(timestamp) {
" " +
pad(d.getHours()) +
":" +
- pad(d.getMinutes())
+ 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);
- renderTransactions(txs, address);
+ loadedTxs = txs;
+ renderTransactions(txs);
} catch (e) {
log.errorf("loadTransactions failed:", e.message);
$("tx-list").innerHTML =
@@ -63,30 +85,65 @@ async function loadTransactions(address) {
}
}
-function renderTransactions(txs, address) {
+function renderTransactions(txs) {
const list = $("tx-list");
if (txs.length === 0) {
list.innerHTML =
'
No transactions found.
';
return;
}
- const addrLower = address.toLowerCase();
- let html = "";
- for (const tx of txs) {
- const arrow = tx.direction === "sent" ? "\u2192" : "\u2190";
+ list.innerHTML = "";
+ txs.forEach((tx, i) => {
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
- const label = tx.direction === "sent" ? "to" : "from";
- const errorClass = tx.isError ? ' style="opacity:0.5"' : "";
- const errorTag = tx.isError
- ? '
[failed]'
- : "";
- html += `
`;
- html += `
${formatDate(tx.timestamp)} ${arrow} ${escapeHtml(tx.value)} ${escapeHtml(tx.symbol)}${errorTag}
`;
- html += `
${label}: ${escapeHtml(counterparty)}
`;
- html += `
`;
- html += `
`;
- }
- list.innerHTML = html;
+ 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) {
@@ -144,6 +201,10 @@ function init(ctx) {
});
$("btn-add-token").addEventListener("click", ctx.showAddTokenView);
+
+ $("btn-tx-back").addEventListener("click", () => {
+ show();
+ });
}
module.exports = { init, show };
diff --git a/src/popup/views/helpers.js b/src/popup/views/helpers.js
index b82fced..71f9425 100644
--- a/src/popup/views/helpers.js
+++ b/src/popup/views/helpers.js
@@ -18,6 +18,7 @@ const VIEWS = [
"receive",
"add-token",
"settings",
+ "transaction",
"approve",
];