diff --git a/Screenshot 2026-02-08 at 21.14.14.png b/Screenshot 2026-02-08 at 21.14.14.png new file mode 100644 index 0000000..ded40a3 Binary files /dev/null and b/Screenshot 2026-02-08 at 21.14.14.png differ diff --git a/src/App.tsx b/src/App.tsx index 9008954..df4ad5a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -61,6 +61,7 @@ function App() { const [loading, setLoading] = useState(false); const [copied, setCopied] = useState(false); const [showQRScanner, setShowQRScanner] = useState(false); + const [isDragging, setIsDragging] = useState(false); const [isReadOnly, setIsReadOnly] = useState(false); const [encryptedMnemonicCache, setEncryptedMnemonicCache] = useState(null); const [showSecurityModal, setShowSecurityModal] = useState(false); @@ -305,6 +306,78 @@ function App() { }; + const handleFileUpload = async (file: File) => { + setLoading(true); + setError(''); + + try { + // Handle image files (QR codes) + if (file.type.startsWith('image/')) { + const img = new Image(); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + await new Promise((resolve, reject) => { + img.onload = resolve; + img.onerror = reject; + img.src = URL.createObjectURL(file); + }); + + canvas.width = img.width; + canvas.height = img.height; + ctx?.drawImage(img, 0, 0); + + const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height); + if (!imageData) throw new Error('Could not read image'); + + const jsqr = (await import('jsqr')).default; + const code = jsqr(imageData.data, imageData.width, imageData.height); + + if (!code) throw new Error('No QR code found in image'); + + // Handle binary or text QR data + const isBinary = (code.binaryData.length === 16 || code.binaryData.length === 32); + if (isBinary) { + const hex = Array.from(code.binaryData).map(b => b.toString(16).padStart(2, '0')).join(''); + setRestoreInput(hex); + } else { + setRestoreInput(code.data); + } + + URL.revokeObjectURL(img.src); + } + // Handle text files (PGP armor, hex, numeric SeedQR) + else if (file.type === 'text/plain' || file.name.endsWith('.txt') || file.name.endsWith('.asc')) { + const text = await file.text(); + setRestoreInput(text.trim()); + } + else { + throw new Error('Unsupported file type. Use PNG/JPG (QR) or TXT/ASC (armor)'); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'File upload failed'); + } finally { + setLoading(false); + } + }; + + const handleDrop = async (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + + const file = e.dataTransfer.files[0]; // Access the first file + if (file) await handleFileUpload(file); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = () => { + setIsDragging(false); + }; + const handleRestore = async () => { setLoading(true); setError(''); @@ -416,452 +489,512 @@ function App() { return ( -
-
setShowSecurityModal(true)} - localItems={localItems} - sessionItems={sessionItems} - onOpenStorageModal={() => setShowStorageModal(true)} - events={clipboardEvents} - onOpenClipboardModal={() => setShowClipboardModal(true)} - activeTab={activeTab} - onRequestTabChange={handleRequestTabChange} - encryptedMnemonicCache={encryptedMnemonicCache} - handleLockAndClear={handleLockAndClear} - appVersion={__APP_VERSION__} - isLocked={isReadOnly} - onToggleLock={handleToggleLock} - /> -
-
- {/* Error Display */} - {error && ( -
- -
-

Error

-

{error}

-
-
- )} +
+ {/* Cyberpunk grid overlay */} +
- {/* Info Banner */} - {recipientFpr && activeTab === 'backup' && ( -
- -
- Recipient Key: {recipientFpr} -
-
- )} - - {/* Main Content Grid */} -
-
- {activeTab === 'backup' ? ( - <> -
- -