This commit is contained in:
ryan
2026-01-18 16:22:43 -05:00
parent 96716d95b6
commit e431ba45e9
9 changed files with 235 additions and 5 deletions

View File

@@ -111,6 +111,17 @@
border: 1px solid #ccc;
border-radius: 8px;
background: white;
transition: all 0.3s ease;
}
.team-card-active-turn {
border: 3px solid #673AB7;
background: #EDE7F6;
box-shadow: 0 0 10px rgba(103, 58, 183, 0.3);
}
.team-card-active-turn .team-name {
color: #673AB7;
}
.team-card-header {

View File

@@ -21,6 +21,8 @@ export default function GameAdminView() {
const [timerExpired, setTimerExpired] = useState(false);
const [timerPaused, setTimerPaused] = useState(false);
const [newTeamName, setNewTeamName] = useState("");
const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null);
const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null);
useEffect(() => {
loadGameState();
@@ -37,6 +39,8 @@ export default function GameAdminView() {
setCurrentQuestion(response.data.current_question);
setQuestionIndex(response.data.current_question_index);
setTotalQuestions(response.data.total_questions);
setCurrentTurnTeamId(response.data.current_turn_team_id);
setCurrentTurnTeamName(response.data.current_turn_team_name);
} catch (error) {
console.error("Error loading game state:", error);
}
@@ -77,12 +81,19 @@ export default function GameAdminView() {
setTimerPaused(false);
});
socket.on("turn_changed", (data) => {
console.log("Turn changed:", data);
setCurrentTurnTeamId(data.current_turn_team_id);
setCurrentTurnTeamName(data.current_turn_team_name);
});
return () => {
socket.off("question_with_answer");
socket.off("score_updated");
socket.off("timer_paused");
socket.off("lifeline_updated");
socket.off("timer_reset");
socket.off("turn_changed");
};
}, [socket]);
@@ -209,6 +220,15 @@ export default function GameAdminView() {
}
};
const handleAdvanceTurn = async () => {
try {
await adminAPI.advanceTurn(gameId);
} catch (error) {
console.error("Error advancing turn:", error);
alert("Error advancing turn");
}
};
const handleToggleAnswer = async () => {
const newShowAnswer = !showAnswer;
try {
@@ -417,7 +437,7 @@ export default function GameAdminView() {
) : (
<div className="teams-list">
{teams.map((team) => (
<div key={team.id} className="team-card">
<div key={team.id} className={`team-card ${team.id === currentTurnTeamId ? 'team-card-active-turn' : ''}`}>
<div className="team-card-header">
<div className="team-name-section">
<strong className="team-name">{team.name}</strong>
@@ -538,6 +558,23 @@ export default function GameAdminView() {
{/* Button grid - 2 columns on desktop, 1 on mobile */}
<div className="controls-button-grid">
{gameState?.is_active && teams.length > 0 && (
<button
onClick={handleAdvanceTurn}
className="btn-next-turn"
style={{
padding: "0.75rem 1.5rem",
background: "#673AB7",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
fontSize: "1rem",
}}
>
Next Turn {currentTurnTeamName ? `(${currentTurnTeamName})` : ""}
</button>
)}
{currentQuestion && (
<button
onClick={handleToggleAnswer}

View File

@@ -18,6 +18,8 @@ export default function ContestantView() {
const [timerActive, setTimerActive] = useState(false);
const [timerPaused, setTimerPaused] = useState(false);
const [categories, setCategories] = useState([]);
const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null);
const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null);
useEffect(() => {
// Load initial game state
@@ -140,6 +142,8 @@ export default function ContestantView() {
setTimerSeconds(30);
setTimerActive(false);
setTimerPaused(false);
setCurrentTurnTeamId(null);
setCurrentTurnTeamName(null);
});
socket.on("lifeline_updated", (data) => {
@@ -154,6 +158,12 @@ export default function ContestantView() {
setTimerPaused(false);
});
socket.on("turn_changed", (data) => {
console.log("Turn changed:", data);
setCurrentTurnTeamId(data.current_turn_team_id);
setCurrentTurnTeamName(data.current_turn_team_name);
});
return () => {
socket.off("game_started");
socket.off("question_changed");
@@ -163,6 +173,7 @@ export default function ContestantView() {
socket.off("game_ended");
socket.off("lifeline_updated");
socket.off("timer_reset");
socket.off("turn_changed");
};
}, [socket]);
@@ -283,6 +294,25 @@ export default function ContestantView() {
>
{currentQuestion ? (
<div style={{ width: "100%", textAlign: "center" }}>
{/* Turn indicator */}
{currentTurnTeamName && (
<div
className="turn-indicator"
style={{
fontSize: "2rem",
fontWeight: "bold",
marginBottom: "1.5rem",
padding: "1rem 2rem",
background: "#673AB7",
color: "white",
borderRadius: "8px",
display: "inline-block",
animation: "pulse 2s infinite",
}}
>
{currentTurnTeamName}'s Turn
</div>
)}
{/* Timer progress bar */}
<div
style={{
@@ -476,6 +506,11 @@ export default function ContestantView() {
justifyContent: "space-between",
alignItems: "baseline",
fontSize: "1.8rem",
padding: "0.5rem 1rem",
borderRadius: "8px",
background: (team.team_id || team.id) === currentTurnTeamId ? "#673AB7" : "transparent",
color: (team.team_id || team.id) === currentTurnTeamId ? "white" : "inherit",
transition: "all 0.3s ease",
}}
>
<div

View File

@@ -54,3 +54,20 @@ button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
/* Turn indicator pulse animation */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(103, 58, 183, 0.4);
}
70% {
box-shadow: 0 0 0 15px rgba(103, 58, 183, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(103, 58, 183, 0);
}
}
.turn-indicator {
animation: pulse 2s infinite;
}

View File

@@ -106,6 +106,7 @@ export const adminAPI = {
api.post(`/admin/game/${gameId}/team/${teamId}/use-lifeline`),
addLifeline: (gameId, teamId) =>
api.post(`/admin/game/${gameId}/team/${teamId}/add-lifeline`),
advanceTurn: (id) => api.post(`/admin/game/${id}/advance-turn`),
};
// Categories API