feat(memory-quiz): persist game settings per-game across sessions

Implement per-game settings persistence so that when users switch between
games and come back, their settings are restored. Settings are saved to
the room's gameConfig field in the database.

Changes:
- Add useUpdateGameConfig hook to save settings to room
- Load settings from roomData.gameConfig on provider initialization
- Merge saved config with initialState using useMemo
- Save settings to database when setConfig is called
- Settings persist across:
  - Game switches (memory-quiz -> matching -> memory-quiz)
  - Page refreshes
  - New arcade sessions

Settings saved: selectedCount, displayTime, selectedDifficulty, playMode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-15 09:45:47 -05:00
parent 9dac9b7a36
commit 05a8e0a842
2 changed files with 66 additions and 4 deletions

View File

@@ -1,10 +1,10 @@
'use client'
import type { ReactNode } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useGameMode } from '@/contexts/GameModeContext'
import { useArcadeSession } from '@/hooks/useArcadeSession'
import { useRoomData } from '@/hooks/useRoomData'
import { useRoomData, useUpdateGameConfig } from '@/hooks/useRoomData'
import { useViewerId } from '@/hooks/useViewerId'
import type { GameMove } from '@/lib/arcade/validation'
import { TEAM_MOVE } from '@/lib/arcade/validation/types'
@@ -238,6 +238,7 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
const { data: viewerId } = useViewerId()
const { roomData } = useRoomData()
const { activePlayers: activePlayerIds, players } = useGameMode()
const { mutate: updateGameConfig } = useUpdateGameConfig()
// Get active player IDs as array
const activePlayers = Array.from(activePlayerIds)
@@ -246,6 +247,23 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
// This prevents sending a network request for every keystroke
const [localCurrentInput, setLocalCurrentInput] = useState('')
// Merge saved game config from room with initialState
const mergedInitialState = useMemo(() => {
const savedConfig = roomData?.gameConfig as Record<string, any> | null | undefined
if (!savedConfig) return initialState
console.log('[RoomMemoryQuizProvider] Loading saved game config:', savedConfig)
return {
...initialState,
// Restore settings from saved config
selectedCount: savedConfig.selectedCount ?? initialState.selectedCount,
displayTime: savedConfig.displayTime ?? initialState.displayTime,
selectedDifficulty: savedConfig.selectedDifficulty ?? initialState.selectedDifficulty,
playMode: savedConfig.playMode ?? initialState.playMode,
}
}, [roomData?.gameConfig])
// Arcade session integration WITH room sync
const {
state,
@@ -255,7 +273,7 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
} = useArcadeSession<SorobanQuizState>({
userId: viewerId || '',
roomId: roomData?.id, // CRITICAL: Pass roomId for network sync across room members
initialState,
initialState: mergedInitialState,
applyMove: applyMoveOptimistically,
})
@@ -421,8 +439,21 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
userId: viewerId || '',
data: { field, value },
})
// Save setting to room's gameConfig for persistence
if (roomData?.id) {
const updatedConfig = {
...(roomData.gameConfig as Record<string, any>),
[field]: value,
}
console.log('[RoomMemoryQuizProvider] Saving game config:', updatedConfig)
updateGameConfig({
roomId: roomData.id,
gameConfig: updatedConfig,
})
}
},
[viewerId, sendMove]
[viewerId, sendMove, roomData?.id, roomData?.gameConfig, updateGameConfig]
)
// Merge network state with local input state

View File

@@ -665,3 +665,34 @@ export function useClearRoomGame() {
},
})
}
/**
* Update game config for current room (game-specific settings)
*/
async function updateGameConfigApi(params: {
roomId: string
gameConfig: Record<string, unknown>
}): Promise<void> {
const response = await fetch(`/api/arcade/rooms/${params.roomId}/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
gameConfig: params.gameConfig,
}),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to update game config')
}
}
/**
* Hook: Update game config for current room
* This allows games to persist their settings (e.g., difficulty, card count)
*/
export function useUpdateGameConfig() {
return useMutation({
mutationFn: updateGameConfigApi,
})
}