"use client" import { useEffect, useRef, useState } from "react" import { Button } from "@/components/ui/button" import { Camera, X, AlertCircle } from "lucide-react" interface BarcodeScannerProps { onScan: (barcode: string) => void onClose: () => void } export function BarcodeScanner({ onScan, onClose }: BarcodeScannerProps) { const [error, setError] = useState(null) const [loading, setLoading] = useState(true) const scannerRef = useRef(null) const stoppedRef = useRef(false) const onScanRef = useRef(onScan) onScanRef.current = onScan const containerRef = useRef("barcode-reader-" + Math.random().toString(36).slice(2)) useEffect(() => { let mounted = true stoppedRef.current = false async function startScanner() { try { // Dynamic import to avoid SSR issues const { Html5Qrcode } = await import("html5-qrcode") if (!mounted) return const scanner = new Html5Qrcode(containerRef.current) scannerRef.current = scanner await scanner.start( { facingMode: "environment" }, { fps: 10, qrbox: { width: 250, height: 100 }, aspectRatio: 1.0, }, (decodedText) => { // Prevent double-fire if (stoppedRef.current) return stoppedRef.current = true // Mark scanner as handled so cleanup doesn't double-stop scannerRef.current = null // Stop camera then report result scanner.stop().then(() => { if (mounted) onScanRef.current(decodedText) }).catch(() => { if (mounted) onScanRef.current(decodedText) }) }, () => { // Per-frame decode failure — ignore } ) if (mounted) setLoading(false) } catch (err) { if (!mounted) return setLoading(false) const isInsecure = typeof window !== "undefined" && window.location.protocol === "http:" && window.location.hostname !== "localhost" if (err instanceof Error) { if (err.message.includes("Permission") || err.message.includes("NotAllowedError")) { setError( isInsecure ? "Camera blocked — HTTPS is required. Access this page via https:// (port 3000) to use the scanner." : "Camera permission denied. Please allow camera access and try again." ) } else if (err.message.includes("NotFoundError") || err.message.includes("Requested device not found")) { setError("No camera found on this device.") } else if (isInsecure) { setError("Camera requires HTTPS. Access this page via https:// (port 3000) to use the scanner.") } else { setError("Could not start camera. Please try again.") } } else if (isInsecure) { setError("Camera requires HTTPS. Access this page via https:// (port 3000) to use the scanner.") } else { setError("Could not start camera. Please try again.") } } } startScanner() return () => { mounted = false // Only stop if the decode callback didn't already stop it try { const scanner = scannerRef.current as { stop?: () => Promise } | null if (scanner?.stop) { scanner.stop().catch(() => {}) } } catch { // Scanner already stopped or disposed } } }, []) // eslint-disable-line react-hooks/exhaustive-deps return (
{/* Close button */} {/* Scanner viewport */}
{loading && !error && (

Starting camera...

)}
{/* Instruction text */} {!error && (

Point your camera at a barcode on a bottle or can

)} {/* Error state */} {error && (

{error}

)}
) }