diff --git a/app/(main)/clue/[clueId]/page.js b/app/(main)/clue/[clueId]/page.js index 7ab21e8..fe37aef 100644 --- a/app/(main)/clue/[clueId]/page.js +++ b/app/(main)/clue/[clueId]/page.js @@ -359,6 +359,12 @@ export default function CluePage({ params }) { "✅ Clue verified! You can now proceed to the next clue." ); + // Make next clue available immediately + setNextClueAvailable(true); + + // Force a refresh of the clue data to update availability + setLastRefresh(0); + // Show success notification setShowSuccessNotification(true); @@ -395,8 +401,8 @@ export default function CluePage({ params }) { // Navigate to the next clue const handleNextClue = () => { if (nextClueId) { - // Check if next clue is available before navigating - if (nextClueAvailable || isAdmin) { + // Always allow navigation if current clue is found + if (clueFound || nextClueAvailable || isAdmin) { router.push(`/clue/${nextClueId}`); } else { // Show notification that the next clue is locked @@ -561,7 +567,12 @@ export default function CluePage({ params }) { {/* QR Scanner Section */}

Scan QR Code

- + {scanMessage && (
{scanMessage} diff --git a/clue_hunt.db b/clue_hunt.db index 35d0760..84414f5 100644 Binary files a/clue_hunt.db and b/clue_hunt.db differ diff --git a/components/EnhancedQRScanner.js b/components/EnhancedQRScanner.js index b462959..e4a6ec5 100644 --- a/components/EnhancedQRScanner.js +++ b/components/EnhancedQRScanner.js @@ -7,6 +7,7 @@ export default function EnhancedQRScanner({ onScan, width = 300, height = 300, + disableTorch = false, // Add option to disable torch }) { const [error, setError] = useState(null); const [isSecureContext, setIsSecureContext] = useState(true); @@ -15,9 +16,13 @@ export default function EnhancedQRScanner({ const [hasCameras, setHasCameras] = useState(true); const [scanSuccess, setScanSuccess] = useState(false); const [manualInputValue, setManualInputValue] = useState(""); + const [cameraPermissionDenied, setCameraPermissionDenied] = useState(false); + const cameraStreamRef = useRef(null); + const [retryCount, setRetryCount] = useState(0); + + // We're keeping torchEnabled state but only using it if torch is enabled const [torchEnabled, setTorchEnabled] = useState(false); const inputRef = useRef(null); - const [cameraPermissionDenied, setCameraPermissionDenied] = useState(false); // Define video constraints explicitly const videoConstraints = { @@ -40,6 +45,28 @@ export default function EnhancedQRScanner({ paused: !cameraActive, }); + // Function to release any existing camera streams + const releaseCameraResources = () => { + try { + // Release any stream that might be stored in our ref + if (cameraStreamRef.current) { + cameraStreamRef.current.getTracks().forEach(track => { + track.stop(); + }); + cameraStreamRef.current = null; + console.log("Released existing camera resources"); + } + + // Also try to find and release any active video tracks that might be running + navigator.mediaDevices.getUserMedia({ audio: false, video: false }) + .catch(() => { + // This intentionally fails but helps release resources in some browsers + }); + } catch (err) { + console.error("Error releasing camera:", err); + } + }; + // Check if we're in a secure context and if camera is accessible useEffect(() => { // Ensure scanner compatibility @@ -57,15 +84,20 @@ export default function EnhancedQRScanner({ throw new Error("MediaDevices API is not supported in this browser"); } + // Release any existing camera streams first + releaseCameraResources(); + // Explicitly request video permission - important to avoid the error const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false, }); - // If we got a stream, we have permission - release it immediately + // If we got a stream, we have permission - store it for later release if needed if (stream) { - stream.getTracks().forEach((track) => track.stop()); + cameraStreamRef.current = stream; + // We'll keep this stream active to prevent other apps from taking the camera + // It will be released when component unmounts or when we retry camera access } // Now enumerate devices to confirm we have video devices @@ -110,11 +142,16 @@ export default function EnhancedQRScanner({ } else { setInitialized(true); // Still mark as initialized even if not secure } + + // Cleanup function to release camera when component unmounts + return () => { + releaseCameraResources(); + }; }, []); - // Toggle torch/flashlight + // Toggle torch/flashlight - only used if torch isn't disabled const toggleTorch = () => { - if (torch) { + if (!disableTorch && torch) { setTorchEnabled(!torchEnabled); torch.toggled = !torchEnabled; } @@ -138,12 +175,19 @@ export default function EnhancedQRScanner({ } }; - // Manually request camera access + // Manually request camera access with robust error handling const requestCameraAccess = async () => { try { setError(null); - - // Explicitly request video permission + setCameraActive(false); + + // First release any existing camera resources + releaseCameraResources(); + + // Add a small delay to ensure resources are properly released + await new Promise(resolve => setTimeout(resolve, 500)); + + // Now try to get fresh camera access const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false, @@ -151,15 +195,39 @@ export default function EnhancedQRScanner({ // If we got a stream, we have permission if (stream) { - // Release the stream since useZxing will request it again + // Store the stream reference for potential future release + cameraStreamRef.current = stream; + + // Release this stream since useZxing will request its own stream.getTracks().forEach((track) => track.stop()); + setCameraActive(true); setCameraPermissionDenied(false); + setRetryCount(0); // Reset retry count on success } } catch (err) { console.error("Failed to get camera permission:", err); - setError(`Camera error: ${err.message}`); - setCameraPermissionDenied(true); + + // Increment retry count + setRetryCount(prev => prev + 1); + + if ( + err.name === "NotAllowedError" || + err.name === "PermissionDeniedError" + ) { + setCameraPermissionDenied(true); + setError( + "Camera permission denied. Please allow camera access in your browser settings." + ); + } else if ( + err.name === "NotReadableError" || + err.name === "TrackStartError" || + retryCount < 2 // Try a few times before showing hardware error + ) { + setError(`Camera temporarily unavailable. Please try again in a few moments.`); + } else { + setError(`Camera error: ${err.message}. You may need to restart your browser or device.`); + } } }; @@ -174,7 +242,12 @@ export default function EnhancedQRScanner({ // Handle camera errors const handleError = (error) => { console.error("QR Scanner error:", error); - setCameraActive(false); + + // Don't immediately deactivate camera on all errors + // Some errors can be temporary + if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") { + setCameraActive(false); + } // More descriptive error messages let errorMessage = "Camera access error. "; @@ -256,10 +329,7 @@ export default function EnhancedQRScanner({ ) : ( @@ -286,8 +356,8 @@ export default function EnhancedQRScanner({ {/* Scanning frame to help user focus */}
- {/* Add flashlight toggle button if available */} - {torch && ( + {/* Only show torch button if torch is available and not disabled */} + {torch && !disableTorch && (