Add a "withdraw full balance" checkbox to the withdrawal form

Checking it disables the amount field, autofills it with the current
balance, and keeps it in sync if the balance changes while checked. The
actual withdrawal request uses the raw balance_sats value directly instead
of round-tripping through the PLM input field, avoiding float-rounding
drift when withdrawing the exact full balance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 14:59:02 +02:00
co-authored by Claude Sonnet 5
parent 80c413ad3d
commit 5f62a8315e
+34 -2
View File
@@ -90,6 +90,7 @@
outline: none; border-color: var(--color-ring); outline: none; border-color: var(--color-ring);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-ring) 25%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-ring) 25%, transparent);
} }
input:disabled { background: var(--color-surface-inset); color: var(--color-muted-foreground); }
button { button {
display: inline-flex; align-items: center; justify-content: center; gap: 6px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
@@ -122,6 +123,15 @@
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.checkbox-row {
display: flex; align-items: center; gap: 8px;
font-size: 0.85rem; font-weight: 500; color: var(--color-foreground);
margin-top: 14px; cursor: pointer;
}
.checkbox-row input[type="checkbox"] {
width: 18px; height: 18px; min-height: auto; flex-shrink: 0; accent-color: var(--color-primary); cursor: pointer;
}
/* --- app navbar: brand/balance row (sticky top) + section nav (bottom tab bar on /* --- app navbar: brand/balance row (sticky top) + section nav (bottom tab bar on
mobile, promoted back to an inline tab strip once there's room — see the mobile, promoted back to an inline tab strip once there's room — see the
min-width breakpoint below), shown only when logged in --- */ min-width breakpoint below), shown only when logged in --- */
@@ -556,6 +566,10 @@
<p class="hint">Solo indirizzi P2WPKH bech32 (quelli che iniziano con <code>plm1q...</code>). Indirizzi legacy (<code>P...</code>) o P2SH non sono supportati.</p> <p class="hint">Solo indirizzi P2WPKH bech32 (quelli che iniziano con <code>plm1q...</code>). Indirizzi legacy (<code>P...</code>) o P2SH non sono supportati.</p>
<label for="wd-amount">Importo (PLM)</label> <label for="wd-amount">Importo (PLM)</label>
<input id="wd-amount" inputmode="decimal" placeholder="es. 2"> <input id="wd-amount" inputmode="decimal" placeholder="es. 2">
<label class="checkbox-row">
<input type="checkbox" id="wd-full-amount" onchange="toggleWithdrawFullAmount()">
Preleva l'intero importo (<span class="mono" id="wd-full-amount-value"></span> PLM)
</label>
<button onclick="withdraw()" id="withdraw-btn">Preleva</button> <button onclick="withdraw()" id="withdraw-btn">Preleva</button>
</div> </div>
</div> </div>
@@ -998,6 +1012,7 @@ async function copyAddress() {
} }
let myUserId = null; let myUserId = null;
let myBalanceSats = 0;
async function refreshMe() { async function refreshMe() {
const btn = document.getElementById('refresh-btn'); const btn = document.getElementById('refresh-btn');
@@ -1005,18 +1020,30 @@ async function refreshMe() {
try { try {
const data = await call('GET', '/users/me'); const data = await call('GET', '/users/me');
myUserId = data.id; myUserId = data.id;
myBalanceSats = data.balance_sats;
document.getElementById('dash-balance').textContent = data.balance_sats / SATS_PER_PLM; document.getElementById('dash-balance').textContent = data.balance_sats / SATS_PER_PLM;
document.getElementById('navbar-balance').textContent = (data.balance_sats / SATS_PER_PLM) + ' PLM'; document.getElementById('navbar-balance').textContent = (data.balance_sats / SATS_PER_PLM) + ' PLM';
document.getElementById('profile-username').textContent = data.username; document.getElementById('profile-username').textContent = data.username;
document.getElementById('profile-address').textContent = data.address; document.getElementById('profile-address').textContent = data.address;
document.getElementById('profile-balance').textContent = data.balance_sats / SATS_PER_PLM; document.getElementById('profile-balance').textContent = data.balance_sats / SATS_PER_PLM;
document.getElementById('profile-created-at').textContent = new Date(data.created_at).toLocaleDateString('it-IT'); document.getElementById('profile-created-at').textContent = new Date(data.created_at).toLocaleDateString('it-IT');
document.getElementById('wd-full-amount-value').textContent = data.balance_sats / SATS_PER_PLM;
if (document.getElementById('wd-full-amount').checked) {
document.getElementById('wd-amount').value = data.balance_sats / SATS_PER_PLM;
}
} catch (e) { } catch (e) {
toast(e.message, 'error'); toast(e.message, 'error');
} }
}); });
} }
function toggleWithdrawFullAmount() {
const checked = document.getElementById('wd-full-amount').checked;
const amountInput = document.getElementById('wd-amount');
amountInput.disabled = checked;
if (checked) amountInput.value = myBalanceSats / SATS_PER_PLM;
}
async function changePassword() { async function changePassword() {
const btn = document.getElementById('change-password-btn'); const btn = document.getElementById('change-password-btn');
const currentPassword = document.getElementById('settings-current-password').value; const currentPassword = document.getElementById('settings-current-password').value;
@@ -1065,12 +1092,17 @@ async function placeBet() {
async function withdraw() { async function withdraw() {
const btn = document.getElementById('withdraw-btn'); const btn = document.getElementById('withdraw-btn');
const ext = document.getElementById('wd-address').value; const ext = document.getElementById('wd-address').value;
const amtPlm = parseFloat(document.getElementById('wd-amount').value); const isFullAmount = document.getElementById('wd-full-amount').checked;
const amtSats = Math.round(amtPlm * SATS_PER_PLM); const amtSats = isFullAmount
? myBalanceSats
: Math.round(parseFloat(document.getElementById('wd-amount').value) * SATS_PER_PLM);
await withLoading(btn, 'Invio…', async () => { await withLoading(btn, 'Invio…', async () => {
try { try {
await call('POST', '/withdrawals', { external_address: ext, amount_sats: amtSats }); await call('POST', '/withdrawals', { external_address: ext, amount_sats: amtSats });
toast('Withdrawal inviato.', 'success'); toast('Withdrawal inviato.', 'success');
document.getElementById('wd-full-amount').checked = false;
toggleWithdrawFullAmount();
document.getElementById('wd-amount').value = '';
} catch (e) { } catch (e) {
toast(e.message, 'error'); toast(e.message, 'error');
} }