import { useState, useRef } from 'react'; import { Camera, Upload, X, CheckCircle2, AlertCircle, Info } from 'lucide-react'; import { Html5Qrcode } from 'html5-qrcode'; interface QRScannerProps { onScanSuccess: (scannedText: string) => void; onClose: () => void; } export default function QRScanner({ onScanSuccess, onClose }: QRScannerProps) { const [scanMode, setScanMode] = useState<'camera' | 'file' | null>(null); const [scanning, setScanning] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); const html5QrCodeRef = useRef(null); const fileInputRef = useRef(null); const startCamera = async () => { setError(''); setScanMode('camera'); setScanning(true); // Wait for DOM to render the #qr-reader div await new Promise(resolve => setTimeout(resolve, 100)); try { // Check if we're on HTTPS or localhost if (window.location.protocol !== 'https:' && !window.location.hostname.includes('localhost')) { throw new Error('Camera requires HTTPS or localhost. Use: bun run dev'); } const html5QrCode = new Html5Qrcode('qr-reader'); html5QrCodeRef.current = html5QrCode; await html5QrCode.start( { facingMode: 'environment' }, { fps: 10, qrbox: { width: 250, height: 250 }, aspectRatio: 1.0, }, (decodedText) => { if (decodedText.startsWith('SEEDPGP1:')) { setSuccess(true); onScanSuccess(decodedText); stopCamera(); } else { setError('QR code found, but not a valid SEEDPGP1 frame'); } }, () => { // Ignore frequent scanning errors } ); } catch (err: any) { console.error('Camera error:', err); setError(`Camera failed: ${err.message || 'Permission denied or not available'}`); setScanning(false); setScanMode(null); } }; const stopCamera = async () => { if (html5QrCodeRef.current) { try { await html5QrCodeRef.current.stop(); html5QrCodeRef.current.clear(); } catch (err) { console.error('Error stopping camera:', err); } html5QrCodeRef.current = null; } setScanning(false); setScanMode(null); }; const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setError(''); setScanMode('file'); setScanning(true); try { const html5QrCode = new Html5Qrcode('qr-reader-file'); // Try scanning with verbose mode const decodedText = await html5QrCode.scanFile(file, true); if (decodedText.startsWith('SEEDPGP1:')) { setSuccess(true); onScanSuccess(decodedText); html5QrCode.clear(); } else { setError(`Found QR code, but not SEEDPGP format: ${decodedText.substring(0, 30)}...`); } } catch (err: any) { console.error('File scan error:', err); // Provide helpful error messages if (err.message?.includes('No MultiFormat')) { setError('Could not detect QR code in image. Try: 1) Taking a clearer photo, 2) Ensuring good lighting, 3) Screenshot from the Backup tab'); } else { setError(`Scan failed: ${err.message || 'Unknown error'}`); } } finally { setScanning(false); // Reset file input so same file can be selected again if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handleClose = async () => { await stopCamera(); onClose(); }; return (
{/* Header */}

Scan QR Code

{/* Content */}
{/* Error Display */} {error && (

{error}

)} {/* Success Display */} {success && (

QR code scanned successfully!

)} {/* Mode Selection */} {!scanMode && (
{/* Info Box */}

Camera: Requires HTTPS or localhost

Upload: Screenshot QR from Backup tab for testing

)} {/* Camera View */} {scanMode === 'camera' && scanning && (
)} {/* File Processing View */} {scanMode === 'file' && scanning && (

Processing image...

)} {/* Hidden div for file scanning */}
); }