From 8a06fd4966d87eb661fe117a24d5008c7e5231a2 Mon Sep 17 00:00:00 2001 From: mudabbir-ahmad Date: Mon, 17 Mar 2025 13:28:55 +0000 Subject: [PATCH] added more clue buttons and positioned them correctly, need to fix elapsed timer on clue page.... --- app/(main)/clue/[clueId]/page.js | 17 ++++- clue_hunt.db | Bin 143360 -> 155648 bytes components/EnhancedQRScanner.js | 106 +++++++++++++++++++++++++------ 3 files changed, 102 insertions(+), 21 deletions(-) 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 35d0760dd830e9a0297a19864472594131d5b83b..84414f5c74436c9680d10270fae24ce6ac1f67bf 100644 GIT binary patch delta 8371 zcma)>*^(T^6^6ST%`g%pMjA8%B!MJGk~h?JR#kU(w~faZHr~L-HnuU|y^Ti0)nZa=xT zjluKHQT%(ZejNYq&aPg6|DzAz|EPQAsri8;y?=JE&iBsuc1{ncgR)<`!L!*Q8(tjb z7hQSA%KBw7jtxMBnO^-kqF21A znDob^OwUwI-0bBbD*BT_o)8so_R_}@z34^xxIaoQJRiH+3qh0{qTJ1%_o9q5DIXWp z3^P3!M4ZX#g=d)QiikdZVz+l|^W7W&s_MtrI~$L7@@3SXM$|Gl&ERYoyGX`e{bhr3 zjA9WP2IERse<{gS_>qRECz=3;=sVpUc^+TxO*@y;JOr4z|iL-C@_Yh0)`$nm>hQU<%X)ZO0&TZzyJ9X9#9~>3nbd^rY>7v9}z@U0t+wjsGwVs`xqX>fDQ% zNG@I_W?~{`LBwS(vtZGxA;L_~iVFf-ltB<-CKWN5V`@C&Ma+eih`G=gQFk)` zPq+OtOeESDJuBf&Y??jg;UMBF;snJi7|zdlk;E^tX~yr5OJnqlr9uurcZd0L`&^%EYZ3UMBJSU1VotWycbEd5;L)`&jk_Jlvp^Uwd+Mv zxp5cA!WpfHf`~K4l}Zt2I_pL0*o~z$&jb;7mzasMJMBdhJ5?WJo-OJJgNSQVSpX4c zdY~1RZS0hvIPU6GL6lq*M40KM7fI|?s+1DD6G6l^sVsm9Gu`h+jGeC8QY!5}5p~OX z_eAgbhQlDpdp#+YrM6s3td0kfu?EI!$BQIZiQTZyjs+2CQq855I=jb<(&xZZVzn(I z?B?uRTkROeDvq}7NTQ<)nd7cL5=2~u%3_Ex)0P)WbX3udBszBo5oSu6V{{ICQM$55 z5}nN;;+j-~LWG&_@*=4$m3t$J)}bKcno=UnbkK_=S_yNE)H=|$;ijU-y@ zL1e6nv0H0J!?vU=7Vrnx%_;Wxt&ywe;u7;#qoHK?#U zc&pj8pi}_Kz|dU=!@O7N0|sA|=1|0}wsFsui_8Onrtj>0!-= z#Ux!NEv>n8g{g3{meVp@St=&d;+pjpXdgpEapS<^P&Lh(!7$0f-QmGkH)0mIs}Yh1yN)XyhL}{H2=d-#MXItTgZ@$( zj5O0_3-q_Ewwu0fLEKTC@-W2a`c{%@d#yS&Wo@s0GcYT+EB09INEvE-@6`b9zg@BA zVsY`Y0ET#-c*A12A)XTE|6S`HV!^as`E^4tO*;N2fZ@^gHH)E*rKcCySn1jIt1C<> z820LZStcN!Y+qS{e8GT8Mu+3Be%W9o-TWuK3I!uAzkbPrjOZ}mUNjhKVk)oY$*q22 z!BpYCDs2Nl`tYpWNjdK7=aYsO?{mtKywBCoSxg(ED!MSsdtH6SfRdm7tdM;Bi^b4$ z@kxLTHuUH9D)qiq$d^-nZ~-yt{$v=+xg(XRU|{Hv29s6E3M$y->OWXe>pJB*Cf)B1 zCf(zTDVmV0e`i536k2;o)IyrvZw*G8TznFO=Xe+Ujb>Q?_>nt`Zw@l<>R)RJOx_+S z6~l2?|H@)ih$_Bk0)p#)X+V;6DuHrIx?flfJ*OHY7})vwen#qFN-uAdPPvZbuD-k< zVkkTVLpr;`z|hYOhK2#vzl622n3^$Zj(Gv{3z)ttWB$P?gdDksH$8lFbV=)ZTAhrVr zc7BjPD`f4LRV5wAUHyH-Pys{Q3t(XAdj?a<9U)- zl62p(AjT+Rh)MTtgOQ|DeIb*idrC1<|M-!HROxZt)!$MOdq#%jT9(oNTwtgBrp3f| zD*hRalflrFNyxRGPL&?VUHyc`(2zRKfq|WG>|@&cm(p`>(y176+|^&-2gz(4e z{2ARd+)*M#XfDl8l3BiZFE0Y(1$pvrj#&^5siKz`@a7(a;W0{o2IWD#*9}`~YTqW|PV66Y4h`3an#dHtfouuCK@>zTHomX%i0sREiob 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 && (