import { useState, useRef, useCallback, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { useToast } from '@/hooks/useToast'; import { cn } from '@/lib/utils'; import { Camera, X, RotateCcw, Check, Loader2, SwitchCamera, } from 'lucide-react'; interface CameraCaptureProps { open: boolean; onOpenChange: (open: boolean) => void; onCapture: (file: File) => void; isUploading?: boolean; } export function CameraCapture({ open, onOpenChange, onCapture, isUploading, }: CameraCaptureProps) { const { toast } = useToast(); const videoRef = useRef(null); const canvasRef = useRef(null); const streamRef = useRef(null); const [hasPermission, setHasPermission] = useState(null); const [capturedImage, setCapturedImage] = useState(null); const [facingMode, setFacingMode] = useState<'user' | 'environment'>('environment'); const [isStarting, setIsStarting] = useState(false); // Start the camera const startCamera = useCallback(async (facing: 'user' | 'environment' = facingMode) => { setIsStarting(true); try { // Stop any existing stream first if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); } const constraints: MediaStreamConstraints = { video: { facingMode: facing, width: { ideal: 1920 }, height: { ideal: 1080 }, }, audio: false, }; const stream = await navigator.mediaDevices.getUserMedia(constraints); streamRef.current = stream; if (videoRef.current) { videoRef.current.srcObject = stream; await videoRef.current.play(); } setHasPermission(true); setFacingMode(facing); } catch (error) { console.error('Camera access error:', error); setHasPermission(false); toast({ title: 'Camera access denied', description: 'Please allow camera access in your browser settings to take photos.', variant: 'destructive', }); } finally { setIsStarting(false); } }, [facingMode, toast]); // Stop the camera const stopCamera = useCallback(() => { if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); streamRef.current = null; } if (videoRef.current) { videoRef.current.srcObject = null; } }, []); // Start camera when dialog opens useEffect(() => { if (open) { startCamera(); } else { stopCamera(); setCapturedImage(null); setHasPermission(null); } return () => { stopCamera(); }; }, [open, startCamera, stopCamera]); // Switch camera (front/back) const switchCamera = () => { const newFacing = facingMode === 'user' ? 'environment' : 'user'; setCapturedImage(null); startCamera(newFacing); }; // Capture photo const capturePhoto = () => { if (!videoRef.current || !canvasRef.current) return; const video = videoRef.current; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); if (!ctx) return; // Set canvas dimensions to video dimensions canvas.width = video.videoWidth; canvas.height = video.videoHeight; // Draw the video frame to the canvas ctx.drawImage(video, 0, 0, canvas.width, canvas.height); // Get the data URL const dataUrl = canvas.toDataURL('image/jpeg', 0.9); setCapturedImage(dataUrl); }; // Retake photo const retakePhoto = () => { setCapturedImage(null); }; // Confirm and upload photo const confirmPhoto = async () => { if (!capturedImage || !canvasRef.current) return; // Convert data URL to Blob then to File const response = await fetch(capturedImage); const blob = await response.blob(); const file = new File([blob], `skate-spot-${Date.now()}.jpg`, { type: 'image/jpeg' }); onCapture(file); }; // Handle close const handleClose = () => { stopCamera(); setCapturedImage(null); onOpenChange(false); }; return ( Take a Photo
{/* Hidden canvas for capturing */} {/* Loading state */} {isStarting && (

Starting camera...

)} {/* No permission state */} {hasPermission === false && (

Camera Access Required

Please allow camera access in your browser to take photos.

)} {/* Video feed */} {!capturedImage && (
); }