From 47db3890e245c4d57ebd15e1051bb22d0c922cd0 Mon Sep 17 00:00:00 2001 From: cottongin Date: Thu, 30 Oct 2025 17:52:44 -0400 Subject: [PATCH] pretty much ready to 'ship'.3 --- frontend/src/config/branding.js | 2 +- frontend/src/pages/History.jsx | 46 ++++++++----- frontend/src/pages/Home.jsx | 52 +++++++-------- frontend/src/pages/Picker.jsx | 113 ++++++++++++++++++-------------- 4 files changed, 119 insertions(+), 94 deletions(-) diff --git a/frontend/src/config/branding.js b/frontend/src/config/branding.js index c0803ba..a3ddb4e 100644 --- a/frontend/src/config/branding.js +++ b/frontend/src/config/branding.js @@ -2,7 +2,7 @@ export const branding = { app: { name: 'HSO Jackbox Game Picker', shortName: 'HSO JGP', - version: '0.3.1', + version: '0.3.2 - Safari Walkabout Edition', description: 'Spicing up Hyper Spaceout game nights!', }, meta: { diff --git a/frontend/src/pages/History.jsx b/frontend/src/pages/History.jsx index ab64191..4eee3b3 100644 --- a/frontend/src/pages/History.jsx +++ b/frontend/src/pages/History.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../context/AuthContext'; import { useToast } from '../components/Toast'; import api from '../api/axios'; @@ -17,9 +17,32 @@ function History() { const [showAllSessions, setShowAllSessions] = useState(false); const [deletingSession, setDeletingSession] = useState(null); + const loadSessions = useCallback(async () => { + try { + const response = await api.get('/sessions'); + setSessions(response.data); + } catch (err) { + console.error('Failed to load sessions', err); + } finally { + setLoading(false); + } + }, []); + + const refreshSessionGames = useCallback(async (sessionId, silent = false) => { + try { + const response = await api.get(`/sessions/${sessionId}/games`); + // Reverse chronological order (most recent first) + setSessionGames(response.data.reverse()); + } catch (err) { + if (!silent) { + console.error('Failed to load session games', err); + } + } + }, []); + useEffect(() => { loadSessions(); - }, []); + }, [loadSessions]); // Auto-select active session if navigating from picker useEffect(() => { @@ -29,7 +52,7 @@ function History() { loadSessionGames(activeSession.id); } } - }, [sessions]); + }, [sessions, selectedSession]); // Poll for session list updates (to detect when sessions end/start) useEffect(() => { @@ -38,7 +61,7 @@ function History() { }, 3000); return () => clearInterval(interval); - }, []); + }, [loadSessions]); // Poll for updates on active session games useEffect(() => { @@ -49,22 +72,11 @@ function History() { // Refresh games every 3 seconds for active session const interval = setInterval(() => { - loadSessionGames(selectedSession, true); // silent refresh + refreshSessionGames(selectedSession, true); // silent refresh }, 3000); return () => clearInterval(interval); - }, [selectedSession, sessions]); - - const loadSessions = async () => { - try { - const response = await api.get('/sessions'); - setSessions(response.data); - } catch (err) { - console.error('Failed to load sessions', err); - } finally { - setLoading(false); - } - }; + }, [selectedSession, sessions, refreshSessionGames]); const handleExport = async (sessionId, format) => { try { diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 35feb17..7663eb1 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import api from '../api/axios'; @@ -11,21 +11,19 @@ function Home() { const [sessionGames, setSessionGames] = useState([]); const [loading, setLoading] = useState(true); - useEffect(() => { - loadActiveSession(); + const loadSessionGames = useCallback(async (sessionId, silent = false) => { + try { + const gamesResponse = await api.get(`/sessions/${sessionId}/games`); + // Reverse chronological order (most recent first) + setSessionGames(gamesResponse.data.reverse()); + } catch (error) { + if (!silent) { + console.error('Failed to load session games', error); + } + } }, []); - // Auto-refresh for active session status and games - useEffect(() => { - // Poll for session status changes every 3 seconds - const interval = setInterval(() => { - loadActiveSession(); - }, 3000); - - return () => clearInterval(interval); - }, []); - - const loadActiveSession = async () => { + const loadActiveSession = useCallback(async () => { try { const response = await api.get('/sessions/active'); @@ -42,19 +40,21 @@ function Home() { } finally { setLoading(false); } - }; + }, [loadSessionGames]); - const loadSessionGames = async (sessionId, silent = false) => { - try { - const gamesResponse = await api.get(`/sessions/${sessionId}/games`); - // Reverse chronological order (most recent first) - setSessionGames(gamesResponse.data.reverse()); - } catch (error) { - if (!silent) { - console.error('Failed to load session games', error); - } - } - }; + useEffect(() => { + loadActiveSession(); + }, [loadActiveSession]); + + // Auto-refresh for active session status and games + useEffect(() => { + // Poll for session status changes every 3 seconds + const interval = setInterval(() => { + loadActiveSession(); + }, 3000); + + return () => clearInterval(interval); + }, [loadActiveSession]); if (loading) { return ( diff --git a/frontend/src/pages/Picker.jsx b/frontend/src/pages/Picker.jsx index 5d2eca6..5468570 100644 --- a/frontend/src/pages/Picker.jsx +++ b/frontend/src/pages/Picker.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import api from '../api/axios'; @@ -42,29 +42,31 @@ function Picker() { // Exclude previously played games const [excludePlayedGames, setExcludePlayedGames] = useState(false); - useEffect(() => { - // Wait for auth to finish loading before checking authentication - if (authLoading) return; - - if (!isAuthenticated) { - navigate('/login'); - return; + const checkActiveSession = useCallback(async () => { + try { + const sessionResponse = await api.get('/sessions/active'); + const session = sessionResponse.data?.session !== undefined + ? sessionResponse.data.session + : sessionResponse.data; + + // Check if session status changed + setActiveSession(prevSession => { + // If we had a session but now don't, mark it as ended + if (prevSession && (!session || !session.id)) { + setSessionEnded(true); + return null; + } else if (session && session.id) { + setSessionEnded(false); + return session; + } + return prevSession; + }); + } catch (err) { + console.error('Failed to check session status', err); } - loadData(); - }, [isAuthenticated, authLoading, navigate]); + }, []); - // Poll for active session status changes - useEffect(() => { - if (!isAuthenticated || authLoading) return; - - const interval = setInterval(() => { - checkActiveSession(); - }, 3000); - - return () => clearInterval(interval); - }, [isAuthenticated, authLoading]); - - const loadData = async () => { + const loadData = useCallback(async () => { try { // Load active session const sessionResponse = await api.get('/sessions/active'); @@ -85,27 +87,29 @@ function Picker() { } finally { setLoading(false); } - }; + }, []); - const checkActiveSession = async () => { - try { - const sessionResponse = await api.get('/sessions/active'); - const session = sessionResponse.data?.session !== undefined - ? sessionResponse.data.session - : sessionResponse.data; - - // If we had a session but now don't, mark it as ended - if (activeSession && (!session || !session.id)) { - setSessionEnded(true); - setActiveSession(null); - } else if (session && session.id) { - setActiveSession(session); - setSessionEnded(false); - } - } catch (err) { - console.error('Failed to check session status', err); + useEffect(() => { + // Wait for auth to finish loading before checking authentication + if (authLoading) return; + + if (!isAuthenticated) { + navigate('/login'); + return; } - }; + loadData(); + }, [isAuthenticated, authLoading, navigate, loadData]); + + // Poll for active session status changes + useEffect(() => { + if (!isAuthenticated || authLoading) return; + + const interval = setInterval(() => { + checkActiveSession(); + }, 3000); + + return () => clearInterval(interval); + }, [isAuthenticated, authLoading, checkActiveSession]); const handleCreateSession = async () => { try { @@ -727,11 +731,7 @@ function SessionInfo({ sessionId, onGamesUpdate }) { const [confirmingRemove, setConfirmingRemove] = useState(null); const [showPopularity, setShowPopularity] = useState(true); - useEffect(() => { - loadGames(); - }, [sessionId, onGamesUpdate]); - - const loadGames = async () => { + const loadGames = useCallback(async () => { try { const response = await api.get(`/sessions/${sessionId}/games`); // Reverse chronological order (most recent first) @@ -741,7 +741,20 @@ function SessionInfo({ sessionId, onGamesUpdate }) { } finally { setLoading(false); } - }; + }, [sessionId]); + + useEffect(() => { + loadGames(); + }, [sessionId, onGamesUpdate, loadGames]); + + // Auto-refresh games list every 3 seconds + useEffect(() => { + const interval = setInterval(() => { + loadGames(); + }, 3000); + + return () => clearInterval(interval); + }, [loadGames]); const handleUpdateStatus = async (gameId, newStatus) => { try { @@ -815,8 +828,8 @@ function SessionInfo({ sessionId, onGamesUpdate }) {

No games played yet. Pick a game to get started!

) : (
- {games.map((game) => { - const index = games.length - games.indexOf(game); + {games.map((game, index) => { + const displayNumber = games.length - index; return (
- {index + 1}. {game.title} + {displayNumber}. {game.title} {getStatusBadge(game.status)} {game.manually_added === 1 && (