mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-07 19:50:20 +00:00
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:
1 parent
6b233c90e7
commit
6ad943a868
7 files changed
+965
-39
No files matched your search
@@ -0,0 +1,358 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function MapPage() {
|
||||
const [clues, setClues] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [huntActive, setHuntActive] = useState(false);
|
||||
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();
|
||||
setHuntActive(data.active || false);
|
||||
|
||||
// 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 fetchClues() {
|
||||
try {
|
||||
// First verify the hunt is active
|
||||
const huntIsActive = await checkHuntStatus();
|
||||
if (!huntIsActive) return;
|
||||
|
||||
// Fetch the clues from the API with proper error handling
|
||||
const response = await fetch("/api/clues", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
// Handle non-OK responses without throwing
|
||||
if (!response.ok) {
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (contentType && contentType.includes("application/json")) {
|
||||
// If it's JSON, parse it for the error message
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || "Failed to fetch clues");
|
||||
} else {
|
||||
// If it's not JSON, use the status text
|
||||
throw new Error(
|
||||
`Failed to fetch clues: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
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);
|
||||
}
|
||||
|
||||
setClues(data.clues || []);
|
||||
} catch (err) {
|
||||
console.error("Error fetching clues:", err);
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchClues();
|
||||
}, [router]);
|
||||
|
||||
// Generate random x positions and sort by descending y positions
|
||||
const generatePositions = (clues) => {
|
||||
const positions = [];
|
||||
const maxWidth = 800;
|
||||
const maxHeight = 500;
|
||||
const padding = 50;
|
||||
|
||||
for (let i = 0; i < clues.length; i++) {
|
||||
positions.push({
|
||||
id: clues[i].id,
|
||||
title: clues[i].title || `Clue ${i + 1}`,
|
||||
x: Math.random() * (maxWidth - 2 * padding) + padding,
|
||||
y: (i + 1) * (maxHeight / (clues.length + 1)),
|
||||
});
|
||||
}
|
||||
|
||||
return positions;
|
||||
};
|
||||
|
||||
const cluePositions = clues.length > 0 ? generatePositions(clues) : [];
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="map-container">
|
||||
<div className="loading">Loading clues...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="map-container">
|
||||
<div className="error">Error: {error}</div>
|
||||
<button onClick={() => router.push("/")} className="back-button">
|
||||
Back to Home
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (clues.length === 0) {
|
||||
return (
|
||||
<div className="map-container">
|
||||
<div className="no-clues">No clues available yet.</div>
|
||||
<button onClick={() => router.push("/")} className="back-button">
|
||||
Back to Home
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="map-container">
|
||||
<h1>Treasure Map</h1>
|
||||
<div className="map-wrapper">
|
||||
<svg
|
||||
className="map-svg"
|
||||
viewBox="0 0 800 500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{/* Map background with a treasure map style */}
|
||||
<rect width="800" height="500" fill="#f5e6ca" />
|
||||
<path
|
||||
d="M0,0 L800,0 L800,500 L0,500 Z"
|
||||
fill="none"
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="10"
|
||||
/>
|
||||
|
||||
{/* Decorative map elements */}
|
||||
<path
|
||||
d="M100,100 Q400,50 700,150"
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeDasharray="5,5"
|
||||
/>
|
||||
<path
|
||||
d="M150,400 Q400,450 650,350"
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeDasharray="5,5"
|
||||
/>
|
||||
<path
|
||||
d="M300,100 Q350,250 300,400"
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeDasharray="5,5"
|
||||
/>
|
||||
<path
|
||||
d="M500,100 Q450,250 500,400"
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
strokeDasharray="5,5"
|
||||
/>
|
||||
|
||||
{/* Connection lines between clue points */}
|
||||
{cluePositions.slice(0, -1).map((clue, index) => (
|
||||
<line
|
||||
key={`line-${index}`}
|
||||
x1={clue.x}
|
||||
y1={clue.y}
|
||||
x2={cluePositions[index + 1].x}
|
||||
y2={cluePositions[index + 1].y}
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="2"
|
||||
strokeDasharray="5,5"
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Clue points */}
|
||||
{cluePositions.map((clue, index) => (
|
||||
<g key={`clue-${index}`} className="clue-point">
|
||||
<circle
|
||||
cx={clue.x}
|
||||
cy={clue.y}
|
||||
r="15"
|
||||
fill="#d4a76a"
|
||||
stroke="#9c6b30"
|
||||
strokeWidth="2"
|
||||
onClick={() => router.push(`/clue/${clue.id}`)}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
<text
|
||||
x={clue.x}
|
||||
y={clue.y}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="central"
|
||||
fill="#5d4037"
|
||||
fontSize="12"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{index + 1}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Clue list */}
|
||||
<div className="clue-list">
|
||||
<h2>Clues</h2>
|
||||
<ul>
|
||||
{cluePositions.map((clue, index) => (
|
||||
<li key={`clue-list-${index}`}>
|
||||
<Link href={`/clue/${clue.id}`}>
|
||||
{index + 1}. {clue.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button onClick={() => router.push("/")} className="back-button">
|
||||
Back to Home
|
||||
</button>
|
||||
|
||||
<style jsx>{`
|
||||
.map-container {
|
||||
padding: 20px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #5d4037;
|
||||
margin-bottom: 20px;
|
||||
font-family: "Pirates", cursive, serif;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.map-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
border: 5px solid #9c6b30;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
background: #f5e6ca;
|
||||
}
|
||||
|
||||
.map-svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.clue-list {
|
||||
margin-top: 30px;
|
||||
background: #f5e6ca;
|
||||
border: 2px solid #9c6b30;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clue-list h2 {
|
||||
color: #5d4037;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 10px 0;
|
||||
padding: 8px;
|
||||
border-bottom: 1px dashed #9c6b30;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #5d4037;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: rgba(156, 107, 48, 0.2);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.back-button {
|
||||
margin-top: 20px;
|
||||
padding: 10px 20px;
|
||||
background-color: #9c6b30;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #7d562a;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error,
|
||||
.no-clues {
|
||||
padding: 50px;
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user