feat(chat): add country flags and competitor numbers with normalized data architecture
Implemented display of country flags and competitor numbers in event chat messages: - Country flags displayed as emoji (🇸🇪, 🇵🇱, etc.) with proper emoji font support - Competitor numbers shown in #123 format next to usernames - Normalized data architecture with user and participant caches on frontend - User data (username, avatar, country) and participant data (competitorNumber) cached separately - Messages store only core data (id, content, userId, createdAt) - Prevents data inconsistency when users update profile information - Fixed duplicate message keys React warning with deduplication logic - Backend sends nested user/participant objects for cache population - Auto-updates across all messages when user changes avatar or country Backend changes: - Socket.IO event_message and message_history include nested user/participant data - API /events/:slug/messages endpoint restructured with same nested format - Batch lookup of competitor numbers for efficiency Frontend changes: - useEventChat hook maintains userCache and participantCache - ChatMessage component accepts separate user/participant props - ChatMessageList performs cache lookups during render - Emoji font family support for cross-platform flag rendering
This commit is contained in:
@@ -144,6 +144,7 @@ function initializeSocket(httpServer) {
|
||||
id: true,
|
||||
username: true,
|
||||
avatar: true,
|
||||
country: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -151,16 +152,43 @@ function initializeSocket(httpServer) {
|
||||
take: 20,
|
||||
});
|
||||
|
||||
// Get competitor numbers for all users in this event
|
||||
const userIds = [...new Set(messages.map(msg => msg.user.id))];
|
||||
const eventParticipants = await prisma.eventParticipant.findMany({
|
||||
where: {
|
||||
eventId: eventId,
|
||||
userId: { in: userIds },
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
competitorNumber: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Create a map of userId to competitorNumber
|
||||
const competitorNumberMap = new Map(
|
||||
eventParticipants.map(ep => [ep.userId, ep.competitorNumber])
|
||||
);
|
||||
|
||||
// Send message history to the joining user (reverse to chronological order)
|
||||
socket.emit('message_history', messages.reverse().map(msg => ({
|
||||
id: msg.id,
|
||||
roomId: msg.roomId,
|
||||
userId: msg.user.id,
|
||||
username: msg.user.username,
|
||||
avatar: msg.user.avatar,
|
||||
content: msg.content,
|
||||
type: msg.type,
|
||||
createdAt: msg.createdAt,
|
||||
// Nested user data for caching
|
||||
user: {
|
||||
id: msg.user.id,
|
||||
username: msg.user.username,
|
||||
avatar: msg.user.avatar,
|
||||
country: msg.user.country,
|
||||
},
|
||||
// Nested participant data for caching
|
||||
participant: {
|
||||
competitorNumber: competitorNumberMap.get(msg.user.id),
|
||||
},
|
||||
})));
|
||||
}
|
||||
|
||||
@@ -249,21 +277,44 @@ function initializeSocket(httpServer) {
|
||||
id: true,
|
||||
username: true,
|
||||
avatar: true,
|
||||
country: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Get competitor number for this user in this event
|
||||
const eventParticipant = await prisma.eventParticipant.findUnique({
|
||||
where: {
|
||||
userId_eventId: {
|
||||
userId: socket.user.id,
|
||||
eventId: eventId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
competitorNumber: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Broadcast message to room
|
||||
io.to(roomName).emit('event_message', {
|
||||
id: message.id,
|
||||
roomId: message.roomId,
|
||||
userId: message.user.id,
|
||||
username: message.user.username,
|
||||
avatar: message.user.avatar,
|
||||
content: message.content,
|
||||
type: message.type,
|
||||
createdAt: message.createdAt,
|
||||
// Nested user data for caching
|
||||
user: {
|
||||
id: message.user.id,
|
||||
username: message.user.username,
|
||||
avatar: message.user.avatar,
|
||||
country: message.user.country,
|
||||
},
|
||||
// Nested participant data for caching
|
||||
participant: {
|
||||
competitorNumber: eventParticipant?.competitorNumber,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`💬 Message in event ${socket.currentEventSlug} from ${socket.user.username}`);
|
||||
|
||||
Reference in New Issue
Block a user