From ab973cbf4f7108ad3abf5c1a465e5c2844878098 Mon Sep 17 00:00:00 2001 From: cottongin Date: Sun, 23 Aug 2026 17:14:14 -0400 Subject: [PATCH] fix: sticker toggle disabled state reactivity and pulse edge case Co-authored-by: Cursor --- frontend/src/pages/Picker.jsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Picker.jsx b/frontend/src/pages/Picker.jsx index 3d68001..d26769e 100644 --- a/frontend/src/pages/Picker.jsx +++ b/frontend/src/pages/Picker.jsx @@ -34,14 +34,19 @@ function Picker() { const [sessionEnded, setSessionEnded] = useState(false); const [stickerMode, setStickerMode] = useState('auto'); const stickerSendRef = useRef(null); + const [stickerWsReady, setStickerWsReady] = useState(false); const [stickerPulse, setStickerPulse] = useState(false); const prevStickerModeRef = useRef(stickerMode); useEffect(() => { if (stickerMode === 'auto' && prevStickerModeRef.current !== 'auto') { setStickerPulse(true); const timer = setTimeout(() => setStickerPulse(false), 2000); + prevStickerModeRef.current = stickerMode; return () => clearTimeout(timer); } + if (stickerMode !== 'auto') { + setStickerPulse(false); + } prevStickerModeRef.current = stickerMode; }, [stickerMode]); @@ -905,7 +910,7 @@ function Picker() { setStickerMode(opt.value); stickerSendRef.current?.(opt.value); }} - disabled={!stickerSendRef.current} + disabled={!stickerWsReady} className={`px-3 py-1.5 text-xs font-medium transition ${ i === 1 ? 'border-x border-gray-300 dark:border-gray-600 ' : '' }${ @@ -913,7 +918,7 @@ function Picker() { ? opt.activeClass : 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300' }${ - !stickerSendRef.current ? ' opacity-50 cursor-not-allowed' : '' + !stickerWsReady ? ' opacity-50 cursor-not-allowed' : '' }`} > {opt.label} @@ -1346,6 +1351,7 @@ function Picker() { setGameSource={setGameSource} setStickerMode={setStickerMode} stickerSendRef={stickerSendRef} + setStickerWsReady={setStickerWsReady} /> @@ -1353,7 +1359,7 @@ function Picker() { ); } -function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, setHasPlayedGames, setLeadingGame, setPollActive, pollActiveRef, setPollResult, setPollEndingAt, setShowEndPollOptions, pollStartedAtRef, setSelectedGame, setGameSource, setStickerMode, stickerSendRef }) { +function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, setHasPlayedGames, setLeadingGame, setPollActive, pollActiveRef, setPollResult, setPollEndingAt, setShowEndPollOptions, pollStartedAtRef, setSelectedGame, setGameSource, setStickerMode, stickerSendRef, setStickerWsReady }) { const { isAuthenticated, token } = useAuth(); const [games, setGames] = useState([]); const [loading, setLoading] = useState(true); @@ -1450,6 +1456,7 @@ function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, se ws.send(JSON.stringify({ type: 'stickers.setMode', data: { mode } })); } }; + setStickerWsReady(true); return; } @@ -1552,6 +1559,7 @@ function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, se console.log('[WebSocket] Disconnected, reconnecting in 3s...'); clearInterval(pingIntervalRef.current); stickerSendRef.current = null; + setStickerWsReady(false); reconnectTimeoutRef.current = setTimeout(connectWs, 3000); }; @@ -1560,7 +1568,7 @@ function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, se console.error('[WebSocket] Failed to connect:', error); reconnectTimeoutRef.current = setTimeout(connectWs, 3000); } - }, [sessionId, token, loadGames, setPollActive, setPollResult, setPollEndingAt, setShowEndPollOptions, setLeadingGame, pollActiveRef, pollStartedAtRef, setSelectedGame, setGameSource, setStickerMode, stickerSendRef]); + }, [sessionId, token, loadGames, setPollActive, setPollResult, setPollEndingAt, setShowEndPollOptions, setLeadingGame, pollActiveRef, pollStartedAtRef, setSelectedGame, setGameSource, setStickerMode, stickerSendRef, setStickerWsReady]); useEffect(() => { connectWs(); @@ -1568,6 +1576,7 @@ function SessionInfo({ sessionId, onGamesUpdate, playingGame, setPlayingGame, se return () => { clearTimeout(reconnectTimeoutRef.current); clearInterval(pingIntervalRef.current); + setStickerWsReady(false); if (wsRef.current) { wsRef.current.onclose = null; wsRef.current.close();