{"version":3,"sources":["../../src/hooks/usePhotoCapture.ts"],"names":[],"mappings":"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","file":"usePhotoCapture.mjs","sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport type { ChangeEvent, RefObject } from 'react';\n\nexport interface UsePhotoCaptureOptions {\n  /** Multipart upload endpoint. Default '/api/uploads/wall-photos'. */\n  uploadEndpoint?: string;\n  /** Camera countdown seconds before capture. Default 3. */\n  countdownSeconds?: number;\n  /** JPEG quality for in-browser captures (0..1). Default 0.9. */\n  jpegQuality?: number;\n}\n\nexport interface UsePhotoCaptureResult {\n  // Current photo state\n  photoUrl: string;\n  photoPreview: string;\n  uploading: boolean;\n  // Camera state\n  cameraOpen: boolean;\n  cameraError: string;\n  countdown: number | null;\n  flashVisible: boolean;\n  // Actions\n  openCamera: () => Promise<void>;\n  takePhoto: () => void;\n  captureFrame: () => void;\n  removePhoto: () => void;\n  handlePhotoSelect: (e: ChangeEvent<HTMLInputElement>) => Promise<void>;\n  // Refs (consumer wires into JSX)\n  fileInputRef: RefObject<HTMLInputElement | null>;\n  videoRef: RefObject<HTMLVideoElement | null>;\n  canvasRef: RefObject<HTMLCanvasElement | null>;\n}\n\n/**\n * Pure UI state for the camera + file-upload UX shared by every wall component.\n * Returns the photo URL (uploaded), an in-browser preview, and refs the consumer\n * wires into its `<input type=\"file\">`, `<video>`, and `<canvas>` JSX.\n */\nexport function usePhotoCapture(opts?: UsePhotoCaptureOptions): UsePhotoCaptureResult {\n  const uploadEndpoint = opts?.uploadEndpoint ?? '/api/uploads/wall-photos';\n  const countdownSeconds = opts?.countdownSeconds ?? 3;\n  const jpegQuality = opts?.jpegQuality ?? 0.9;\n\n  const [photoUrl, setPhotoUrl] = useState('');\n  const [photoPreview, setPhotoPreview] = useState('');\n  const [uploading, setUploading] = useState(false);\n  const [cameraOpen, setCameraOpen] = useState(false);\n  const [cameraError, setCameraError] = useState('');\n  const [countdown, setCountdown] = useState<number | null>(null);\n  const [flashVisible, setFlashVisible] = useState(false);\n\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const streamRef = useRef<MediaStream | null>(null);\n\n  const stopCamera = useCallback(() => {\n    if (streamRef.current) {\n      streamRef.current.getTracks().forEach(track => track.stop());\n      streamRef.current = null;\n    }\n    setCameraOpen(false);\n    setCameraError('');\n    setCountdown(null);\n  }, []);\n\n  const uploadBlob = useCallback(\n    async (blob: Blob, filename: string) => {\n      setUploading(true);\n      try {\n        const formData = new FormData();\n        formData.append('file', blob, filename);\n        const res = await fetch(uploadEndpoint, { method: 'POST', body: formData });\n        if (!res.ok) throw new Error('Upload failed');\n        const data: { url?: string } = await res.json();\n        setPhotoUrl(data.url ?? '');\n      } catch {\n        setPhotoPreview('');\n        setPhotoUrl('');\n      } finally {\n        setUploading(false);\n      }\n    },\n    [uploadEndpoint],\n  );\n\n  const openCamera = useCallback(async () => {\n    setCameraError('');\n    try {\n      const stream = await navigator.mediaDevices.getUserMedia({\n        video: {\n          facingMode: 'user',\n          width: { ideal: 1280 },\n          height: { ideal: 960 },\n        },\n        audio: false,\n      });\n      streamRef.current = stream;\n      setCameraOpen(true);\n      requestAnimationFrame(() => {\n        if (videoRef.current) {\n          videoRef.current.srcObject = stream;\n        }\n      });\n    } catch {\n      setCameraError(\n        'Could not access camera. Please allow camera permissions or use \"Choose Photo\" instead.',\n      );\n    }\n  }, []);\n\n  const captureFrame = useCallback(() => {\n    const video = videoRef.current;\n    const canvas = canvasRef.current;\n    if (!video || !canvas) return;\n    canvas.width = video.videoWidth;\n    canvas.height = video.videoHeight;\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n    ctx.translate(canvas.width, 0);\n    ctx.scale(-1, 1);\n    ctx.drawImage(video, 0, 0);\n    setFlashVisible(true);\n    setTimeout(() => setFlashVisible(false), 200);\n    canvas.toBlob(\n      blob => {\n        if (!blob) return;\n        setPhotoPreview(URL.createObjectURL(blob));\n        uploadBlob(blob, 'photobooth.jpg');\n        stopCamera();\n      },\n      'image/jpeg',\n      jpegQuality,\n    );\n  }, [jpegQuality, stopCamera, uploadBlob]);\n\n  const takePhoto = useCallback(() => {\n    setCountdown(countdownSeconds);\n    let count = countdownSeconds;\n    const interval = setInterval(() => {\n      count -= 1;\n      if (count > 0) {\n        setCountdown(count);\n      } else {\n        clearInterval(interval);\n        setCountdown(null);\n        captureFrame();\n      }\n    }, 1000);\n  }, [captureFrame, countdownSeconds]);\n\n  const handlePhotoSelect = useCallback(\n    async (e: ChangeEvent<HTMLInputElement>) => {\n      const file = e.target.files?.[0];\n      if (!file) return;\n      setPhotoPreview(URL.createObjectURL(file));\n      setUploading(true);\n      try {\n        const formData = new FormData();\n        formData.append('file', file);\n        const res = await fetch(uploadEndpoint, { method: 'POST', body: formData });\n        if (!res.ok) throw new Error('Upload failed');\n        const data: { url?: string } = await res.json();\n        setPhotoUrl(data.url ?? '');\n      } catch {\n        setPhotoPreview('');\n        setPhotoUrl('');\n      } finally {\n        setUploading(false);\n      }\n    },\n    [uploadEndpoint],\n  );\n\n  const removePhoto = useCallback(() => {\n    setPhotoUrl('');\n    setPhotoPreview('');\n    stopCamera();\n    if (fileInputRef.current) fileInputRef.current.value = '';\n  }, [stopCamera]);\n\n  return {\n    photoUrl,\n    photoPreview,\n    uploading,\n    cameraOpen,\n    cameraError,\n    countdown,\n    flashVisible,\n    openCamera,\n    takePhoto,\n    captureFrame,\n    removePhoto,\n    handlePhotoSelect,\n    fileInputRef,\n    videoRef,\n    canvasRef,\n  };\n}\n"]}