From 8fd2cabbe07bdd350d4c3323f645992995c870ae Mon Sep 17 00:00:00 2001 From: mudabbir-ahmad Date: Tue, 18 Mar 2025 23:09:57 +0000 Subject: [PATCH] fixed elapsed timer on clue page cannot fix it on admin page. I am done. now to move on to the styling to match UI --- app/(main)/admin/page.js | 107 ++++++++++++++++++------------ app/(main)/clue/[clueId]/page.js | 108 +++++++++++++++++++++++-------- app/(main)/map/page.js | 23 ++++++- app/api/clues/route.js | 15 +---- app/api/teams/create/route.js | 9 --- clue_hunt.db | Bin 180224 -> 200704 bytes components/CreateTeamForm.js | 2 +- components/TeamHeader.js | 64 +++++++++--------- lib/auth.js | 7 -- utils/timerService.js | 27 +++++++- 10 files changed, 226 insertions(+), 136 deletions(-) diff --git a/app/(main)/admin/page.js b/app/(main)/admin/page.js index 6a85bdb..b3a9e7b 100644 --- a/app/(main)/admin/page.js +++ b/app/(main)/admin/page.js @@ -7,6 +7,7 @@ import { clearHuntTimer, getHuntTimer, formatElapsedTime, + syncTimerWithServer, } from "@/utils/timerService"; export default function AdminDashboard() { @@ -14,7 +15,6 @@ export default function AdminDashboard() { const [timerActive, setTimerActive] = useState(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); - const [elapsedTime, setElapsedTime] = useState(0); // in seconds const [timerDisplay, setTimerDisplay] = useState("--:--:--"); const [presetHours, setPresetHours] = useState(""); const [presetMinutes, setPresetMinutes] = useState(""); @@ -22,6 +22,7 @@ export default function AdminDashboard() { const [timerEndTime, setTimerEndTime] = useState(null); const timerRef = useRef(null); const timerIntervalRef = useRef(null); + const timerStartTimeRef = useRef(null); // New ref to store the start time const router = useRouter(); // Load timer status on page load @@ -33,8 +34,12 @@ export default function AdminDashboard() { // First, check if we already have timer data in localStorage const existingTimer = getHuntTimer(); if (existingTimer && existingTimer.startTime) { + // Store the start time in the ref to prevent it from being lost on re-renders + timerStartTimeRef.current = existingTimer.startTime; + // If we have timer data, immediately set the display - setTimerActive(existingTimer.isActive); + setTimerActive(existingTimer.isActive !== false); + setTimerDisplay(formatElapsedTime(existingTimer.startTime)); if (existingTimer.endTime) { setTimerEndTime(new Date(existingTimer.endTime).getTime()); } @@ -61,10 +66,15 @@ export default function AdminDashboard() { if (isActive && data.startTime) { setTimerActive(true); + // Store the start time in the ref for persistence across renders + timerStartTimeRef.current = data.startTime; + // Store the start time in the centralized service - setHuntTimer(data.startTime, data.endTime); + setHuntTimer(data.startTime, data.endTime, true); // Explicitly set isActive=true + + // Update the display immediately with the correct time + setTimerDisplay(formatElapsedTime(data.startTime)); - // If there's an end time, set it if (data.endTime) { setTimerEndTime(new Date(data.endTime).getTime()); } else { @@ -72,10 +82,12 @@ export default function AdminDashboard() { } } else { // Only clear local timer if server explicitly says timer is not active - // This prevents clearing a valid timer if the server response is ambiguous if (data.active === false && data.timerActive === false) { clearHuntTimer(); + timerStartTimeRef.current = null; // Clear the start time ref setTimerActive(false); + setTimerDisplay("--:--:--"); + setTimerEndTime(null); } } @@ -111,48 +123,53 @@ export default function AdminDashboard() { } }, [session, status, router]); - // Create a separate effect for updating the timer display + // Create a separate effect for updating the timer display - but only have one instance useEffect(() => { - let timerId; + // Clear any existing interval to avoid duplicates + if (timerIntervalRef.current) { + clearInterval(timerIntervalRef.current); + timerIntervalRef.current = null; + } - // Initialize timer - async function initializeTimer() { - // Set up timer updates - const updateTimer = () => { - const timerData = getHuntTimer(); - if (timerData && timerData.startTime) { - setTimerDisplay(formatElapsedTime(timerData.startTime)); + const updateTimer = () => { + // Use the ref to access the persisted start time + const startTime = timerStartTimeRef.current; - // If there's an end time, check if we've reached it - if (timerEndTime && Date.now() >= timerEndTime) { - stopTimer(); - } - } else { - setTimerDisplay("--:--:--"); + if (timerActive && startTime) { + // Format time using the start time from our ref, which persists across renders + setTimerDisplay(formatElapsedTime(startTime)); + + // Check if timer has ended + if (timerEndTime && Date.now() >= timerEndTime) { + stopTimer(); } - }; - - // Initial update - updateTimer(); - - // Update every second - timerId = setInterval(updateTimer, 1000); - } - - // Only initialize timer if the timer is active - if (timerActive) { - initializeTimer(); - } else { - setTimerDisplay("--:--:--"); - } - - // Clean up interval on unmount - return () => { - if (timerId) { - clearInterval(timerId); + } else if (timerActive) { + // If timer is active but no data, try to restore + syncTimerWithServer().then((result) => { + if (result.success && result.data) { + timerStartTimeRef.current = result.data.startTime; + } + }); + } else { + setTimerDisplay("--:--:--"); } }; - }, [timerActive, timerEndTime]); + + // Initial update + updateTimer(); + + // Set up interval for timer updates + if (timerActive) { + timerIntervalRef.current = setInterval(updateTimer, 1000); + } + + return () => { + if (timerIntervalRef.current) { + clearInterval(timerIntervalRef.current); + timerIntervalRef.current = null; + } + }; + }, [timerActive, timerEndTime]); // Include timerEndTime as a dependency // Add a function to fetch timer status directly from the server const fetchTimerStatusFromServer = async () => { @@ -177,8 +194,8 @@ export default function AdminDashboard() { if (isActive && data.startTime) { setTimerActive(true); + timerStartTimeRef.current = data.startTime; // Update the ref setHuntTimer(data.startTime, data.endTime); - if (data.endTime) { setTimerEndTime(new Date(data.endTime).getTime()); } else { @@ -189,6 +206,7 @@ export default function AdminDashboard() { } else if (data.active === false && data.timerActive === false) { // Only clear timer if server explicitly says timer is inactive setTimerActive(false); + timerStartTimeRef.current = null; // Clear the ref clearHuntTimer(); setTimerDisplay("--:--:--"); } @@ -250,6 +268,9 @@ export default function AdminDashboard() { if (data.success) { setTimerActive(true); + // Store the start time in the ref + timerStartTimeRef.current = data.startTime; + if (endTime) { setTimerEndTime(new Date(endTime).getTime()); } else { @@ -293,6 +314,7 @@ export default function AdminDashboard() { if (data.success) { setTimerActive(false); + timerStartTimeRef.current = null; // Clear the ref setTimerEndTime(null); setTimerDisplay("--:--:--"); @@ -465,6 +487,7 @@ export default function AdminDashboard() { padding: 1rem; display: flex; flex-direction: column; + justify-content: center; align-items: center; padding-bottom: 70px; /* Add space for the navbar */ } diff --git a/app/(main)/clue/[clueId]/page.js b/app/(main)/clue/[clueId]/page.js index 0870169..5d06b08 100644 --- a/app/(main)/clue/[clueId]/page.js +++ b/app/(main)/clue/[clueId]/page.js @@ -47,6 +47,7 @@ export default function CluePage({ params }) { const notificationTimeoutRef = useRef(null); const navErrorTimeoutRef = useRef(null); const timerIdRef = useRef(null); + const timerInitializedRef = useRef(false); // Add this ref to track if timer is already initialized // Check if user is admin const isAdmin = session?.user?.role === "admin"; @@ -194,7 +195,7 @@ export default function CluePage({ params }) { }; }, [router, previousHuntState]); - // Function to ensure we have timer data from the server + // Function to ensure we have timer data from the server - only if needed async function fetchTeamHuntStartTime() { try { // First try to get any existing timer data @@ -204,6 +205,7 @@ export default function CluePage({ params }) { } // If no timer data exists, sync with server + console.log("No timer data found, syncing with server..."); const result = await syncTimerWithServer(); return result.success; } catch (err) { @@ -212,44 +214,53 @@ export default function CluePage({ params }) { } } - // Updated timer effect that follows the same pattern as the map page + // Updated timer effect with protection against multiple initializations useEffect(() => { + // Prevent multiple initializations of the timer + if (timerInitializedRef.current) { + return; + } + // Initialize timer async function initializeTimer() { // First ensure we have timer data await fetchTeamHuntStartTime(); - // Set up timer updates - const updateTimer = () => { - const timerData = getHuntTimer(); - if (timerData && timerData.startTime) { - setElapsedTime(formatElapsedTime(timerData.startTime)); + // Set up timer updates if not already set + if (!timerIdRef.current) { + const updateTimer = () => { + const timerData = getHuntTimer(); + if (timerData && timerData.startTime) { + setElapsedTime(formatElapsedTime(timerData.startTime)); - // Check if timer has ended (if there's an endTime) - if (timerData.endTime) { - const now = Date.now(); - const endTime = new Date(timerData.endTime).getTime(); + // Check if timer has ended (if there's an endTime) + if (timerData.endTime) { + const now = Date.now(); + const endTime = new Date(timerData.endTime).getTime(); - // If current time is past the end time, hunt has ended - if (now >= endTime) { - setHuntActive(false); - clearInterval(timerIdRef.current); + // If current time is past the end time, hunt has ended + if (now >= endTime) { + setHuntActive(false); + clearInterval(timerIdRef.current); + timerIdRef.current = null; - // Redirect to leaderboard with huntEnded flag - console.log( - "Hunt end time reached! Redirecting to leaderboard..." - ); - router.push("/leaderboard?huntEnded=true"); + // Redirect to leaderboard with huntEnded flag + console.log( + "Hunt end time reached! Redirecting to leaderboard..." + ); + router.push("/leaderboard?huntEnded=true"); + } } } - } - }; + }; - // Initial update - updateTimer(); + // Initial update + updateTimer(); - // Update every second - timerIdRef.current = setInterval(updateTimer, 1000); + // Update every second + timerIdRef.current = setInterval(updateTimer, 1000); + timerInitializedRef.current = true; + } } initializeTimer(); @@ -259,9 +270,52 @@ export default function CluePage({ params }) { if (timerIdRef.current) { clearInterval(timerIdRef.current); timerIdRef.current = null; + timerInitializedRef.current = false; } }; - }, [router]); + }, [router]); // Only depend on router + + // Make sure hunt status check doesn't interfere with the timer + useEffect(() => { + // This function should NOT reset the timer or recreate it + async function checkHuntStatus() { + try { + const response = await fetch("/api/hunt-status", { + method: "GET", + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache, no-store, must-revalidate", + }, + credentials: "include", + }); + + if (response.ok) { + const data = await response.json(); + + // Only update hunt state, don't touch the timer + const newHuntState = !!data.active; + + // If hunt was active but is now inactive, redirect to leaderboard + if (previousHuntState === true && newHuntState === false) { + console.log("Hunt has ended! Redirecting to leaderboard..."); + router.push("/leaderboard?huntEnded=true"); + return; + } + + // Update the previous hunt state for next comparison + setPreviousHuntState(newHuntState); + } + } catch (err) { + console.error("Failed to check hunt status:", err); + } + } + + const intervalId = setInterval(checkHuntStatus, 5000); + + return () => { + clearInterval(intervalId); + }; + }, [router, previousHuntState]); // Fetch team data on component mount useEffect(() => { diff --git a/app/(main)/map/page.js b/app/(main)/map/page.js index af8e670..6cdaa3c 100644 --- a/app/(main)/map/page.js +++ b/app/(main)/map/page.js @@ -27,6 +27,8 @@ export default function MapPage() { // Add local state for hunt active status const [huntActive, setHuntActive] = useState(null); + // Add previous hunt state to detect when hunt ends + const [previousHuntState, setPreviousHuntState] = useState(null); // Use useCallback for functions called in useEffect to avoid dependency issues const softRedirect = useCallback( @@ -293,6 +295,18 @@ export default function MapPage() { } }, [status, fetchTeamData]); + // Add this useEffect to detect when hunt ends + useEffect(() => { + // If previous state was active but current state is not active, the hunt just ended + if (previousHuntState === true && huntActive === false) { + console.log("Hunt has ended! Redirecting to leaderboard..."); + router.push("/leaderboard?huntEnded=true"); + } + + // Update previous state after checking + setPreviousHuntState(huntActive); + }, [huntActive, previousHuntState, router]); + if (status === "loading" || loading) { return (
@@ -302,8 +316,11 @@ export default function MapPage() { ); } - // If hunt status is known to be inactive, show message - if (huntActive === false) { + // Remove or modify this conditional block since we're now handling hunt ending differently + // Instead of showing an error message, we'll redirect in the useEffect + if (huntActive === false && previousHuntState === null) { + // Only show this if we just landed on the page and hunt is inactive + // (not when the hunt just ended) return (

Hunt Not Active

@@ -371,7 +388,7 @@ export default function MapPage() { {/* Map Banner with team info and timer */}
- {teamData?.team?.name || "Loading..."} + Team Name: {teamData?.team?.name || "Loading..."}
{elapsedTime} diff --git a/app/api/clues/route.js b/app/api/clues/route.js index d2e40af..3937932 100644 --- a/app/api/clues/route.js +++ b/app/api/clues/route.js @@ -1,7 +1,7 @@ import { query, queryOne, run } from "@/lib/db"; import { getToken } from "next-auth/jwt"; -export const dynamic = "force-dynamic"; // Disable caching +export const dynamic = "force-dynamic"; export async function GET(request) { try { @@ -17,7 +17,6 @@ export async function GET(request) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } - // Check if user has a team if (!token.teamId) { console.log("API: User without team tried to access clues"); return Response.json( @@ -26,9 +25,7 @@ export async function GET(request) { ); } - // First, ensure the team_clues table has the 'skipped' column try { - // Check if team_clues table exists, create it if it doesn't await run(` CREATE TABLE IF NOT EXISTS team_clues ( team_id INTEGER NOT NULL, @@ -40,7 +37,6 @@ export async function GET(request) { ) `); - // Check if skipped column exists const tableInfo = await query("PRAGMA table_info(team_clues)"); const hasSkippedColumn = tableInfo.some((col) => col.name === "skipped"); @@ -52,18 +48,14 @@ export async function GET(request) { } } catch (schemaError) { console.error("Error ensuring database schema:", schemaError); - // Continue execution - if this fails, the main query might still work - // with existing columns } - // Get all clues with explicit ordering const clues = await query(` SELECT id, title, description, location FROM clues ORDER BY id ASC `); - // For each clue, check if the team has found it const cluesWithStatus = await Promise.all( clues.map(async (clue) => { let clueStatus; @@ -74,7 +66,6 @@ export async function GET(request) { ); } catch (error) { console.error(`Error querying status for clue ${clue.id}:`, error); - // Fallback if query fails (e.g., skipped column still doesn't exist) clueStatus = await queryOne( "SELECT found_at FROM team_clues WHERE team_id = ? AND clue_id = ?", [token.teamId, clue.id] @@ -85,19 +76,17 @@ export async function GET(request) { ...clue, found: !!clueStatus && !(clueStatus.skipped === 1), skipped: !!clueStatus && clueStatus.skipped === 1, - available: false, // Will be determined after processing all clues + available: false, }; }) ); - // Determine which clues should be available let firstUnavailableFound = false; const finalClues = cluesWithStatus.map((clue, index, arr) => { if (clue.found || clue.skipped) { return { ...clue, available: true }; } - // If we haven't found an unavailable clue yet, this one is available if (!firstUnavailableFound) { firstUnavailableFound = true; return { ...clue, available: true }; diff --git a/app/api/teams/create/route.js b/app/api/teams/create/route.js index be7a613..7715b3b 100644 --- a/app/api/teams/create/route.js +++ b/app/api/teams/create/route.js @@ -3,7 +3,6 @@ import { queryOne, run } from "@/lib/db"; export async function POST(request) { try { - // Verify user authentication const token = await getToken({ req: request, secret: @@ -15,20 +14,16 @@ export async function POST(request) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } - // Get user ID from token const userId = token.id; - // Get team name from request body const { name } = await request.json(); if (!name || !name.trim()) { return Response.json({ error: "Team name is required" }, { status: 400 }); } - // Generate a unique team code const code = generateTeamCode(); - // Create teams table if it doesn't exist await run(` CREATE TABLE IF NOT EXISTS teams ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -38,7 +33,6 @@ export async function POST(request) { ) `); - // Check if team name already exists const existingTeam = await queryOne("SELECT id FROM teams WHERE name = ?", [ name, ]); @@ -50,7 +44,6 @@ export async function POST(request) { ); } - // Insert team const result = await run("INSERT INTO teams (name, code) VALUES (?, ?)", [ name, code, @@ -61,10 +54,8 @@ export async function POST(request) { throw new Error("Failed to create team - no ID returned"); } - // Update the user's team_id await run("UPDATE users SET team_id = ? WHERE id = ?", [teamId, userId]); - // Return success with team info return Response.json({ success: true, teamId, diff --git a/clue_hunt.db b/clue_hunt.db index de55ce0f632d81169f99cff1dcedbc5939395f85..725003afff276deb28512077d69b5028d09850fd 100644 GIT binary patch delta 16511 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 KoZF;0X8sv7g=H21 delta 411 zcmW-cJxE(o7>3Vx&izYF{1R<$Qmq&>6%3M#p^9iDQp7@u8MH$Yyso9tR2?1ST@(>| zn-e`wl0m3-sLB~`acC=aDn%EC3=S?1L8wEqidN{w<$?EkrngiLl}Zh6ixyZXgrq+t zpVGkku052a`mx-|*-3-pXM$(=Q-cy3z*JQSXJF@WtH9Qc!B88e-3wmFdg&_GP-eo^)ql@%{GZva1NC0Q=ta z8ufHxUJ^hwfqi&=4$bRS@U_``L0!Nl^u|k>02~lrl|$z{1ricvqt|Y#S8U2>!w~*-=b%7 diff --git a/components/CreateTeamForm.js b/components/CreateTeamForm.js index 9d89151..fc7f0dd 100644 --- a/components/CreateTeamForm.js +++ b/components/CreateTeamForm.js @@ -9,7 +9,7 @@ export default function CreateTeamForm({ onSubmit }) { e.preventDefault(); if (!name.trim()) { - return; // Don't submit if name is empty + return; } setSubmitting(true); diff --git a/components/TeamHeader.js b/components/TeamHeader.js index 6a9b0ec..99b5346 100644 --- a/components/TeamHeader.js +++ b/components/TeamHeader.js @@ -1,38 +1,38 @@ -'use client'; -import { useSession } from 'next-auth/react'; -import { useState, useEffect } from 'react'; +"use client"; +import { useSession } from "next-auth/react"; +import { useState, useEffect } from "react"; export default function TeamHeader() { - const { data: session } = useSession(); - const [teamName, setTeamName] = useState(null); + const { data: session } = useSession(); + const [teamName, setTeamName] = useState(null); - useEffect(() => { - // Only fetch team info if user is logged in and not an admin - if (session?.user?.teamId && session?.user?.role !== 'admin') { - fetchTeamInfo(); - } - }, [session]); - - const fetchTeamInfo = async () => { - try { - const response = await fetch(`/api/teams/${session.user.teamId}`); - if (response.ok) { - const data = await response.json(); - setTeamName(data.name); - } - } catch (error) { - console.error('Failed to fetch team info:', error); - } - }; - - // Don't render anything if no team name or user is admin - if (!teamName || session?.user?.role === 'admin') { - return null; + useEffect(() => { + // Only fetch team info if user is logged in and not an admin + if (session?.user?.teamId && session?.user?.role !== "admin") { + fetchTeamInfo(); } + }, [session]); - return ( -
-
{teamName}
-
- ); + const fetchTeamInfo = async () => { + try { + const response = await fetch(`/api/teams/${session.user.teamId}`); + if (response.ok) { + const data = await response.json(); + setTeamName(data.name); + } + } catch (error) { + console.error("Failed to fetch team info:", error); + } + }; + + // Don't render anything if no team name or user is admin + if (!teamName || session?.user?.role === "admin") { + return null; + } + + return ( +
+
Team Name: {teamName}
+
+ ); } diff --git a/lib/auth.js b/lib/auth.js index 17f61ad..2d38cf1 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,20 +1,16 @@ import jwt from "jsonwebtoken"; -// Get JWT secret from environment or use fallback const JWT_SECRET = process.env.NEXTAUTH_SECRET || process.env.JWT_SECRET || "your-fallback-secret-should-be-at-least-32-chars"; -// Create a new JWT token export function createToken(payload) { - // Check if payload already has an expiration date set const options = payload.exp ? {} : { expiresIn: "30d" }; return jwt.sign(payload, JWT_SECRET, options); } -// Verify a JWT token export function verifyToken(token) { try { if (!token) { @@ -26,13 +22,11 @@ export function verifyToken(token) { const decoded = jwt.verify(token, JWT_SECRET); - // Validate that we have the minimum required fields if (!decoded.sub && !decoded.id) { console.error("Token missing required user identifier"); return null; } - // Log successful token verification with user ID console.log( "Token verified successfully for user:", decoded.sub || decoded.id @@ -40,7 +34,6 @@ export function verifyToken(token) { return decoded; } catch (error) { - // Provide detailed error logs if (error.name === "TokenExpiredError") { console.error("Token expired at:", error.expiredAt); } else if (error.name === "JsonWebTokenError") { diff --git a/utils/timerService.js b/utils/timerService.js index 64b2eb9..b53a3b9 100644 --- a/utils/timerService.js +++ b/utils/timerService.js @@ -154,18 +154,41 @@ export const syncTimerWithServer = async () => { } const data = await response.json(); + console.log("Timer sync: Server returned data:", data); + + // Don't change timer if we already have it and server confirms it's active + const existingTimer = getHuntTimer(); + if (existingTimer && existingTimer.startTime && data.active) { + console.log("Timer sync: Using existing timer data, confirmed by server"); + return { + success: true, + data: { + startTime: existingTimer.startTime, + endTime: existingTimer.endTime || data.endTime, + active: true, + }, + }; + } if (data.active && data.startTime) { - setHuntTimer(data.startTime, data.endTime); + console.log( + "Timer sync: Setting timer with startTime:", + new Date(data.startTime).toISOString() + ); + + // Ensure isActive is explicitly true + setHuntTimer(data.startTime, data.endTime, true); + return { success: true, data: { startTime: data.startTime, endTime: data.endTime, - active: data.active, + active: true, }, }; } else { + console.log("Timer sync: Clearing timer as server reports inactive"); clearHuntTimer(); return { success: true, data: null }; }