From dbf90504b481d85a4598209d5df43f2c71bdff0c Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 21:49:00 +0200 Subject: [PATCH] feat(P5B.T3): useGame hook with realtime + makeMove --- apps/desktop/src/hooks/useGame.ts | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 apps/desktop/src/hooks/useGame.ts diff --git a/apps/desktop/src/hooks/useGame.ts b/apps/desktop/src/hooks/useGame.ts new file mode 100644 index 0000000..dc37b95 --- /dev/null +++ b/apps/desktop/src/hooks/useGame.ts @@ -0,0 +1,75 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { getGame, makeGameMove, type GameRecord } from '@chat-app/shared/chat'; + +import { supabase } from '../lib/supabase'; + +export function useGame(gameId: string | null): { + game: GameRecord | null; + loading: boolean; + error: string | null; + makeMove: (move: object) => Promise; +} { + const [game, setGame] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!gameId) { + setGame(null); + setLoading(false); + setError(null); + return; + } + let cancelled = false; + void (async () => { + try { + setLoading(true); + setError(null); + const fresh = await getGame(supabase, gameId); + if (!cancelled) { + setGame(fresh); + setLoading(false); + } + } catch (err) { + if (!cancelled) { + setLoading(false); + setError(err instanceof Error ? err.message : 'failed to load game'); + } + } + })(); + const channel = supabase + .channel('game:' + gameId) + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'conversation_games', + filter: 'id=eq.' + gameId, + }, + () => { + void getGame(supabase, gameId) + .then((fresh) => { if (fresh) setGame(fresh); }) + .catch((err) => { console.warn('game realtime refetch failed', err); }); + }, + ) + .subscribe(); + return () => { + cancelled = true; + void supabase.removeChannel(channel); + }; + }, [gameId]); + + const makeMove = useCallback(async (move: object) => { + if (!gameId) return; + try { + await makeGameMove(supabase, { gameId, move }); + } catch (err) { + setError(err instanceof Error ? err.message : 'move failed'); + throw err; + } + }, [gameId]); + + return { game, loading, error, makeMove }; +}