Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea23651cb6 | ||
|
|
2273c71a87 | ||
|
|
9cb5fdd2fa | ||
|
|
73c54a7ebc | ||
|
|
7cea297095 | ||
|
|
019d36a0ab | ||
|
|
1922b2122b | ||
|
|
3dfe54f1cb | ||
|
|
5f04a3b622 | ||
|
|
05a8e0a842 | ||
|
|
9dac9b7a36 | ||
|
|
b99e754395 |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,3 +1,44 @@
|
||||
## [3.17.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.2...v3.17.3) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **arcade:** preserve gameConfig when switching games ([2273c71](https://github.com/antialias/soroban-abacus-flashcards/commit/2273c71a872a5122d0b2023835fe30640106048e))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* remove verbose console logging for cleaner debugging ([9cb5fdd](https://github.com/antialias/soroban-abacus-flashcards/commit/9cb5fdd2fa43560adc32dd052f47a7b06b2c5b69))
|
||||
|
||||
## [3.17.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.1...v3.17.2) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **room-data:** update query cache when gameConfig changes ([7cea297](https://github.com/antialias/soroban-abacus-flashcards/commit/7cea297095b78d74f5b77ca83489ec1be684a486))
|
||||
|
||||
## [3.17.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.0...v3.17.1) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **arcade-rooms:** navigate to invite link after room creation ([1922b21](https://github.com/antialias/soroban-abacus-flashcards/commit/1922b2122bb1bc4aeada7526d8c46aa89024bb00))
|
||||
* **memory-quiz:** scope game settings by game name for proper persistence ([3dfe54f](https://github.com/antialias/soroban-abacus-flashcards/commit/3dfe54f1cb89bd636e763e1c5acb03776f97c011))
|
||||
|
||||
## [3.17.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.16.0...v3.17.0) (2025-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **memory-quiz:** persist game settings per-game across sessions ([05a8e0a](https://github.com/antialias/soroban-abacus-flashcards/commit/05a8e0a84272c6c45a4014413ee00726eb88b76a))
|
||||
|
||||
## [3.16.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.15.2...v3.16.0) (2025-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **arcade:** broadcast game selection changes to all room members ([b99e754](https://github.com/antialias/soroban-abacus-flashcards/commit/b99e7543952bb0d47f42e79dc4226b3c1280a0ee))
|
||||
|
||||
## [3.15.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.15.1...v3.15.2) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +111,23 @@ export async function PATCH(req: NextRequest, context: RouteContext) {
|
||||
.where(eq(schema.arcadeRooms.id, roomId))
|
||||
.returning()
|
||||
|
||||
// Broadcast game change to all room members
|
||||
if (body.gameName !== undefined) {
|
||||
const io = await getSocketIO()
|
||||
if (io) {
|
||||
try {
|
||||
console.log(`[Settings API] Broadcasting game change to room ${roomId}: ${body.gameName}`)
|
||||
io.to(`room:${roomId}`).emit('room-game-changed', {
|
||||
roomId,
|
||||
gameName: body.gameName,
|
||||
gameConfig: body.gameConfig || {},
|
||||
})
|
||||
} catch (socketError) {
|
||||
console.error('[Settings API] Failed to broadcast game change:', socketError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If setting to retired, expel all non-owner members
|
||||
if (body.accessMode === 'retired') {
|
||||
const nonOwnerMembers = members.filter((m) => !m.isCreator)
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function RoomBrowserPage() {
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
router.push(`/arcade-rooms/${data.room.id}`)
|
||||
router.push(`/join/${data.room.code}`)
|
||||
} catch (err) {
|
||||
console.error('Failed to create room:', err)
|
||||
showError('Failed to create room', err instanceof Error ? err.message : undefined)
|
||||
|
||||
@@ -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'
|
||||
@@ -55,35 +55,21 @@ function applyMoveOptimistically(state: SorobanQuizState, move: GameMove): Sorob
|
||||
const activePlayers = move.data.activePlayers || []
|
||||
const playerMetadata = move.data.playerMetadata || {}
|
||||
|
||||
console.log('🎯 [START_QUIZ] Initializing player scores:', {
|
||||
activePlayers,
|
||||
playerMetadata,
|
||||
})
|
||||
|
||||
// Extract unique userIds from playerMetadata
|
||||
const uniqueUserIds = new Set<string>()
|
||||
for (const playerId of activePlayers) {
|
||||
const metadata = playerMetadata[playerId]
|
||||
console.log('🎯 [START_QUIZ] Processing player:', {
|
||||
playerId,
|
||||
metadata,
|
||||
hasUserId: !!metadata?.userId,
|
||||
})
|
||||
if (metadata?.userId) {
|
||||
uniqueUserIds.add(metadata.userId)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🎯 [START_QUIZ] Unique userIds found:', Array.from(uniqueUserIds))
|
||||
|
||||
// Initialize scores for each userId
|
||||
const playerScores = Array.from(uniqueUserIds).reduce((acc: any, userId: string) => {
|
||||
acc[userId] = { correct: 0, incorrect: 0 }
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
console.log('🎯 [START_QUIZ] Initialized playerScores:', playerScores)
|
||||
|
||||
return {
|
||||
...state,
|
||||
quizCards,
|
||||
@@ -122,12 +108,6 @@ function applyMoveOptimistically(state: SorobanQuizState, move: GameMove): Sorob
|
||||
const foundNumbers = state.foundNumbers || []
|
||||
const numberFoundBy = state.numberFoundBy || {}
|
||||
|
||||
console.log('✅ [ACCEPT_NUMBER] Before update:', {
|
||||
moveUserId: move.userId,
|
||||
currentPlayerScores: playerScores,
|
||||
number: move.data.number,
|
||||
})
|
||||
|
||||
const newPlayerScores = { ...playerScores }
|
||||
const newNumberFoundBy = { ...numberFoundBy }
|
||||
|
||||
@@ -139,15 +119,6 @@ function applyMoveOptimistically(state: SorobanQuizState, move: GameMove): Sorob
|
||||
}
|
||||
// Track who found this number
|
||||
newNumberFoundBy[move.data.number] = move.userId
|
||||
|
||||
console.log('✅ [ACCEPT_NUMBER] After update:', {
|
||||
userId: move.userId,
|
||||
newScore: newPlayerScores[move.userId],
|
||||
allScores: newPlayerScores,
|
||||
numberFoundBy: move.data.number,
|
||||
})
|
||||
} else {
|
||||
console.warn('⚠️ [ACCEPT_NUMBER] No userId in move!')
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
@@ -162,11 +133,6 @@ function applyMoveOptimistically(state: SorobanQuizState, move: GameMove): Sorob
|
||||
// Defensive check: ensure state properties exist
|
||||
const playerScores = state.playerScores || {}
|
||||
|
||||
console.log('❌ [REJECT_NUMBER] Before update:', {
|
||||
moveUserId: move.userId,
|
||||
currentPlayerScores: playerScores,
|
||||
})
|
||||
|
||||
const newPlayerScores = { ...playerScores }
|
||||
if (move.userId) {
|
||||
const currentScore = newPlayerScores[move.userId] || { correct: 0, incorrect: 0 }
|
||||
@@ -174,13 +140,6 @@ function applyMoveOptimistically(state: SorobanQuizState, move: GameMove): Sorob
|
||||
...currentScore,
|
||||
incorrect: currentScore.incorrect + 1,
|
||||
}
|
||||
console.log('❌ [REJECT_NUMBER] After update:', {
|
||||
userId: move.userId,
|
||||
newScore: newPlayerScores[move.userId],
|
||||
allScores: newPlayerScores,
|
||||
})
|
||||
} else {
|
||||
console.warn('⚠️ [REJECT_NUMBER] No userId in move!')
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
@@ -238,6 +197,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 +206,44 @@ 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
|
||||
// Settings are scoped by game name to preserve settings when switching games
|
||||
const mergedInitialState = useMemo(() => {
|
||||
const gameConfig = roomData?.gameConfig as Record<string, any> | null | undefined
|
||||
console.log('[RoomMemoryQuizProvider] Initializing - gameConfig:', gameConfig)
|
||||
|
||||
if (!gameConfig) {
|
||||
console.log('[RoomMemoryQuizProvider] No gameConfig, using initialState')
|
||||
return initialState
|
||||
}
|
||||
|
||||
// Get settings for this specific game (memory-quiz)
|
||||
const savedConfig = gameConfig['memory-quiz'] as Record<string, any> | null | undefined
|
||||
console.log('[RoomMemoryQuizProvider] Loading saved config for memory-quiz:', savedConfig)
|
||||
|
||||
if (!savedConfig) {
|
||||
console.log('[RoomMemoryQuizProvider] No saved config for memory-quiz, using initialState')
|
||||
return initialState
|
||||
}
|
||||
|
||||
const merged = {
|
||||
...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,
|
||||
}
|
||||
console.log('[RoomMemoryQuizProvider] Merged state:', {
|
||||
selectedCount: merged.selectedCount,
|
||||
displayTime: merged.displayTime,
|
||||
selectedDifficulty: merged.selectedDifficulty,
|
||||
playMode: merged.playMode,
|
||||
})
|
||||
|
||||
return merged
|
||||
}, [roomData?.gameConfig])
|
||||
|
||||
// Arcade session integration WITH room sync
|
||||
const {
|
||||
state,
|
||||
@@ -255,7 +253,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,
|
||||
})
|
||||
|
||||
@@ -287,19 +285,8 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
// Build player metadata from room data and player map
|
||||
const buildPlayerMetadata = useCallback(() => {
|
||||
console.log('🔍 [buildPlayerMetadata] Starting:', {
|
||||
roomData: roomData?.id,
|
||||
activePlayers,
|
||||
viewerId,
|
||||
playersMapSize: players.size,
|
||||
})
|
||||
|
||||
const playerOwnership = buildPlayerOwnershipFromRoomData(roomData)
|
||||
console.log('🔍 [buildPlayerMetadata] Player ownership:', playerOwnership)
|
||||
|
||||
const metadata = buildPlayerMetadataUtil(activePlayers, playerOwnership, players, viewerId)
|
||||
console.log('🔍 [buildPlayerMetadata] Built metadata:', metadata)
|
||||
|
||||
return metadata
|
||||
}, [activePlayers, players, roomData, viewerId])
|
||||
|
||||
@@ -313,13 +300,6 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
// Build player metadata for multiplayer
|
||||
const playerMetadata = buildPlayerMetadata()
|
||||
|
||||
console.log('🚀 [startQuiz] Sending START_QUIZ move:', {
|
||||
viewerId,
|
||||
activePlayers,
|
||||
playerMetadata,
|
||||
numbers,
|
||||
})
|
||||
|
||||
sendMove({
|
||||
type: 'START_QUIZ',
|
||||
playerId: TEAM_MOVE, // Team move - all players act together
|
||||
@@ -358,11 +338,6 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
// Clear local input immediately
|
||||
setLocalCurrentInput('')
|
||||
|
||||
console.log('🚀 [acceptNumber] Sending ACCEPT_NUMBER move:', {
|
||||
viewerId,
|
||||
number,
|
||||
})
|
||||
|
||||
sendMove({
|
||||
type: 'ACCEPT_NUMBER',
|
||||
playerId: TEAM_MOVE, // Team move - can't identify specific player
|
||||
@@ -377,10 +352,6 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
// Clear local input immediately
|
||||
setLocalCurrentInput('')
|
||||
|
||||
console.log('🚀 [rejectNumber] Sending REJECT_NUMBER move:', {
|
||||
viewerId,
|
||||
})
|
||||
|
||||
sendMove({
|
||||
type: 'REJECT_NUMBER',
|
||||
playerId: TEAM_MOVE, // Team move - can't identify specific player
|
||||
@@ -415,14 +386,42 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const setConfig = useCallback(
|
||||
(field: 'selectedCount' | 'displayTime' | 'selectedDifficulty' | 'playMode', value: any) => {
|
||||
console.log(`[RoomMemoryQuizProvider] setConfig called: ${field} = ${value}`)
|
||||
|
||||
sendMove({
|
||||
type: 'SET_CONFIG',
|
||||
playerId: TEAM_MOVE,
|
||||
userId: viewerId || '',
|
||||
data: { field, value },
|
||||
})
|
||||
|
||||
// Save setting to room's gameConfig for persistence
|
||||
// Settings are scoped by game name to preserve settings when switching games
|
||||
if (roomData?.id) {
|
||||
const currentGameConfig = (roomData.gameConfig as Record<string, any>) || {}
|
||||
console.log('[RoomMemoryQuizProvider] Current gameConfig:', currentGameConfig)
|
||||
|
||||
const currentMemoryQuizConfig =
|
||||
(currentGameConfig['memory-quiz'] as Record<string, any>) || {}
|
||||
console.log('[RoomMemoryQuizProvider] Current memory-quiz config:', currentMemoryQuizConfig)
|
||||
|
||||
const updatedConfig = {
|
||||
...currentGameConfig,
|
||||
'memory-quiz': {
|
||||
...currentMemoryQuizConfig,
|
||||
[field]: value,
|
||||
},
|
||||
}
|
||||
console.log('[RoomMemoryQuizProvider] Saving updated gameConfig:', updatedConfig)
|
||||
updateGameConfig({
|
||||
roomId: roomData.id,
|
||||
gameConfig: updatedConfig,
|
||||
})
|
||||
} else {
|
||||
console.warn('[RoomMemoryQuizProvider] No roomData.id, cannot save config')
|
||||
}
|
||||
},
|
||||
[viewerId, sendMove]
|
||||
[viewerId, sendMove, roomData?.id, roomData?.gameConfig, updateGameConfig]
|
||||
)
|
||||
|
||||
// Merge network state with local input state
|
||||
|
||||
@@ -111,13 +111,13 @@ export default function RoomPage() {
|
||||
console.log('[RoomPage] Calling setRoomGame with:', {
|
||||
roomId: roomData.id,
|
||||
gameName: internalGameName,
|
||||
gameConfig: {},
|
||||
preservingGameConfig: true,
|
||||
})
|
||||
|
||||
// Don't pass gameConfig - we want to preserve existing settings for all games
|
||||
setRoomGame({
|
||||
roomId: roomData.id,
|
||||
gameName: internalGameName,
|
||||
gameConfig: {},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface RoomData {
|
||||
name: string
|
||||
code: string
|
||||
gameName: string | null // Nullable to support game selection in room
|
||||
gameConfig?: Record<string, unknown> | null // Game-specific settings
|
||||
accessMode: 'open' | 'password' | 'approval-only' | 'restricted' | 'locked' | 'retired'
|
||||
members: RoomMember[]
|
||||
memberPlayers: Record<string, RoomPlayer[]> // userId -> players
|
||||
@@ -71,6 +72,7 @@ async function fetchCurrentRoom(): Promise<RoomData | null> {
|
||||
name: data.room.name,
|
||||
code: data.room.code,
|
||||
gameName: data.room.gameName,
|
||||
gameConfig: data.room.gameConfig || null,
|
||||
accessMode: data.room.accessMode || 'open',
|
||||
members: data.members || [],
|
||||
memberPlayers: data.memberPlayers || {},
|
||||
@@ -105,6 +107,7 @@ async function createRoomApi(params: CreateRoomParams): Promise<RoomData> {
|
||||
name: data.room.name,
|
||||
code: data.room.code,
|
||||
gameName: data.room.gameName,
|
||||
gameConfig: data.room.gameConfig || null,
|
||||
accessMode: data.room.accessMode || 'open',
|
||||
members: data.members || [],
|
||||
memberPlayers: data.memberPlayers || {},
|
||||
@@ -141,6 +144,7 @@ async function joinRoomApi(params: {
|
||||
name: data.room.name,
|
||||
code: data.room.code,
|
||||
gameName: data.room.gameName,
|
||||
gameConfig: data.room.gameConfig || null,
|
||||
accessMode: data.room.accessMode || 'open',
|
||||
members: data.members || [],
|
||||
memberPlayers: data.memberPlayers || {},
|
||||
@@ -183,6 +187,7 @@ async function getRoomByCodeApi(code: string): Promise<RoomData> {
|
||||
name: data.room.name,
|
||||
code: data.room.code,
|
||||
gameName: data.room.gameName,
|
||||
gameConfig: data.room.gameConfig || null,
|
||||
accessMode: data.room.accessMode || 'open',
|
||||
members: data.members || [],
|
||||
memberPlayers: data.memberPlayers || {},
|
||||
@@ -348,7 +353,6 @@ export function useRoomData() {
|
||||
|
||||
// Moderation event handlers
|
||||
const handleKickedFromRoom = (data: { roomId: string; kickedBy: string; reason?: string }) => {
|
||||
console.log('[useRoomData] User was kicked from room:', data)
|
||||
setModerationEvent({
|
||||
type: 'kicked',
|
||||
data: {
|
||||
@@ -362,7 +366,6 @@ export function useRoomData() {
|
||||
}
|
||||
|
||||
const handleBannedFromRoom = (data: { roomId: string; bannedBy: string; reason: string }) => {
|
||||
console.log('[useRoomData] User was banned from room:', data)
|
||||
setModerationEvent({
|
||||
type: 'banned',
|
||||
data: {
|
||||
@@ -386,7 +389,6 @@ export function useRoomData() {
|
||||
createdAt: Date
|
||||
}
|
||||
}) => {
|
||||
console.log('[useRoomData] New report submitted:', data)
|
||||
setModerationEvent({
|
||||
type: 'report',
|
||||
data: {
|
||||
@@ -411,7 +413,6 @@ export function useRoomData() {
|
||||
createdAt: Date
|
||||
}
|
||||
}) => {
|
||||
console.log('[useRoomData] Room invitation received:', data)
|
||||
setModerationEvent({
|
||||
type: 'invitation',
|
||||
data: {
|
||||
@@ -434,7 +435,6 @@ export function useRoomData() {
|
||||
createdAt: Date
|
||||
}
|
||||
}) => {
|
||||
console.log('[useRoomData] New join request submitted:', data)
|
||||
setModerationEvent({
|
||||
type: 'join-request',
|
||||
data: {
|
||||
@@ -446,6 +446,23 @@ export function useRoomData() {
|
||||
})
|
||||
}
|
||||
|
||||
const handleRoomGameChanged = (data: {
|
||||
roomId: string
|
||||
gameName: string | null
|
||||
gameConfig: Record<string, unknown>
|
||||
}) => {
|
||||
console.log('[useRoomData] Room game changed:', data)
|
||||
if (data.roomId === roomData?.id) {
|
||||
queryClient.setQueryData<RoomData | null>(roomKeys.current(), (prev) => {
|
||||
if (!prev) return null
|
||||
return {
|
||||
...prev,
|
||||
gameName: data.gameName,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('room-joined', handleRoomJoined)
|
||||
socket.on('member-joined', handleMemberJoined)
|
||||
socket.on('member-left', handleMemberLeft)
|
||||
@@ -455,6 +472,7 @@ export function useRoomData() {
|
||||
socket.on('report-submitted', handleReportSubmitted)
|
||||
socket.on('room-invitation-received', handleInvitationReceived)
|
||||
socket.on('join-request-submitted', handleJoinRequestSubmitted)
|
||||
socket.on('room-game-changed', handleRoomGameChanged)
|
||||
|
||||
return () => {
|
||||
socket.off('room-joined', handleRoomJoined)
|
||||
@@ -466,13 +484,13 @@ export function useRoomData() {
|
||||
socket.off('report-submitted', handleReportSubmitted)
|
||||
socket.off('room-invitation-received', handleInvitationReceived)
|
||||
socket.off('join-request-submitted', handleJoinRequestSubmitted)
|
||||
socket.off('room-game-changed', handleRoomGameChanged)
|
||||
}
|
||||
}, [socket, roomData?.id, queryClient])
|
||||
|
||||
// Function to notify room members of player updates
|
||||
const notifyRoomOfPlayerUpdate = useCallback(() => {
|
||||
if (socket && roomData?.id && userId) {
|
||||
console.log('[useRoomData] Notifying room of player update')
|
||||
socket.emit('players-updated', { roomId: roomData.id, userId })
|
||||
}
|
||||
}, [socket, roomData?.id, userId])
|
||||
@@ -646,3 +664,47 @@ 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() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: updateGameConfigApi,
|
||||
onSuccess: (_, variables) => {
|
||||
// Update the cache with the new gameConfig
|
||||
queryClient.setQueryData<RoomData | null>(roomKeys.current(), (prev) => {
|
||||
if (!prev) return null
|
||||
return {
|
||||
...prev,
|
||||
gameConfig: variables.gameConfig,
|
||||
}
|
||||
})
|
||||
console.log('[useUpdateGameConfig] Updated cache with new gameConfig:', variables.gameConfig)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "3.15.2",
|
||||
"version": "3.17.3",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user