aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/names.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-05 19:48:58 -0700
committerAdam Soltys <[email protected]>2026-09-05 19:48:58 -0700
commitb6f620d97507dcbf5526c61e13675c74776df15d (patch)
tree137f0ca94b5aa1787570b824a5068f59825f5027 /src/features/names.js
parentc8c18d838573859b61c47875a4320b9ad0ad7514 (diff)
downloadcoinosv3-b6f620d97507dcbf5526c61e13675c74776df15d.tar.gz
coinosv3-b6f620d97507dcbf5526c61e13675c74776df15d.zip
Settings: lay the payment-address sections out as cards; zap settings under Nostr
Custom name, point of sale, the terminal's tap-to-pay link, own node and own domain each get a card on Settings → Payments instead of hiding in collapsed sections. The one-tap zap amount and the CLINK zap code move to Settings → Nostr, where the other nostr settings are. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMi2Y1oKPZ3TeeFmLej47y
Diffstat (limited to 'src/features/names.js')
-rw-r--r--src/features/names.js120
1 files changed, 63 insertions, 57 deletions
diff --git a/src/features/names.js b/src/features/names.js
index 06bb678..3a8a14e 100644
--- a/src/features/names.js
+++ b/src/features/names.js
@@ -412,66 +412,52 @@ export function namesFeature(ctx) {
} }, ui.busy ? h('span', { class: 'spinner' }) : t('namesClaim')));
}
- function namesCard() {
- if (!available()) return null;
+ // Settings: one card per concern, all laid out (no collapsed sections —
+ // Adam: "I don't like how so much is hidden behind toggles"). The settings
+ // page reverses the collected list, so this returns bottom-to-top.
+ function namesCards() {
+ if (!available()) return [];
const st = load();
- if (st.name) {
- const addr = `${st.name}@${st.domain || DOMAIN()}`;
- return h('div', { class: 'card col' },
+ if (!st.name) {
+ return [h('div', { class: 'card col', 'data-key': 'names' },
h('h3', {}, t('namesTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('namesDesc')),
+ claimForm(false))];
+ }
+ const domain = st.domain || DOMAIN();
+ const addr = `${st.name}@${domain}`;
+ const card = (key, title, how, ...body) => h('div', { class: 'card col', 'data-key': key },
+ h('h3', {}, title),
+ how ? h('p', { class: 'small muted', style: 'margin:0' }, how) : null,
+ ...body);
+ return [
+ card('owndomain', t('namesOwnDomain'), t('namesOwnDomainHow'),
+ h('div', { class: 'addr-box break', style: 'font-size:11px' },
+ `${st.name}.user._bitcoin-payment.yourdomain.com. CNAME ${st.name}.user._bitcoin-payment.${domain}.`)),
+ card('ownnode', t('namesOwnNode'), t('namesOwnNodeMenuHow'), ownNodeSection()),
+ posLinkCard(st),
+ posCard(st),
+ card('custom', t('namesCustom'), t('namesCustomHow'), claimForm(false)),
+ card('names', t('namesTitle'), null,
h('div', { class: 'addr-box break', style: 'font-size:14px' }, addr),
h('div', { class: 'row gap6' },
- copyBtn(`${st.name}@${st.domain || DOMAIN()}`, t('namesCopy')),
+ copyBtn(addr, t('namesCopy')),
h('button', { class: 'btn-ghost btn-sm', onClick: async () => {
await release(); toast(t('namesReleased')); render();
- } }, t('namesRelease'))),
- h('details', { class: 'small faint', 'data-key': 'custom' },
- h('summary', {}, t('namesCustom')),
- h('p', { style: 'margin:4px 0' }, t('namesCustomHow')),
- claimForm(false)),
- (() => {
- const code = hook('nwcOfferString');
- return code ? h('details', { class: 'small faint', 'data-key': 'zapcode' },
- h('summary', {}, t('namesZapCode')),
- h('p', { style: 'margin:4px 0' }, t('namesZapCodeHow')),
- h('div', { class: 'addr-box break', style: 'font-size:10px' }, code),
- copyBtn(code, t('namesZapCodeCopy'))) : null;
- })(),
- h('details', { class: 'small faint', 'data-key': 'pos' },
- h('summary', {}, t('namesPos')),
- h('p', { style: 'margin:4px 0' }, t('namesPosHow')),
- posSection(st)),
- h('details', { class: 'small faint', 'data-key': 'ownnode' },
- h('summary', {}, t('namesOwnNode')),
- h('p', { style: 'margin:4px 0' }, t('namesOwnNodeMenuHow')),
- ownNodeSection()),
- h('details', { class: 'small faint', 'data-key': 'owndomain' },
- h('summary', {}, t('namesOwnDomain')),
- h('p', { style: 'margin:4px 0' }, t('namesOwnDomainHow')),
- h('div', { class: 'addr-box break', style: 'font-size:11px' },
- `${st.name}.user._bitcoin-payment.yourdomain.com. CNAME ${st.name}.user._bitcoin-payment.${st.domain || DOMAIN()}.`)));
- }
- return h('div', { class: 'card col' },
- h('h3', {}, t('namesTitle')),
- h('p', { class: 'small muted', style: 'margin:0' }, t('namesDesc')),
- claimForm(false));
+ } }, t('namesRelease')))),
+ ].filter(Boolean);
}
// A coinos POS terminal (coinos-pos) takes payments to this name with a
// bearer token the registrar mints for the owner: the terminal can ring up
// sales and see them settle, never spend. Shown once, never stored — the
// token goes straight into the terminal's config at coinos.io/pos.
- function posSection(st) {
+ function posCard(st) {
const tok = ui.posToken;
- const domain = st.domain || DOMAIN();
- const body = () => ({ name: st.name, domain });
- // The link the terminal broadcasts over NFC: the name's lnurlp on the
- // registrar (it pins the sale just rung up), as a bech32 LNURL for
- // wallet scanners and wrapped in coinos.io/ln/ for phones without one.
- const payUrl = `${REGISTRAR}/lnurlp/${st.name}${domain === 'coinos.io' ? '' : `?domain=${encodeURIComponent(domain)}`}`;
- const lnurl = bech32.encode('lnurl', bech32.toWords(new TextEncoder().encode(payUrl)), 2000).toUpperCase();
- const link = `https://coinos.io/ln/${lnurl.toLowerCase()}`;
- return h('div', { class: 'col', style: 'gap:6px' },
+ const body = () => ({ name: st.name, domain: st.domain || DOMAIN() });
+ return h('div', { class: 'card col', 'data-key': 'pos' },
+ h('h3', {}, t('namesPos')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('namesPosHow')),
tok ? h('div', { class: 'addr-box break', style: 'font-size:10px' }, tok) : null,
h('div', { class: 'row gap6', style: 'flex-wrap:wrap' },
h('button', { class: 'btn-ghost btn-sm', onClick: async () => {
@@ -482,15 +468,25 @@ export function namesFeature(ctx) {
h('button', { class: 'btn-ghost btn-sm', onClick: async () => {
try { await post('/pos/token', 'DELETE', body()); ui.posToken = null; toast(t('namesPosRevoked')); render(); }
catch (e) { toast(e.message); }
- } }, t('namesPosRevoke'))),
- h('details', { class: 'small faint', style: 'margin-top:6px', 'data-key': 'poslink' },
- h('summary', {}, t('namesPosLink')),
- h('p', { style: 'margin:4px 0' }, t('namesPosLinkHow')),
- h('div', { style: 'align-self:center;max-width:220px', html: qrSvg(lnurl, { ec: 'L', mode: 'Alphanumeric' }) }),
- h('div', { class: 'addr-box break', style: 'font-size:10px' }, lnurl),
- h('div', { class: 'row gap6', style: 'flex-wrap:wrap' },
- copyBtn(lnurl, t('namesPosCopyLnurl')),
- copyBtn(link, t('namesPosCopyLink')))));
+ } }, t('namesPosRevoke'))));
+ }
+
+ // The link the terminal broadcasts over NFC: the name's lnurlp on the
+ // registrar (it pins the sale just rung up), as a bech32 LNURL for wallet
+ // scanners and wrapped in coinos.io/ln/ for phones without one.
+ function posLinkCard(st) {
+ const domain = st.domain || DOMAIN();
+ const payUrl = `${REGISTRAR}/lnurlp/${st.name}${domain === 'coinos.io' ? '' : `?domain=${encodeURIComponent(domain)}`}`;
+ const lnurl = bech32.encode('lnurl', bech32.toWords(new TextEncoder().encode(payUrl)), 2000).toUpperCase();
+ const link = `https://coinos.io/ln/${lnurl.toLowerCase()}`;
+ return h('div', { class: 'card col', 'data-key': 'poslink' },
+ h('h3', {}, t('namesPosLink')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('namesPosLinkHow')),
+ h('div', { style: 'align-self:center;max-width:220px', html: qrSvg(lnurl, { ec: 'L', mode: 'Alphanumeric' }) }),
+ h('div', { class: 'addr-box break', style: 'font-size:10px' }, lnurl),
+ h('div', { class: 'row gap6', style: 'flex-wrap:wrap' },
+ copyBtn(lnurl, t('namesPosCopyLnurl')),
+ copyBtn(link, t('namesPosCopyLink'))));
}
// The Receive tab's default pane: your name, big and scannable.
@@ -889,6 +885,16 @@ export function namesFeature(ctx) {
}
return null;
},
- settingsCards() { return [namesCard()]; },
+ settingsCards() { return namesCards(); },
+ // Settings → Nostr: the CLINK zap code (a nostr offer, so it lives with
+ // the other nostr settings rather than under the payment address).
+ nostrSettingsCards() {
+ const code = available() && hook('nwcOfferString');
+ return code ? [h('div', { class: 'card col', 'data-key': 'zapcode' },
+ h('h3', {}, t('namesZapCode')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('namesZapCodeHow')),
+ h('div', { class: 'addr-box break', style: 'font-size:10px' }, code),
+ h('div', { class: 'row gap6' }, copyBtn(code, t('namesZapCodeCopy'))))] : [];
+ },
};
}