AdoptionConsentModal.tsx

3.3 KB · raw

1import { Show, createSignal } from "solid-js";23export interface AdoptionConsentModalProps {4  receiverDeviceName: string;5  sas: string;6  verifyMasterPassword(password: string): Promise<boolean>;7  onCancel(): void;8  onApprove(): Promise<void>;9}1011export function AdoptionConsentModal(props: AdoptionConsentModalProps) {12  const [password, setPassword] = createSignal("");13  const [error, setError] = createSignal("");14  const [busy, setBusy] = createSignal(false);1516  async function submit() {17    if (!password() || busy()) return;18    setBusy(true);19    setError("");20    try {21      const ok = await props.verifyMasterPassword(password());22      if (!ok) {23        setPassword("");24        setError("Incorrect password");25        queueMicrotask(() => document.getElementById("adoption-consent-password")?.focus());26        return;27      }28      await props.onApprove();29    } finally {30      setBusy(false);31    }32  }3334  return (35    <div class="peach-adoption-consent-backdrop" role="presentation">36      <section37        class="peach-adoption-consent"38        role="dialog"39        aria-modal="true"40        aria-labelledby="adoption-consent-title"41      >42        <div class="peach-adoption-consent-icon" aria-hidden="true">!</div>43        <h2 id="adoption-consent-title">Share vault with new device?</h2>44        <p class="peach-adoption-consent-device">45          <strong>{props.receiverDeviceName}</strong> wants to join this vault.46        </p>47        <p class="peach-adoption-consent-warning">48          This copies your entire vault including all passwords and Pro to that device.49        </p>50        <div class="peach-adoption-consent-sas">51          <span>Confirm the code matches</span>52          <strong>{props.sas.replace(/(\d)(?=\d)/g, "$1 ")}</strong>53        </div>54        <label class="peach-label" for="adoption-consent-password">55          Enter your master password to approve56        </label>57        <input58          id="adoption-consent-password"59          class="peach-underline-input"60          type="password"61          autocomplete="current-password"62          value={password()}63          disabled={busy()}64          aria-invalid={error() ? "true" : "false"}65          aria-describedby={error() ? "adoption-consent-error" : undefined}66          onInput={(event) => {67            setPassword((event.target as HTMLInputElement).value);68            if (error()) setError("");69          }}70          onKeyDown={(event) => {71            if (event.key === "Enter") void submit();72            if (event.key === "Escape") props.onCancel();73          }}74        />75        <Show when={error()}>76          <p id="adoption-consent-error" class="peach-hint peach-hint-error">{error()}</p>77        </Show>78        <div class="peach-adoption-consent-actions">79          <button80            type="button"81            class="peach-btn peach-btn--ghost peach-btn--md"82            disabled={busy()}83            onClick={props.onCancel}84          >85            Cancel86          </button>87          <button88            type="button"89            class="peach-btn peach-btn--accent peach-btn--md peach-adoption-consent-share"90            disabled={!password() || busy()}91            onClick={() => void submit()}92          >93            {busy() ? "Sharing..." : "Share vault ->"}94          </button>95        </div>96      </section>97    </div>98  );99}