feat: add socket listener and polling for approval notifications

When users request to join an approval-only room, they now receive real-time
notifications when their request is approved:

- Add socket.io-client listener for 'join-request-approved' events
- Implement polling fallback (every 5 seconds) to check approval status
- Automatically join room when approval is detected via socket or polling
- Apply to both share link page and JoinRoomModal

This completes the approval flow - users no longer need to reload the page
to see if their join request was approved.

🤖 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-14 07:37:11 -05:00
parent ba916e0f65
commit 35b4a72c8b
2 changed files with 127 additions and 1 deletions

View File

@@ -2,6 +2,7 @@
import { useRouter } from 'next/navigation'
import { useCallback, useEffect, useState } from 'react'
import { io } from 'socket.io-client'
import { useGetRoomByCode, useJoinRoom, useRoomData } from '@/hooks/useRoomData'
import { getRoomDisplayWithEmoji } from '@/utils/room-display'
@@ -351,6 +352,61 @@ export default function JoinRoomPage({ params }: { params: { code: string } }) {
}
}
// Socket listener and polling for approval notifications
useEffect(() => {
if (!approvalRequested || !targetRoomData) return
console.log('[Join Page] Setting up approval listeners for room:', targetRoomData.id)
// Socket listener for real-time approval notification
const socket = io({ path: '/api/socket' })
socket.on('connect', () => {
console.log('[Join Page] Socket connected')
})
socket.on('join-request-approved', (data: { roomId: string; requestId: string }) => {
console.log('[Join Page] Request approved via socket!', data)
if (data.roomId === targetRoomData.id) {
console.log('[Join Page] Joining room automatically...')
handleJoin(targetRoomData.id)
}
})
socket.on('connect_error', (error) => {
console.error('[Join Page] Socket connection error:', error)
})
// Polling fallback - check every 5 seconds
const pollInterval = setInterval(async () => {
try {
console.log('[Join Page] Polling for approval status...')
const res = await fetch(`/api/arcade/rooms/${targetRoomData.id}/join-requests`)
if (res.ok) {
const data = await res.json()
// Check if any request for this user was approved
const approvedRequest = data.requests?.find(
(r: { status: string }) => r.status === 'approved'
)
if (approvedRequest) {
console.log('[Join Page] Request approved via polling! Joining room...')
clearInterval(pollInterval)
socket.disconnect()
handleJoin(targetRoomData.id)
}
}
} catch (err) {
console.error('[Join Page] Failed to poll join requests:', err)
}
}, 5000)
return () => {
console.log('[Join Page] Cleaning up approval listeners')
socket.disconnect()
clearInterval(pollInterval)
}
}, [approvalRequested, targetRoomData, handleJoin])
// Only show error page for non-password and non-approval errors
if (error && !showPasswordPrompt && !showApprovalPrompt) {
return (

View File

@@ -1,4 +1,5 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { io } from 'socket.io-client'
import { Modal } from '@/components/common/Modal'
import type { schema } from '@/db'
import { useRoomData } from '@/hooks/useRoomData'
@@ -142,6 +143,75 @@ export function JoinRoomModal({ isOpen, onClose, onSuccess }: JoinRoomModalProps
}
}
// Socket listener and polling for approval notifications
useEffect(() => {
if (!approvalRequested || !roomInfo) return
console.log('[JoinRoomModal] Setting up approval listeners for room:', roomInfo.id)
// Socket listener for real-time approval notification
const socket = io({ path: '/api/socket' })
socket.on('connect', () => {
console.log('[JoinRoomModal] Socket connected')
})
socket.on('join-request-approved', async (data: { roomId: string; requestId: string }) => {
console.log('[JoinRoomModal] Request approved via socket!', data)
if (data.roomId === roomInfo.id) {
console.log('[JoinRoomModal] Joining room automatically...')
try {
await joinRoom(roomInfo.id)
handleClose()
onSuccess?.()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to join room')
setIsLoading(false)
}
}
})
socket.on('connect_error', (error) => {
console.error('[JoinRoomModal] Socket connection error:', error)
})
// Polling fallback - check every 5 seconds
const pollInterval = setInterval(async () => {
try {
console.log('[JoinRoomModal] Polling for approval status...')
const res = await fetch(`/api/arcade/rooms/${roomInfo.id}/join-requests`)
if (res.ok) {
const data = await res.json()
// Check if any request for this user was approved
const approvedRequest = data.requests?.find(
(r: { status: string }) => r.status === 'approved'
)
if (approvedRequest) {
console.log('[JoinRoomModal] Request approved via polling! Joining room...')
clearInterval(pollInterval)
socket.disconnect()
try {
await joinRoom(roomInfo.id)
handleClose()
onSuccess?.()
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to join room')
setIsLoading(false)
}
}
}
} catch (err) {
console.error('[JoinRoomModal] Failed to poll join requests:', err)
}
}, 5000)
return () => {
console.log('[JoinRoomModal] Cleaning up approval listeners')
socket.disconnect()
clearInterval(pollInterval)
}
}, [approvalRequested, roomInfo, joinRoom, handleClose, onSuccess])
return (
<Modal isOpen={isOpen} onClose={handleClose}>
<div