5133483cf65f

feat: settings — license key visible with one-tap copy (v2.12.2)

time
2026-04-29 17:44 UTC · 6 months ago
machine
koh2
parent
5e15c7c0eafe
4 files manifest.firefox.json+1 −1manifest.json+1 −1src/popup/styles/popup.css+45 −0src/popup/views/Settings.tsx+32 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.12.1",4+  "version": "2.12.2",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.12.1",4+  "version": "2.12.2",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

src/popup/styles/popup.css+45 −0
@@ -6678,6 +6678,51 @@66786678   animation: peach-panic-pulse 2s ease-in-out infinite;66796679 }66806680 6681+.peach-settings-license {6682+  margin-top: 10px;6683+  padding-top: 10px;6684+  border-top: 1px solid var(--peach-t3);6685+}6686+6687+.peach-settings-label {6688+  font-size: 10px;6689+  letter-spacing: 0.08em;6690+  color: var(--peach-t3);6691+  display: block;6692+  margin-bottom: 6px;6693+}6694+6695+.peach-settings-license-row {6696+  display: flex;6697+  align-items: center;6698+  justify-content: space-between;6699+  gap: 8px;6700+}6701+6702+.peach-settings-license-key {6703+  font-family: 'IBM Plex Mono', monospace;6704+  font-size: 12px;6705+  color: var(--peach-t0);6706+  letter-spacing: 0.04em;6707+  word-break: break-all;6708+}6709+6710+.peach-settings-license-copy {6711+  font-size: 11px;6712+  color: var(--peach-accent);6713+  background: transparent;6714+  border: none;6715+  cursor: pointer;6716+  padding: 4px 8px;6717+  border-radius: 4px;6718+  flex-shrink: 0;6719+  transition: opacity 150ms ease;6720+}6721+6722+.peach-settings-license-copy:hover {6723+  opacity: 0.75;6724+}6725+66816726 @keyframes peach-panic-pulse {66826727   0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.4); }66836728   50%      { box-shadow: 0 0 0 8px rgba(220, 38, 38, 0); }

view file

src/popup/views/Settings.tsx+32 −1
@@ -153,12 +153,13 @@153153   const [fbResult, setFbResult] = createSignal<"success" | "error" | null>(null);154154 155155   onMount(async () => {156-    const [licenseRes, syncRes, passkeyRes, duressRes, settingsRes] = await Promise.all([156+    const [licenseRes, syncRes, passkeyRes, duressRes, settingsRes, keyRes] = await Promise.all([157157       sendMessage({ type: "GET_LICENSE" }),158158       sendMessage({ type: "GET_SYNC_STATE" }),159159       sendMessage({ type: "GET_PASSKEY_PROVIDER_STATE" }),160160       sendMessage({ type: "IS_DURESS_ENABLED" }),161161       sendMessage({ type: "GET_SETTINGS" }),162+      sendMessage({ type: "GET_LICENSE_KEY" }),162163     ]);163164 164165     if ((licenseRes as { ok: boolean }).ok) {@@ -197,6 +198,10 @@197198       setDeclinedCardCount(sData?.declinedCardFingerprints?.length ?? 0);198199     }199200 201+    if ((keyRes as { ok: boolean }).ok) {202+      setLicenseKey((keyRes as { ok: boolean; data?: string }).data ?? null);203+    }204+200205     const entriesRes = await sendMessage({ type: "GET_ENTRIES" });201206     if ((entriesRes as { ok: boolean }).ok) {202207       const entries = (entriesRes as { ok: boolean; data?: Array<{id: string; title: string; canary?: Canary}> }).data ?? [];@@ -239,6 +244,14 @@239244     setChangePwSaving(false);240245   }241246 247+  const [copied, setCopied] = createSignal(false);248+249+  async function copyLicenseKey(key: string) {250+    await navigator.clipboard.writeText(key);251+    setCopied(true);252+    setTimeout(() => setCopied(false), 2000);253+  }254+242255   async function handleActivateLicense() {243256     const key = licenseKeyInput().trim();244257     if (!key) return;@@ -251,6 +264,7 @@251264       const data = (res as { ok: boolean; data?: LicenseInfo }).data;252265       if (data?.valid) {253266         setLicenseInfo(data);267+        setLicenseKey(key);254268         setLicenseSuccess("License activated!");255269         setLicenseKeyInput("");256270       } else {@@ -1403,6 +1417,8 @@14031417     { feature: "smart_password_change", name: "Smart Password Change", desc: "Guided reset · pre-filled · vault updated" },14041418   ];14051419 1420+  const [licenseKey, setLicenseKey] = createSignal<string | null>(null);1421+14061422   const [showKeyEntry, setShowKeyEntry] = createSignal(false);14071423   const [deactivating, setDeactivating] = createSignal(false);14081424 @@ -1527,6 +1543,21 @@15271543               <h2 class="peach-pro-active-title">Peach Pro</h2>15281544               <p class="peach-pro-active-since">{formatActivatedSince(licenseInfo()?.issued)}</p>15291545             </div>1546+1547+            <Show when={licenseKey()}>1548+              <div class="peach-settings-license">1549+                <span class="peach-settings-label">LICENSE KEY</span>1550+                <div class="peach-settings-license-row">1551+                  <span class="peach-settings-license-key">{licenseKey()!}</span>1552+                  <button1553+                    class="peach-settings-license-copy"1554+                    onClick={() => copyLicenseKey(licenseKey()!)}1555+                  >1556+                    {copied() ? "✓" : "copy"}1557+                  </button>1558+                </div>1559+              </div>1560+            </Show>15301561 15311562             <div class="peach-pro-active-features">15321563               <For each={PRO_FEATURES}>

view file