Skip to content

Commit 12966c7

Browse files
Add custom amount input for credit top-ups, whole dollars only (#29)
1 parent 43674a8 commit 12966c7

3 files changed

Lines changed: 79 additions & 36 deletions

File tree

services/ui/static/app.js

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -549,12 +549,26 @@ function showCreditTopup(conversationId) {
549549
<button class="credit-btn" data-cents="2500">$25</button>
550550
<button class="credit-btn" data-cents="5000">$50</button>
551551
</div>
552+
<div class="credits-dropdown-custom">
553+
<span>$</span>
554+
<input type="number" min="5" max="1000" step="1" placeholder="custom" class="credits-custom-input" />
555+
<button class="credit-btn credits-custom-btn">add</button>
556+
</div>
552557
<div class="credit-topup-status"></div>
553558
</div>
554559
`;
555-
el.querySelectorAll('.credit-btn').forEach((btn) => {
560+
el.querySelectorAll('.credit-btn[data-cents]').forEach((btn) => {
556561
btn.addEventListener('click', () => handleCreditTopup(el, Number(btn.dataset.cents), conversationId));
557562
});
563+
const customBtn = el.querySelector('.credits-custom-btn');
564+
const customInput = el.querySelector('.credits-custom-input');
565+
if (customBtn && customInput) {
566+
customBtn.addEventListener('click', () => {
567+
const dollars = parseInt(customInput.value, 10);
568+
if (!dollars || dollars < 5 || dollars > 1000) { customInput.focus(); return; }
569+
handleCreditTopup(el, dollars * 100, conversationId);
570+
});
571+
}
558572
const messagesEl = $('branch-messages');
559573
if (messagesEl) {
560574
messagesEl.appendChild(el);
@@ -582,7 +596,7 @@ async function handleCreditTopup(widget, amountCents, conversationId) {
582596
buttons.forEach((b) => { b.disabled = false; });
583597
return;
584598
}
585-
statusEl.textContent = `Added $${(amountCents / 100).toFixed(2)} in credits. You can continue working.`;
599+
statusEl.textContent = `Added $${amountCents / 100} in credits. You can continue working.`;
586600
statusEl.className = 'credit-topup-status success';
587601
} catch (err) {
588602
statusEl.textContent = 'Network error — try again';
@@ -1766,43 +1780,57 @@ else if (mobileMq.addListener) mobileMq.addListener(syncViewportMode);
17661780
document.addEventListener('click', () => dropdown.classList.remove('open'));
17671781
dropdown.addEventListener('click', (e) => e.stopPropagation());
17681782

1769-
dropdown.querySelectorAll('.credit-btn').forEach((btn) => {
1770-
btn.addEventListener('click', () => {
1771-
const cents = Number(btn.dataset.cents);
1772-
const statusEl = $('credits-dropdown-status');
1773-
const buttons = dropdown.querySelectorAll('.credit-btn');
1774-
buttons.forEach((b) => { b.disabled = true; });
1775-
statusEl.textContent = 'Charging card...';
1776-
statusEl.className = 'credits-dropdown-status';
1777-
1778-
fetch(`${API}/topup-credits`, {
1779-
method: 'POST',
1780-
headers: { 'Content-Type': 'application/json' },
1781-
body: JSON.stringify({ amountCents: cents }),
1782-
})
1783-
.then((r) => r.json().then((d) => ({ ok: r.ok, data: d })))
1784-
.then(({ ok, data }) => {
1785-
if (!ok) {
1786-
statusEl.textContent = data.error || 'Top-up failed';
1787-
statusEl.className = 'credits-dropdown-status error';
1788-
buttons.forEach((b) => { b.disabled = false; });
1789-
} else {
1790-
statusEl.textContent = `Added $${(cents / 100).toFixed(2)}`;
1791-
statusEl.className = 'credits-dropdown-status success';
1792-
setTimeout(() => {
1793-
dropdown.classList.remove('open');
1794-
statusEl.textContent = '';
1795-
buttons.forEach((b) => { b.disabled = false; });
1796-
}, 2000);
1797-
}
1798-
})
1799-
.catch(() => {
1800-
statusEl.textContent = 'Network error';
1783+
function chargeDropdown(cents) {
1784+
const statusEl = $('credits-dropdown-status');
1785+
const buttons = dropdown.querySelectorAll('.credit-btn');
1786+
buttons.forEach((b) => { b.disabled = true; });
1787+
statusEl.textContent = 'Charging card...';
1788+
statusEl.className = 'credits-dropdown-status';
1789+
1790+
fetch(`${API}/topup-credits`, {
1791+
method: 'POST',
1792+
headers: { 'Content-Type': 'application/json' },
1793+
body: JSON.stringify({ amountCents: cents }),
1794+
})
1795+
.then((r) => r.json().then((d) => ({ ok: r.ok, data: d })))
1796+
.then(({ ok, data }) => {
1797+
if (!ok) {
1798+
statusEl.textContent = data.error || 'Top-up failed';
18011799
statusEl.className = 'credits-dropdown-status error';
18021800
buttons.forEach((b) => { b.disabled = false; });
1803-
});
1804-
});
1801+
} else {
1802+
statusEl.textContent = `Added $${(cents / 100)}`;
1803+
statusEl.className = 'credits-dropdown-status success';
1804+
setTimeout(() => {
1805+
dropdown.classList.remove('open');
1806+
statusEl.textContent = '';
1807+
buttons.forEach((b) => { b.disabled = false; });
1808+
}, 2000);
1809+
}
1810+
})
1811+
.catch(() => {
1812+
statusEl.textContent = 'Network error';
1813+
statusEl.className = 'credits-dropdown-status error';
1814+
buttons.forEach((b) => { b.disabled = false; });
1815+
});
1816+
}
1817+
1818+
dropdown.querySelectorAll('.credit-btn[data-cents]').forEach((btn) => {
1819+
btn.addEventListener('click', () => chargeDropdown(Number(btn.dataset.cents)));
18051820
});
1821+
1822+
const customBtn = $('credits-custom-btn');
1823+
const customInput = $('credits-custom-amount');
1824+
if (customBtn && customInput) {
1825+
customBtn.addEventListener('click', () => {
1826+
const dollars = parseInt(customInput.value, 10);
1827+
if (!dollars || dollars < 5 || dollars > 1000) { customInput.focus(); return; }
1828+
chargeDropdown(dollars * 100);
1829+
});
1830+
customInput.addEventListener('keydown', (e) => {
1831+
if (e.key === 'Enter') customBtn.click();
1832+
});
1833+
}
18061834
})();
18071835

18081836
// =============================================================================

services/ui/static/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ <h1>▸ reef</h1>
2828
<button class="credit-btn" data-cents="2500">$25</button>
2929
<button class="credit-btn" data-cents="5000">$50</button>
3030
</div>
31+
<div class="credits-dropdown-custom">
32+
<span>$</span>
33+
<input type="number" id="credits-custom-amount" min="5" max="1000" step="1" placeholder="custom" />
34+
<button class="credit-btn" id="credits-custom-btn">add</button>
35+
</div>
3136
<div class="credits-dropdown-status" id="credits-dropdown-status"></div>
3237
</div>
3338
</div>

services/ui/static/style.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,16 @@ header h1 {
10731073
.credits-dropdown.open { display: block; }
10741074
.credits-dropdown-title { color: var(--text-bright); font-weight: 600; margin-bottom: 8px; font-size: 12px; }
10751075
.credits-dropdown-amounts { display: flex; gap: 6px; flex-wrap: wrap; }
1076+
.credits-dropdown-custom {
1077+
display: flex; align-items: center; gap: 4px; margin-top: 6px;
1078+
}
1079+
.credits-dropdown-custom span { color: var(--text-dim); font-size: 13px; }
1080+
.credits-dropdown-custom input {
1081+
width: 70px; padding: 4px 6px;
1082+
background: var(--bg); border: 1px solid var(--border-light); border-radius: 4px;
1083+
color: var(--text-bright); font-family: var(--font); font-size: 13px;
1084+
}
1085+
.credits-dropdown-custom input:focus { outline: none; border-color: var(--accent-dim); }
10761086
.credits-dropdown-status { font-size: 11px; color: var(--text-dim); margin-top: 8px; min-height: 1em; }
10771087
.credits-dropdown-status.success { color: var(--accent); }
10781088
.credits-dropdown-status.error { color: var(--error); }

0 commit comments

Comments
 (0)