index.tsx raw

   1  import { Button } from '@/components/ui/button'
   2  import { X } from 'lucide-react'
   3  import QrScanner from 'qr-scanner'
   4  import { useCallback, useEffect, useRef, useState } from 'react'
   5  import { useTranslation } from 'react-i18next'
   6  
   7  export default function QrScannerModal({
   8    onScan,
   9    onClose
  10  }: {
  11    onScan: (result: string) => void
  12    onClose: () => void
  13  }) {
  14    const { t } = useTranslation()
  15    const videoRef = useRef<HTMLVideoElement>(null)
  16    const scannerRef = useRef<QrScanner | null>(null)
  17    const [error, setError] = useState<string | null>(null)
  18  
  19    const handleScan = useCallback(
  20      (result: QrScanner.ScanResult) => {
  21        onScan(result.data)
  22        onClose()
  23      },
  24      [onScan, onClose]
  25    )
  26  
  27    useEffect(() => {
  28      if (!videoRef.current) return
  29  
  30      const scanner = new QrScanner(videoRef.current, handleScan, {
  31        preferredCamera: 'environment',
  32        highlightScanRegion: true,
  33        highlightCodeOutline: true
  34      })
  35  
  36      scannerRef.current = scanner
  37  
  38      scanner.start().catch(() => {
  39        setError(t('Failed to access camera'))
  40      })
  41  
  42      return () => {
  43        scanner.destroy()
  44      }
  45    }, [handleScan, t])
  46  
  47    return (
  48      <div className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center">
  49        <div className="relative w-full max-w-sm mx-4">
  50          <Button
  51            variant="ghost"
  52            size="icon"
  53            className="absolute -top-12 right-0 text-white hover:bg-white/20"
  54            onClick={onClose}
  55          >
  56            <X className="h-6 w-6" />
  57          </Button>
  58          <div className="rounded-lg overflow-hidden bg-black">
  59            {error ? (
  60              <div className="p-8 text-center text-destructive">{error}</div>
  61            ) : (
  62              <video ref={videoRef} className="w-full" />
  63            )}
  64          </div>
  65          <p className="text-center text-white/70 text-sm mt-4">
  66            {t('Point camera at QR code')}
  67          </p>
  68        </div>
  69      </div>
  70    )
  71  }
  72