mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-07 19:50:20 +00:00
fixed the map, made it so the map is scrollable and synced the timing between the admin page and map page. They now share the same elapsed time. need to fix map css for altering the svg.
Grey = cant click Red = current Green = complete blue = skipped? Need to add Clue page Need to fix Leaderboard.
This commit is contained in:
1 parent
e7ad53643c
commit
16319f1333
14 files changed
+791
-472
No files matched your search
@@ -0,0 +1,3 @@
|
||||
export default function ClueLayout({ children }) {
|
||||
return <div className="clue-layout">{children}</div>;
|
||||
}
|
||||
+149
-134
@@ -1,96 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useHuntStatusCheck } from "@/hooks/useHuntStatusCheck";
|
||||
import Link from "next/link";
|
||||
import { use } from "react"; // Add this import for React.use()
|
||||
import { useSession } from "next-auth/react";
|
||||
import { formatElapsedTime, getHuntTimer } from "@/utils/timerService";
|
||||
|
||||
export default function CluePage({ params }) {
|
||||
const { data: session, status } = useSession();
|
||||
// Unwrap the params Promise using React.use()
|
||||
const unwrappedParams = use(params);
|
||||
const { clueId } = unwrappedParams;
|
||||
|
||||
const [clue, setClue] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [foundNow, setFoundNow] = useState(false);
|
||||
const [elapsedTime, setElapsedTime] = useState("00:00:00");
|
||||
const router = useRouter();
|
||||
|
||||
// Add this line to use the hunt status hook
|
||||
const huntActive = useHuntStatusCheck();
|
||||
const { status } = useSession();
|
||||
const { clueId } = params;
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchClueDetails() {
|
||||
if (status === "unauthenticated") {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch clue data
|
||||
async function fetchClue() {
|
||||
try {
|
||||
// Try to check hunt status first with robust error handling
|
||||
try {
|
||||
const statusResponse = await fetch("/api/hunt-status", {
|
||||
credentials: "include",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
|
||||
// Read as text first
|
||||
const statusText = await statusResponse.text();
|
||||
|
||||
try {
|
||||
const statusData = JSON.parse(statusText);
|
||||
if (!statusData.active && statusData.authenticated) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error("Failed to parse hunt status response:", parseError);
|
||||
// Continue anyway to try fetching clue details
|
||||
}
|
||||
} catch (statusErr) {
|
||||
console.error("Error checking hunt status:", statusErr);
|
||||
// Continue anyway to try fetching clue details
|
||||
}
|
||||
|
||||
// Now fetch clue details with robust error handling
|
||||
setLoading(true);
|
||||
const response = await fetch(`/api/clues/${clueId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
},
|
||||
credentials: "include",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
|
||||
// Read as text first
|
||||
const responseText = await response.text();
|
||||
|
||||
// Try to parse as JSON
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
} catch (parseError) {
|
||||
console.error("Failed to parse clue details response:", parseError);
|
||||
console.log("Response received:", responseText.slice(0, 200));
|
||||
throw new Error("Invalid response from server. Please try again.");
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error("Clue not found");
|
||||
} else {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || "Failed to fetch clue");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for error in the response
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
setClue(data.clue);
|
||||
const data = await response.json();
|
||||
setClue(data);
|
||||
setFoundNow(data.foundNow);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
console.error("Error fetching clue details:", err);
|
||||
console.error("Error fetching clue:", err);
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (clueId) {
|
||||
fetchClueDetails();
|
||||
fetchClue();
|
||||
}, [clueId, router, status]);
|
||||
|
||||
// Update timer display
|
||||
useEffect(() => {
|
||||
// Set up timer display updates
|
||||
const timerData = getHuntTimer();
|
||||
if (!timerData || !timerData.isActive) {
|
||||
return;
|
||||
}
|
||||
}, [clueId, router]);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
setElapsedTime(formatElapsedTime(timerData.startTime));
|
||||
}, 1000);
|
||||
|
||||
// Initial set
|
||||
setElapsedTime(formatElapsedTime(timerData.startTime));
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="clue-container">
|
||||
<div className="loading">Loading clue details...</div>
|
||||
<div className="loading">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>Loading clue...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -130,6 +124,15 @@ export default function CluePage({ params }) {
|
||||
return (
|
||||
<div className="clue-container">
|
||||
<div className="clue-scroll">
|
||||
{foundNow && (
|
||||
<div className="new-discovery">
|
||||
<div className="discovery-icon">🎉</div>
|
||||
<div className="discovery-text">New Clue Discovered!</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="timer-display">{elapsedTime}</div>
|
||||
|
||||
<h1 className="clue-title">{clue.title}</h1>
|
||||
|
||||
<div className="clue-content">
|
||||
@@ -165,59 +168,13 @@ export default function CluePage({ params }) {
|
||||
}
|
||||
|
||||
.clue-scroll {
|
||||
background-color: #f5e6ca;
|
||||
border: 2px solid #9c6b30;
|
||||
background-color: #f8f3e3;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
max-width: 600px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
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;
|
||||
}
|
||||
@@ -253,51 +210,109 @@ export default function CluePage({ params }) {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.map-button {
|
||||
background-color: #9c6b30;
|
||||
background-color: #68543c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
background-color: #5d4037;
|
||||
background-color: #a98467;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.map-button:hover,
|
||||
.home-button:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.map-button:hover {
|
||||
background-color: #7d562a;
|
||||
}
|
||||
|
||||
.home-button:hover {
|
||||
background-color: #4a332e;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.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;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #9c6b30;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
border-color: #d32f2f;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.clue-title {
|
||||
color: #68543c;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #9c6b30;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.timer-display {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.new-discovery {
|
||||
background-color: #feefb3;
|
||||
border: 1px solid #9f6000;
|
||||
color: #9f6000;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.discovery-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.discovery-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user