mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-07 19:50:20 +00:00
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
This commit is contained in:
1 parent
4cd449c0cf
commit
8fd2cabbe0
10 files changed
+226
-136
No files matched your search
+65
-42
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
+20
-3
@@ -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 (
|
||||
<div className={styles["loading-container"]}>
|
||||
@@ -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 (
|
||||
<div className={styles["map-error"]}>
|
||||
<h2>Hunt Not Active</h2>
|
||||
@@ -371,7 +388,7 @@ export default function MapPage() {
|
||||
{/* Map Banner with team info and timer */}
|
||||
<div className={styles["map-banner"]}>
|
||||
<div className={styles["team-name"]}>
|
||||
{teamData?.team?.name || "Loading..."}
|
||||
Team Name: {teamData?.team?.name || "Loading..."}
|
||||
</div>
|
||||
<div className={styles["elapsed-time"]}>
|
||||
<span className={styles["time-value"]}>{elapsedTime}</span>
|
||||
|
||||
Reference in new issue
Block a user