import { useState, useRef, useEffect } from 'react'; import QRCode from 'qrcode'; import QrScanner from 'qr-scanner'; import { getUuid, setUuid } from '../api/client'; import { cn } from '../lib/utils'; import { Scan } from 'lucide-react'; import { useTheme } from '../lib/theme'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../components/ui/dialog'; interface IdentityPanelProps { open: boolean; onClose: () => void; } export function IdentityPanel({ open, onClose }: IdentityPanelProps) { const [copied, setCopied] = useState(false); const [scanning, setScanning] = useState(false); const canvasRef = useRef(null); const videoRef = useRef(null); const scannerRef = useRef(null); const uuid = getUuid(); const { resolvedTheme } = useTheme(); const qrColors = resolvedTheme === 'dark' ? { dark: '#ECE9E0', light: '#1C1B18' } : { dark: '#141413', light: '#FDFCF8' }; useEffect(() => { if (!open) return; // Defer one frame so Radix Dialog Portal has finished mounting the canvas const rafId = requestAnimationFrame(() => { if (!canvasRef.current) return; const qrUrl = `${window.location.origin}/code?uuid=${encodeURIComponent(uuid)}`; QRCode.toCanvas(canvasRef.current, qrUrl, { width: 200, margin: 1, color: qrColors, }).catch((err: unknown) => { console.error('QR generation failed:', err); }); }); return () => cancelAnimationFrame(rafId); }, [open, uuid, resolvedTheme]); // Cleanup scanner on close useEffect(() => { if (!open && scannerRef.current) { scannerRef.current.stop(); scannerRef.current.destroy(); scannerRef.current = null; setScanning(false); } }, [open]); if (!open) return null; const handleCopy = async () => { await navigator.clipboard.writeText(uuid); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const startCamera = async () => { if (!videoRef.current) return; setScanning(true); try { const scanner = new QrScanner( videoRef.current, result => { handleScannedData(result.data); }, { returnDetailedScanResult: true }, ); scannerRef.current = scanner; await scanner.start(); } catch (e) { console.error('Camera error:', e); setScanning(false); } }; const stopCamera = () => { if (scannerRef.current) { scannerRef.current.stop(); scannerRef.current.destroy(); scannerRef.current = null; } setScanning(false); }; const handleScannedData = (data: string) => { try { // Try ACP format: { url, token } const parsed = JSON.parse(data); if (parsed.url && parsed.token) { // Store ACP connection data and navigate to ACP direct connect view stopCamera(); onClose(); sessionStorage.setItem('acp_connection', JSON.stringify({ url: parsed.url, token: parsed.token })); window.location.href = '/code/?acp=1'; return; } } catch { // Not JSON } // Try URL with uuid param try { const url = new URL(data); const importedUuid = url.searchParams.get('uuid'); if (importedUuid) { setUuid(importedUuid); stopCamera(); onClose(); return; } } catch { // Not a URL } // Raw UUID string if (data.length >= 32) { setUuid(data); stopCamera(); onClose(); return; } }; const handleScanUpload = () => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = async e => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; try { const result = await QrScanner.scanImage(file, { returnDetailedScanResult: true, }); handleScannedData(result.data); } catch { alert('No QR code found in image'); } }; input.click(); }; return ( { if (!o) onClose(); }} > Identity
{/* UUID */}
{uuid}
{/* QR Code display */} {!scanning && (
)} {/* Camera scanner */} {scanning && (
)} {/* Action buttons */}
); }