diff --git a/admin/index.html b/admin/index.html index a7d1af8..f1ffd45 100644 --- a/admin/index.html +++ b/admin/index.html @@ -150,6 +150,17 @@

TLD registry

+

Every TLD registered on the beacon. Operators + (signed-in) can toggle a TLD's visibility on the public /api/tlds listing — hidden TLDs stop + appearing in the name-search UIs, but on-chain registrations under them keep resolving.

+
+ + +
+ + +
+
Loading current registry…
@@ -222,7 +233,26 @@ async function enterAdmin() { } } -// ---------- TLD registry (always visible, signed-in or not) ---------- +// ---------- operator token ---------- +// Stored in sessionStorage so refreshing the tab keeps the token for the +// session but a full browser close forgets it. Not localStorage — the +// weaker persistence is the right default for a POST-write credential. +const opToken = () => { try { return sessionStorage.getItem("bnsOperatorToken") || ""; } catch { return ""; } }; +function setOpTokenMsg(text, ok) { + const el = $("op-token-msg"); el.textContent = text; + el.style.color = ok === true ? "var(--ok)" : ok === false ? "var(--taken)" : "var(--dim)"; +} +$("op-token").value = opToken(); +$("op-token-save").addEventListener("click", () => { + try { sessionStorage.setItem("bnsOperatorToken", $("op-token").value.trim()); } catch {} + setOpTokenMsg(opToken() ? "saved for this session" : "cleared", opToken() ? true : null); +}); + +// ---------- TLD registry with hide/unhide ---------- +// Fetches BOTH /api/tlds?include_hidden=1 (so we see the hidden ones the +// public listing filters out) and the current /api/tld-visibility. Renders +// one row per TLD with a checkbox for the hidden flag — flipping it POSTs +// to /api/tld-visibility with the operator token. async function loadRegistry() { const status = $("registry-status"); const table = $("registry-table"); @@ -230,22 +260,64 @@ async function loadRegistry() { status.textContent = "Loading current registry from the chain…"; table.innerHTML = ""; try { - const r = await fetch("https://navigate.st/api/tlds"); - if (!r.ok) throw new Error("API " + r.status); - const body = await r.json(); - // /api/tlds returns { source, root, count, legend, tlds: [{tld, certificate:{category, owner, regHeight,...}, records}] } - const tlds = body.tlds || []; + const [rTlds, rVis] = await Promise.all([ + fetch("https://silentmode.st/api/tlds?include_hidden=1"), + fetch("https://silentmode.st/api/tld-visibility"), + ]); + if (!rTlds.ok) throw new Error("api/tlds " + rTlds.status); + if (!rVis.ok) throw new Error("api/tld-visibility " + rVis.status); + const body = await rTlds.json(); + const vis = await rVis.json(); + const hidden = new Set(vis.hidden || []); + const tlds = (body.tlds || []).sort((a, b) => a.tld.localeCompare(b.tld)); status.className = "status ok"; - status.textContent = `${tlds.length} TLDs on the beacon` + (body.root ? ` · root ${body.root.slice(0, 16)}…` : ""); - let html = ''; - for (const t of tlds.sort((a, b) => a.tld.localeCompare(b.tld))) { - const cat = (t.certificate?.category || t.category || "").slice(0, 16); - const recs = t.records && Object.keys(t.records).length ? JSON.stringify(t.records) : ""; - const h = t.certificate?.regHeight ?? t.regHeight ?? ""; - html += ``; + status.textContent = `${tlds.length} TLDs on the beacon · ${hidden.size} hidden from public listing`; + let html = '
TLDCategoryRecordsHeight
.${esc(t.tld)}${esc(cat)}${cat ? "…" : ""}${esc(recs)}${esc(String(h))}
'; + for (const t of tlds) { + const isHidden = hidden.has(t.tld); + html += ` + + + + + `; } html += "
TLDNamesAdvertisedHidden from public
.${esc(t.tld)}${t.registered_count ?? 0}${t.in_advertised ? "yes" : ""}
"; table.innerHTML = html; + // Wire visibility checkboxes. + table.querySelectorAll("input[data-hide-tld]").forEach((cb) => { + cb.addEventListener("change", async (e) => { + const tld = e.target.getAttribute("data-hide-tld"); + const hide = e.target.checked; + if (!opToken()) { + e.target.checked = !hide; + setOpTokenMsg("paste your operator token first — required for /api/tld-visibility writes", false); + return; + } + e.target.disabled = true; + try { + const r = await fetch("https://silentmode.st/api/tld-visibility", { + method: "POST", + headers: { "content-type": "application/json", authorization: "Bearer " + opToken() }, + body: JSON.stringify({ tld, hide }), + }); + if (!r.ok) { + const j = await r.json().catch(() => ({})); + throw new Error(j.error || "http " + r.status); + } + setOpTokenMsg(`.${tld} is now ${hide ? "hidden" : "public"}`, true); + setTimeout(loadRegistry, 400); // refresh row states + counts + } catch (err) { + e.target.checked = !hide; + setOpTokenMsg("update failed: " + err.message, false); + } finally { + e.target.disabled = false; + } + }); + }); } catch (e) { status.className = "status err"; status.textContent = "Could not load registry: " + (e.message || e); @@ -301,6 +373,6 @@ $("mint-btn").addEventListener("click", async () => { }); - + diff --git a/brand/index.html b/brand/index.html index 596c12c..f65d642 100644 --- a/brand/index.html +++ b/brand/index.html @@ -219,6 +219,6 @@ - + diff --git a/docs/index.html b/docs/index.html index 54672cc..3aced1c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -425,6 +425,6 @@ process.exit(0);" - + diff --git a/index.html b/index.html index 021de37..d874c6f 100644 --- a/index.html +++ b/index.html @@ -421,9 +421,9 @@ process.exit(0);" - + - + diff --git a/js/profile-menu.js b/js/profile-menu.js index ec9c174..339ada6 100644 --- a/js/profile-menu.js +++ b/js/profile-menu.js @@ -90,19 +90,26 @@
` : ""; const headerHint = hasSaved - ? `Locked wallet on this deviceUnlock or pick another option` - : `No wallet yetSign in or create one`; - // One primary action ("Sign in or create") — opens the shared modal at - // the wallet-choice step. The modal shows all four options - // (Unlock/Create/Import/WizardConnect) as clear cards; clicking one - // reveals the form for that path. Choice first, form second — no - // inline forms surprising the user. + ? `Locked wallet on this deviceUnlock or pick another action` + : `No wallet yetPick how to sign in`; + // Four direct actions — each opens the shared modal at the matching + // step (Unlock/Create/Import/WizardConnect) with tabs across the top + // of the modal so the user can switch between them without going back + // to a landing screen. menu.innerHTML = `
${headerHint}
${unlockItem} - - 🔑 - Sign in or create walletpick from create · import · WizardConnect + + 🆕 + Create a walletgenerate a fresh 12-word phrase (sign up) + + + 📥 + Import a walletseed phrase you already have (sign in) + + + 🔗 + WizardConnectCashonize / Paytaca via QR `; } @@ -153,7 +160,7 @@ location.reload(); return; } - if (["choose", "new", "import", "wc", "unlock"].includes(action)) { + if (["new", "import", "wc", "unlock"].includes(action)) { close(); const savedLabel = btn.querySelector(".wallet-label")?.textContent; const walletLabel = btn.querySelector(".wallet-label"); diff --git a/js/register-flow.js b/js/register-flow.js index a5deb51..1265681 100644 --- a/js/register-flow.js +++ b/js/register-flow.js @@ -76,6 +76,19 @@ const sats = (v) => Number(v).toLocaleString("en-US"); #sirius-register-modal .wopt span{color:var(--mut,#8b98a9);font-size:13px} #sirius-register-modal .chk{display:flex;gap:9px;align-items:flex-start;margin-top:14px;font-size:13.5px;color:var(--mut,#8b98a9)} #sirius-register-modal .chk input{margin-top:3px} + #sirius-register-modal .signin-tabs{ + display:flex;gap:2px;margin:-8px -12px 18px;padding:0 4px 0; + border-bottom:1px solid var(--line,rgba(255,255,255,.09));overflow-x:auto + } + #sirius-register-modal .signin-tabs button{ + background:none;border:none;color:var(--mut,#8b98a9);font-family:inherit;font-size:12.5px; + padding:10px 12px;cursor:pointer;border-bottom:2px solid transparent; + white-space:nowrap;letter-spacing:.2px;transition:color .1s,border-color .1s + } + #sirius-register-modal .signin-tabs button:hover{color:var(--ink,#e7eaf1)} + #sirius-register-modal .signin-tabs button.active{ + color:var(--acid,#d6ff3d);border-bottom-color:var(--acid,#d6ff3d) + } #sirius-register-modal .btn{display:inline-block;text-decoration:none;padding:10px 18px;border-radius:10px; background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit} #sirius-register-modal .btn.ghost{background:transparent;border:1px solid var(--line,rgba(255,255,255,.09));color:var(--ink,#e7eaf1)} @@ -155,6 +168,43 @@ $("sirius-reg-close").onclick = close; modal.addEventListener("click", (e) => { if (e.target === modal) close(); }); function render(html) { sheet.innerHTML = html; } + +// Tab bar for sign-in mode — lets the user jump between the four wallet +// actions without going back to a wallet-choice screen. Prepended to the +// sheet content by each sign-in step. Only shown in signInOnly mode. +function renderTabs(active) { + if (!state.signInOnly) return ""; + const saved = BNS.BuiltInWallet.exists(); + const tabs = []; + if (saved) tabs.push(["unlock", "🔓 Unlock", "Wallet stored on this device"]); + tabs.push(["new", "🆕 Create", "Sign up · fresh phrase"]); + tabs.push(["import", "📥 Import", "Sign in · seed you have"]); + tabs.push(["wc", "🔗 WizardConnect", "Cashonize / Paytaca"]); + return ` +
+ ${tabs.map(([id, label, hint]) => ` + + `).join("")} +
+ `; +} +// Wire tab clicks — event delegation on the sheet so freshly-rendered +// tabs pick up the handler without rewiring per step. Switching away from +// a live WC session tears it down first so we don't leak connections. +sheet.addEventListener("click", async (e) => { + const t = e.target.closest("[data-tab]"); + if (!t) return; + const which = t.dataset.tab; + if (which !== "wc" && state.session) { + try { await state.session.disconnect(); } catch {} + state.session = null; + } + if (which === "unlock") stepUnlock(); + else if (which === "new") stepCreate(); + else if (which === "import") stepImport(); + else if (which === "wc") stepExternal(); +}); function err(e) { sheet.querySelectorAll(".err").forEach((n) => n.remove()); const box = document.createElement("div"); @@ -256,7 +306,7 @@ function stepWallet() { // ---------- WizardConnect (external wallet) ---------- async function stepExternal() { const label = "WizardConnect"; - render(`

