diff --git a/apps/web/src/components/PageWithNav.tsx b/apps/web/src/components/PageWithNav.tsx index 52ced90c..06608701 100644 --- a/apps/web/src/components/PageWithNav.tsx +++ b/apps/web/src/components/PageWithNav.tsx @@ -3,6 +3,8 @@ import React from 'react' import { useGameMode } from '../contexts/GameModeContext' import { useArcadeGuard } from '../hooks/useArcadeGuard' +import { useRoomData } from '../hooks/useRoomData' +import { useViewerId } from '../hooks/useViewerId' import { AppNavBar } from './AppNavBar' import { GameContextNav } from './nav/GameContextNav' import { PlayerConfigDialog } from './nav/PlayerConfigDialog' @@ -30,6 +32,8 @@ export function PageWithNav({ }: PageWithNavProps) { const { players, activePlayers, setActive, activePlayerCount } = useGameMode() const { hasActiveSession, activeSession } = useArcadeGuard({ enabled: false }) // Don't redirect, just get info + const { roomData, isInRoom } = useRoomData() + const { data: viewerId } = useViewerId() const [mounted, setMounted] = React.useState(false) const [configurePlayerId, setConfigurePlayerId] = React.useState(null) @@ -80,17 +84,33 @@ export function PageWithNav({ // Compute arcade session info for display const roomInfo = - hasActiveSession && activeSession + isInRoom && roomData ? { - gameName: activeSession.currentGame, - playerCount: activePlayerCount, // TODO: Get actual player count from session when available + roomName: roomData.name, + gameName: roomData.gameName, + playerCount: roomData.members.length, } - : undefined + : hasActiveSession && activeSession + ? { + gameName: activeSession.currentGame, + playerCount: activePlayerCount, + } + : undefined - // Compute network players (other players in the arcade session) - // For now, we don't have this info in activeSession, so return empty array - // TODO: When arcade room system is implemented, fetch other players from session - const networkPlayers: Array<{ id: string; emoji?: string; name?: string }> = [] + // Compute network players (other players in the room, excluding current user) + const networkPlayers: Array<{ id: string; emoji?: string; name?: string }> = + isInRoom && roomData + ? roomData.members + .filter((member) => member.userId !== viewerId) + .flatMap((member) => { + const memberPlayerList = roomData.memberPlayers[member.userId] || [] + return memberPlayerList.map((player) => ({ + id: player.id, + emoji: player.emoji, + name: `${player.name} (${member.displayName})`, + })) + }) + : [] // Create nav content if title is provided const navContent = navTitle ? ( diff --git a/apps/web/src/components/nav/GameContextNav.tsx b/apps/web/src/components/nav/GameContextNav.tsx index a62214a4..165e5858 100644 --- a/apps/web/src/components/nav/GameContextNav.tsx +++ b/apps/web/src/components/nav/GameContextNav.tsx @@ -22,6 +22,7 @@ interface NetworkPlayer { } interface ArcadeRoomInfo { + roomName?: string gameName: string playerCount: number } @@ -134,6 +135,7 @@ export function GameContextNav({ {/* Room Info - show when in arcade session */} {roomInfo && !showFullscreenSelection && ( - Arcade Session + {roomName ? 'Room' : 'Arcade Session'}
- {gameName} + {roomName || gameName}
diff --git a/apps/web/src/hooks/useRoomData.ts b/apps/web/src/hooks/useRoomData.ts new file mode 100644 index 00000000..c719b8bf --- /dev/null +++ b/apps/web/src/hooks/useRoomData.ts @@ -0,0 +1,162 @@ +import { useEffect, useState } from 'react' +import { usePathname } from 'next/navigation' +import { io, type Socket } from 'socket.io-client' + +export interface RoomMember { + id: string + userId: string + displayName: string + isOnline: boolean + isCreator: boolean +} + +export interface RoomPlayer { + id: string + name: string + emoji: string + color: string +} + +export interface RoomData { + id: string + name: string + code: string + gameName: string + members: RoomMember[] + memberPlayers: Record // userId -> players +} + +/** + * Hook to fetch and subscribe to room data when on a room page + * Returns null if not on a room page + */ +export function useRoomData() { + const pathname = usePathname() + const [socket, setSocket] = useState(null) + const [roomData, setRoomData] = useState(null) + const [isLoading, setIsLoading] = useState(false) + + // Extract roomId from pathname like /arcade/rooms/[roomId]/... + const roomId = pathname?.match(/\/arcade\/rooms\/([^/]+)/)?.[1] + + // Initialize socket connection when on a room page + useEffect(() => { + if (!roomId) { + if (socket) { + socket.disconnect() + setSocket(null) + } + return + } + + const sock = io({ path: '/api/socket' }) + setSocket(sock) + + return () => { + sock.disconnect() + } + }, [roomId]) + + useEffect(() => { + if (!roomId) { + setRoomData(null) + return + } + + setIsLoading(true) + + // Fetch initial room data + fetch(`/api/arcade/rooms/${roomId}`) + .then((res) => { + if (!res.ok) throw new Error('Failed to fetch room') + return res.json() + }) + .then((data) => { + setRoomData({ + id: data.room.id, + name: data.room.name, + code: data.room.code, + gameName: data.room.gameName, + members: data.members || [], + memberPlayers: data.memberPlayers || {}, + }) + setIsLoading(false) + }) + .catch((error) => { + console.error('Failed to fetch room data:', error) + setIsLoading(false) + }) + }, [roomId]) + + // Subscribe to real-time updates via socket + useEffect(() => { + if (!socket || !roomId) return + + const handleMemberJoined = (data: { + roomId: string + userId: string + members: RoomMember[] + memberPlayers: Record + }) => { + if (data.roomId === roomId) { + setRoomData((prev) => { + if (!prev) return null + return { + ...prev, + members: data.members, + memberPlayers: data.memberPlayers, + } + }) + } + } + + const handleMemberLeft = (data: { + roomId: string + userId: string + members: RoomMember[] + memberPlayers: Record + }) => { + if (data.roomId === roomId) { + setRoomData((prev) => { + if (!prev) return null + return { + ...prev, + members: data.members, + memberPlayers: data.memberPlayers, + } + }) + } + } + + const handleRoomPlayersUpdated = (data: { + roomId: string + memberPlayers: Record + }) => { + if (data.roomId === roomId) { + setRoomData((prev) => { + if (!prev) return null + return { + ...prev, + memberPlayers: data.memberPlayers, + } + }) + } + } + + socket.on('member-joined', handleMemberJoined) + socket.on('member-left', handleMemberLeft) + socket.on('room-players-updated', handleRoomPlayersUpdated) + + return () => { + socket.off('member-joined', handleMemberJoined) + socket.off('member-left', handleMemberLeft) + socket.off('room-players-updated', handleRoomPlayersUpdated) + } + }, [socket, roomId]) + + return { + roomData, + isLoading, + isInRoom: !!roomId, + } +}