AdoptionConsentModal.tsx

4.7 KB · raw

1// DRAFT DESTINATION: src/popup/components/AdoptionConsentModal.tsx2// Phase 4: source-device consent modal for granting full vault adoption.34import { Show, createSignal } from "solid-js";56interface AdoptionConsentModalProps {7  receiverDeviceName: string;8  sas: string;9  verifyMasterPassword(password: string): Promise<boolean>;10  onCancel(): void;11  onApprove(): Promise<void>;12}1314export function AdoptionConsentModal(props: AdoptionConsentModalProps) {15  const [password, setPassword] = createSignal("");16  const [error, setError] = createSignal("");17  const [busy, setBusy] = createSignal(false);1819  async function submit() {20    if (!password() || busy()) return;21    setBusy(true);22    setError("");23    const ok = await props.verifyMasterPassword(password());24    if (!ok) {25      setPassword("");26      setError("Incorrect password");27      setBusy(false);28      queueMicrotask(() => document.getElementById("adoption-consent-password")?.focus());29      return;30    }31    await props.onApprove();32    setBusy(false);33  }3435  return (36    <div class="peach-adoption-consent-backdrop" role="presentation">37      <section38        class="peach-adoption-consent"39        role="dialog"40        aria-modal="true"41        aria-labelledby="adoption-consent-title"42      >43        <div class="peach-adoption-consent-icon" aria-hidden="true">!</div>44        <h2 id="adoption-consent-title">Share vault with new device?</h2>45        <p class="peach-adoption-consent-device">46          "{props.receiverDeviceName}" wants to join this vault.47        </p>48        <p class="peach-adoption-consent-warning">49          This copies your entire vault including all passwords and Pro to that device.50        </p>51        <div class="peach-adoption-consent-sas">52          <span>Confirm the code matches:</span>53          <strong>{props.sas.replace(/(\d)(?=\d)/g, "$1 ")}</strong>54        </div>55        <label class="peach-label" for="adoption-consent-password">56          Enter your master password to approve57        </label>58        <input59          id="adoption-consent-password"60          class="peach-underline-input"61          type="password"62          autocomplete="current-password"63          value={password()}64          disabled={busy()}65          onInput={(event) => setPassword((event.target as HTMLInputElement).value)}66          onKeyDown={(event) => {67            if (event.key === "Enter") void submit();68            if (event.key === "Escape") props.onCancel();69          }}70        />71        <Show when={error()}>72          <p class="peach-hint peach-hint-error">{error()}</p>73        </Show>74        <div class="peach-adoption-consent-actions">75          <button76            type="button"77            class="peach-btn peach-btn--ghost peach-btn--md"78            disabled={busy()}79            onClick={props.onCancel}80          >81            Cancel82          </button>83          <button84            type="button"85            class="peach-btn peach-btn--accent peach-btn--md peach-adoption-consent-share"86            disabled={!password() || busy()}87            onClick={() => void submit()}88          >89            {busy() ? "Sharing..." : "Share vault ->"}90          </button>91        </div>92      </section>93    </div>94  );95}9697// DRAFT DESTINATION: src/popup/popup.css98// Add amber/warning styling. Keep it visually distinct from standard confirm.99export const ADOPTION_CONSENT_CSS = `100.peach-adoption-consent-backdrop {101  position: fixed;102  inset: 0;103  z-index: 1000;104  display: grid;105  place-items: center;106  padding: 18px;107  background: rgba(15, 10, 5, 0.72);108}109.peach-adoption-consent {110  width: min(100%, 340px);111  border: 1px solid color-mix(in srgb, #f59e0b 54%, var(--peach-border));112  border-radius: 8px;113  padding: 18px;114  background: color-mix(in srgb, #2c1b08 84%, var(--peach-surface));115  color: var(--peach-t0);116  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.42);117}118.peach-adoption-consent-icon {119  width: 32px;120  height: 32px;121  display: grid;122  place-items: center;123  border-radius: 50%;124  background: #f59e0b;125  color: #1f1304;126  font-weight: 800;127}128.peach-adoption-consent h2 {129  margin: 12px 0 8px;130  font-size: 1.22rem;131  line-height: 1.15;132}133.peach-adoption-consent-device,134.peach-adoption-consent-warning {135  margin: 0 0 12px;136  color: var(--peach-t1);137  line-height: 1.35;138}139.peach-adoption-consent-warning {140  color: #ffd79a;141}142.peach-adoption-consent-sas {143  display: flex;144  justify-content: space-between;145  align-items: center;146  gap: 12px;147  margin: 14px 0;148  padding: 10px;149  border-radius: 6px;150  background: rgba(245, 158, 11, 0.12);151}152.peach-adoption-consent-sas strong {153  font-variant-numeric: tabular-nums;154  letter-spacing: 0;155}156.peach-adoption-consent-actions {157  display: flex;158  justify-content: space-between;159  gap: 10px;160  margin-top: 16px;161}162.peach-adoption-consent-actions > button {163  flex: 1;164}165`;166