76 lines
2.0 KiB
TypeScript
76 lines
2.0 KiB
TypeScript
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<void>;
|
|
} {
|
|
const [game, setGame] = useState<GameRecord | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(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 };
|
|
}
|