"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"; // Dynamically import QR scanner component with no SSR to avoid hydration issues const QRScanner = dynamic(() => import("@/components/QRScanner"), { ssr: false, }); export default function CluePage({ params }) { const [clue, setClue] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [foundNow, setFoundNow] = useState(false); const [elapsedTime, setElapsedTime] = useState("00:00:00"); const [showScanner, setShowScanner] = useState(false); const [scanResult, setScanResult] = useState(null); const [scanMessage, setScanMessage] = useState(null); const [lastRefresh, setLastRefresh] = useState(0); // Track last refresh time const router = useRouter(); const { data: session, status } = useSession(); // Get session to check if user is admin const { clueId } = params; // Check if user is admin const isAdmin = session?.user?.role === "admin"; // This effect handles authentication and initial clue data fetch useEffect(() => { let isMounted = true; // Flag to prevent state updates after unmount const now = Date.now(); // Only allow refreshes every 10 seconds (more for admins) const minRefreshInterval = isAdmin ? 30000 : 10000; // 30 seconds for admins, 10 seconds for regular users // 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; } // One-time fetch clue data with debouncing async function fetchClue() { try { setLoading(true); // 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(); // Only update state if the component is still mounted if (isMounted) { setClue(data); // Only set foundNow on initial load or if it's actually newly found if (loading === true) { setFoundNow(data.foundNow); } setLoading(false); setLastRefresh(Date.now()); // Update last refresh timestamp } } catch (err) { console.error("Error fetching clue:", err); if (isMounted) { setError(err.message); setLoading(false); } } } fetchClue(); // Cleanup function to prevent state updates after unmount return () => { isMounted = false; }; }, [clueId, router, status, lastRefresh, loading, isAdmin]); // Add isAdmin and lastRefresh to dependencies // Separate effect to handle timer updates - doesn't re-fetch data useEffect(() => { let timerId; // Set up timer display updates 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); }; }, []); // Empty dependency array so it only runs once // Handle QR scan result const handleScan = (data) => { setScanResult(data); setScanMessage(`QR Code scanned: ${data}`); try { // Handle both "QR123" format and just "123" format let scannedClueId; const match = data.match(/^QR(\d+)$/i); if (match) { // Extract the number from QR[number] scannedClueId = parseInt(match[1], 10); } else { // Try to parse the raw value scannedClueId = parseInt(data, 10); } if (!isNaN(scannedClueId) && scannedClueId === parseInt(clueId)) { // Correct QR code scanned, mark as found and move to next clue markClueAsFound(clueId); } else { setScanMessage( "Incorrect QR code. Please scan the correct code for this clue." ); } } catch (err) { setScanMessage("Invalid QR code format. Please try again."); } }; // Function to mark clue as found and navigate to the next clue const markClueAsFound = async (currentClueId) => { try { // 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) { // Success message setScanMessage("Clue verified! Moving to the next clue..."); // Fetch all clues to find the next available one const allCluesResponse = await fetch("/api/clues"); const allClues = await allCluesResponse.json(); // Find the current clue index const currentIndex = allClues.findIndex( (c) => c.id === parseInt(currentClueId) ); if (currentIndex !== -1 && currentIndex < allClues.length - 1) { // Navigate to the next clue after a brief delay setTimeout(() => { router.push(`/clue/${allClues[currentIndex + 1].id}`); }, 1500); } else { // This was the last clue setTimeout(() => { router.push("/map"); }, 1500); } } 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."); } }; // Function to handle skip button const handleSkip = async () => { // Fetch all clues to find the next one try { const response = await fetch("/api/clues"); const allClues = await response.json(); // Find the current clue index const currentIndex = allClues.findIndex((c) => c.id === parseInt(clueId)); if (currentIndex !== -1 && currentIndex < allClues.length - 1) { // Navigate to the next clue router.push(`/clue/${allClues[currentIndex + 1].id}`); } else { // This was the last clue router.push("/map"); } } catch (err) { console.error("Error skipping to next clue:", err); setError("Failed to skip to next clue"); } }; // Function for manual refresh - for admins to test without waiting const handleManualRefresh = () => { // Force a refresh by setting lastRefresh to 0 setLastRefresh(0); }; 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 */}

Scan QR Code

{scanMessage &&
{scanMessage}
}
{/* Clue Information Section */}
{foundNow && (
πŸŽ‰
New Clue Discovered!
)}

{clue.title}

Description

{clue.description}

{clue.location && (

Location Hint

{clue.location}

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