hermes panel: mirror the Sign in / Sign up tab bar from the web
Theseus's sidebar panel.html had the pre-redesign flow (two primary buttons + Create a new account toggle + Name+password/Seed-phrase chip switcher). Replaced with the same persistent pill tab bar the web messenger uses: Sign in / Sign up visible together, active one filled, inactive outlined; tapping an inactive tab swaps the form below with no back step. "Recover with seed phrase" is a quiet link that opens its own form (with ← Back). The Theseus-only "Open with Theseus ID" quick path now lives inside the Sign in tab's form and hides entirely when the vault is locked — no more disabled button + hint about unlocking the vault cluttering the signin screen. Bumped the bundled addon to 0.2.9.
This commit is contained in:
parent
4b3fd4be42
commit
c944952f04
2 changed files with 98 additions and 69 deletions
File diff suppressed because one or more lines are too long
|
|
@ -257,55 +257,65 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== SIGNIN SCREEN ========== -->
|
||||
<!-- ========== SIGNIN SCREEN ==========
|
||||
Same tab bar as the web messenger: Sign in / Sign up persistent pill,
|
||||
active tab filled. The Theseus-only "Open with Theseus ID" quick path
|
||||
slots into the Sign in tab's form when the vault is unlocked. -->
|
||||
<div class="screen active" id="signin">
|
||||
<div class="mk" aria-hidden="true">
|
||||
<img src="assets/mark.svg" alt="" width="72" height="72" style="display:block">
|
||||
</div>
|
||||
<h1>Hermes</h1>
|
||||
<p class="lede">End-to-end encrypted DMs. Your identity is your on-chain registered name — same wallet seed as Theseus.</p>
|
||||
<div class="actions">
|
||||
<button class="primary" id="useVaultBtn" disabled>Open with Theseus ID</button>
|
||||
<button class="secondary" id="toggleCreateBtn">Create a new account</button>
|
||||
|
||||
<!-- Persistent tab bar. Clicking an inactive tab swaps the form below. -->
|
||||
<div id="startRow" style="display:flex;gap:0;width:100%;max-width:300px;border:1px solid var(--line);border-radius:999px;padding:3px;background:var(--panel)">
|
||||
<button class="tab tab-active" id="startSignInBtn" style="flex:1;font:inherit;font-weight:600;font-size:13px;padding:8px 10px;background:var(--sky);color:var(--sky-ink);border:0;border-radius:999px;cursor:pointer">Sign in</button>
|
||||
<button class="tab" id="startSignUpBtn" style="flex:1;font:inherit;font-weight:600;font-size:13px;padding:8px 10px;background:transparent;color:var(--ink);border:0;border-radius:999px;cursor:pointer">Sign up</button>
|
||||
</div>
|
||||
<div class="mnemonic" id="createBox">
|
||||
<input id="createHandle" type="text" placeholder="alice" autocomplete="off" spellcheck="false" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
|
||||
<!-- Sign-in form (default). Theseus-ID button appears above when the
|
||||
vault is unlocked (toggled by boot code based on canUseVault). -->
|
||||
<div id="signinForm" style="display:flex;width:100%;max-width:300px;flex-direction:column;gap:8px">
|
||||
<button class="primary" id="useVaultBtn" disabled style="display:none">Open with Theseus ID</button>
|
||||
<input id="signinHandle" type="text" placeholder="alice" autocomplete="username" spellcheck="false" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<input id="signinPassword" type="password" placeholder="password" autocomplete="current-password" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<button class="primary" id="signinBtn">Sign in</button>
|
||||
</div>
|
||||
|
||||
<!-- Sign-up form (hidden until Sign up tab active). -->
|
||||
<div id="signupForm" style="display:none;width:100%;max-width:300px;flex-direction:column;gap:8px">
|
||||
<input id="createHandle" type="text" placeholder="pick a name — alice" autocomplete="off" spellcheck="false" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<input id="createPassword" type="password" placeholder="password (min 6)" autocomplete="new-password" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<input id="createPassword2" type="password" placeholder="confirm password" autocomplete="new-password" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<label style="display:flex;align-items:flex-start;gap:8px;font-size:12px;color:var(--mut);text-align:left;cursor:pointer">
|
||||
<input id="createCloud" type="checkbox" style="margin-top:3px;accent-color:var(--sky)">
|
||||
<span>Back up to the cloud so I can sign in on other devices with this name + password. (You can turn this off in Settings. Your password is the only key; relays never see the seed.)</span>
|
||||
<span>Back up to the cloud so I can sign in on other devices with this name + password.</span>
|
||||
</label>
|
||||
<button class="primary" id="useCreateBtn">Create account</button>
|
||||
<div class="hint">You'll get a handle like <code>alice.erm</code>. We'll generate a seed phrase — back it up in Settings.</div>
|
||||
<div class="hint" style="margin-top:0">We'll generate a seed phrase — back it up in Settings after you sign in.</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:10px;width:100%;max-width:300px;margin-top:8px">
|
||||
<span style="flex:1;height:1px;background:var(--line)"></span>
|
||||
<span style="color:var(--mut);font-size:11.5px;letter-spacing:.4px;text-transform:uppercase">Sign in</span>
|
||||
<span style="flex:1;height:1px;background:var(--line)"></span>
|
||||
<!-- Recover-with-seed form. Reached from the "Recover with seed phrase"
|
||||
link, has its own ← Back since recovery is a one-shot flow. -->
|
||||
<div id="recoverForm" style="display:none;width:100%;max-width:300px;flex-direction:column;gap:8px">
|
||||
<textarea id="mnemonicInput" placeholder="nsec1… or 12/24-word mnemonic" autocomplete="off" spellcheck="false" style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:10px;width:100%;min-height:86px;resize:vertical"></textarea>
|
||||
<button class="primary" id="recoverBtn">Recover account</button>
|
||||
<button class="secondary" id="recoverBack" style="font:inherit;background:transparent;color:var(--mut);border:0;cursor:pointer;font-size:12px;padding:4px">← Back to sign in</button>
|
||||
<div class="hint">Stays in this panel, never leaves the device.</div>
|
||||
</div>
|
||||
|
||||
<div class="signin-chips" style="display:flex;gap:6px;width:100%;max-width:300px">
|
||||
<button class="chip chip-active" data-method="namepw" style="flex:1;font:inherit;font-size:12px;padding:7px 8px;background:var(--sky);color:#06111a;border:1px solid var(--sky);border-radius:6px;cursor:pointer;font-weight:600">Name + password</button>
|
||||
<button class="chip" data-method="seed" style="flex:1;font:inherit;font-size:12px;padding:7px 8px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:6px;cursor:pointer">Seed phrase</button>
|
||||
<!-- Hidden legacy button so the old toggleCreateBtn handler doesn't crash. -->
|
||||
<div style="display:none">
|
||||
<button id="toggleCreateBtn"></button>
|
||||
</div>
|
||||
|
||||
<div id="methodNamepw" class="method" style="width:100%;max-width:300px;display:flex;flex-direction:column;gap:8px">
|
||||
<input id="signinHandle" type="text" placeholder="alice" autocomplete="username" spellcheck="false" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<input id="signinPassword" type="password" placeholder="password" autocomplete="current-password" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
|
||||
<div class="hint">Works on this device, or on any device if you turned on cloud backup. Otherwise use seed phrase.</div>
|
||||
<div id="recoverLinkRow" style="margin-top:6px">
|
||||
<a href="#" id="recoverLink" style="color:var(--mut);font-size:12px;text-decoration:underline">Recover with seed phrase</a>
|
||||
</div>
|
||||
|
||||
<div id="methodSeed" class="method" style="width:100%;max-width:300px;display:none;flex-direction:column;gap:8px">
|
||||
<textarea id="mnemonicInput" placeholder="nsec1… or 12/24-word mnemonic — stays in this tab, never sent to the server" autocomplete="off" spellcheck="false" style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:10px;width:100%;min-height:86px;resize:vertical"></textarea>
|
||||
<div class="hint">Paste your 12 or 24 words.</div>
|
||||
</div>
|
||||
|
||||
<button class="primary" id="signinBtn" style="width:100%;max-width:300px;margin-top:4px">Sign in</button>
|
||||
<div class="err" id="signinErr"></div>
|
||||
<div class="hint" id="vaultHint"></div>
|
||||
<div class="hint" style="margin-top:4px;max-width:300px">You'll stay signed in. Sign out from the ⋯ menu.</div>
|
||||
<div class="hint" id="vaultHint" style="font-size:11px;max-width:300px;text-align:center"></div>
|
||||
</div>
|
||||
|
||||
<!-- ========== APP SCREEN ========== -->
|
||||
|
|
@ -637,6 +647,34 @@ function showSignin() {
|
|||
$("signin").classList.add("active");
|
||||
$("menuBtn").style.display = "none";
|
||||
closeMenu();
|
||||
showSigninView("signin");
|
||||
}
|
||||
|
||||
// One of: "signin" (default), "signup", "recover". Sign in / Sign up tabs
|
||||
// stay visible on the first two; recover hides them (its own ← Back).
|
||||
function showSigninView(name) {
|
||||
const inTabs = name === "signin" || name === "signup";
|
||||
$("startRow").style.display = inTabs ? "flex" : "none";
|
||||
$("signinForm").style.display = name === "signin" ? "flex" : "none";
|
||||
$("signupForm").style.display = name === "signup" ? "flex" : "none";
|
||||
$("recoverForm").style.display = name === "recover" ? "flex" : "none";
|
||||
$("recoverLinkRow").style.display = inTabs ? "block" : "none";
|
||||
|
||||
const styleTab = (btn, active) => {
|
||||
btn.classList.toggle("tab-active", active);
|
||||
btn.style.background = active ? "var(--sky)" : "transparent";
|
||||
btn.style.color = active ? "var(--sky-ink)" : "var(--ink)";
|
||||
};
|
||||
styleTab($("startSignInBtn"), name === "signin");
|
||||
styleTab($("startSignUpBtn"), name === "signup");
|
||||
|
||||
err($("signinErr"), "");
|
||||
const focusEl = {
|
||||
signin: $("signinHandle"),
|
||||
signup: $("createHandle"),
|
||||
recover: $("mnemonicInput"),
|
||||
}[name];
|
||||
if (focusEl) setTimeout(() => focusEl.focus(), 50);
|
||||
}
|
||||
|
||||
function openMenu() { $("menu").style.display = "block"; }
|
||||
|
|
@ -650,7 +688,7 @@ async function initWithOpts(opts) {
|
|||
err($("signinErr"), "");
|
||||
const btn = opts.create ? $("useCreateBtn")
|
||||
: opts.useVault ? $("useVaultBtn")
|
||||
: $("useMnemonicBtn");
|
||||
: $("recoverBtn");
|
||||
const prev = btn.textContent;
|
||||
btn.disabled = true; btn.textContent = "Opening…";
|
||||
try {
|
||||
|
|
@ -688,14 +726,18 @@ async function initWithOpts(opts) {
|
|||
}
|
||||
} catch {}
|
||||
|
||||
// Enable "Open with Theseus ID" iff the vault is unlocked AND was set up with a mnemonic.
|
||||
// Enable "Open with Theseus ID" iff the vault is unlocked AND was set up
|
||||
// with a mnemonic. The button hides entirely when unavailable so the
|
||||
// signin form stays clean on untyped vaults.
|
||||
try {
|
||||
const v = await bridge.invoke("canUseVault");
|
||||
state.canUseVault = !!(v && v.available);
|
||||
$("useVaultBtn").disabled = !state.canUseVault;
|
||||
const btn = $("useVaultBtn");
|
||||
btn.disabled = !state.canUseVault;
|
||||
btn.style.display = state.canUseVault ? "block" : "none";
|
||||
$("vaultHint").textContent = state.canUseVault
|
||||
? "Uses the same wallet seed as Theseus — nothing new to remember."
|
||||
: "To use Theseus ID, unlock the password vault first (set up with a 12/24-word mnemonic).";
|
||||
: "";
|
||||
} catch {}
|
||||
|
||||
// Panel event bus — pushes from main.js.
|
||||
|
|
@ -720,12 +762,16 @@ async function initWithOpts(opts) {
|
|||
setInterval(refreshStatus, 7000);
|
||||
})();
|
||||
|
||||
// ---- events ----
|
||||
// ---- events: tab switcher + recover link ----
|
||||
$("startSignInBtn").addEventListener("click", () => showSigninView("signin"));
|
||||
$("startSignUpBtn").addEventListener("click", () => showSigninView("signup"));
|
||||
$("recoverLink").addEventListener("click", (e) => { e.preventDefault(); showSigninView("recover"); });
|
||||
$("recoverBack").addEventListener("click", () => showSigninView("signin"));
|
||||
showSigninView("signin");
|
||||
|
||||
// Theseus-only quick path: unlock Hermes with the vault seed.
|
||||
$("useVaultBtn").addEventListener("click", () => initWithOpts({ useVault: true }));
|
||||
$("toggleCreateBtn").addEventListener("click", () => {
|
||||
$("createBox").classList.toggle("show");
|
||||
if ($("createBox").classList.contains("show")) $("createHandle").focus();
|
||||
});
|
||||
|
||||
$("useCreateBtn").addEventListener("click", async () => {
|
||||
const handle = $("createHandle").value.trim();
|
||||
const p1 = $("createPassword").value;
|
||||
|
|
@ -743,34 +789,13 @@ $("useCreateBtn").addEventListener("click", async () => {
|
|||
}
|
||||
});
|
||||
|
||||
// --- unified Sign in button + chip switcher ---
|
||||
function switchMethod(name) {
|
||||
for (const c of document.querySelectorAll(".signin-chips .chip")) {
|
||||
const active = c.dataset.method === name;
|
||||
c.classList.toggle("chip-active", active);
|
||||
c.style.background = active ? "var(--sky)" : "var(--panel)";
|
||||
c.style.color = active ? "#06111a" : "var(--ink)";
|
||||
c.style.borderColor = active ? "var(--sky)" : "var(--line)";
|
||||
c.style.fontWeight = active ? "600" : "400";
|
||||
}
|
||||
$("methodNamepw").style.display = name === "namepw" ? "flex" : "none";
|
||||
$("methodSeed").style.display = name === "seed" ? "flex" : "none";
|
||||
state._signinMethod = name;
|
||||
const focusEl = name === "namepw" ? $("signinHandle") : $("mnemonicInput");
|
||||
setTimeout(() => focusEl && focusEl.focus(), 50);
|
||||
}
|
||||
state._signinMethod = "namepw";
|
||||
for (const c of document.querySelectorAll(".signin-chips .chip")) {
|
||||
c.addEventListener("click", () => switchMethod(c.dataset.method));
|
||||
}
|
||||
$("signinBtn").addEventListener("click", async () => {
|
||||
const m = state._signinMethod || "namepw";
|
||||
err($("signinErr"), "");
|
||||
if (m === "namepw") {
|
||||
const handle = $("signinHandle").value.trim();
|
||||
const password = $("signinPassword").value;
|
||||
if (!handle) { err($("signinErr"), "enter your name"); return; }
|
||||
if (!password) { err($("signinErr"), "enter your password"); return; }
|
||||
const handle = $("signinHandle").value.trim();
|
||||
const password = $("signinPassword").value;
|
||||
if (!handle) { err($("signinErr"), "enter your name"); return; }
|
||||
if (!password) { err($("signinErr"), "enter your password"); return; }
|
||||
{
|
||||
const btn = $("signinBtn"); const prev = btn.textContent;
|
||||
btn.disabled = true; btn.textContent = "Signing in\u2026";
|
||||
try {
|
||||
|
|
@ -794,15 +819,19 @@ $("signinBtn").addEventListener("click", async () => {
|
|||
} catch (e) {
|
||||
err($("signinErr"), (e && e.message) || String(e));
|
||||
} finally { btn.disabled = false; btn.textContent = prev; }
|
||||
} else {
|
||||
const mnemonic = $("mnemonicInput").value.trim();
|
||||
if (!mnemonic || mnemonic.split(/\s+/).length < 12) {
|
||||
err($("signinErr"), "paste a 12- or 24-word mnemonic"); return;
|
||||
}
|
||||
initWithOpts({ useVault: false, mnemonic });
|
||||
}
|
||||
});
|
||||
|
||||
// Submit: Recover with seed phrase
|
||||
$("recoverBtn").addEventListener("click", () => {
|
||||
err($("signinErr"), "");
|
||||
const mnemonic = $("mnemonicInput").value.trim();
|
||||
if (!mnemonic || mnemonic.split(/\s+/).length < 12) {
|
||||
err($("signinErr"), "paste a 12- or 24-word mnemonic"); return;
|
||||
}
|
||||
initWithOpts({ useVault: false, mnemonic });
|
||||
});
|
||||
|
||||
$("popOutBtn").addEventListener("click", () => bridge.invoke("open-messages"));
|
||||
$("menuBtn").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue