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,
|
clearHuntTimer,
|
||||||
getHuntTimer,
|
getHuntTimer,
|
||||||
formatElapsedTime,
|
formatElapsedTime,
|
||||||
|
syncTimerWithServer,
|
||||||
} from "@/utils/timerService";
|
} from "@/utils/timerService";
|
||||||
|
|
||||||
export default function AdminDashboard() {
|
export default function AdminDashboard() {
|
||||||
@@ -14,7 +15,6 @@ export default function AdminDashboard() {
|
|||||||
const [timerActive, setTimerActive] = useState(false);
|
const [timerActive, setTimerActive] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [elapsedTime, setElapsedTime] = useState(0); // in seconds
|
|
||||||
const [timerDisplay, setTimerDisplay] = useState("--:--:--");
|
const [timerDisplay, setTimerDisplay] = useState("--:--:--");
|
||||||
const [presetHours, setPresetHours] = useState("");
|
const [presetHours, setPresetHours] = useState("");
|
||||||
const [presetMinutes, setPresetMinutes] = useState("");
|
const [presetMinutes, setPresetMinutes] = useState("");
|
||||||
@@ -22,6 +22,7 @@ export default function AdminDashboard() {
|
|||||||
const [timerEndTime, setTimerEndTime] = useState(null);
|
const [timerEndTime, setTimerEndTime] = useState(null);
|
||||||
const timerRef = useRef(null);
|
const timerRef = useRef(null);
|
||||||
const timerIntervalRef = useRef(null);
|
const timerIntervalRef = useRef(null);
|
||||||
|
const timerStartTimeRef = useRef(null); // New ref to store the start time
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Load timer status on page load
|
// Load timer status on page load
|
||||||
@@ -33,8 +34,12 @@ export default function AdminDashboard() {
|
|||||||
// First, check if we already have timer data in localStorage
|
// First, check if we already have timer data in localStorage
|
||||||
const existingTimer = getHuntTimer();
|
const existingTimer = getHuntTimer();
|
||||||
if (existingTimer && existingTimer.startTime) {
|
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
|
// If we have timer data, immediately set the display
|
||||||
setTimerActive(existingTimer.isActive);
|
setTimerActive(existingTimer.isActive !== false);
|
||||||
|
setTimerDisplay(formatElapsedTime(existingTimer.startTime));
|
||||||
if (existingTimer.endTime) {
|
if (existingTimer.endTime) {
|
||||||
setTimerEndTime(new Date(existingTimer.endTime).getTime());
|
setTimerEndTime(new Date(existingTimer.endTime).getTime());
|
||||||
}
|
}
|
||||||
@@ -61,10 +66,15 @@ export default function AdminDashboard() {
|
|||||||
if (isActive && data.startTime) {
|
if (isActive && data.startTime) {
|
||||||
setTimerActive(true);
|
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
|
// 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) {
|
if (data.endTime) {
|
||||||
setTimerEndTime(new Date(data.endTime).getTime());
|
setTimerEndTime(new Date(data.endTime).getTime());
|
||||||
} else {
|
} else {
|
||||||
@@ -72,10 +82,12 @@ export default function AdminDashboard() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only clear local timer if server explicitly says timer is not active
|
// 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) {
|
if (data.active === false && data.timerActive === false) {
|
||||||
clearHuntTimer();
|
clearHuntTimer();
|
||||||
|
timerStartTimeRef.current = null; // Clear the start time ref
|
||||||
setTimerActive(false);
|
setTimerActive(false);
|
||||||
|
setTimerDisplay("--:--:--");
|
||||||
|
setTimerEndTime(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,48 +123,53 @@ export default function AdminDashboard() {
|
|||||||
}
|
}
|
||||||
}, [session, status, router]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
let timerId;
|
// Clear any existing interval to avoid duplicates
|
||||||
|
if (timerIntervalRef.current) {
|
||||||
|
clearInterval(timerIntervalRef.current);
|
||||||
|
timerIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize timer
|
const updateTimer = () => {
|
||||||
async function initializeTimer() {
|
// Use the ref to access the persisted start time
|
||||||
// Set up timer updates
|
const startTime = timerStartTimeRef.current;
|
||||||
const updateTimer = () => {
|
|
||||||
const timerData = getHuntTimer();
|
|
||||||
if (timerData && timerData.startTime) {
|
|
||||||
setTimerDisplay(formatElapsedTime(timerData.startTime));
|
|
||||||
|
|
||||||
// If there's an end time, check if we've reached it
|
if (timerActive && startTime) {
|
||||||
if (timerEndTime && Date.now() >= timerEndTime) {
|
// Format time using the start time from our ref, which persists across renders
|
||||||
stopTimer();
|
setTimerDisplay(formatElapsedTime(startTime));
|
||||||
}
|
|
||||||
} else {
|
// Check if timer has ended
|
||||||
setTimerDisplay("--:--:--");
|
if (timerEndTime && Date.now() >= timerEndTime) {
|
||||||
|
stopTimer();
|
||||||
}
|
}
|
||||||
};
|
} else if (timerActive) {
|
||||||
|
// If timer is active but no data, try to restore
|
||||||
// Initial update
|
syncTimerWithServer().then((result) => {
|
||||||
updateTimer();
|
if (result.success && result.data) {
|
||||||
|
timerStartTimeRef.current = result.data.startTime;
|
||||||
// Update every second
|
}
|
||||||
timerId = setInterval(updateTimer, 1000);
|
});
|
||||||
}
|
} else {
|
||||||
|
setTimerDisplay("--:--:--");
|
||||||
// Only initialize timer if the timer is active
|
|
||||||
if (timerActive) {
|
|
||||||
initializeTimer();
|
|
||||||
} else {
|
|
||||||
setTimerDisplay("--:--:--");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up interval on unmount
|
|
||||||
return () => {
|
|
||||||
if (timerId) {
|
|
||||||
clearInterval(timerId);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [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
|
// Add a function to fetch timer status directly from the server
|
||||||
const fetchTimerStatusFromServer = async () => {
|
const fetchTimerStatusFromServer = async () => {
|
||||||
@@ -177,8 +194,8 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
if (isActive && data.startTime) {
|
if (isActive && data.startTime) {
|
||||||
setTimerActive(true);
|
setTimerActive(true);
|
||||||
|
timerStartTimeRef.current = data.startTime; // Update the ref
|
||||||
setHuntTimer(data.startTime, data.endTime);
|
setHuntTimer(data.startTime, data.endTime);
|
||||||
|
|
||||||
if (data.endTime) {
|
if (data.endTime) {
|
||||||
setTimerEndTime(new Date(data.endTime).getTime());
|
setTimerEndTime(new Date(data.endTime).getTime());
|
||||||
} else {
|
} else {
|
||||||
@@ -189,6 +206,7 @@ export default function AdminDashboard() {
|
|||||||
} else if (data.active === false && data.timerActive === false) {
|
} else if (data.active === false && data.timerActive === false) {
|
||||||
// Only clear timer if server explicitly says timer is inactive
|
// Only clear timer if server explicitly says timer is inactive
|
||||||
setTimerActive(false);
|
setTimerActive(false);
|
||||||
|
timerStartTimeRef.current = null; // Clear the ref
|
||||||
clearHuntTimer();
|
clearHuntTimer();
|
||||||
setTimerDisplay("--:--:--");
|
setTimerDisplay("--:--:--");
|
||||||
}
|
}
|
||||||
@@ -250,6 +268,9 @@ export default function AdminDashboard() {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
setTimerActive(true);
|
setTimerActive(true);
|
||||||
|
|
||||||
|
// Store the start time in the ref
|
||||||
|
timerStartTimeRef.current = data.startTime;
|
||||||
|
|
||||||
if (endTime) {
|
if (endTime) {
|
||||||
setTimerEndTime(new Date(endTime).getTime());
|
setTimerEndTime(new Date(endTime).getTime());
|
||||||
} else {
|
} else {
|
||||||
@@ -293,6 +314,7 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setTimerActive(false);
|
setTimerActive(false);
|
||||||
|
timerStartTimeRef.current = null; // Clear the ref
|
||||||
setTimerEndTime(null);
|
setTimerEndTime(null);
|
||||||
setTimerDisplay("--:--:--");
|
setTimerDisplay("--:--:--");
|
||||||
|
|
||||||
@@ -465,6 +487,7 @@ export default function AdminDashboard() {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: 70px; /* Add space for the navbar */
|
padding-bottom: 70px; /* Add space for the navbar */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default function CluePage({ params }) {
|
|||||||
const notificationTimeoutRef = useRef(null);
|
const notificationTimeoutRef = useRef(null);
|
||||||
const navErrorTimeoutRef = useRef(null);
|
const navErrorTimeoutRef = useRef(null);
|
||||||
const timerIdRef = 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
|
// Check if user is admin
|
||||||
const isAdmin = session?.user?.role === "admin";
|
const isAdmin = session?.user?.role === "admin";
|
||||||
@@ -194,7 +195,7 @@ export default function CluePage({ params }) {
|
|||||||
};
|
};
|
||||||
}, [router, previousHuntState]);
|
}, [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() {
|
async function fetchTeamHuntStartTime() {
|
||||||
try {
|
try {
|
||||||
// First try to get any existing timer data
|
// 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
|
// If no timer data exists, sync with server
|
||||||
|
console.log("No timer data found, syncing with server...");
|
||||||
const result = await syncTimerWithServer();
|
const result = await syncTimerWithServer();
|
||||||
return result.success;
|
return result.success;
|
||||||
} catch (err) {
|
} 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(() => {
|
useEffect(() => {
|
||||||
|
// Prevent multiple initializations of the timer
|
||||||
|
if (timerInitializedRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize timer
|
// Initialize timer
|
||||||
async function initializeTimer() {
|
async function initializeTimer() {
|
||||||
// First ensure we have timer data
|
// First ensure we have timer data
|
||||||
await fetchTeamHuntStartTime();
|
await fetchTeamHuntStartTime();
|
||||||
|
|
||||||
// Set up timer updates
|
// Set up timer updates if not already set
|
||||||
const updateTimer = () => {
|
if (!timerIdRef.current) {
|
||||||
const timerData = getHuntTimer();
|
const updateTimer = () => {
|
||||||
if (timerData && timerData.startTime) {
|
const timerData = getHuntTimer();
|
||||||
setElapsedTime(formatElapsedTime(timerData.startTime));
|
if (timerData && timerData.startTime) {
|
||||||
|
setElapsedTime(formatElapsedTime(timerData.startTime));
|
||||||
|
|
||||||
// Check if timer has ended (if there's an endTime)
|
// Check if timer has ended (if there's an endTime)
|
||||||
if (timerData.endTime) {
|
if (timerData.endTime) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const endTime = new Date(timerData.endTime).getTime();
|
const endTime = new Date(timerData.endTime).getTime();
|
||||||
|
|
||||||
// If current time is past the end time, hunt has ended
|
// If current time is past the end time, hunt has ended
|
||||||
if (now >= endTime) {
|
if (now >= endTime) {
|
||||||
setHuntActive(false);
|
setHuntActive(false);
|
||||||
clearInterval(timerIdRef.current);
|
clearInterval(timerIdRef.current);
|
||||||
|
timerIdRef.current = null;
|
||||||
|
|
||||||
// Redirect to leaderboard with huntEnded flag
|
// Redirect to leaderboard with huntEnded flag
|
||||||
console.log(
|
console.log(
|
||||||
"Hunt end time reached! Redirecting to leaderboard..."
|
"Hunt end time reached! Redirecting to leaderboard..."
|
||||||
);
|
);
|
||||||
router.push("/leaderboard?huntEnded=true");
|
router.push("/leaderboard?huntEnded=true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Initial update
|
// Initial update
|
||||||
updateTimer();
|
updateTimer();
|
||||||
|
|
||||||
// Update every second
|
// Update every second
|
||||||
timerIdRef.current = setInterval(updateTimer, 1000);
|
timerIdRef.current = setInterval(updateTimer, 1000);
|
||||||
|
timerInitializedRef.current = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeTimer();
|
initializeTimer();
|
||||||
@@ -259,9 +270,52 @@ export default function CluePage({ params }) {
|
|||||||
if (timerIdRef.current) {
|
if (timerIdRef.current) {
|
||||||
clearInterval(timerIdRef.current);
|
clearInterval(timerIdRef.current);
|
||||||
timerIdRef.current = null;
|
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
|
// Fetch team data on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
+20
-3
@@ -27,6 +27,8 @@ export default function MapPage() {
|
|||||||
|
|
||||||
// Add local state for hunt active status
|
// Add local state for hunt active status
|
||||||
const [huntActive, setHuntActive] = useState(null);
|
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
|
// Use useCallback for functions called in useEffect to avoid dependency issues
|
||||||
const softRedirect = useCallback(
|
const softRedirect = useCallback(
|
||||||
@@ -293,6 +295,18 @@ export default function MapPage() {
|
|||||||
}
|
}
|
||||||
}, [status, fetchTeamData]);
|
}, [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) {
|
if (status === "loading" || loading) {
|
||||||
return (
|
return (
|
||||||
<div className={styles["loading-container"]}>
|
<div className={styles["loading-container"]}>
|
||||||
@@ -302,8 +316,11 @@ export default function MapPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If hunt status is known to be inactive, show message
|
// Remove or modify this conditional block since we're now handling hunt ending differently
|
||||||
if (huntActive === false) {
|
// 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 (
|
return (
|
||||||
<div className={styles["map-error"]}>
|
<div className={styles["map-error"]}>
|
||||||
<h2>Hunt Not Active</h2>
|
<h2>Hunt Not Active</h2>
|
||||||
@@ -371,7 +388,7 @@ export default function MapPage() {
|
|||||||
{/* Map Banner with team info and timer */}
|
{/* Map Banner with team info and timer */}
|
||||||
<div className={styles["map-banner"]}>
|
<div className={styles["map-banner"]}>
|
||||||
<div className={styles["team-name"]}>
|
<div className={styles["team-name"]}>
|
||||||
{teamData?.team?.name || "Loading..."}
|
Team Name: {teamData?.team?.name || "Loading..."}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["elapsed-time"]}>
|
<div className={styles["elapsed-time"]}>
|
||||||
<span className={styles["time-value"]}>{elapsedTime}</span>
|
<span className={styles["time-value"]}>{elapsedTime}</span>
|
||||||
|
|||||||
+2
-13
@@ -1,7 +1,7 @@
|
|||||||
import { query, queryOne, run } from "@/lib/db";
|
import { query, queryOne, run } from "@/lib/db";
|
||||||
import { getToken } from "next-auth/jwt";
|
import { getToken } from "next-auth/jwt";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic"; // Disable caching
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export async function GET(request) {
|
export async function GET(request) {
|
||||||
try {
|
try {
|
||||||
@@ -17,7 +17,6 @@ export async function GET(request) {
|
|||||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has a team
|
|
||||||
if (!token.teamId) {
|
if (!token.teamId) {
|
||||||
console.log("API: User without team tried to access clues");
|
console.log("API: User without team tried to access clues");
|
||||||
return Response.json(
|
return Response.json(
|
||||||
@@ -26,9 +25,7 @@ export async function GET(request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, ensure the team_clues table has the 'skipped' column
|
|
||||||
try {
|
try {
|
||||||
// Check if team_clues table exists, create it if it doesn't
|
|
||||||
await run(`
|
await run(`
|
||||||
CREATE TABLE IF NOT EXISTS team_clues (
|
CREATE TABLE IF NOT EXISTS team_clues (
|
||||||
team_id INTEGER NOT NULL,
|
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 tableInfo = await query("PRAGMA table_info(team_clues)");
|
||||||
const hasSkippedColumn = tableInfo.some((col) => col.name === "skipped");
|
const hasSkippedColumn = tableInfo.some((col) => col.name === "skipped");
|
||||||
|
|
||||||
@@ -52,18 +48,14 @@ export async function GET(request) {
|
|||||||
}
|
}
|
||||||
} catch (schemaError) {
|
} catch (schemaError) {
|
||||||
console.error("Error ensuring database schema:", 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(`
|
const clues = await query(`
|
||||||
SELECT id, title, description, location
|
SELECT id, title, description, location
|
||||||
FROM clues
|
FROM clues
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// For each clue, check if the team has found it
|
|
||||||
const cluesWithStatus = await Promise.all(
|
const cluesWithStatus = await Promise.all(
|
||||||
clues.map(async (clue) => {
|
clues.map(async (clue) => {
|
||||||
let clueStatus;
|
let clueStatus;
|
||||||
@@ -74,7 +66,6 @@ export async function GET(request) {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error querying status for clue ${clue.id}:`, 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(
|
clueStatus = await queryOne(
|
||||||
"SELECT found_at FROM team_clues WHERE team_id = ? AND clue_id = ?",
|
"SELECT found_at FROM team_clues WHERE team_id = ? AND clue_id = ?",
|
||||||
[token.teamId, clue.id]
|
[token.teamId, clue.id]
|
||||||
@@ -85,19 +76,17 @@ export async function GET(request) {
|
|||||||
...clue,
|
...clue,
|
||||||
found: !!clueStatus && !(clueStatus.skipped === 1),
|
found: !!clueStatus && !(clueStatus.skipped === 1),
|
||||||
skipped: !!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;
|
let firstUnavailableFound = false;
|
||||||
const finalClues = cluesWithStatus.map((clue, index, arr) => {
|
const finalClues = cluesWithStatus.map((clue, index, arr) => {
|
||||||
if (clue.found || clue.skipped) {
|
if (clue.found || clue.skipped) {
|
||||||
return { ...clue, available: true };
|
return { ...clue, available: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we haven't found an unavailable clue yet, this one is available
|
|
||||||
if (!firstUnavailableFound) {
|
if (!firstUnavailableFound) {
|
||||||
firstUnavailableFound = true;
|
firstUnavailableFound = true;
|
||||||
return { ...clue, available: true };
|
return { ...clue, available: true };
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { queryOne, run } from "@/lib/db";
|
|||||||
|
|
||||||
export async function POST(request) {
|
export async function POST(request) {
|
||||||
try {
|
try {
|
||||||
// Verify user authentication
|
|
||||||
const token = await getToken({
|
const token = await getToken({
|
||||||
req: request,
|
req: request,
|
||||||
secret:
|
secret:
|
||||||
@@ -15,20 +14,16 @@ export async function POST(request) {
|
|||||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user ID from token
|
|
||||||
const userId = token.id;
|
const userId = token.id;
|
||||||
|
|
||||||
// Get team name from request body
|
|
||||||
const { name } = await request.json();
|
const { name } = await request.json();
|
||||||
|
|
||||||
if (!name || !name.trim()) {
|
if (!name || !name.trim()) {
|
||||||
return Response.json({ error: "Team name is required" }, { status: 400 });
|
return Response.json({ error: "Team name is required" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a unique team code
|
|
||||||
const code = generateTeamCode();
|
const code = generateTeamCode();
|
||||||
|
|
||||||
// Create teams table if it doesn't exist
|
|
||||||
await run(`
|
await run(`
|
||||||
CREATE TABLE IF NOT EXISTS teams (
|
CREATE TABLE IF NOT EXISTS teams (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
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 = ?", [
|
const existingTeam = await queryOne("SELECT id FROM teams WHERE name = ?", [
|
||||||
name,
|
name,
|
||||||
]);
|
]);
|
||||||
@@ -50,7 +44,6 @@ export async function POST(request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert team
|
|
||||||
const result = await run("INSERT INTO teams (name, code) VALUES (?, ?)", [
|
const result = await run("INSERT INTO teams (name, code) VALUES (?, ?)", [
|
||||||
name,
|
name,
|
||||||
code,
|
code,
|
||||||
@@ -61,10 +54,8 @@ export async function POST(request) {
|
|||||||
throw new Error("Failed to create team - no ID returned");
|
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]);
|
await run("UPDATE users SET team_id = ? WHERE id = ?", [teamId, userId]);
|
||||||
|
|
||||||
// Return success with team info
|
|
||||||
return Response.json({
|
return Response.json({
|
||||||
success: true,
|
success: true,
|
||||||
teamId,
|
teamId,
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ export default function CreateTeamForm({ onSubmit }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!name.trim()) {
|
if (!name.trim()) {
|
||||||
return; // Don't submit if name is empty
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|||||||
+32
-32
@@ -1,38 +1,38 @@
|
|||||||
'use client';
|
"use client";
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from "next-auth/react";
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
export default function TeamHeader() {
|
export default function TeamHeader() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const [teamName, setTeamName] = useState(null);
|
const [teamName, setTeamName] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only fetch team info if user is logged in and not an admin
|
// Only fetch team info if user is logged in and not an admin
|
||||||
if (session?.user?.teamId && session?.user?.role !== 'admin') {
|
if (session?.user?.teamId && session?.user?.role !== "admin") {
|
||||||
fetchTeamInfo();
|
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;
|
|
||||||
}
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
return (
|
const fetchTeamInfo = async () => {
|
||||||
<div className="team-header">
|
try {
|
||||||
<div className="team-name">{teamName}</div>
|
const response = await fetch(`/api/teams/${session.user.teamId}`);
|
||||||
</div>
|
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 (
|
||||||
|
<div className="team-header">
|
||||||
|
<div className="team-name">Team Name: {teamName}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,16 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
// Get JWT secret from environment or use fallback
|
|
||||||
const JWT_SECRET =
|
const JWT_SECRET =
|
||||||
process.env.NEXTAUTH_SECRET ||
|
process.env.NEXTAUTH_SECRET ||
|
||||||
process.env.JWT_SECRET ||
|
process.env.JWT_SECRET ||
|
||||||
"your-fallback-secret-should-be-at-least-32-chars";
|
"your-fallback-secret-should-be-at-least-32-chars";
|
||||||
|
|
||||||
// Create a new JWT token
|
|
||||||
export function createToken(payload) {
|
export function createToken(payload) {
|
||||||
// Check if payload already has an expiration date set
|
|
||||||
const options = payload.exp ? {} : { expiresIn: "30d" };
|
const options = payload.exp ? {} : { expiresIn: "30d" };
|
||||||
|
|
||||||
return jwt.sign(payload, JWT_SECRET, options);
|
return jwt.sign(payload, JWT_SECRET, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify a JWT token
|
|
||||||
export function verifyToken(token) {
|
export function verifyToken(token) {
|
||||||
try {
|
try {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -26,13 +22,11 @@ export function verifyToken(token) {
|
|||||||
|
|
||||||
const decoded = jwt.verify(token, JWT_SECRET);
|
const decoded = jwt.verify(token, JWT_SECRET);
|
||||||
|
|
||||||
// Validate that we have the minimum required fields
|
|
||||||
if (!decoded.sub && !decoded.id) {
|
if (!decoded.sub && !decoded.id) {
|
||||||
console.error("Token missing required user identifier");
|
console.error("Token missing required user identifier");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log successful token verification with user ID
|
|
||||||
console.log(
|
console.log(
|
||||||
"Token verified successfully for user:",
|
"Token verified successfully for user:",
|
||||||
decoded.sub || decoded.id
|
decoded.sub || decoded.id
|
||||||
@@ -40,7 +34,6 @@ export function verifyToken(token) {
|
|||||||
|
|
||||||
return decoded;
|
return decoded;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Provide detailed error logs
|
|
||||||
if (error.name === "TokenExpiredError") {
|
if (error.name === "TokenExpiredError") {
|
||||||
console.error("Token expired at:", error.expiredAt);
|
console.error("Token expired at:", error.expiredAt);
|
||||||
} else if (error.name === "JsonWebTokenError") {
|
} else if (error.name === "JsonWebTokenError") {
|
||||||
|
|||||||
+25
-2
@@ -154,18 +154,41 @@ export const syncTimerWithServer = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
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) {
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
startTime: data.startTime,
|
startTime: data.startTime,
|
||||||
endTime: data.endTime,
|
endTime: data.endTime,
|
||||||
active: data.active,
|
active: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
console.log("Timer sync: Clearing timer as server reports inactive");
|
||||||
clearHuntTimer();
|
clearHuntTimer();
|
||||||
return { success: true, data: null };
|
return { success: true, data: null };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user