From 5473f0b7a43e6ed6aa62483bb8f0658836498b7a Mon Sep 17 00:00:00 2001 From: Sven Reiser Date: Sun, 21 Jun 2026 12:54:43 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20replace=20native=20confirm(?= =?UTF-8?q?)=20dialogs=20with=20@mind-studio/ui=20AlertDialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vault delete confirmation and ItemEditor discard-unsaved-changes prompt now use the themed AlertDialog instead of blocking window.confirm(). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/apps/vault/ItemEditor.tsx | 38 +++++++++++++++++++++++++++++++++-- src/apps/vault/index.tsx | 35 ++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/apps/vault/ItemEditor.tsx b/src/apps/vault/ItemEditor.tsx index e9176f3..c8d9428 100644 --- a/src/apps/vault/ItemEditor.tsx +++ b/src/apps/vault/ItemEditor.tsx @@ -1,6 +1,14 @@ "use client"; import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, Button, Dialog, DialogContent, @@ -57,6 +65,7 @@ export function ItemEditor({ const [showGen, setShowGen] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); + const [confirmDiscardOpen, setConfirmDiscardOpen] = useState(false); useEffect(() => { if (!draft) return; @@ -92,11 +101,21 @@ export function ItemEditor({ expiry !== (draft.secret.expiry ?? "") || cvv !== (draft.secret.cvv ?? ""); - const requestClose = () => { - if (isDirty && !confirm("Discard your unsaved changes?")) return; + // The original close action that ran after the (now-removed) confirm guard. + // Both the not-dirty path and the Discard button call this. + const proceedClose = () => { + setConfirmDiscardOpen(false); onClose(); }; + const requestClose = () => { + if (isDirty) { + setConfirmDiscardOpen(true); + return; + } + proceedClose(); + }; + // TOTP secrets are base32 (RFC 4648: A–Z, 2–7, optional `=` padding). Flag a // malformed seed up front instead of letting code generation silently fail. const totpNorm = totpSecret.replace(/\s+/g, "").toUpperCase(); @@ -316,6 +335,21 @@ export function ItemEditor({ + + + + + Discard unsaved changes? + + You have unsaved changes. If you close now, your edits will be lost. + + + + Keep editing + Discard + + + ); } diff --git a/src/apps/vault/index.tsx b/src/apps/vault/index.tsx index 4d7898f..231b9e9 100644 --- a/src/apps/vault/index.tsx +++ b/src/apps/vault/index.tsx @@ -17,6 +17,14 @@ */ import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, Button, Dialog, DialogContent, @@ -324,6 +332,7 @@ function UnlockedVault({ const [draft, setDraft] = useState(null); const [showGen, setShowGen] = useState(false); const [showChangePw, setShowChangePw] = useState(false); + const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false); const selected = vault.index.find((m) => m.id === selectedId) ?? null; @@ -380,13 +389,18 @@ function UnlockedVault({ setSelectedId(saved.id); }; - const onDeleteItem = async () => { + const onDeleteItem = () => { + if (!selected) return; + setConfirmDeleteOpen(true); + }; + + const confirmDelete = async () => { if (!selected) return; - if (!confirm(`Delete “${selected.title}”? This cannot be undone.`)) return; await deleteItem(zone, vault, selected.id); onVaultChange(vault); setSelectedId(null); setSecret(null); + setConfirmDeleteOpen(false); }; return ( @@ -506,6 +520,23 @@ function UnlockedVault({ }} /> )} + + + + + Delete item? + + {selected + ? `Delete “${selected.title}”? This cannot be undone.` + : "This cannot be undone."} + + + + Cancel + Delete + + + ); }