fix: show every password error in its own fixed-height line (closes #493)
The add wallet screen reported a missing, short or mismatched password in the flash line at the top of the popup, and the private key export, recovery phrase and delete wallet screens each wrote to a line of their own above the password field. All four now use showError() and hideError() with a fixed-height error line below the field, as the send confirmation and approval screens do. On the add wallet screen the line sits beside the Import button, which keeps its place at 360x600. The line clears when the screen is shown again and when the password is tried again. Other add wallet messages stay in the flash line. Model: opus-5-5
This commit was merged in pull request #496.
This commit is contained in:
+30
-20
@@ -207,12 +207,22 @@
|
||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
id="btn-add-wallet-confirm"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<!-- The error line sits beside Import, not above it: at
|
||||
360x600 the button already starts near the bottom of
|
||||
the popup, and a line of its own would push it below
|
||||
the fold. The longest error fits on one line here. -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
id="btn-add-wallet-confirm"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<div
|
||||
id="add-wallet-password-error"
|
||||
class="text-xs min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ MAIN VIEW: ALL WALLETS & ADDRESSES ============ -->
|
||||
@@ -396,10 +406,6 @@
|
||||
Warning: anyone with this private key can access and
|
||||
transfer all funds from this address. Never share it.
|
||||
</p>
|
||||
<div
|
||||
id="export-privkey-flash"
|
||||
class="text-xs mb-2 min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
<div id="export-privkey-password-section" class="mb-2">
|
||||
<label class="block mb-1">Password</label>
|
||||
<input
|
||||
@@ -408,9 +414,13 @@
|
||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||
placeholder="Enter your password to continue"
|
||||
/>
|
||||
<div
|
||||
id="export-privkey-password-error"
|
||||
class="text-xs mt-2 mb-2 min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
<button
|
||||
id="btn-export-privkey-confirm"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mt-2"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Reveal
|
||||
</button>
|
||||
@@ -1116,10 +1126,6 @@
|
||||
<strong id="delete-wallet-name"></strong> is permanent. Any
|
||||
funds will be unrecoverable without your recovery phrase.
|
||||
</p>
|
||||
<div
|
||||
id="delete-wallet-flash"
|
||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
<div class="mb-2">
|
||||
<label class="block mb-1">Password</label>
|
||||
<input
|
||||
@@ -1129,6 +1135,10 @@
|
||||
placeholder="Enter your password to confirm"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="delete-wallet-password-error"
|
||||
class="text-xs mb-2 min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
<button
|
||||
id="btn-delete-wallet-confirm"
|
||||
class="border border-border text-red-500 px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
@@ -1273,10 +1283,6 @@
|
||||
this wallet, from any device, without your password. Never
|
||||
type them into a website and never show them to anyone.
|
||||
</div>
|
||||
<div
|
||||
id="show-phrase-flash"
|
||||
class="text-xs text-red-500 mb-2 min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
<div id="show-phrase-password-section" class="mb-2">
|
||||
<label class="block mb-1">Password</label>
|
||||
<input
|
||||
@@ -1285,9 +1291,13 @@
|
||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||
placeholder="Enter your password to continue"
|
||||
/>
|
||||
<div
|
||||
id="show-phrase-password-error"
|
||||
class="text-xs mt-2 mb-2 min-h-[1.25rem] invisible"
|
||||
></div>
|
||||
<button
|
||||
id="btn-show-phrase-reveal"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mt-2"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Reveal
|
||||
</button>
|
||||
|
||||
@@ -2,6 +2,8 @@ const {
|
||||
$,
|
||||
showView,
|
||||
showFlash,
|
||||
showError,
|
||||
hideError,
|
||||
goBack,
|
||||
clearViewStack,
|
||||
onViewLeave,
|
||||
@@ -98,6 +100,7 @@ function clear() {
|
||||
$("add-wallet-password").value = "";
|
||||
$("add-wallet-password-confirm").value = "";
|
||||
$("add-wallet-phrase-warning").style.visibility = "hidden";
|
||||
hideError("add-wallet-password-error");
|
||||
}
|
||||
|
||||
// Each wallet has its own password (its own encryptedSecret), so adding a
|
||||
@@ -125,15 +128,18 @@ function validatePassword() {
|
||||
const pw = $("add-wallet-password").value;
|
||||
const pw2 = $("add-wallet-password-confirm").value;
|
||||
if (!pw) {
|
||||
showFlash("Please choose a password.");
|
||||
showError("add-wallet-password-error", "Please choose a password.");
|
||||
return null;
|
||||
}
|
||||
if (pw.length < 12) {
|
||||
showFlash("Password must be at least 12 characters.");
|
||||
showError(
|
||||
"add-wallet-password-error",
|
||||
"Password must be at least 12 characters.",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if (pw !== pw2) {
|
||||
showFlash("Passwords do not match.");
|
||||
showError("add-wallet-password-error", "Passwords do not match.");
|
||||
return null;
|
||||
}
|
||||
return pw;
|
||||
@@ -342,8 +348,10 @@ function init(ctx) {
|
||||
$("add-wallet-phrase-warning").style.visibility = "visible";
|
||||
});
|
||||
|
||||
// Import / confirm
|
||||
// Import / confirm. Each press starts with no password error on screen:
|
||||
// validatePassword() puts it back if the password is still wrong.
|
||||
$("btn-add-wallet-confirm").addEventListener("click", async () => {
|
||||
hideError("add-wallet-password-error");
|
||||
if (currentMode === "mnemonic") {
|
||||
await importMnemonic(ctx);
|
||||
} else if (currentMode === "privkey") {
|
||||
|
||||
@@ -2,6 +2,8 @@ const {
|
||||
$,
|
||||
showView,
|
||||
showFlash,
|
||||
showError,
|
||||
hideError,
|
||||
goBack,
|
||||
clearViewStack,
|
||||
onViewLeave,
|
||||
@@ -55,8 +57,7 @@ function confirmKey(name) {
|
||||
function clear() {
|
||||
deleteWalletIndex = null;
|
||||
$("delete-wallet-password").value = "";
|
||||
$("delete-wallet-flash").textContent = "";
|
||||
$("delete-wallet-flash").style.visibility = "hidden";
|
||||
hideError("delete-wallet-password-error");
|
||||
}
|
||||
|
||||
// The lost-password screen holds no secret — a wallet name is not one —
|
||||
@@ -232,19 +233,22 @@ function init(_ctx) {
|
||||
$("btn-delete-wallet-confirm").addEventListener("click", async () => {
|
||||
const pw = $("delete-wallet-password").value;
|
||||
if (!pw) {
|
||||
$("delete-wallet-flash").textContent =
|
||||
"Please enter your password.";
|
||||
$("delete-wallet-flash").style.visibility = "visible";
|
||||
showError(
|
||||
"delete-wallet-password-error",
|
||||
"Please enter your password.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (deleteWalletIndex === null) {
|
||||
$("delete-wallet-flash").textContent =
|
||||
"No wallet selected for deletion.";
|
||||
$("delete-wallet-flash").style.visibility = "visible";
|
||||
showError(
|
||||
"delete-wallet-password-error",
|
||||
"No wallet selected for deletion.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
hideError("delete-wallet-password-error");
|
||||
const btn = $("btn-delete-wallet-confirm");
|
||||
btn.disabled = true;
|
||||
btn.classList.add("text-muted");
|
||||
@@ -256,9 +260,10 @@ function init(_ctx) {
|
||||
try {
|
||||
await decryptWithPassword(wallet.encryptedSecret, pw);
|
||||
} catch {
|
||||
$("delete-wallet-flash").textContent =
|
||||
"That password is incorrect. Please try again.";
|
||||
$("delete-wallet-flash").style.visibility = "visible";
|
||||
showError(
|
||||
"delete-wallet-password-error",
|
||||
"That password is incorrect. Please try again.",
|
||||
);
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("text-muted");
|
||||
return;
|
||||
|
||||
@@ -20,6 +20,8 @@ const {
|
||||
$,
|
||||
showView,
|
||||
showFlash,
|
||||
showError,
|
||||
hideError,
|
||||
flashCopyFeedback,
|
||||
goBack,
|
||||
onViewLeave,
|
||||
@@ -56,11 +58,6 @@ function isCurrentReveal(generation) {
|
||||
);
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
$("export-privkey-flash").textContent = message;
|
||||
$("export-privkey-flash").style.visibility = "visible";
|
||||
}
|
||||
|
||||
// Wipe every trace of the key and drop the address selection. Safe to call
|
||||
// when nothing was ever revealed, and safe to call twice.
|
||||
function clear() {
|
||||
@@ -71,8 +68,7 @@ function clear() {
|
||||
$("export-privkey-password").value = "";
|
||||
$("export-privkey-result").classList.add("hidden");
|
||||
$("export-privkey-password-section").classList.remove("hidden");
|
||||
$("export-privkey-flash").textContent = "";
|
||||
$("export-privkey-flash").style.visibility = "hidden";
|
||||
hideError("export-privkey-password-error");
|
||||
}
|
||||
|
||||
function show(walletIdx, addrIdx) {
|
||||
@@ -112,15 +108,19 @@ function show(walletIdx, addrIdx) {
|
||||
async function reveal() {
|
||||
const password = $("export-privkey-password").value;
|
||||
if (!password) {
|
||||
fail("Please enter your password.");
|
||||
showError(
|
||||
"export-privkey-password-error",
|
||||
"Please enter your password.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (walletIndex === null) {
|
||||
fail("No address is selected.");
|
||||
showError("export-privkey-password-error", "No address is selected.");
|
||||
return;
|
||||
}
|
||||
const wallet = state.wallets[walletIndex];
|
||||
|
||||
hideError("export-privkey-password-error");
|
||||
const btn = $("btn-export-privkey-confirm");
|
||||
btn.disabled = true;
|
||||
btn.classList.add("text-muted");
|
||||
@@ -140,11 +140,12 @@ async function reveal() {
|
||||
$("export-privkey-password-section").classList.add("hidden");
|
||||
$("export-privkey-value").textContent = signer.privateKey;
|
||||
$("export-privkey-result").classList.remove("hidden");
|
||||
$("export-privkey-flash").textContent = "";
|
||||
$("export-privkey-flash").style.visibility = "hidden";
|
||||
} catch {
|
||||
if (!isCurrentReveal(generation)) return;
|
||||
fail("That password is incorrect. Please try again.");
|
||||
showError(
|
||||
"export-privkey-password-error",
|
||||
"That password is incorrect. Please try again.",
|
||||
);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("text-muted");
|
||||
|
||||
@@ -21,6 +21,8 @@ const {
|
||||
$,
|
||||
showView,
|
||||
showFlash,
|
||||
showError,
|
||||
hideError,
|
||||
flashCopyFeedback,
|
||||
goBack,
|
||||
onViewLeave,
|
||||
@@ -52,11 +54,6 @@ function isCurrentReveal(generation) {
|
||||
);
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
$("show-phrase-flash").textContent = message;
|
||||
$("show-phrase-flash").style.visibility = "visible";
|
||||
}
|
||||
|
||||
// Wipe every trace of the phrase and drop the wallet selection. Safe to
|
||||
// call when nothing was ever revealed, and safe to call twice.
|
||||
function clear() {
|
||||
@@ -66,8 +63,7 @@ function clear() {
|
||||
$("show-phrase-password").value = "";
|
||||
$("show-phrase-result").classList.add("hidden");
|
||||
$("show-phrase-password-section").classList.remove("hidden");
|
||||
$("show-phrase-flash").textContent = "";
|
||||
$("show-phrase-flash").style.visibility = "hidden";
|
||||
hideError("show-phrase-password-error");
|
||||
}
|
||||
|
||||
function show(walletIdx) {
|
||||
@@ -90,19 +86,23 @@ function show(walletIdx) {
|
||||
async function reveal() {
|
||||
const password = $("show-phrase-password").value;
|
||||
if (!password) {
|
||||
fail("Please enter your password.");
|
||||
showError("show-phrase-password-error", "Please enter your password.");
|
||||
return;
|
||||
}
|
||||
if (walletIndex === null) {
|
||||
fail("No wallet is selected.");
|
||||
showError("show-phrase-password-error", "No wallet is selected.");
|
||||
return;
|
||||
}
|
||||
const wallet = state.wallets[walletIndex];
|
||||
if (!walletHasRecoveryPhrase(wallet)) {
|
||||
fail("This wallet does not have a recovery phrase.");
|
||||
showError(
|
||||
"show-phrase-password-error",
|
||||
"This wallet does not have a recovery phrase.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
hideError("show-phrase-password-error");
|
||||
const btn = $("btn-show-phrase-reveal");
|
||||
btn.disabled = true;
|
||||
btn.classList.add("text-muted");
|
||||
@@ -120,13 +120,14 @@ async function reveal() {
|
||||
$("show-phrase-password-section").classList.add("hidden");
|
||||
$("show-phrase-value").textContent = phrase;
|
||||
$("show-phrase-result").classList.remove("hidden");
|
||||
$("show-phrase-flash").textContent = "";
|
||||
$("show-phrase-flash").style.visibility = "hidden";
|
||||
} catch {
|
||||
if (!isCurrentReveal(generation)) return;
|
||||
// Deliberately not the caught error: the message is fixed so that
|
||||
// nothing derived from the ciphertext or the attempt can surface.
|
||||
fail("That password is incorrect. Please try again.");
|
||||
showError(
|
||||
"show-phrase-password-error",
|
||||
"That password is incorrect. Please try again.",
|
||||
);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("text-muted");
|
||||
|
||||
Reference in New Issue
Block a user