import { useState, useEffect, useCallback, useMemo } from "react"; import GlassContainer from "./GlassContainer"; import GlassButton from "./GlassButton"; import { GLASS_EFFECTS } from "../constants"; const ERROR_TYPES = { HTTPS: "https", NOT_SUPPORTED: "not-supported", PERMISSION: "permission", GENERAL: "general", } as const; const VIDEO_CONSTRAINTS = { video: { width: { ideal: 1920, max: 1920 }, height: { ideal: 1080, max: 1080 }, facingMode: "user", }, }; interface ErrorInfo { type: (typeof ERROR_TYPES)[keyof typeof ERROR_TYPES]; message: string; } interface WebcamPermissionDialogProps { onPermissionGranted: (stream: MediaStream) => void; } export default function WebcamPermissionDialog({ onPermissionGranted }: WebcamPermissionDialogProps) { const [isRequesting, setIsRequesting] = useState(false); const [error, setError] = useState(null); const getErrorInfo = (err: unknown): ErrorInfo => { if (!navigator.mediaDevices) { return { type: ERROR_TYPES.HTTPS, message: "Camera access requires a secure connection (HTTPS)", }; } if (!navigator.mediaDevices.getUserMedia) { return { type: ERROR_TYPES.NOT_SUPPORTED, message: "Camera access not supported in this browser", }; } if (err instanceof DOMException) { switch (err.name) { case "NotAllowedError": return { type: ERROR_TYPES.PERMISSION, message: "Camera access denied", }; case "NotFoundError": return { type: ERROR_TYPES.GENERAL, message: "No camera found", }; case "NotReadableError": return { type: ERROR_TYPES.GENERAL, message: "Camera is in use by another application", }; case "OverconstrainedError": return { type: ERROR_TYPES.GENERAL, message: "Camera doesn't meet requirements", }; case "SecurityError": return { type: ERROR_TYPES.HTTPS, message: "Security error accessing camera", }; default: return { type: ERROR_TYPES.GENERAL, message: `Camera error: ${err.name}`, }; } } return { type: ERROR_TYPES.GENERAL, message: "Failed to access camera", }; }; const requestWebcamAccess = useCallback(async () => { setIsRequesting(true); setError(null); try { if (!navigator.mediaDevices?.getUserMedia) { throw new Error("NOT_SUPPORTED"); } const stream = await navigator.mediaDevices.getUserMedia(VIDEO_CONSTRAINTS); onPermissionGranted(stream); } catch (err) { const errorInfo = getErrorInfo(err); setError(errorInfo); console.error("Error accessing webcam:", err, errorInfo); } finally { setIsRequesting(false); } }, [onPermissionGranted]); useEffect(() => { requestWebcamAccess(); }, [requestWebcamAccess]); const troubleshootingData = useMemo( () => ({ [ERROR_TYPES.HTTPS]: { title: "🔒 HTTPS Required", items: [ "Access this app via https:// instead of http://", "If developing locally, use localhost (exempt from HTTPS requirement)", "Deploy to a hosting service that provides HTTPS (Vercel, Netlify, GitHub Pages)", ], }, [ERROR_TYPES.NOT_SUPPORTED]: { title: "🌐 Browser Compatibility", items: [ "Update your browser to the latest version", "Use Chrome 120+, Edge 120+, Firefox Nightly, or Safari 26 beta+", "Enable JavaScript if disabled", ], }, [ERROR_TYPES.PERMISSION]: { title: "🚫 Permission Issues", items: [ "Click the camera icon in your browser's address bar", 'Select "Always allow" for camera access', "Check browser settings → Privacy & Security → Camera", "Clear browser data and try again", ], }, [ERROR_TYPES.GENERAL]: { title: "General Troubleshooting", items: [ "Check if camera permissions are blocked in your browser", "Try refreshing the page and allowing access", "Ensure no other apps are using your camera", "Try using a different browser or device", ], }, }), [], ); const getErrorStyling = (isSecurityIssue: boolean) => ({ container: `border rounded-lg p-4 ${ isSecurityIssue ? "bg-orange-900/20 border-orange-500/30" : "bg-red-900/20 border-red-500/30" }`, text: `text-sm ${isSecurityIssue ? "text-orange-400" : "text-red-400"}`, troubleshooting: { bg: `border rounded-lg p-3 ${ isSecurityIssue ? "bg-orange-900/20 border-orange-500/30" : "bg-red-900/20 border-red-500/30" }`, title: `text-xs font-semibold mb-2 ${isSecurityIssue ? "text-orange-400" : "text-red-400"}`, list: `text-xs space-y-1 ${isSecurityIssue ? "text-orange-300" : "text-red-300"}`, }, }); const renderIcon = () => { if (isRequesting) { return (
); } const iconClass = "w-8 h-8"; const containerClass = `w-16 h-16 rounded-full flex items-center justify-center ${ error ? "bg-red-500/20" : "bg-blue-500/20" }`; return ( ); }; const getTroubleshootingContent = () => { if (!error) return null; const content = troubleshootingData[error.type]; const isSecurityIssue = error.type === ERROR_TYPES.HTTPS; const styling = getErrorStyling(isSecurityIssue); return (

{content.title}

    {content.items.map((item, index) => (
  • • {item}
  • ))}
); }; const getTitle = () => { if (isRequesting) return "Requesting Camera Access"; if (error) return "Camera Access Required"; return "Camera Permission Required"; }; const getDescription = () => { if (isRequesting) return "Please allow camera access in your browser to continue..."; if (error) return error.message; return "This app requires camera access for live video captioning. Please grant permission to continue."; }; const isSecurityIssue = error?.type === ERROR_TYPES.HTTPS; const errorStyling = error ? getErrorStyling(isSecurityIssue) : null; return (
{renderIcon()}

{getTitle()}

{getDescription()}

{error && errorStyling && (

{isSecurityIssue ? "This app requires HTTPS to access your camera. Live video captioning cannot work without camera input." : "Camera access is required for this app to function. Live video captioning cannot work without camera input."}

Try Again
)} {isRequesting && (

If you don't see a permission dialog, check your browser settings or try refreshing the page.

)}
{error && (

Troubleshooting:

{getTroubleshootingContent()}

Current URL:{" "} {window.location.protocol}//{window.location.host}

{isSecurityIssue && !window.location.protocol.startsWith("https") && (

⚠️ Non-secure connection detected

)}
)}
); }