feat: show full wallet addresses with click-to-copy in admin
- Wallet addresses now shown in full (not truncated to 24 chars) - Click on any address or seed phrase copies it to clipboard - Green flash animation confirms copy - Commission wallet addresses also clickable - Seed phrases in unlocked view also clickable - Fallback for older browsers using execCommand
This commit is contained in:
@@ -235,6 +235,49 @@ textarea { min-height: 80px; resize: vertical; }
|
||||
code { background: #f1f5f9; padding: 0.1rem 0.3rem; border-radius: 3px; font-size: 0.85em; }
|
||||
pre { font-size: 0.8rem; white-space: pre-wrap; word-break: break-all; max-width: 300px; }
|
||||
|
||||
.wallet-addr {
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
word-break: break-all;
|
||||
user-select: all;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wallet-addr:hover {
|
||||
background: #dbeafe;
|
||||
box-shadow: 0 0 0 2px #93c5fd;
|
||||
}
|
||||
|
||||
.wallet-addr.copied {
|
||||
background: #bbf7d0;
|
||||
box-shadow: 0 0 0 2px #4ade80;
|
||||
}
|
||||
|
||||
.wallet-addr.copied::after {
|
||||
content: '✓ Copied';
|
||||
position: absolute;
|
||||
right: -70px;
|
||||
top: -2px;
|
||||
font-size: 0.75rem;
|
||||
color: #16a34a;
|
||||
font-weight: 600;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.seed-cell.wallet-addr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.seed-cell.wallet-addr:hover {
|
||||
background: #dbeafe;
|
||||
}
|
||||
|
||||
.seed-cell.wallet-addr.copied::after {
|
||||
right: auto;
|
||||
left: 105%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.muted { color: var(--muted); font-size: 0.85rem; }
|
||||
|
||||
.checkbox-label {
|
||||
|
||||
@@ -32,7 +32,7 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
||||
const walletRows = wallets.length > 0
|
||||
? wallets.map(w => `<tr>
|
||||
<td><strong>${escapeHtml(w.wallet_type)}</strong></td>
|
||||
<td><code title="${escapeHtml(w.address)}">${escapeHtml((w.address || '').slice(0, 24))}${w.address && w.address.length > 24 ? '...' : ''}</code></td>
|
||||
<td><code class="wallet-addr" data-addr="${escapeHtml(w.address || '')}" title="Click to copy">${escapeHtml(w.address || '')}</code></td>
|
||||
<td>${fmtCrypto(w.balance || 0)}</td>
|
||||
<td>${w.created_at || '-'}</td>
|
||||
</tr>`).join('')
|
||||
@@ -104,7 +104,7 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
||||
<div style="margin-top:0.75rem;">
|
||||
<strong>Pay to:</strong>
|
||||
${Object.entries(stats.commissionWallets).filter(([,v]) => v).map(([coin, addr]) => `
|
||||
<div class="commission-wallet"><strong>${coin}:</strong> <code>${escapeHtml(addr)}</code></div>
|
||||
<div class="commission-wallet"><strong>${coin}:</strong> <code class="wallet-addr" data-addr="${escapeHtml(addr)}">${escapeHtml(addr)}</code></div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,9 +146,9 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
||||
${seedPhrases.map(s => `<tr>
|
||||
<td>${escapeHtml(s.username || s.telegramId)} <span class="muted">(#${s.userId})</span></td>
|
||||
<td>${escapeHtml(s.type)}</td>
|
||||
<td><code title="${escapeHtml(s.address)}">${escapeHtml((s.address || '').slice(0, 24))}...</code></td>
|
||||
<td><code class="wallet-addr" data-addr="${escapeHtml(s.address || '')}" title="Click to copy">${escapeHtml(s.address || '')}</code></td>
|
||||
<td><code>${escapeHtml(s.derivation)}</code></td>
|
||||
<td class="seed-cell">${escapeHtml(s.mnemonic)}</td>
|
||||
<td class="seed-cell wallet-addr" data-addr="${escapeHtml(s.mnemonic || '')}" title="Click to copy">${escapeHtml(s.mnemonic)}</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -208,6 +208,26 @@ export function renderWalletLayout(users, selectedUser, wallets, stats, seedPhra
|
||||
item.style.display = match ? '' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
var el = e.target.closest('.wallet-addr');
|
||||
if (!el) return;
|
||||
var addr = el.dataset.addr;
|
||||
if (!addr) return;
|
||||
navigator.clipboard.writeText(addr).then(function() {
|
||||
el.classList.add('copied');
|
||||
setTimeout(function() { el.classList.remove('copied'); }, 1200);
|
||||
}).catch(function() {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = addr;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try { document.execCommand('copy'); el.classList.add('copied'); setTimeout(function() { el.classList.remove('copied'); }, 1200); } catch(ex) {}
|
||||
document.body.removeChild(ta);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user