diff options
| author | Adam Soltys <[email protected]> | 2026-11-15 14:32:10 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-11-15 14:32:10 -0700 |
| commit | 5cc640839a14f7a1c821b3630a591bca76717243 (patch) | |
| tree | cf831f0c50571fdded5f0f9d6089a0adf0d22bd2 /src | |
| parent | 513fd7db08fb3c7e89fe651ac70913264a4bff22 (diff) | |
| download | coinosv3-5cc640839a14f7a1c821b3630a591bca76717243.tar.gz coinosv3-5cc640839a14f7a1c821b3630a591bca76717243.zip | |
Receive → Amount: ask for a sum with the point of sale's pad. "Add an 'Amount' button to receive that basically brings up the PoS screen" (Adam, 2026-11-15). Under the payment address on Spending's Receive (and under the Ark address where there's no name), Amount opens the till's amount screen as "Request an amount": the same pad, a What it's for note, Create invoice → the QR and amount → paid, then Done back to Receive. It leaves the till out: no tip prompt, no tip settings, no day's sales, and its invoices are marked so the till's sales list doesn't count them. Also: the HISTORY heading under Receive/Send no longer stands over an empty wallet (the last change hid the 'No transactions yet' card but not its heading). pos-test (request mode: no tips, straight to the invoice, Done, kept out of the sales) and signup-quick (Amount on Receive, opens the pad, Back; no History heading) pass, with footer-support and features-smoke
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VMan6SwkivduFDDkMmnXjZ
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 6 | ||||
| -rw-r--r-- | src/features/names.js | 2 | ||||
| -rw-r--r-- | src/features/pos.js | 30 | ||||
| -rw-r--r-- | src/i18n.js | 5 |
4 files changed, 29 insertions, 14 deletions
@@ -5744,7 +5744,8 @@ function tabContent() { if (ui.tab === 'send' && sendFocused) return pane; return h('div', { class: 'col', style: 'gap:16px' }, pane, - h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.06em;margin-top:4px' }, t('tabHistory')), + // no heading over nothing: it comes with the first payment + hist ? h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.06em;margin-top:4px' }, t('tabHistory')) : null, hist); } @@ -5832,7 +5833,8 @@ function receiveTab() { { class: 'card col', style: 'align-items:center;gap:14px' }, h('div', { html: qrSvg(ui.arkRecvAddr) }), h('div', { class: 'addr-box break', style: 'width:100%' }, ui.arkRecvAddr), - copyBtn(ui.arkRecvAddr, t('copyAddress')) + copyBtn(ui.arkRecvAddr, t('copyAddress')), + featureHook('posAvailable') ? h('button', { class: 'btn-block', id: 'recv-amount', onClick: () => featureHook('openAmountRequest') }, t('recvAmount')) : null ); } } diff --git a/src/features/names.js b/src/features/names.js index 3f23a0c..81b64e6 100644 --- a/src/features/names.js +++ b/src/features/names.js @@ -683,6 +683,8 @@ export function namesFeature(ctx) { onClick: () => copy(addr), html: '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>', }))), + // ask for a sum: the point of sale's amount pad and invoice, simplified + hook('posAvailable') ? h('button', { class: 'btn-block', id: 'recv-amount', onClick: () => hook('openAmountRequest') }, t('recvAmount')) : null, moreSection()); } diff --git a/src/features/pos.js b/src/features/pos.js index 3727862..eb4b0e9 100644 --- a/src/features/pos.js +++ b/src/features/pos.js @@ -44,8 +44,11 @@ export function posFeature(ctx) { // ---- flow ---------------------------------------------------------------- // ui.pos: { step: 'amount'|'tip'|'invoice'|'paid', amount, note, tipMode, tipCustom, sale, error, busy } - function open() { - ui.pos = { step: 'amount', amount: '', note: '', tipMode: null, tipCustom: '', sale: null, error: '' }; + // `request`: Receive → Amount (Adam, 2026-11-15) — the same amount pad + // and invoice for asking someone for a sum, without the till around it: + // no tip prompt, no tip settings or day's sales, and Back is Receive. + function open(opts = {}) { + ui.pos = { step: 'amount', amount: '', note: '', tipMode: null, tipCustom: '', sale: null, error: '', request: !!opts.request }; render(); } // Digits on screen, into the same field a keyboard or a paste would fill. @@ -90,7 +93,7 @@ export function posFeature(ctx) { if (!bill) { p.error = t('posEnterAmount'); render(); return; } p.error = ''; if (!hook('arkReady')) { p.error = t('posNeedsSpending'); render(); return; } - if (st().askTip) { p.step = 'tip'; p.tipMode = null; p.tipCustom = ''; render(); return; } + if (st().askTip && !p.request) { p.step = 'tip'; p.tipMode = null; p.tipCustom = ''; render(); return; } makeSale(bill, 0, 0); } function tipSat() { @@ -107,6 +110,7 @@ export function posFeature(ctx) { id: 's' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), at: Date.now(), billSat: bill, tipSat: tip, tipPct, note: (p.note || '').trim().slice(0, 80), status: 'open', fiat: rateNow() ? { code: getCurrency(), rate: rateNow() } : null, + ...(p.request ? { request: true } : {}), }; try { const memo = (sale.note ? sale.note + ' · ' : '') + t('posMemo', { bill: fmtAmount(bill) + ' ' + unitLabel(), tip: tip ? fmtAmount(tip) + ' ' + unitLabel() : '0' }); @@ -145,7 +149,7 @@ export function posFeature(ctx) { function newSale() { const p = ui.pos; if (p.sale && p.sale.status === 'open') voidSale(p.sale); - Object.assign(p, { step: 'amount', amount: '', note: '', tipMode: null, tipCustom: '', sale: null, error: '' }); + Object.assign(p, { step: 'amount', amount: '', note: '', tipMode: null, tipCustom: '', sale: null, error: '', request: p.request }); render(); } @@ -194,7 +198,7 @@ export function posFeature(ctx) { return h('div', { class: 'col', style: 'gap:16px' }, ctx.brandHeader(false), h('div', { class: 'card col', style: 'gap:12px' }, - h('h3', { style: 'margin:0' }, t('posTitle')), + h('h3', { style: 'margin:0' }, t(p.request ? 'recvAmountTitle' : 'posTitle')), h('div', { class: 'input-group' }, h('input', { type: 'text', inputmode: 'decimal', class: 'pos-amount', placeholder: '0', value: p.amount, @@ -204,12 +208,12 @@ export function posFeature(ctx) { unitTag()), getUnit() !== 'fiat' && billSat() && fiatLine(billSat()) ? h('div', { class: 'small muted', style: 'text-align:right' }, fiatLine(billSat())) : null, numpad(() => p.amount, (v) => { p.amount = v; }), - h('input', { type: 'text', placeholder: t('posNoteHint'), value: p.note, maxlength: '80', onInput: (e) => { p.note = e.target.value; } }), + h('input', { type: 'text', placeholder: t(p.request ? 'recvAmountNote' : 'posNoteHint'), value: p.note, maxlength: '80', onInput: (e) => { p.note = e.target.value; } }), p.error ? h('div', { class: 'notice error small' }, p.error) : null, - h('button', { class: 'btn-primary btn-block', disabled: !!p.busy, onClick: charge }, p.busy ? h('span', { class: 'spinner sm' }) : t('posCharge')), + h('button', { class: 'btn-primary btn-block', id: 'pos-charge', disabled: !!p.busy, onClick: charge }, p.busy ? h('span', { class: 'spinner sm' }) : t(p.request ? 'recvAmountGo' : 'posCharge')), p.busy ? h('div', { class: 'small muted', style: 'text-align:center' }, t('posMakingInvoice')) : null), - tipSettingsCard(s), - salesCard(s), + p.request ? null : tipSettingsCard(s), + p.request ? null : salesCard(s), back()); } function tipSettingsCard(s) { @@ -231,7 +235,7 @@ export function posFeature(ctx) { } function salesCard(s) { adoptPaid(s); - const sales = [...s.sales].reverse(); + const sales = s.sales.filter((x) => !x.request).reverse(); // Receive → Amount isn't a sale if (!sales.length) return null; const today = dayKey(Date.now()); const paidToday = sales.filter((x) => x.status === 'paid' && dayKey(x.paidAt || x.at) === today); @@ -310,8 +314,8 @@ export function posFeature(ctx) { h('h3', { style: 'margin:0' }, t('posPaid')), money(sale.billSat + sale.tipSat, sale.fiat && sale.fiat.rate), sale.tipSat ? h('div', { class: 'small muted' }, t('posBreakdown', { bill: fmtAmount(sale.billSat), tip: fmtAmount(sale.tipSat), u: unitLabel() })) : null, - h('button', { class: 'btn-primary btn-block', onClick: newSale }, t('posNewSale'))), - back()); + p.request ? null : h('button', { class: 'btn-primary btn-block', onClick: newSale }, t('posNewSale'))), + p.request ? h('button', { class: 'btn-primary btn-block', onClick: close }, t('recvAmountDone')) : back()); } function posScreen() { const p = ui.pos; @@ -346,6 +350,8 @@ export function posFeature(ctx) { h('p', { class: 'small faint', style: 'margin:0' }, t('posLinkHint', { url })))]; }, openPos: open, + openAmountRequest: () => open({ request: true }), + posAvailable: () => !!hook('arkReady'), // The history detail of a till payment says what was the bill and what the tip. arkMoveDetailExtra(m, row) { const sale = saleOfMovement(m); diff --git a/src/i18n.js b/src/i18n.js index 121c806..97bb066 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -1677,6 +1677,11 @@ const STR = { settingsNotifDesc: 'What buzzes this device', settingsAdvancedDesc: 'Network, rarities, rescues', aboutTitle: 'About', + recvAmount: 'Amount', + recvAmountTitle: 'Request an amount', + recvAmountNote: 'What it’s for (optional)', + recvAmountGo: 'Create invoice', + recvAmountDone: 'Done', settingsAboutDesc: 'How it works, support, app, theme', settingsPosDesc: 'Till mode, tips and the /pos link', unlockForLabel: 'Stay unlocked', |
