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 */}
); }