Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 'use client' import type { ReactNode } from 'react' import { useMemo, useCallback } from 'react' import { GameModeProvider, type RoomData } from '@/contexts/GameModeContext' import { GameCompletionProvider } from '@/contexts/GameCompletionContext' import type { Player as DBPlayer } from '@/db/schema/players' import { useUserId } from '@/hooks/useUserId' interface StudentInfo { id: string name: string emoji: string color: string } interface PracticeGameModeProviderProps { student: StudentInfo roomData: RoomData | null children: ReactNode /** * Callback fired when the game transitions to 'results' phase. * * This enables the practice system to detect when a student finishes a game * and end the game break early (before the timer expires). * * The callback receives the full game state so the caller can generate * a results report using the game's validator.getResultsReport() method. * * Note: Not all games have a 'results' phase. Endless games (e.g., complement-race) * will only end via timeout or manual skip. This is expected behavior. * * @param gameState The final game state when transitioning to 'results' * @see docs in .claude/ARCADE_ROOM_ARCHITECTURE.md for the full protocol */ onGameComplete?: (gameState: Record<string, unknown>) => void } /** * Wraps GameModeProvider with fake player data for practice game breaks. * * The arcade system normally loads players from the database via getRoomActivePlayers(). * But practice students aren't real DB players - they're curriculum players. * * This provider: * 1. Creates a fake DBPlayer from the student info * 2. Injects that player into roomData.memberPlayers so GameModeContext sees them * 3. Provides no-op mutations (we don't want to modify player data during game breaks) * 4. Listens for game completion (transition to 'results' phase) to notify parent */ export function PracticeGameModeProvider({ student, roomData, children, onGameComplete, }: PracticeGameModeProviderProps) { const { data: viewerId } = useUserId() // Game completion is now detected via GameCompletionContext. // The matching Provider (or any game provider) calls the completion callback // when transitioning to 'results' phase, so we don't need our own socket. const handleGameComplete = useCallback( (gameState: Record<string, unknown>) => { onGameComplete?.(gameState) }, [onGameComplete] ) // Use the actual student's player ID so that downstream consumers // (e.g. GenericResultsPhase → record-game) send a real player ID that // passes canPerformAction auth checks. The student.id IS in the players // table and is owned by the current user, so auth succeeds. const playerId = student.id // Create a fake DBPlayer from the practice student const dbPlayers: DBPlayer[] = useMemo( () => [ { id: playerId, userId: playerId, name: student.name, emoji: student.emoji, color: student.color, isActive: true, createdAt: new Date(), helpSettings: null, notes: null, isArchived: false, isPracticeStudent: true, isExpungeable: false, birthday: null, familyCode: null, familyCodeGeneratedAt: null, }, ], [student, playerId] ) // Inject the fake player into roomData.memberPlayers // This is necessary because getRoomActivePlayers() queries the DB, // but our practice student isn't a real DB player. const enrichedRoomData: RoomData | null = useMemo(() => { if (!roomData) return roomData return { ...roomData, memberPlayers: { ...roomData.memberPlayers, [playerId]: [ { id: playerId, name: student.name, emoji: student.emoji, color: student.color, }, ], }, } }, [roomData, playerId, student]) // No-op mutations - we don't want to modify player data during game breaks const createPlayer = useCallback(() => {}, []) const updatePlayerMutation = useCallback(() => {}, []) const deletePlayer = useCallback(() => {}, []) const notifyRoomOfPlayerUpdate = useCallback(() => {}, []) return ( <GameCompletionProvider onGameComplete={handleGameComplete}> <GameModeProvider dbPlayers={dbPlayers} isLoading={false} createPlayer={createPlayer} updatePlayerMutation={updatePlayerMutation} deletePlayer={deletePlayer} roomData={enrichedRoomData} notifyRoomOfPlayerUpdate={notifyRoomOfPlayerUpdate} viewerId={viewerId} > {children} </GameModeProvider> </GameCompletionProvider> ) } |