From 7f52da0d62c7f919b246428905a35ff40f684762 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Thu, 23 Apr 2026 14:24:32 -0400 Subject: [PATCH] fix: handle server behavior around reconnects, fix fetching reservations --- app/play/page.tsx | 24 +++++++++++++++++++++--- components/AdminSettingsPanel.tsx | 1 + lib/connection.tsx | 7 +++++++ next-env.d.ts | 2 +- tsconfig.json | 24 +++++++++++++++++++----- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app/play/page.tsx b/app/play/page.tsx index 00d5c57..39d19ad 100644 --- a/app/play/page.tsx +++ b/app/play/page.tsx @@ -23,6 +23,8 @@ export default function PlayPage() { role, username, status, + isInMatch, + shouldGoFirst, send, subscribe, reconnectAttempts, @@ -86,13 +88,25 @@ export default function PlayPage() { } if (status === "connected" && gamePhase === "idle") { - setGamePhase("connected"); + // Mid-match reconnect can remount with phase idle while still in a match; avoid + // the pre-queue "connected" / Ready Up state until we know we are not in-game. + setGamePhase(isInMatch ? "playing" : "connected"); + if (isInMatch) { + const color: 1 | 2 = shouldGoFirst ? 1 : 2; + setMyColor(color); + myColorRef.current = color; + + setIsMyTurn(shouldGoFirst); + isMyTurnRef.current = shouldGoFirst; + } + } }, [ role, status, router, gamePhase, + isInMatch, shouldRedirectToConnect, clearRedirectFlag, ]); @@ -102,7 +116,11 @@ export default function PlayPage() { switch (msg.type) { case "CONNECT_ACK": case "RECONNECT_ACK": - setGamePhase((prev) => (prev === "idle" ? "connected" : prev)); + setGamePhase((prev) => { + if (prev !== "idle") return prev; + if (isInMatch) return "playing"; + return "connected"; + }); break; case "ERROR": @@ -193,7 +211,7 @@ export default function PlayPage() { }); return unsubscribe; - }, [resetGame, send, subscribe]); + }, [resetGame, send, subscribe, isInMatch, username]); const handleColumnClick = useCallback( (col: number) => { diff --git a/components/AdminSettingsPanel.tsx b/components/AdminSettingsPanel.tsx index 523b895..645681d 100644 --- a/components/AdminSettingsPanel.tsx +++ b/components/AdminSettingsPanel.tsx @@ -152,6 +152,7 @@ export default function AdminSettingsPanel() { break; case "RESERVATION_LIST": setReservations(message.reservations); + setActionFeedback("Loaded reservations."); break; case "RESERVATION_ADD": setReservations((prev) => { diff --git a/lib/connection.tsx b/lib/connection.tsx index f29b726..c47fceb 100644 --- a/lib/connection.tsx +++ b/lib/connection.tsx @@ -40,6 +40,7 @@ interface ConnectionContextValue { username: string; status: ConnectionStatus; isInMatch: boolean; + shouldGoFirst: boolean; isAdmin: boolean; reconnectAttempts: number; shouldRedirectToConnect: boolean; @@ -70,6 +71,7 @@ export function ConnectionProvider({ const [username, setUsername] = useState(""); const [status, setStatus] = useState("idle"); const [isInMatch, setIsInMatch] = useState(false); + const [shouldGoFirst, setShouldGoFirst] = useState(false); const [isAdmin, setIsAdmin] = useState(false); const [reconnectAttempts, setReconnectAttempts] = useState(0); const [shouldRedirectToConnect, setShouldRedirectToConnect] = useState(false); @@ -81,6 +83,7 @@ export function ConnectionProvider({ const reconnectDeadlineRef = useRef(null); const reconnectActiveRef = useRef(false); const isInMatchRef = useRef(false); + const shouldGoFirstRef = useRef(false); const sessionRef = useRef(null); const clearReconnectTimer = useCallback(() => { @@ -202,6 +205,8 @@ export function ConnectionProvider({ if (parsed.type === "GAME_START") { isInMatchRef.current = true; setIsInMatch(true); + shouldGoFirstRef.current = parsed.goesFirst; + setShouldGoFirst(parsed.goesFirst); } if ( @@ -377,6 +382,7 @@ export function ConnectionProvider({ username, status, isInMatch, + shouldGoFirst, isAdmin, reconnectAttempts, shouldRedirectToConnect, @@ -394,6 +400,7 @@ export function ConnectionProvider({ username, status, isInMatch, + shouldGoFirst, isAdmin, reconnectAttempts, shouldRedirectToConnect, diff --git a/next-env.d.ts b/next-env.d.ts index 830fb59..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -/// +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/tsconfig.json b/tsconfig.json index 64c2104..9e9bbf7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -10,7 +14,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -18,10 +22,20 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": [ + "./*" + ] }, "target": "ES2017" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }