"use client"; import { useState, useEffect, useRef } from "react"; import { Scanner } from "@yudiel/react-qr-scanner"; export default function EnhancedQRScanner({ onScan, width = 300, height = 300, }) { const [error, setError] = useState(null); const [isSecureContext, setIsSecureContext] = useState(true); const [initialized, setInitialized] = useState(false); const [cameraActive, setCameraActive] = useState(false); const [hasCameras, setHasCameras] = useState(true); const [retryCount, setRetryCount] = useState(0); const maxRetries = 3; const scannerRef = useRef(null); // Check if we're in a secure context useEffect(() => { // Check if we're in a secure context (https or localhost) const secure = window.isSecureContext; setIsSecureContext(secure); // Check if camera can be accessed async function checkCameraAccess() { try { const devices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = devices.filter( (device) => device.kind === "videoinput" ); if (videoDevices.length === 0) { setError( "No camera detected. Please make sure your device has a camera." ); setHasCameras(false); } else { setHasCameras(true); } setInitialized(true); } catch (err) { console.error("Error checking camera devices:", err); setError(`Camera initialization error: ${err.message}`); setInitialized(true); } } checkCameraAccess(); }, []); // Handle successful scan const handleDecode = (result) => { if (onScan && result) { onScan(result); } }; // Try to restart the camera const retryCamera = () => { if (retryCount < maxRetries) { setError(null); setRetryCount((prev) => prev + 1); // Force re-render of scanner component setCameraActive(false); setTimeout(() => setCameraActive(true), 500); } else { setError( "Could not access camera after multiple attempts. Please refresh the page or try another device." ); } }; // Handle camera errors const handleError = (error) => { console.error("QR Scanner error:", error); setCameraActive(false); // More descriptive error messages let errorMessage = "Camera access error. "; if (!isSecureContext) { errorMessage += "Your browser requires HTTPS for camera access. When running locally, use http://localhost instead of your IP address."; } else if ( error.name === "NotAllowedError" || error.name === "PermissionDeniedError" ) { errorMessage += "Camera permission denied. Please allow camera access in your browser settings."; } else if ( error.name === "NotFoundError" || error.name === "DevicesNotFoundError" ) { errorMessage += "No camera found. Please ensure your device has a working camera."; } else if ( error.name === "NotReadableError" || error.name === "TrackStartError" ) { errorMessage += "Camera is already in use by another application or encountered a hardware error."; } else { errorMessage += `${ error.message || "Please check your camera permissions and refresh the page." }`; } setError(errorMessage); }; // When initialized, activate camera useEffect(() => { if (initialized && hasCameras && isSecureContext && !error) { setCameraActive(true); } }, [initialized, hasCameras, isSecureContext, error]); // Handle secure context warning if (!isSecureContext) { return (

Camera access requires a secure context (HTTPS or localhost).

When running locally, use http://localhost:3000 instead of your network IP.

); } // Handle initialization state if (!initialized) { return (
Initializing camera...
); } return (
{error && (

{error}

{retryCount < maxRetries && ( )}
)} {cameraActive && !error && (
)} {/* Text input fallback when camera fails */} {(!cameraActive || error) && (

Having trouble with the camera? Try entering a code manually:

{ if (e.key === "Enter" && e.target.value) { onScan(e.target.value); e.target.value = ""; } }} />
)}
); }