Compare commits

...

2 Commits

Author SHA1 Message Date
semantic-release-bot
0a768c65fb chore(release): 2.7.1 [skip ci]
## [2.7.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.7.0...v2.7.1) (2025-10-08)

### Bug Fixes

* resolve race condition in /arcade/room redirect ([5ed2ab2](5ed2ab21ca))
2025-10-08 15:26:51 +00:00
Thomas Hallock
5ed2ab21ca fix: resolve race condition in /arcade/room redirect
The /arcade/room page was redirecting to /arcade before userId loaded,
causing a race condition where the page would redirect even when the user
was in a valid room.

Root cause:
- useViewerId() loads asynchronously
- useRoomData depended on userId but didn't expose userId loading state
- Page checked !isLoading && !roomData and redirected immediately
- By the time userId loaded and room data fetched, redirect already happened

Fix:
- Track isPending from useViewerId in useRoomData
- Combine isUserIdPending with room data loading state
- Page now waits for both userId and room data before redirecting

Added debug logging to help diagnose future issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 10:25:53 -05:00
6 changed files with 35 additions and 27 deletions

View File

@@ -1,3 +1,10 @@
## [2.7.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.7.0...v2.7.1) (2025-10-08)
### Bug Fixes
* resolve race condition in /arcade/room redirect ([5ed2ab2](https://github.com/antialias/soroban-abacus-flashcards/commit/5ed2ab21cab408147081a493c8dd6b1de48b2d01))
## [2.7.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.6.0...v2.7.0) (2025-10-08)

View File

@@ -12,11 +12,14 @@ import { getViewerId } from '@/lib/viewer'
export async function GET() {
try {
const userId = await getViewerId()
console.log('[Current Room API] Fetching for user:', userId)
// Get all rooms user is in (should be at most 1 due to modal room enforcement)
const roomIds = await getUserRooms(userId)
console.log('[Current Room API] User rooms:', roomIds)
if (roomIds.length === 0) {
console.log('[Current Room API] User is not in any room')
return NextResponse.json({ room: null }, { status: 200 })
}
@@ -25,6 +28,7 @@ export async function GET() {
// Get room data
const room = await getRoomById(roomId)
if (!room) {
console.log('[Current Room API] Room not found:', roomId)
return NextResponse.json({ error: 'Room not found' }, { status: 404 })
}
@@ -40,6 +44,12 @@ export async function GET() {
memberPlayersObj[uid] = players
}
console.log('[Current Room API] Returning room:', {
roomId: room.id,
roomName: room.name,
memberCount: members.length,
})
return NextResponse.json({
room,
members,

View File

@@ -15,6 +15,11 @@ export default function RoomPage() {
const router = useRouter()
const { roomData, isLoading } = useRoomData()
// Debug logging
useEffect(() => {
console.log('[RoomPage] State:', { isLoading, hasRoomData: !!roomData, roomData })
}, [isLoading, roomData])
// Redirect to arcade if no room
useEffect(() => {
if (!isLoading && !roomData) {

View File

@@ -1,7 +1,6 @@
'use client'
import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from 'react'
import { io } from 'socket.io-client'
import type { Player as DBPlayer } from '@/db/schema/players'
import {
useCreatePlayer,
@@ -158,26 +157,6 @@ export function GameModeProvider({ children }: { children: ReactNode }) {
}
}, [dbPlayers, isLoading, isInitialized, createPlayer])
// When in a room, broadcast player updates to other members
useEffect(() => {
if (!roomData || !viewerId || !isInitialized) return
const socket = io({ path: '/api/socket' })
// Wait for connection before emitting
socket.on('connect', () => {
console.log('[GameModeContext] Emitting players-updated for room:', roomData.id)
socket.emit('players-updated', {
roomId: roomData.id,
userId: viewerId,
})
})
return () => {
socket.disconnect()
}
}, [dbPlayers, roomData, viewerId, isInitialized])
const addPlayer = (playerData?: Partial<Player>) => {
const playerList = Array.from(players.values())

View File

@@ -31,7 +31,7 @@ export interface RoomData {
* Returns null if user is not in any room
*/
export function useRoomData() {
const { data: userId } = useViewerId()
const { data: userId, isPending: isUserIdPending } = useViewerId()
const [socket, setSocket] = useState<Socket | null>(null)
const [roomData, setRoomData] = useState<RoomData | null>(null)
const [isLoading, setIsLoading] = useState(false)
@@ -39,35 +39,42 @@ export function useRoomData() {
// Fetch the user's current room
useEffect(() => {
if (!userId) {
console.log('[useRoomData] No userId, clearing room data')
setRoomData(null)
return
}
console.log('[useRoomData] Fetching current room for user:', userId)
setIsLoading(true)
// Fetch current room data
fetch('/api/arcade/rooms/current')
.then((res) => {
console.log('[useRoomData] API response status:', res.status)
if (!res.ok) throw new Error('Failed to fetch current room')
return res.json()
})
.then((data) => {
console.log('[useRoomData] API response data:', data)
if (data.room) {
setRoomData({
const roomData = {
id: data.room.id,
name: data.room.name,
code: data.room.code,
gameName: data.room.gameName,
members: data.members || [],
memberPlayers: data.memberPlayers || {},
})
}
console.log('[useRoomData] Setting room data:', roomData)
setRoomData(roomData)
} else {
console.log('[useRoomData] No room in response, clearing room data')
setRoomData(null)
}
setIsLoading(false)
})
.catch((error) => {
console.error('Failed to fetch room data:', error)
console.error('[useRoomData] Failed to fetch room data:', error)
setRoomData(null)
setIsLoading(false)
})
@@ -197,7 +204,7 @@ export function useRoomData() {
return {
roomData,
isLoading,
isLoading: isLoading || isUserIdPending, // Wait for both userId and room data
isInRoom: !!roomData,
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "soroban-monorepo",
"version": "2.7.0",
"version": "2.7.1",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [