{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "qr-code-reader",
  "title": "QR Code Reader",
  "author": "AlgorizeTech",
  "description": "A camera-based QR scanner that decodes frames in the browser and reports matches. Use it for check-in and pairing flows.",
  "dependencies": [
    "jsqr",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/QrCodeReaderBlock.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useRef, useCallback } from \"react\";\nimport jsQR from \"jsqr\";\nimport { UploadCloud, CheckCircle2, Copy, AlertCircle } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface QrCodeReaderProps extends React.HTMLAttributes<HTMLDivElement> {\n  onRead?: (data: string) => void;\n}\n\nexport function QrCodeReader({ onRead, className, ...props }: QrCodeReaderProps) {\n  const [dragActive, setDragActive] = useState(false);\n  const [result, setResult] = useState<string | null>(null);\n  const [error, setError] = useState<string | null>(null);\n  const [copied, setCopied] = useState(false);\n  const [imagePreview, setImagePreview] = useState<string | null>(null);\n  \n  const inputRef = useRef<HTMLInputElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n\n  const handleDrag = useCallback((e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    if (e.type === \"dragenter\" || e.type === \"dragover\") {\n      setDragActive(true);\n    } else if (e.type === \"dragleave\") {\n      setDragActive(false);\n    }\n  }, []);\n\n  const processFile = (file: File) => {\n    if (!file || !file.type.startsWith(\"image/\")) {\n      setError(\"Please upload a valid image file.\");\n      setResult(null);\n      setImagePreview(null);\n      return;\n    }\n\n    setError(null);\n    setResult(null);\n\n    const objectUrl = URL.createObjectURL(file);\n    setImagePreview(objectUrl);\n\n    const img = new Image();\n    img.onload = () => {\n      const canvas = canvasRef.current;\n      if (!canvas) return;\n      \n      const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n      if (!ctx) return;\n\n      // Scale down if image is too large for performance\n      const MAX_WIDTH = 800;\n      const MAX_HEIGHT = 800;\n      let width = img.width;\n      let height = img.height;\n\n      if (width > MAX_WIDTH || height > MAX_HEIGHT) {\n        const ratio = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height);\n        width *= ratio;\n        height *= ratio;\n      }\n\n      canvas.width = width;\n      canvas.height = height;\n      ctx.drawImage(img, 0, 0, width, height);\n      \n      const imageData = ctx.getImageData(0, 0, width, height);\n      const code = jsQR(imageData.data, imageData.width, imageData.height, {\n        inversionAttempts: \"attemptBoth\",\n      });\n\n      if (code && code.data) {\n        setResult(code.data);\n        if (onRead) onRead(code.data);\n      } else {\n        setError(\"No QR code found in the image. Please try another image.\");\n      }\n    };\n    img.src = objectUrl;\n  };\n\n  const handleDrop = useCallback((e: React.DragEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setDragActive(false);\n    \n    if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n      processFile(e.dataTransfer.files[0]);\n    }\n  }, []);\n\n  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    e.preventDefault();\n    if (e.target.files && e.target.files[0]) {\n      processFile(e.target.files[0]);\n    }\n  };\n\n  const copyToClipboard = () => {\n    if (result) {\n      navigator.clipboard.writeText(result);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n    }\n  };\n\n  const resetState = () => {\n    setResult(null);\n    setError(null);\n    setImagePreview(null);\n    if (inputRef.current) inputRef.current.value = \"\";\n  };\n\n  return (\n    <div \n      className={cn(\n        \"w-full max-w-md mx-auto flex flex-col items-center bg-card text-card-foreground border border-border rounded-2xl shadow-sm p-6 overflow-hidden\",\n        className\n      )}\n      {...props}\n    >\n      <canvas ref={canvasRef} className=\"hidden\" />\n\n      {/* Upload Area */}\n      {!imagePreview && (\n        <div \n          className={cn(\n            \"w-full h-64 border-2 border-dashed rounded-xl flex flex-col items-center justify-center p-6 text-center cursor-pointer transition-all duration-200\",\n            dragActive \n              ? \"border-primary bg-primary/10\" \n              : \"border-border hover:border-foreground/30 hover:bg-muted\"\n          )}\n          onDragEnter={handleDrag}\n          onDragLeave={handleDrag}\n          onDragOver={handleDrag}\n          onDrop={handleDrop}\n          onClick={() => inputRef.current?.click()}\n        >\n          <input \n            ref={inputRef}\n            type=\"file\" \n            accept=\"image/*\" \n            onChange={handleChange} \n            className=\"hidden\" \n          />\n          <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4 text-muted-foreground\">\n            <UploadCloud size={24} />\n          </div>\n          <h3 className=\"text-sm font-semibold text-foreground mb-1\">\n            Click or drag image here\n          </h3>\n          <p className=\"text-xs text-muted-foreground max-w-[200px]\">\n            Upload a QR code image to scan and extract the embedded link or text.\n          </p>\n        </div>\n      )}\n\n      {/* Image Preview & Results */}\n      {imagePreview && (\n        <div className=\"w-full flex flex-col gap-4\">\n          <div className=\"relative w-full h-48 bg-muted rounded-xl overflow-hidden border border-border flex items-center justify-center p-4\">\n            <img \n              src={imagePreview} \n              alt=\"Uploaded QR Code\" \n              className=\"max-w-full max-h-full object-contain rounded-md\"\n            />\n          </div>\n\n          {/* Success State */}\n          {result && (\n            <div className=\"w-full bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-900/50 rounded-xl p-4 animate-in fade-in slide-in-from-bottom-2\">\n              <div className=\"flex items-center gap-2 text-green-700 dark:text-green-400 mb-2 font-medium\">\n                <CheckCircle2 size={16} />\n                <span className=\"text-sm\">Scan Successful</span>\n              </div>\n              <div className=\"flex items-center gap-2\">\n                <div className=\"flex-1 bg-background border border-green-200 dark:border-green-900/50 rounded-lg p-2.5 overflow-x-auto custom-scrollbar text-sm text-foreground whitespace-nowrap\">\n                  {result}\n                </div>\n                <button \n                  onClick={copyToClipboard}\n                  className=\"shrink-0 w-10 h-10 flex items-center justify-center rounded-lg bg-background border border-green-200 dark:border-green-900/50 text-muted-foreground hover:text-green-600 dark:hover:text-green-400 transition-colors\"\n                  title=\"Copy to clipboard\"\n                >\n                  {copied ? <CheckCircle2 size={16} className=\"text-green-500\" /> : <Copy size={16} />}\n                </button>\n              </div>\n            </div>\n          )}\n\n          {/* Error State */}\n          {error && (\n            <div className=\"w-full bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900/50 rounded-xl p-4 animate-in fade-in slide-in-from-bottom-2\">\n              <div className=\"flex items-start gap-2 text-red-600 dark:text-red-400\">\n                <AlertCircle size={18} className=\"shrink-0 mt-0.5\" />\n                <p className=\"text-sm font-medium leading-snug\">{error}</p>\n              </div>\n            </div>\n          )}\n\n          <button \n            onClick={resetState}\n            className=\"w-full py-2.5 text-sm font-medium text-muted-foreground hover:text-foreground bg-muted hover:bg-muted rounded-lg transition-colors mt-2\"\n          >\n            Scan Another Image\n          </button>\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/QrCodeReaderBlock.tsx"
    }
  ],
  "docs": "Named export: QrCodeReader. `onRead` fires with the decoded string. Requires camera permission **and a secure context** — it works on HTTPS and localhost, and silently fails on plain HTTP. Decoding runs on the main thread via jsQR, so expect load on low-end devices. Handle permission denial in your own UI.",
  "categories": [
    "utilities"
  ],
  "type": "registry:component"
}