Connect with ${esc(label)}

Starting the connection…

`); + render(`${renderTabs("wc")}

Connect with ${esc(label)}

Starting the connection…

`); let session; try { const mod = await import("https://silentmode.st/js/wizardconnect.js"); @@ -280,6 +330,7 @@ async function stepExternal() { const uri = session.qrUri ?? session.uri; render(` + ${renderTabs("wc")}

Open your wallet

Scan or paste this into ${esc(label)}-capable wallet, then approve the connection.

${esc(uri)}
@@ -380,6 +431,7 @@ function stepCreate() { // with no chosen name, so "Back" should just close the modal in that case. const goBack = state.signInOnly ? close : stepWallet; render(` + ${renderTabs("new")}

Create your wallet

Generated in this browser. The phrase is never sent anywhere — not to us, not to anyone.

@@ -427,6 +479,7 @@ function stepPhrase() { function stepImport() { const goBack = state.signInOnly ? close : stepWallet; render(` + ${renderTabs("import")}

Import a recovery phrase

12 words, separated by spaces. It stays in this browser.

@@ -451,6 +504,7 @@ function stepImport() { function stepUnlock() { const goBack = state.signInOnly ? close : stepWallet; render(` + ${renderTabs("unlock")}

Unlock your wallet

Decrypts the wallet stored in this browser.

diff --git a/portal.html b/portal.html index 22feefc..efaacb1 100644 --- a/portal.html +++ b/portal.html @@ -812,6 +812,6 @@ $("tld-submit").addEventListener("click", async () => { updateTldPrice(); - + diff --git a/theseus/index.html b/theseus/index.html index eb14896..fa483b1 100644 --- a/theseus/index.html +++ b/theseus/index.html @@ -170,6 +170,6 @@ sha256sum <downloaded-file>.exe - + diff --git a/tld.html b/tld.html index 9d6a069..1b66f88 100644 --- a/tld.html +++ b/tld.html @@ -249,8 +249,8 @@ })(); - + - +