diff --git a/app/(main)/admin/clues/page.js b/app/(main)/admin/clues/page.js
index 24137e4..fd4384c 100644
--- a/app/(main)/admin/clues/page.js
+++ b/app/(main)/admin/clues/page.js
@@ -1,5 +1,5 @@
"use client";
-import { useState, useEffect } from "react";
+import { useState, useEffect, useRef } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
@@ -9,12 +9,24 @@ export default function AdminCluesPage() {
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 - removed coordinates
+ // 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
@@ -30,15 +42,63 @@ export default function AdminCluesPage() {
}
}, [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);
- const response = await fetch("/api/admin/clues", {
+ // 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",
});
@@ -57,8 +117,13 @@ export default function AdminCluesPage() {
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);
@@ -81,6 +146,8 @@ export default function AdminCluesPage() {
e.preventDefault();
try {
setLoading(true);
+
+ // Now include the QR code in the request
const response = await fetch("/api/admin/clues", {
method: "POST",
headers: {
@@ -90,18 +157,23 @@ export default function AdminCluesPage() {
credentials: "include",
});
+ const data = await response.json();
+
if (!response.ok) {
- throw new Error("Failed to add clue");
+ throw new Error(
+ data.error || `Failed to add clue (Status: ${response.status})`
+ );
}
- // Refresh the clue list
- fetchClues();
+ // Refresh the clue list with force refresh
+ await fetchClues();
- // Reset the form - removed coordinates
+ // Reset the form - now with qrCode
setNewClue({
title: "",
description: "",
location: "",
+ qr_code: "",
});
setError("");
@@ -113,12 +185,102 @@ export default function AdminCluesPage() {
}
};
- // Handle deleting a clue
- const handleDeleteClue = async (clueId) => {
- if (!confirm("Are you sure you want to delete this clue?")) return;
+ // 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: {
@@ -127,16 +289,23 @@ export default function AdminCluesPage() {
credentials: "include",
});
+ // Parse the response data for detailed error messages
+ const data = await response.json().catch(() => ({}));
+
if (!response.ok) {
- throw new Error("Failed to delete clue");
+ throw new Error(
+ data.error || `Failed to delete clue (Status: ${response.status})`
+ );
}
- // Refresh the clue list
+ // 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(err.message);
+ setError(`Error deleting clue: ${err.message}`);
} finally {
setLoading(false);
}
@@ -154,62 +323,186 @@ export default function AdminCluesPage() {
Manage Clues
-
+
+
+
+
{error &&
{error}
}
+ {fixMessage &&
{fixMessage}
}
- {/* Add New Clue Form - removed coordinate fields */}
-
-
Add New Clue
-
+
+ ) : (
+
+
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)
+ )}
+