added map page files and fixed code so that when an admin starts the timer the user can move on and isnt accidentally being redirected to the page to show that the timer isn't active even though it is

This commit is contained in:
bobbert committed 2025-03-14 15:39:05 +00:00
1 parent 6b233c90e7
commit 6ad943a868
7 files changed
+965 -39

No files matched your search

+298
View File
@@ -0,0 +1,298 @@
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
export default function CluePage({ params }) {
const { clueId } = params;
const [clue, setClue] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const router = useRouter();
useEffect(() => {
async function checkHuntStatus() {
try {
const response = await fetch("/api/hunt-status", {
credentials: "include",
});
if (response.ok) {
const data = await response.json();
// If hunt is not active, redirect to the home page
if (!data.active) {
router.push("/");
return false;
}
return true;
} else {
console.error("Failed to check hunt status");
return false;
}
} catch (err) {
console.error("Error checking hunt status:", err);
return false;
}
}
async function fetchClueDetails() {
try {
// First verify the hunt is active
const huntIsActive = await checkHuntStatus();
if (!huntIsActive) return;
// Fetch the specific clue from the API with improved error handling
const response = await fetch(`/api/clues/${clueId}`, {
credentials: "include",
});
// Better error handling for non-JSON responses
if (!response.ok) {
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const errorData = await response.json();
throw new Error(errorData.error || "Failed to fetch clue details");
} else {
throw new Error(
`Failed to fetch clue details: ${response.status} ${response.statusText}`
);
}
}
const data = await response.json();
// If we got back an error field, show it as an error
if (data.error) {
throw new Error(data.error);
}
setClue(data.clue);
} catch (err) {
console.error("Error fetching clue details:", err);
setError(err.message);
} finally {
setLoading(false);
}
}
if (clueId) {
fetchClueDetails();
}
}, [clueId, router]);
if (loading) {
return (
<div className="clue-container">
<div className="loading">Loading clue details...</div>
</div>
);
}
if (error) {
return (
<div className="clue-container">
<div className="error">Error: {error}</div>
<div className="nav-buttons">
<button onClick={() => router.push("/map")} className="map-button">
Back to Map
</button>
<button onClick={() => router.push("/")} className="home-button">
Back to Home
</button>
</div>
</div>
);
}
if (!clue) {
return (
<div className="clue-container">
<div className="not-found">Clue not found</div>
<div className="nav-buttons">
<button onClick={() => router.push("/map")} className="map-button">
Back to Map
</button>
<button onClick={() => router.push("/")} className="home-button">
Back to Home
</button>
</div>
</div>
);
}
return (
<div className="clue-container">
<div className="clue-scroll">
<h1 className="clue-title">{clue.title}</h1>
<div className="clue-content">
<h2>Description</h2>
<p className="clue-description">{clue.description}</p>
{clue.location && (
<div className="clue-location">
<h2>Location Hint</h2>
<p>{clue.location}</p>
</div>
)}
</div>
<div className="nav-buttons">
<button onClick={() => router.push("/map")} className="map-button">
Back to Map
</button>
<button onClick={() => router.push("/")} className="home-button">
Back to Home
</button>
</div>
</div>
<style jsx>{`
.clue-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f4f1ea;
padding: 20px;
}
.clue-scroll {
background-color: #f5e6ca;
border: 2px solid #9c6b30;
border-radius: 10px;
padding: 30px;
max-width: 800px;
width: 100%;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
position: relative;
}
.clue-scroll::before,
.clue-scroll::after {
content: "";
position: absolute;
height: 30px;
width: 100%;
left: 0;
}
.clue-scroll::before {
top: 0;
background: linear-gradient(
180deg,
rgba(156, 107, 48, 0.2) 0%,
rgba(156, 107, 48, 0) 100%
);
border-radius: 8px 8px 0 0;
}
.clue-scroll::after {
bottom: 0;
background: linear-gradient(
0deg,
rgba(156, 107, 48, 0.2) 0%,
rgba(156, 107, 48, 0) 100%
);
border-radius: 0 0 8px 8px;
}
.clue-title {
color: #5d4037;
font-size: 2rem;
text-align: center;
margin-bottom: 20px;
font-family: "Pirates", cursive, serif;
border-bottom: 2px dashed #9c6b30;
padding-bottom: 15px;
}
.clue-content {
padding: 20px;
background-color: #f9f3e3;
border-radius: 8px;
margin-bottom: 20px;
border: 1px dashed #9c6b30;
}
h2 {
color: #9c6b30;
font-size: 1.5rem;
margin-top: 10px;
margin-bottom: 15px;
}
.clue-description {
font-size: 1.2rem;
line-height: 1.6;
color: #5d4037;
margin-bottom: 20px;
}
.clue-location {
margin-top: 20px;
padding-top: 15px;
border-top: 1px dashed #9c6b30;
}
.nav-buttons {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.map-button,
.home-button {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.map-button {
background-color: #9c6b30;
color: white;
}
.home-button {
background-color: #5d4037;
color: white;
}
.map-button:hover,
.home-button:hover {
transform: translateY(-3px);
}
.map-button:hover {
background-color: #7d562a;
}
.home-button:hover {
background-color: #4a332e;
}
.loading,
.error,
.not-found {
padding: 50px;
text-align: center;
border: 2px dashed #9c6b30;
border-radius: 8px;
margin: 30px 0;
color: #5d4037;
background: #f5e6ca;
font-size: 1.2rem;
}
.error {
border-color: #d32f2f;
color: #d32f2f;
}
`}</style>
</div>
);
}