"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useSession } from "next-auth/react"; import { formatElapsedTime, getHuntTimer } from "@/utils/timerService"; import Link from "next/link"; import dynamic from "next/dynamic"; import { use } from "react"; import QRCodeHelper from "@/components/QRCodeHelper"; // Dynamically import the new QR scanner component with no SSR const EnhancedQRScanner = dynamic( () => import("@/components/EnhancedQRScanner"), { ssr: false, loading: () =>
Loading scanner...
, } ); export default function CluePage({ params }) { // Unwrap params properly using use() from React const unwrappedParams = use(params); const clueId = unwrappedParams.clueId; const [clue, setClue] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [elapsedTime, setElapsedTime] = useState("00:00:00"); const [scanMessage, setScanMessage] = useState(null); const [lastRefresh, setLastRefresh] = useState(0); const [clueFound, setClueFound] = useState(false); const [allClues, setAllClues] = useState([]); const [nextClueId, setNextClueId] = useState(null); const router = useRouter(); const { data: session, status } = useSession(); // Check if user is admin const isAdmin = session?.user?.role === "admin"; // This effect handles authentication and initial clue data fetch useEffect(() => { let isMounted = true; const now = Date.now(); const minRefreshInterval = isAdmin ? 30000 : 10000; // Skip if we refreshed too recently and this isn't the first load if (loading === false && now - lastRefresh < minRefreshInterval) { return; } if (status === "unauthenticated") { router.push("/login"); return; } if (status === "loading") { return; } // Fetch all clues to get navigation context const fetchAllClues = async () => { try { const response = await fetch("/api/clues"); if (!response.ok) return []; const data = await response.json(); if (isMounted) { setAllClues(data); // Find next clue for navigation const currentIndex = data.findIndex((c) => c.id === parseInt(clueId)); if (currentIndex !== -1 && currentIndex < data.length - 1) { setNextClueId(data[currentIndex + 1].id); } } return data; } catch (err) { console.error("Error fetching all clues:", err); return []; } }; // Fetch current clue details async function fetchClue() { try { setLoading(true); await fetchAllClues(); // Add timestamp to avoid browser caching const timestamp = Date.now(); const response = await fetch(`/api/clues/${clueId}?t=${timestamp}`, { method: "GET", headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", }, credentials: "include", }); if (!response.ok) { if (response.status === 404) { throw new Error("Clue not found"); } else { const data = await response.json(); throw new Error(data.error || "Failed to fetch clue"); } } const data = await response.json(); if (isMounted) { setClue(data); setClueFound(data.found); setLoading(false); setLastRefresh(Date.now()); } } catch (err) { console.error("Error fetching clue:", err); if (isMounted) { setError(err.message); setLoading(false); } } } fetchClue(); return () => { isMounted = false; }; }, [clueId, router, status, lastRefresh, loading, isAdmin]); // Separate effect to handle timer updates useEffect(() => { let timerId; const timerData = getHuntTimer(); if (!timerData || !timerData.isActive) { return; } // Initial set setElapsedTime(formatElapsedTime(timerData.startTime)); // Update timer every second timerId = setInterval(() => { setElapsedTime(formatElapsedTime(timerData.startTime)); }, 1000); return () => { if (timerId) clearInterval(timerId); }; }, []); // Handle QR scan result const handleScan = (data) => { setScanMessage(`QR Code scanned: ${data}`); try { // Handle multiple QR code formats: "QR123", "123", or even "Clue 123" let scannedClueId; // Try regex patterns const qrPattern = /^QR[^0-9]*(\d+)$/i; const cluePattern = /^clue[^0-9]*(\d+)$/i; const numberPattern = /^(\d+)$/; let match = data.match(qrPattern) || data.match(cluePattern) || data.match(numberPattern); if (match) { scannedClueId = parseInt(match[1], 10); } else { // Try direct parsing as a last resort scannedClueId = parseInt(data, 10); } if (!isNaN(scannedClueId) && scannedClueId === parseInt(clueId)) { // Correct QR code scanned, mark as found setScanMessage("βœ“ Correct QR code found!"); markClueAsFound(clueId); } else if (!isNaN(scannedClueId)) { setScanMessage( `QR code for clue ${scannedClueId} scanned, but you need clue ${clueId}. Please scan the correct code.` ); } else { setScanMessage( "Couldn't recognize this QR code. Please scan the correct code for this clue." ); } } catch (err) { console.error("Error processing scan result:", err); setScanMessage("Invalid QR code format. Please try again."); } }; // Function to mark clue as found via API const markClueAsFound = async (currentClueId) => { try { // Only proceed if the clue isn't already found if (clueFound) { setScanMessage("You've already found this clue!"); return; } // Mark the clue as found via API const response = await fetch("/api/mark-clue", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clueId: currentClueId }), }); if (response.ok) { // Update local state to show clue is found setClueFound(true); setScanMessage( "βœ… Clue verified! You can now proceed to the next clue." ); } else { setScanMessage("Error verifying clue. Please try again."); } } catch (err) { console.error("Error marking clue as found:", err); setScanMessage("Error verifying clue. Please try again."); } }; // Navigate to the next clue const handleNextClue = () => { if (nextClueId) { router.push(`/clue/${nextClueId}`); } else { router.push("/map"); } }; // Navigate to the previous clue const handlePrevClue = () => { if (allClues.length > 0) { const currentIndex = allClues.findIndex((c) => c.id === parseInt(clueId)); if (currentIndex > 0) { router.push(`/clue/${allClues[currentIndex - 1].id}`); } else { router.push("/map"); } } else { router.push("/map"); } }; // Function for manual refresh - for admins to test without waiting const handleManualRefresh = () => { setLastRefresh(0); }; // Rendering states if (loading) { return (

Loading clue...

); } if (error) { return (
Error: {error}
); } if (!clue) { return (
Clue not found
); } return (
{elapsedTime}
{/* Admin tools section - only visible to admins */} {isAdmin && (
Admin View
)} {/* QR Scanner Section - Removed manual entry button */}

Scan QR Code

{scanMessage && (
{scanMessage}
)} {/* Add the QR Code helper component */}
{/* Clue Information Section */}

{clue?.title}

Description

{clue?.description}

{clue?.location && (

Location Hint

{clue.location}

)}
{/* Navigation Buttons */}
{/* Bottom Navigation */}
πŸ—ΊοΈ
Map
πŸ“Š
Stats
πŸ‘₯
Team
🏠
Home
); }