mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-08 03:50:21 +00:00
fixed skip clue implimentation
This commit is contained in:
1 parent
fccc2a4edc
commit
2f9a6c732c
18 files changed
+524
-151
No files matched your search
@@ -122,7 +122,7 @@ export default function AdminDashboard() {
|
||||
const timerData = getHuntTimer();
|
||||
if (timerData && timerData.startTime) {
|
||||
setTimerDisplay(formatElapsedTime(timerData.startTime));
|
||||
|
||||
|
||||
// If there's an end time, check if we've reached it
|
||||
if (timerEndTime && Date.now() >= timerEndTime) {
|
||||
stopTimer();
|
||||
|
||||
@@ -32,6 +32,7 @@ export default function CluePage({ params }) {
|
||||
const [scanMessage, setScanMessage] = useState(null);
|
||||
const [lastRefresh, setLastRefresh] = useState(0);
|
||||
const [clueFound, setClueFound] = useState(false);
|
||||
const [clueSkipped, setClueSkipped] = useState(false); // Add state for skipped status
|
||||
const [allClues, setAllClues] = useState([]);
|
||||
const [nextClueId, setNextClueId] = useState(null);
|
||||
const [nextClueAvailable, setNextClueAvailable] = useState(false);
|
||||
@@ -126,6 +127,7 @@ export default function CluePage({ params }) {
|
||||
if (isMounted) {
|
||||
setClue(data);
|
||||
setClueFound(data.found);
|
||||
setClueSkipped(data.skipped); // Set skipped status from API response
|
||||
setLoading(false);
|
||||
setLastRefresh(Date.now());
|
||||
}
|
||||
@@ -398,6 +400,56 @@ export default function CluePage({ params }) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Replace the previous clue function with skip clue functionality
|
||||
const handleSkipClue = async () => {
|
||||
// Show confirmation dialog
|
||||
if (
|
||||
!confirm(
|
||||
"Are you sure you want to skip this clue? You can come back to it later."
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Make an API call to mark this clue as skipped
|
||||
const response = await fetch("/api/mark-clue", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
clueId: parseInt(clueId),
|
||||
skipped: true,
|
||||
}),
|
||||
});
|
||||
|
||||
// Get the response data to handle errors better
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setScanMessage("Clue skipped. You can return to it later.");
|
||||
|
||||
// Make next clue available immediately
|
||||
setNextClueAvailable(true);
|
||||
|
||||
// Navigate to the next clue if available, otherwise go to map
|
||||
if (nextClueId) {
|
||||
router.push(`/clue/${nextClueId}`);
|
||||
} else {
|
||||
router.push("/map");
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to skip clue:",
|
||||
data.error || response.statusText
|
||||
);
|
||||
setScanMessage(`Error: ${data.error || "Could not skip clue"}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error skipping clue:", err);
|
||||
setScanMessage("Error: Could not connect to server");
|
||||
}
|
||||
};
|
||||
|
||||
// Navigate to the next clue
|
||||
const handleNextClue = () => {
|
||||
if (nextClueId) {
|
||||
@@ -423,20 +475,6 @@ export default function CluePage({ params }) {
|
||||
}
|
||||
};
|
||||
|
||||
// Navigate to the previous clue
|
||||
const handlePrevClue = () => {
|
||||
if (allClues.length > 0) {
|
||||
const currentIndex = allClues.findIndex((c) => c.id === parseInt(clueId));
|
||||
if (currentIndex > 0) {
|
||||
router.push(`/clue/${allClues[currentIndex - 1].id}`);
|
||||
} else {
|
||||
router.push("/map");
|
||||
}
|
||||
} else {
|
||||
router.push("/map");
|
||||
}
|
||||
};
|
||||
|
||||
// Function for manual refresh - for admins to test without waiting
|
||||
const handleManualRefresh = () => {
|
||||
setLastRefresh(0);
|
||||
@@ -541,7 +579,7 @@ export default function CluePage({ params }) {
|
||||
)}
|
||||
|
||||
<div className="clue-content-wrapper">
|
||||
{/* Show a notification when the clue is already found */}
|
||||
{/* Show notification based on found OR skipped status */}
|
||||
{clueFound && (
|
||||
<div className="clue-found-notification">
|
||||
<div className="clue-found-icon">✓</div>
|
||||
@@ -550,6 +588,15 @@ export default function CluePage({ params }) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{clueSkipped && !clueFound && (
|
||||
<div className="clue-skipped-notification">
|
||||
<div className="clue-skipped-icon">↷</div>
|
||||
<div className="clue-skipped-text">
|
||||
You skipped this clue earlier. Try to solve it now!
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Admin tools section - only visible to admins */}
|
||||
{isAdmin && (
|
||||
@@ -598,8 +645,8 @@ export default function CluePage({ params }) {
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<div className="navigation-buttons">
|
||||
<button onClick={handlePrevClue} className="nav-button prev-button">
|
||||
Previous Clue
|
||||
<button onClick={handleSkipClue} className="nav-button prev-button">
|
||||
Skip Clue
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push("/map")}
|
||||
@@ -741,6 +788,28 @@ export default function CluePage({ params }) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.clue-skipped-notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #e3f2fd;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 15px;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.clue-skipped-icon {
|
||||
font-size: 1.5rem;
|
||||
color: #1976d2;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.clue-skipped-text {
|
||||
color: #1976d2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@keyframes slide-down {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
@@ -1055,13 +1124,13 @@ export default function CluePage({ params }) {
|
||||
background-color: rgba(104, 84, 60, 0.1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
.timer-label {
|
||||
font-weight: bold;
|
||||
margin-right: 8px;
|
||||
color: #68543c;
|
||||
}
|
||||
|
||||
|
||||
.timer-value {
|
||||
font-family: monospace;
|
||||
font-size: 1.1em;
|
||||
|
||||
@@ -271,6 +271,13 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Add this new class for skipped clues after your existing clue status styles */
|
||||
.clue-skipped {
|
||||
background-color: #1976d2; /* Dark blue for skipped clues */
|
||||
border-color: #0d47a1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Pulse animation for available clues */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
|
||||
+23
-14
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
@@ -94,23 +95,30 @@ export default function MapPage() {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch clues");
|
||||
// Extract the error message from the response if possible
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
console.error("API response details:", {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
errorData,
|
||||
});
|
||||
|
||||
throw new Error(
|
||||
errorData.error ||
|
||||
`Failed to fetch clues: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Process the clues to determine which are available
|
||||
// A clue is available if it's either found already or the next in sequence
|
||||
const processedClues = data.map((clue, index, arr) => {
|
||||
// Find the first unfound clue's index
|
||||
const firstUnfoundIndex = arr.findIndex((c) => !c.found);
|
||||
|
||||
// A clue is available if:
|
||||
// 1. It's already found, OR
|
||||
// 2. It's the next unfound clue in the sequence
|
||||
const available = clue.found || index === firstUnfoundIndex;
|
||||
|
||||
return { ...clue, available };
|
||||
const processedClues = data.map((clue) => {
|
||||
// The API now returns clues with availability already determined
|
||||
return {
|
||||
...clue,
|
||||
available: clue.available || clue.found || clue.skipped,
|
||||
};
|
||||
});
|
||||
|
||||
setClues(processedClues);
|
||||
@@ -118,7 +126,8 @@ export default function MapPage() {
|
||||
return processedClues;
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch clues:", err);
|
||||
setError(err.message);
|
||||
setError(err.message); // Make sure we set the error state
|
||||
setLoading(false);
|
||||
return [];
|
||||
}
|
||||
}, []);
|
||||
@@ -329,8 +338,8 @@ export default function MapPage() {
|
||||
// Function to determine clue button class based on status
|
||||
const getClueButtonClass = (clue) => {
|
||||
if (clue.found) return `${styles["clue-button"]} ${styles["clue-found"]}`;
|
||||
if (clue.available)
|
||||
return `${styles["clue-button"]} ${styles["clue-available"]}`;
|
||||
if (clue.skipped) return `${styles["clue-button"]} ${styles["clue-skipped"]}`;
|
||||
if (clue.available) return `${styles["clue-button"]} ${styles["clue-available"]}`;
|
||||
return `${styles["clue-button"]} ${styles["clue-locked"]}`;
|
||||
};
|
||||
|
||||
|
||||
+59
-12
@@ -79,6 +79,54 @@ export default function StatsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const renderCluesList = () => {
|
||||
if (!teamStats) return null;
|
||||
|
||||
const { foundClues, skippedClues } = teamStats;
|
||||
|
||||
return (
|
||||
<div className="clues-section">
|
||||
<h3 className="section-title">Found Clues ({foundClues?.length || 0})</h3>
|
||||
<ul className="clue-list">
|
||||
{foundClues?.length > 0 ? (
|
||||
foundClues.map(clue => (
|
||||
<li key={clue.id} className="clue-item">
|
||||
<div className="clue-icon found">✓</div>
|
||||
<div className="clue-details">
|
||||
<div className="clue-title">{clue.title || `Clue #${clue.id}`}</div>
|
||||
<div className="clue-time">
|
||||
{new Date(clue.found_at).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li className="empty-state">No clues found yet</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<h3 className="section-title">Skipped Clues ({skippedClues?.length || 0})</h3>
|
||||
<ul className="clue-list">
|
||||
{skippedClues?.length > 0 ? (
|
||||
skippedClues.map(clue => (
|
||||
<li key={clue.id} className="clue-item">
|
||||
<div className="clue-icon skipped">↷</div>
|
||||
<div className="clue-details">
|
||||
<div className="clue-title">{clue.title || `Clue #${clue.id}`}</div>
|
||||
<div className="clue-time">
|
||||
Skipped at {new Date(clue.found_at).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li className="empty-state">No clues skipped</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@@ -167,18 +215,7 @@ export default function StatsPage() {
|
||||
|
||||
{expanded && (
|
||||
<div className={styles.cluesList}>
|
||||
{teamStats.foundClues.length === 0 ? (
|
||||
<p className={styles.emptyMessage}>No clues found yet.</p>
|
||||
) : (
|
||||
teamStats.foundClues.map((clue) => (
|
||||
<div key={clue.id} className={styles.clueItem}>
|
||||
<span className={styles.clueTitle}>{clue.title}</span>
|
||||
<span className={styles.clueTimestamp}>
|
||||
{formatTimestamp(clue.found_at)}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
{renderCluesList()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -205,6 +242,16 @@ export default function StatsPage() {
|
||||
<div className={styles.navLabel}>Leaderboard</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.clue-icon.found {
|
||||
background-color: #2e7d32;
|
||||
}
|
||||
|
||||
.clue-icon.skipped {
|
||||
background-color: #1976d2;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,12 +16,14 @@
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 5px 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.teamName {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.statsCard {
|
||||
|
||||
Reference in new issue
Block a user