Panic.tsx

6.0 KB · raw

1import { createSignal, Show, Switch, Match } from "solid-js";2import { sendMessage } from "../App";3import { RuneCanvas } from "../components/RuneCanvas";45interface PanicProps {6  onCancel: () => void;7  onOpenExport: () => void;8  onDestroyed: () => void | Promise<void>;9}1011// Pseudo-random spread of PeachScript PUA codepoints for the Step 3 backdrop.12const PANIC_RUNES = Array.from({ length: 80 }, (_, i) =>13  String.fromCodePoint(0xe000 + (i * 19 + 7) % 256)14).join("");1516function OverlayHeader(props: { title: string; onBack: () => void }) {17  return (18    <div class="peach-overlay-header">19      <button20        class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"21        onClick={props.onBack}22        aria-label="Back"23      >24        <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">25          <path d="M6 1L1 6L6 11" />26        </svg>27      </button>28      <span class="peach-overlay-title">{props.title}</span>29      <div class="peach-overlay-spacer"></div>30    </div>31  );32}3334export function PanicView(props: PanicProps) {35  const [step, setStep] = createSignal<1 | 2 | 3>(1);36  const [confirmText, setConfirmText] = createSignal("");37  const [destroying, setDestroying] = createSignal(false);3839  const canDestroy = () => confirmText() === "DESTROY";4041  async function handleDestroy() {42    if (destroying()) return;43    setDestroying(true);44    const res = await sendMessage({ type: "DESTROY_VAULT" });45    if (!res?.ok) {46      setDestroying(false);47      return;48    }49    await props.onDestroyed();50  }5152  return (53    <div class="peach-overlay-view ov-enter-right" style="display:flex;flex-direction:column">54      <Switch>55        <Match when={step() === 1}>56          <OverlayHeader title="danger zone" onBack={props.onCancel} />57          <div class="peach-panic">58            <h2 class="peach-panic-title">Before you destroy your vault</h2>59            <p class="peach-panic-body">60              Once destroyed, your vault cannot be recovered. Ever. There is no61              undo, no support ticket, no way back.62            </p>63            <p class="peach-panic-body">64              If you haven't already, export your vault now. It takes ten65              seconds and you'll be glad you did.66            </p>67            <button68              class="peach-panic-button peach-panic-button--primary"69              onClick={() => props.onOpenExport()}70            >71              Export my vault first72            </button>73            <div class="peach-panic-divider"></div>74            <p class="peach-panic-body">75              I have already exported my vault or I don't want to.76            </p>77            <button class="peach-panic-link" onClick={() => setStep(2)}>78              Continue to destruction →79            </button>80          </div>81        </Match>8283        <Match when={step() === 2}>84          <div class="peach-panic peach-panic-step2">85            <span class="peach-panic-icon" aria-hidden="true">⚠</span>86            <h2 class="peach-panic-title peach-panic-title--severe">87              This will permanently destroy your vault.88            </h2>89            <p class="peach-panic-body peach-panic-body--severe">90              Every password, card, passkey, note, and setting will be wiped91              from this device. This cannot be undone.92            </p>93            <p class="peach-panic-confirm-label">Type DESTROY to confirm:</p>94            <input95              class="peach-panic-confirm-input"96              classList={{ "peach-panic-confirm-input--match": canDestroy() }}97              type="text"98              value={confirmText()}99              onInput={(e) => setConfirmText(e.currentTarget.value)}100              autocomplete="off"101              spellcheck={false}102              autocapitalize="off"103              aria-label="Type DESTROY to confirm"104            />105            <div class="peach-panic-actions">106              <button107                class="peach-panic-button peach-panic-button--danger"108                disabled={!canDestroy()}109                onClick={() => setStep(3)}110              >111                Yes, destroy my vault112              </button>113              <button114                class="peach-panic-button peach-panic-button--ghost"115                onClick={() => {116                  setConfirmText("");117                  props.onCancel();118                }}119              >120                Cancel — keep my vault121              </button>122            </div>123          </div>124        </Match>125126        <Match when={step() === 3}>127          <div class="peach-panic peach-panic-step3">128            <div class="peach-panic-rune-bg" aria-hidden="true">129              <RuneCanvas runeString={PANIC_RUNES} width={320} height={480} />130            </div>131            <div class="peach-panic-bar">THIS IS YOUR LAST CHANCE</div>132            <p class="peach-panic-body peach-panic-body--final-intro">133              Your vault is about to be permanently destroyed.134            </p>135            <p class="peach-panic-body peach-panic-body--final">136              There is no recovery.137              <br />138              There is no support.139              <br />140              There is no undo.141            </p>142            <div class="peach-panic-divider"></div>143            <div class="peach-panic-actions">144              <button145                class="peach-panic-final-button"146                disabled={destroying()}147                onClick={() => void handleDestroy()}148              >149                {destroying() ? "Destroying…" : "Destroy my vault forever"}150              </button>151              <Show when={!destroying()}>152                <button153                  class="peach-panic-button peach-panic-button--ghost"154                  onClick={() => {155                    setConfirmText("");156                    setStep(1);157                  }}158                >159                  No — take me back160                </button>161              </Show>162            </div>163          </div>164        </Match>165      </Switch>166    </div>167  );168}