"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import styles from "./map.module.css"; import NavBar from "@/components/NavBar"; import { getHuntTimer, setHuntTimer, clearHuntTimer, formatElapsedTime, syncTimerWithServer, } from "@/utils/timerService"; export default function MapPage() { const { data: session, status } = useSession(); const [loading, setLoading] = useState(true); const [mapError, setMapError] = useState(false); const [error, setError] = useState(null); const [teamData, setTeamData] = useState(null); const [elapsedTime, setElapsedTime] = useState("00:00:00"); const [clues, setClues] = useState([]); const mapContainerRef = useRef(null); const router = useRouter(); const isMounted = useRef(true); const [huntActive, setHuntActive] = useState(null); const [previousHuntState, setPreviousHuntState] = useState(null); const softRedirect = useCallback( (url) => { if (isMounted.current) { router.push(url); } }, [router] ); const hardRedirect = useCallback((url) => { window.location.href = url; }, []); const fetchTeamData = useCallback(async () => { try { const response = await fetch("/api/user/team", { method: "GET", headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", }, credentials: "include", }); if (response.status === 401) { softRedirect("/login"); return null; } if (response.status === 404) { softRedirect("/team-selection"); return null; } if (!response.ok) { throw new Error("Failed to fetch team data"); } const data = await response.json(); console.log("Team data loaded:", data); setTeamData(data); return data; } catch (err) { console.error("Failed to fetch team data:", err); setError(err.message); return null; } }, [softRedirect]); const fetchClues = useCallback(async () => { try { const response = await fetch("/api/clues", { method: "GET", headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", }, credentials: "include", }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); console.error("API response details:", { status: response.status, statusText: response.statusText, errorData, }); throw new Error( errorData.error || `Failed to fetch clues: ${response.status} ${response.statusText}` ); } const data = await response.json(); const processedClues = data.map((clue) => { return { ...clue, available: clue.available || clue.found || clue.skipped, }; }); setClues(processedClues); setLoading(false); return processedClues; } catch (err) { console.error("Failed to fetch clues:", err); setError(err.message); setLoading(false); return []; } }, []); const handleClueClick = useCallback( (clueId, isAvailable, isFound) => { if (isFound || isAvailable) { router.push(`/clue/${clueId}`); } else { alert("Complete previous clues to unlock this one!"); } }, [router] ); const setupOverlay = useCallback(() => { const overlay = document.getElementById("map-overlay-container"); const mapImg = document.getElementById("treasure-map-img"); if (overlay && mapImg) { mapImg.onload = () => { const mapRect = mapImg.getBoundingClientRect(); overlay.style.width = `${mapRect.width}px`; overlay.style.height = `${mapRect.height}px`; }; const resizeOverlay = () => { const mapRect = mapImg.getBoundingClientRect(); overlay.style.width = `${mapRect.width}px`; overlay.style.height = `${mapRect.height}px`; }; setTimeout(resizeOverlay, 300); window.addEventListener("resize", resizeOverlay); return () => { window.removeEventListener("resize", resizeOverlay); }; } }, []); useEffect(() => { if (status === "authenticated") { fetchClues(); fetchTeamData(); } else if (status === "unauthenticated") { softRedirect("/login"); } return () => { isMounted.current = false; }; }, [status, fetchClues, fetchTeamData, softRedirect]); useEffect(() => { if (!loading && clues.length > 0) { return setupOverlay(); } }, [loading, clues.length, setupOverlay]); async function checkHuntAndTimer() { try { const response = await fetch("/api/hunt-status", { method: "GET", headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", }, credentials: "include", }); if (response.ok) { const data = await response.json(); setHuntActive(data.active); if (data.active === false) { router.push("/waiting"); return; } if (data.active && data.startTime) { setHuntTimer(data.startTime); } } } catch (err) { console.error("Failed to check hunt status:", err); } } async function fetchTeamHuntStartTime() { try { const timerData = getHuntTimer(); if (timerData && timerData.startTime) { return true; } const result = await syncTimerWithServer(); return result.success; } catch (err) { console.error("Error fetching team hunt start time:", err); return false; } } useEffect(() => { let timerId; async function initializeTimer() { await fetchTeamHuntStartTime(); const updateTimer = () => { const timerData = getHuntTimer(); if (timerData && timerData.startTime) { setElapsedTime(formatElapsedTime(timerData.startTime)); } }; updateTimer(); timerId = setInterval(updateTimer, 1000); } initializeTimer(); return () => { if (timerId) clearInterval(timerId); }; }, []); useEffect(() => { checkHuntAndTimer(); const huntStatusInterval = setInterval(checkHuntAndTimer, 10000); return () => { clearInterval(huntStatusInterval); }; }, [softRedirect, hardRedirect]); useEffect(() => { if (status === "authenticated") { fetchTeamData(); } }, [status, fetchTeamData]); useEffect(() => { if (previousHuntState === true && huntActive === false) { console.log("Hunt has ended! Redirecting to leaderboard..."); router.push("/leaderboard?huntEnded=true"); } setPreviousHuntState(huntActive); }, [huntActive, previousHuntState, router]); if (status === "loading" || loading) { return (
The treasure hunt has not started yet.
{error}
We couldn't load the treasure map. Please try again.