From dd3176196e88229143b5d7c69a665bfc5e6e2d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Tue, 2 Dec 2025 23:38:46 +0100 Subject: [PATCH] fix(chat): real-time active users list updates Fixed issue where active users list in event chat did not update in real-time when new users joined. Users had to refresh the page to see newly joined participants. Root Cause: - getAllDisplayUsers() used checkedInUsers (loaded once from API) as base list, with activeUsers (Socket.IO real-time) only for isOnline flag - When new user joined chat, they appeared in activeUsers but not in checkedInUsers, so they were not displayed Solution: - Rewrote getAllDisplayUsers() to prioritize activeUsers (real-time data) - Merges activeUsers (online) with checkedInUsers (offline checked-in users) - Uses Socket.IO data as source of truth for online users - Enriches with database data when available (firstName, lastName, etc) - Sorts online users first, offline second Changes: - EventChatPage.jsx: Rewrote getAllDisplayUsers() to merge activeUsers with checkedInUsers, prioritizing real-time Socket.IO data - useEventChat.js: Added debug logging for active_users events - socket/index.js: Added debug logging for active_users emissions Testing: - User A in chat sees User B appear immediately when B joins - No page refresh required - Online/offline status updates in real-time --- backend/src/socket/index.js | 3 +++ frontend/src/hooks/useEventChat.js | 2 ++ frontend/src/pages/EventChatPage.jsx | 38 ++++++++++++++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/backend/src/socket/index.js b/backend/src/socket/index.js index 0dc5251..ee7018d 100644 --- a/backend/src/socket/index.js +++ b/backend/src/socket/index.js @@ -225,6 +225,7 @@ function initializeSocket(httpServer) { // Broadcast updated active users list const users = Array.from(activeUsers.get(eventId)).map(u => JSON.parse(u)); + console.log(`📡 Emitting active_users to room ${roomName}:`, users.length, 'users'); io.to(roomName).emit('active_users', users); // Notify room about new user @@ -276,6 +277,7 @@ function initializeSocket(httpServer) { // Broadcast updated list const updatedUsers = Array.from(users).map(u => JSON.parse(u)); + console.log(`📡 Emitting active_users (after leave) to room ${roomName}:`, updatedUsers.length, 'users'); io.to(roomName).emit('active_users', updatedUsers); } @@ -599,6 +601,7 @@ function initializeSocket(httpServer) { // Broadcast updated list const updatedUsers = Array.from(users).map(u => JSON.parse(u)); + console.log(`📡 Emitting active_users (after disconnect) to room ${socket.currentEventRoom}:`, updatedUsers.length, 'users'); io.to(socket.currentEventRoom).emit('active_users', updatedUsers); // Notify about user leaving diff --git a/frontend/src/hooks/useEventChat.js b/frontend/src/hooks/useEventChat.js index db661dd..f1fdb32 100644 --- a/frontend/src/hooks/useEventChat.js +++ b/frontend/src/hooks/useEventChat.js @@ -143,12 +143,14 @@ const useEventChat = (slug, userId, event, messagesContainerRef) => { // Receive active users list socket.on('active_users', (users) => { + console.log('📡 Received active_users event:', users); // Filter out duplicates and current user const uniqueUsers = users .filter((u, index, self) => index === self.findIndex((t) => t.userId === u.userId) ) .filter((u) => u.userId !== userId); + console.log('👥 Updating active users:', uniqueUsers); setActiveUsers(uniqueUsers); }); diff --git a/frontend/src/pages/EventChatPage.jsx b/frontend/src/pages/EventChatPage.jsx index 1bd4744..96d71c2 100644 --- a/frontend/src/pages/EventChatPage.jsx +++ b/frontend/src/pages/EventChatPage.jsx @@ -246,16 +246,38 @@ const EventChatPage = () => { // Combine checked-in users with online status const getAllDisplayUsers = () => { - const activeUserIds = new Set(activeUsers.map(u => u.userId)); + // Create map of userId -> user data from activeUsers (real-time socket data) + const activeUsersMap = new Map(activeUsers.map(u => [u.userId, { ...u, isOnline: true }])); - // Merge checked-in users with online status - const allUsers = checkedInUsers.map(user => ({ - ...user, - isOnline: activeUserIds.has(user.userId), - })); + // Create map of userId -> user data from checkedInUsers (database data) + const checkedInUsersMap = new Map(checkedInUsers.map(u => [u.userId, { ...u, isOnline: false }])); - // Sort: online first, then offline - return allUsers.sort((a, b) => { + // Merge: prioritize activeUsers data (it's real-time), add offline users from checkedInUsers + const mergedMap = new Map(); + + // Add all active users (online) + activeUsers.forEach(u => { + const checkedInData = checkedInUsersMap.get(u.userId); + mergedMap.set(u.userId, { + userId: u.userId, + username: u.username, + avatar: u.avatar, + // Use additional data from checkedInUsers if available + ...checkedInData, + // Override with socket data + isOnline: true, + }); + }); + + // Add offline checked-in users (not in activeUsers) + checkedInUsers.forEach(u => { + if (!mergedMap.has(u.userId)) { + mergedMap.set(u.userId, { ...u, isOnline: false }); + } + }); + + // Convert to array and sort: online first, then offline + return Array.from(mergedMap.values()).sort((a, b) => { if (a.isOnline === b.isOnline) return 0; return a.isOnline ? -1 : 1; });