"use client"; import { useCallback, useEffect, useRef, useState } from "react"; 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>; setImage?: React.Dispatch>; setQrBytesRaw?: React.Dispatch>; } export default function Camera({ isOpen, setIsOpen, setImage, setQrBytesRaw }: Props) { const [isVisible, setIsVisible] = useState(false); const [permissionGranted, setPermissionGranted] = useState(null); const [devices, setDevices] = useState([]); const [selectedDeviceId, setSelectedDeviceId] = useState(null); const videoRef = useRef(null); const requestRef = useRef(null); const canvasRef = useRef(null); const cameraItems = devices.map((device) => ({ value: device.deviceId, label: device.label || `Camera ${devices.indexOf(device) + 1}`, })); 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 takePicture = useCallback(() => { if (!isOpen) return; // Continue scanning in a loop if (setQrBytesRaw) requestRef.current = requestAnimationFrame(takePicture); const video = videoRef.current; const canvas = canvasRef.current; if (!video || video.videoWidth === 0 || video.videoHeight === 0 || !canvas) 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); if (setImage) { setImage(canvas.toDataURL()); close(); return; } if (!setQrBytesRaw) return; const imageData = ctx.getImageData(0, 0, video.videoWidth, video.videoHeight); const code = jsQR(imageData.data, imageData.width, imageData.height); if (!code || !code.binaryData) return; // Cancel animation frame to stop scanning if (requestRef.current) { cancelAnimationFrame(requestRef.current); requestRef.current = null; } setQrBytesRaw(code.binaryData); close(); }, [isOpen, setIsOpen, setQrBytesRaw]); const requestPermission = () => { if (!navigator.mediaDevices) return; navigator.mediaDevices .getUserMedia({ video: true, audio: false }) .then(() => setPermissionGranted(true)) .catch((err) => { setPermissionGranted(false); console.error("An error occurred trying to access the camera", err); }); }; const close = () => { setIsVisible(false); setTimeout(() => { setIsOpen(false); }, 300); }; useEffect(() => { if (isOpen) { // slight delay to trigger animation setTimeout(() => setIsVisible(true), 10); requestPermission(); } }, [isOpen]); useEffect(() => { if (!isOpen || !permissionGranted) return; navigator.mediaDevices .enumerateDevices() .then((devices) => { const videoDevices = devices.filter((d) => d.kind === "videoinput"); setDevices(videoDevices); const targetDeviceId = selectedDeviceId || videoDevices[0]?.deviceId; if (!targetDeviceId) return; setSelectedDeviceId(targetDeviceId); // start camera stream return navigator.mediaDevices.getUserMedia({ video: { deviceId: targetDeviceId }, audio: false, }); }) .then((stream) => { if (!stream || !videoRef.current) return; videoRef.current.srcObject = stream; videoRef.current.play(); }) .catch((err) => console.error("Camera error", err)); if (setQrBytesRaw) requestRef.current = requestAnimationFrame(takePicture); // cleanup return () => { if (requestRef.current) { cancelAnimationFrame(requestRef.current); } if (videoRef.current?.srcObject) { const stream = videoRef.current.srcObject as MediaStream; stream.getTracks().forEach((track) => track.stop()); videoRef.current.srcObject = null; } }; }, [isOpen, permissionGranted, selectedDeviceId, takePicture]); return (

{setQrBytesRaw ? "Scan QR Code" : "Take Picture"}

{/* 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 {setQrBytesRaw ? "scan QR codes" : "take pictures"}

)}
{setImage && ( )}
); }