Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import { ButtonAction } from "$lib/ui";
import BottomSheet from "$lib/ui/BottomSheet/BottomSheet.svelte";
import { PERSONAL_BINDING_MAX_LENGTH } from "$lib/utils/personalBinding";
import { Cancel01Icon } from "@hugeicons/core-free-icons";
import {
Cancel01Icon,
ViewIcon,
ViewOffIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/svelte";

interface IAddKnowledgeSheetProps {
Expand All @@ -19,13 +23,17 @@ let {

let question = $state("");
let answer = $state("");
// The answer is sensitive — mask it by default, with an eye toggle to reveal
// (the spelling tip above means the user needs to be able to check it).
let showAnswer = $state(false);

// On open: seed the question from the prop. Answer always starts blank —
// we never round-trip the raw answer, so editing means re-entering it.
$effect(() => {
if (!isOpen) return;
question = currentQuestion;
answer = "";
showAnswer = false;
});

const canSave = $derived(
Expand Down Expand Up @@ -85,17 +93,35 @@ function close() {
<label for="kn-answer" class="block text-black-500 mb-2">
Answer
</label>
<input
id="kn-answer"
type="text"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
bind:value={answer}
maxlength={PERSONAL_BINDING_MAX_LENGTH}
class="w-full bg-card-alternative rounded-full px-5 py-4 placeholder:text-black-300 outline-none focus:ring-2 focus:ring-primary"
/>
<div class="relative">
<input
id="kn-answer"
type={showAnswer ? "text" : "password"}
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
bind:value={answer}
maxlength={PERSONAL_BINDING_MAX_LENGTH}
class="w-full bg-card-alternative rounded-full pl-5 pr-14 py-4 placeholder:text-black-300 outline-none focus:ring-2 focus:ring-primary"
/>
<button
type="button"
onclick={() => {
showAnswer = !showAnswer;
}}
aria-label={showAnswer ? "Hide answer" : "Show answer"}
aria-pressed={showAnswer}
class="absolute inset-y-0 right-0 flex items-center pr-5 text-black-500 active:opacity-70"
>
<HugeiconsIcon
icon={showAnswer ? ViewOffIcon : ViewIcon}
size={20}
color="currentColor"
strokeWidth={2}
/>
</button>
</div>
{#if answer.length > PERSONAL_BINDING_MAX_LENGTH - 150}
<p class="text-xs text-black-500 text-right mt-1">
{answer.length} / {PERSONAL_BINDING_MAX_LENGTH}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import BottomSheet from "$lib/ui/BottomSheet/BottomSheet.svelte";
import LoadingSheet from "$lib/ui/LoadingSheet/LoadingSheet.svelte";
import { capitalize } from "$lib/utils";
import { PERSONAL_BINDING_BY_TYPE_QUERY } from "$lib/utils/personalBinding";
import { ArrowLeft01Icon } from "@hugeicons/core-free-icons";
import {
ArrowLeft01Icon,
ViewIcon,
ViewOffIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/svelte";
import {
Format,
Expand Down Expand Up @@ -124,6 +128,9 @@ let enameLoading = $state(false);
let answerInput = $state("");
let answerError = $state<string | null>(null);
let answerLoading = $state(false);
// The security answer is sensitive — mask it by default, with an eye toggle
// to reveal (spelling matters, so the user needs to be able to check it).
let showAnswer = $state(false);

// Question text + envelope id are pulled from the target eVault during
// handleSubmitEname. The envelope id is the metaEnvelopeId of the
Expand Down Expand Up @@ -554,6 +561,7 @@ async function handleSubmitEname() {
answerInput = "";
answerError = null;
lockedUntilLabel = null;
showAnswer = false;
step = "unverified-answer";
} catch (err: unknown) {
console.error("[RECOVERY/unverified] eName lookup error:", err);
Expand Down Expand Up @@ -1337,17 +1345,35 @@ onMount(() => {
>
{securityQuestion} <span class="text-danger">*</span>
</label>
<input
id="recover-answer"
type="text"
bind:value={answerInput}
autocomplete="off"
autocapitalize="off"
autocorrect="off"
spellcheck="false"
placeholder="Your answer"
class="w-full bg-card-alternative rounded-full px-5 py-4 placeholder:text-black-300 outline-none focus:ring-2 focus:ring-primary"
/>
<div class="relative">
<input
id="recover-answer"
type={showAnswer ? "text" : "password"}
bind:value={answerInput}
autocomplete="off"
autocapitalize="off"
autocorrect="off"
spellcheck="false"
placeholder="Your answer"
class="w-full bg-card-alternative rounded-full pl-5 pr-14 py-4 placeholder:text-black-300 outline-none focus:ring-2 focus:ring-primary"
/>
<button
type="button"
onclick={() => {
showAnswer = !showAnswer;
}}
aria-label={showAnswer ? "Hide answer" : "Show answer"}
aria-pressed={showAnswer}
class="absolute inset-y-0 right-0 flex items-center pr-5 text-black-500 active:opacity-70"
>
<HugeiconsIcon
icon={showAnswer ? ViewOffIcon : ViewIcon}
size={20}
color="currentColor"
strokeWidth={2}
/>
</button>
</div>
{#if answerError}
<p class="text-danger text-sm font-medium" role="alert">
{answerError}
Expand Down
Loading