"use client"; import { useState, useEffect, useRef } from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; export default function AdminCluesPage() { const { data: session, status } = useSession(); const [clues, setClues] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const router = useRouter(); const [isEditMode, setIsEditMode] = useState(false); const [editingClue, setEditingClue] = useState(null); const [fixMessage, setFixMessage] = useState(""); // Add a lastRefresh timestamp to track when data was last fetched const [lastRefresh, setLastRefresh] = useState(Date.now()); // Add next refresh countdown state const [nextRefresh, setNextRefresh] = useState(10); // Add auto-refresh enabled/disabled state const [autoRefreshEnabled, setAutoRefreshEnabled] = useState(true); // Ref to store interval ID for cleanup const refreshIntervalRef = useRef(null); // Form state for adding new clues - add qrCode field back const [newClue, setNewClue] = useState({ title: "", description: "", location: "", qr_code: "", // Add QR code field }); // Check if user is admin useEffect(() => { if (status === "authenticated") { if (session?.user?.role !== "admin") { router.push("/team-selection"); } else { fetchClues(); } } else if (status === "unauthenticated") { router.push("/login"); } }, [session, status, router]); // Controlled auto-refresh effect useEffect(() => { // Only set up auto-refresh if enabled and not in edit mode if (autoRefreshEnabled && !isEditMode && status === "authenticated") { // Clear any existing interval if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current); } // Set initial countdown value setNextRefresh(10); // Set up countdown interval refreshIntervalRef.current = setInterval(() => { setNextRefresh((prev) => { // When countdown reaches 0, refresh the data and reset countdown if (prev <= 1) { fetchClues(); return 10; } return prev - 1; }); }, 1000); } // Clean up interval on unmount or when dependencies change return () => { if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current); } }; }, [autoRefreshEnabled, isEditMode, status]); // Disable auto-refresh when entering edit mode useEffect(() => { if (isEditMode) { setAutoRefreshEnabled(false); } }, [isEditMode]); // Fetch all clues const fetchClues = async () => { // Skip fetch if in edit mode to prevent data loss if (isEditMode) { return; } try { setLoading(true); // Add timestamp parameter to bust cache const timestamp = Date.now(); const response = await fetch(`/api/admin/clues?t=${timestamp}`, { 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(() => ({})); throw new Error( errorData.error || `Failed to fetch clues (${response.status})` ); } const data = await response.json(); // Validate the response data if (!Array.isArray(data)) { throw new Error("Invalid response format from server"); } console.log("Fetched clues:", data); // Debug log to see what's coming from API setClues(data); setError(""); // Update lastRefresh timestamp setLastRefresh(Date.now()); // Reset countdown after manual refresh setNextRefresh(10); } catch (err) { console.error("Error fetching clues:", err); setError(err.message); } finally { setLoading(false); } }; // Handle input change for new clue form const handleInputChange = (e) => { const { name, value } = e.target; setNewClue((prev) => ({ ...prev, [name]: value, })); }; // Handle form submission to add new clue const handleAddClue = async (e) => { e.preventDefault(); try { setLoading(true); // Now include the QR code in the request const response = await fetch("/api/admin/clues", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(newClue), credentials: "include", }); const data = await response.json(); if (!response.ok) { throw new Error( data.error || `Failed to add clue (Status: ${response.status})` ); } // Refresh the clue list with force refresh await fetchClues(); // Reset the form - now with qrCode setNewClue({ title: "", description: "", location: "", qr_code: "", }); setError(""); } catch (err) { console.error("Error adding clue:", err); setError(err.message); } finally { setLoading(false); } }; // Start editing a clue const handleEditStart = (clue) => { // Disable auto-refresh when starting to edit setAutoRefreshEnabled(false); setEditingClue({ id: clue.id, title: clue.title, description: clue.description, location: clue.location || "", // Use the actual qr_code from the clue object if available qr_code: clue.qr_code || `QR${clue.id}`, }); setIsEditMode(true); }; // Handle input change for editing clue const handleEditChange = (e) => { const { name, value } = e.target; setEditingClue((prev) => ({ ...prev, [name]: value, })); }; // Handle form submission to update clue const handleUpdateClue = async (e) => { e.preventDefault(); try { setLoading(true); const response = await fetch(`/api/admin/clues/${editingClue.id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title: editingClue.title, description: editingClue.description, location: editingClue.location, qr_code: editingClue.qr_code, // Add QR code to the update }), credentials: "include", }); const data = await response.json(); if (!response.ok) { throw new Error( data.error || `Failed to update clue (Status: ${response.status})` ); } // Refresh the clue list with force refresh await fetchClues(); setIsEditMode(false); setEditingClue(null); setError(""); } catch (err) { console.error("Error updating clue:", err); setError(err.message); } finally { setLoading(false); } }; // Cancel editing const handleCancelEdit = () => { setIsEditMode(false); setEditingClue(null); // Re-enable auto-refresh when canceling edit setAutoRefreshEnabled(true); }; // Force refresh the clue list const handleForceRefresh = () => { fetchClues(); }; // Toggle auto-refresh const toggleAutoRefresh = () => { setAutoRefreshEnabled((prev) => !prev); }; // Handle deleting a clue with automatic QR code renumbering const handleDeleteClue = async (clueId, qrCode) => { if ( !confirm( "Are you sure you want to delete this clue?\n\nQR codes will be automatically renumbered to prevent gaps in the sequence." ) ) return; try { setLoading(true); // QR codes will always be renumbered - no need to pass this parameter anymore const response = await fetch(`/api/admin/clues/${clueId}`, { method: "DELETE", headers: { "Content-Type": "application/json", }, credentials: "include", }); // Parse the response data for detailed error messages const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error( data.error || `Failed to delete clue (Status: ${response.status})` ); } // Success message and refresh console.log("Clue deleted successfully:", data.message || "Success"); // Refresh the clue list to show updated QR codes fetchClues(); setError(""); } catch (err) { console.error("Error deleting clue:", err); setError(`Error deleting clue: ${err.message}`); } finally { setLoading(false); } }; if (status === "loading" || loading) { return
Loading...
; } if (status === "unauthenticated") { return null; // Will redirect via useEffect } return (
Manage Clues
{error &&

{error}

} {fixMessage &&

{fixMessage}

} {/* Add New Clue Form */} {!isEditMode ? (

Add New Clue

{/* Add QR code field with new label */}
QR codes should follow the format QR[number] (e.g., QR1, QR2). Leave blank to auto-generate.
) : (

Edit Clue

QR codes should follow the format QR[number] (e.g., QR1, QR2). The QR code value determines what users scan to find this clue.
)} {/* Existing Clues List */}

Existing Clues

QR codes are the values that users scan to find each clue. You can customize these values, but they should usually follow the format QR[number].

Last updated: {new Date(lastRefresh).toLocaleTimeString()} {autoRefreshEnabled && !isEditMode && ( (next refresh in {nextRefresh}s) )}
{clues.length === 0 ? (

No clues found.

) : (
{clues.map((clue) => (

{clue.title}

{clue.description}

{clue.location && (

Location: {clue.location}

)}

Position: Clue #{clue.id} in sequence

QR Code: {clue.qr_code || `QR${clue.id}`}

))}
)}
{/* Admin Navigation Bar */}
); }