"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import Webcam from "react-webcam"; import jsQR from "jsqr"; import { Icon } from "@iconify/react"; import QrFinder from "./qr-finder"; import { useSelect } from "downshift"; interface Props { isOpen: boolean; setIsOpen: React.Dispatch>; setQrBytesRaw: React.Dispatch>; } export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { const [permissionGranted, setPermissionGranted] = useState(null); const [devices, setDevices] = useState([]); const [selectedDeviceId, setSelectedDeviceId] = useState(null); const webcamRef = useRef(null); const requestRef = useRef(null); const canvasRef = useRef(null); const cameraItems = devices.map((device) => ({ value: device.deviceId, label: device.label || `Camera ${device.deviceId.slice(-5)}`, })); const { isOpen: isDropdownOpen, getToggleButtonProps, getMenuProps, getItemProps, highlightedIndex, selectedItem, } = useSelect({ items: cameraItems, selectedItem: cameraItems.find((item) => item.value === selectedDeviceId) ?? null, onSelectedItemChange: ({ selectedItem }) => { setSelectedDeviceId(selectedItem?.value ?? null); }, }); const scanQRCode = useCallback(() => { if (!isOpen) return; // Continue scanning in a loop requestRef.current = requestAnimationFrame(scanQRCode); const webcam = webcamRef.current; const canvas = canvasRef.current; if (!webcam || !canvas) return; const video = webcam.video; if (!video || video.videoWidth === 0 || video.videoHeight === 0) return; const ctx = canvas.getContext("2d"); if (!ctx) return; canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); const imageData = ctx.getImageData(0, 0, video.videoWidth, video.videoHeight); const code = jsQR(imageData.data, imageData.width, imageData.height); if (!code) return; console.log(code); // Cancel animation frame to stop scanning if (requestRef.current) { cancelAnimationFrame(requestRef.current); } setQrBytesRaw(code.binaryData!); setIsOpen(false); }, [isOpen, setIsOpen, setQrBytesRaw]); const requestPermission = async () => { navigator.mediaDevices .getUserMedia({ video: true }) .then(() => setPermissionGranted(true)) .catch(() => setPermissionGranted(false)); }; useEffect(() => { if (!isOpen) return; requestPermission(); navigator.mediaDevices.enumerateDevices().then((devices) => { const videoDevices = devices.filter((d) => d.kind === "videoinput"); setDevices(videoDevices); if (!selectedDeviceId && videoDevices.length > 0) { setSelectedDeviceId(videoDevices[0].deviceId); } }); }, [isOpen]); useEffect(() => { if (!isOpen && !permissionGranted) return; requestRef.current = requestAnimationFrame(scanQRCode); }, [permissionGranted]); if (isOpen) return (

Scan QR Code

{devices.length > 0 && (
{/* Toggle button to open the dropdown */} {/* Dropdown menu */}
    {isDropdownOpen && cameraItems.map((item, index) => (
  • {item.label}
  • ))}
)}
{!permissionGranted ? (

Camera access denied

Please allow camera access in your browser settings to scan QR codes

) : ( <> )}
); else return null; }