From 2a7ac1cce07a7c14c3698260b26d18dca2147586 Mon Sep 17 00:00:00 2001 From: LC mac Date: Sat, 31 Jan 2026 01:25:27 +0800 Subject: [PATCH] feat: Implement 'Lock/Edit' mode with blur and confirmation dialog --- src/App.tsx | 124 +++++++++++++++++------- src/components/Footer.tsx | 18 ++++ src/components/Header.tsx | 17 +++- src/components/PgpKeyInput.tsx | 11 ++- src/components/QRScanner.tsx | 6 +- src/components/ReadOnly.tsx | 2 +- src/components/badges/EditLockBadge.tsx | 28 ++++++ src/components/badges/StorageBadge.tsx | 2 +- src/vite-env.d.ts | 1 + vite.config.ts | 1 + 10 files changed, 158 insertions(+), 52 deletions(-) create mode 100644 src/components/Footer.tsx create mode 100644 src/components/badges/EditLockBadge.tsx diff --git a/src/App.tsx b/src/App.tsx index 5724b89..8db4aa5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,11 +17,11 @@ import { validateBip39Mnemonic } from './lib/bip39'; import { buildPlaintext, encryptToSeedPgp, decryptSeedPgp } from './lib/seedpgp'; import * as openpgp from 'openpgp'; import { SecurityWarnings } from './components/SecurityWarnings'; -import { ReadOnly } from './components/ReadOnly'; import { getSessionKey, encryptJsonToBlob, destroySessionKey, EncryptedBlob } from './lib/sessionCrypto'; import Header from './components/Header'; import { StorageDetails } from './components/StorageDetails'; import { ClipboardDetails } from './components/ClipboardDetails'; +import Footer from './components/Footer'; console.log("OpenPGP.js version:", openpgp.config.versionString); @@ -64,6 +64,7 @@ function App() { const [localItems, setLocalItems] = useState([]); const [sessionItems, setSessionItems] = useState([]); const [clipboardEvents, setClipboardEvents] = useState([]); + const [showLockConfirm, setShowLockConfirm] = useState(false); const SENSITIVE_PATTERNS = ['key', 'mnemonic', 'seed', 'private', 'secret', 'pgp', 'password']; @@ -100,21 +101,7 @@ function App() { return () => clearInterval(interval); }, []); - useEffect(() => { - // When entering read-only mode, clear sensitive data for security. - if (isReadOnly) { - setMnemonic(''); - setBackupMessagePassword(''); - setRestoreMessagePassword(''); - setPublicKeyInput(''); - setPrivateKeyInput(''); - setPrivateKeyPassphrase(''); - setQrPayload(''); - setRestoreInput(''); - setDecryptedRestoredMnemonic(null); - setError(''); - } - }, [isReadOnly]); + // Cleanup session key on component unmount useEffect(() => { @@ -319,6 +306,21 @@ function App() { setShowQRScanner(false); }; + const handleToggleLock = () => { + if (!isReadOnly) { + // About to lock - show confirmation + setShowLockConfirm(true); + } else { + // Unlocking - no confirmation needed + setIsReadOnly(false); + } + }; + + const confirmLock = () => { + setIsReadOnly(true); + setShowLockConfirm(false); + }; + return (
@@ -334,6 +336,8 @@ function App() { encryptedMnemonicCache={encryptedMnemonicCache} handleLockAndClear={handleLockAndClear} appVersion={__APP_VERSION__} + isLocked={isReadOnly} + onToggleLock={handleToggleLock} />
@@ -350,10 +354,10 @@ function App() { {/* Info Banner */} {recipientFpr && activeTab === 'backup' && ( -
+
- Recipient Key: {recipientFpr} + Recipient Key: {recipientFpr}
)} @@ -364,9 +368,11 @@ function App() { {activeTab === 'backup' ? ( <>
- +