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:
@@ -159,6 +159,7 @@ router.get('/:slug/messages', authenticate, async (req, res, next) => {
|
|||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
avatar: true,
|
avatar: true,
|
||||||
|
country: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -166,6 +167,24 @@ router.get('/:slug/messages', authenticate, async (req, res, next) => {
|
|||||||
take: parseInt(limit),
|
take: parseInt(limit),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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: event.id,
|
||||||
|
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])
|
||||||
|
);
|
||||||
|
|
||||||
// Return in chronological order (oldest first)
|
// Return in chronological order (oldest first)
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -173,11 +192,20 @@ router.get('/:slug/messages', authenticate, async (req, res, next) => {
|
|||||||
id: msg.id,
|
id: msg.id,
|
||||||
roomId: msg.roomId,
|
roomId: msg.roomId,
|
||||||
userId: msg.user.id,
|
userId: msg.user.id,
|
||||||
username: msg.user.username,
|
|
||||||
avatar: msg.user.avatar,
|
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
type: msg.type,
|
type: msg.type,
|
||||||
createdAt: msg.createdAt,
|
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),
|
||||||
|
},
|
||||||
})),
|
})),
|
||||||
hasMore: messages.length === parseInt(limit),
|
hasMore: messages.length === parseInt(limit),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ function initializeSocket(httpServer) {
|
|||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
avatar: true,
|
avatar: true,
|
||||||
|
country: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -151,16 +152,43 @@ function initializeSocket(httpServer) {
|
|||||||
take: 20,
|
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)
|
// Send message history to the joining user (reverse to chronological order)
|
||||||
socket.emit('message_history', messages.reverse().map(msg => ({
|
socket.emit('message_history', messages.reverse().map(msg => ({
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
roomId: msg.roomId,
|
roomId: msg.roomId,
|
||||||
userId: msg.user.id,
|
userId: msg.user.id,
|
||||||
username: msg.user.username,
|
|
||||||
avatar: msg.user.avatar,
|
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
type: msg.type,
|
type: msg.type,
|
||||||
createdAt: msg.createdAt,
|
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,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
avatar: 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
|
// Broadcast message to room
|
||||||
io.to(roomName).emit('event_message', {
|
io.to(roomName).emit('event_message', {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
roomId: message.roomId,
|
roomId: message.roomId,
|
||||||
userId: message.user.id,
|
userId: message.user.id,
|
||||||
username: message.user.username,
|
|
||||||
avatar: message.user.avatar,
|
|
||||||
content: message.content,
|
content: message.content,
|
||||||
type: message.type,
|
type: message.type,
|
||||||
createdAt: message.createdAt,
|
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}`);
|
console.log(`💬 Message in event ${socket.currentEventSlug} from ${socket.user.username}`);
|
||||||
|
|||||||
@@ -1,13 +1,82 @@
|
|||||||
import Avatar from '../common/Avatar';
|
import Avatar from '../common/Avatar';
|
||||||
|
|
||||||
|
// Helper function to convert country name to flag emoji
|
||||||
|
const getCountryFlag = (country) => {
|
||||||
|
if (!country) return null;
|
||||||
|
|
||||||
|
// Map of country names to their ISO 3166-1 alpha-2 codes
|
||||||
|
const countryCodeMap = {
|
||||||
|
'United States': 'US',
|
||||||
|
'United Kingdom': 'GB',
|
||||||
|
'Poland': 'PL',
|
||||||
|
'Sweden': 'SE',
|
||||||
|
'Germany': 'DE',
|
||||||
|
'France': 'FR',
|
||||||
|
'Spain': 'ES',
|
||||||
|
'Italy': 'IT',
|
||||||
|
'Netherlands': 'NL',
|
||||||
|
'Belgium': 'BE',
|
||||||
|
'Switzerland': 'CH',
|
||||||
|
'Austria': 'AT',
|
||||||
|
'Denmark': 'DK',
|
||||||
|
'Norway': 'NO',
|
||||||
|
'Finland': 'FI',
|
||||||
|
'Portugal': 'PT',
|
||||||
|
'Greece': 'GR',
|
||||||
|
'Czech Republic': 'CZ',
|
||||||
|
'Hungary': 'HU',
|
||||||
|
'Romania': 'RO',
|
||||||
|
'Bulgaria': 'BG',
|
||||||
|
'Croatia': 'HR',
|
||||||
|
'Slovakia': 'SK',
|
||||||
|
'Slovenia': 'SI',
|
||||||
|
'Lithuania': 'LT',
|
||||||
|
'Latvia': 'LV',
|
||||||
|
'Estonia': 'EE',
|
||||||
|
'Ireland': 'IE',
|
||||||
|
'Luxembourg': 'LU',
|
||||||
|
'Canada': 'CA',
|
||||||
|
'Australia': 'AU',
|
||||||
|
'New Zealand': 'NZ',
|
||||||
|
'Japan': 'JP',
|
||||||
|
'South Korea': 'KR',
|
||||||
|
'China': 'CN',
|
||||||
|
'Brazil': 'BR',
|
||||||
|
'Argentina': 'AR',
|
||||||
|
'Mexico': 'MX',
|
||||||
|
'Russia': 'RU',
|
||||||
|
'Ukraine': 'UA',
|
||||||
|
'Turkey': 'TR',
|
||||||
|
'Israel': 'IL',
|
||||||
|
'South Africa': 'ZA',
|
||||||
|
'India': 'IN',
|
||||||
|
'Singapore': 'SG',
|
||||||
|
'Malaysia': 'MY',
|
||||||
|
'Thailand': 'TH',
|
||||||
|
'Indonesia': 'ID',
|
||||||
|
'Philippines': 'PH',
|
||||||
|
'Vietnam': 'VN',
|
||||||
|
};
|
||||||
|
|
||||||
|
const code = countryCodeMap[country];
|
||||||
|
if (!code) return null;
|
||||||
|
|
||||||
|
// Convert country code to flag emoji
|
||||||
|
// Flag emojis are made from regional indicator symbols (U+1F1E6 to U+1F1FF)
|
||||||
|
const codePoints = [...code].map(char => 127397 + char.charCodeAt(0));
|
||||||
|
return String.fromCodePoint(...codePoints);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Individual Chat Message component
|
* Individual Chat Message component
|
||||||
*
|
*
|
||||||
* @param {object} message - Message object with { id, content, username, avatar, createdAt, userId/user_id }
|
* @param {object} message - Core message object with { id, content, createdAt, userId }
|
||||||
|
* @param {object} user - User data { id, username, avatar, country }
|
||||||
|
* @param {object} participant - Participant data { competitorNumber }
|
||||||
* @param {boolean} isOwn - Whether this message belongs to the current user
|
* @param {boolean} isOwn - Whether this message belongs to the current user
|
||||||
* @param {function} formatTime - Optional custom time formatter (default: toLocaleTimeString)
|
* @param {function} formatTime - Optional custom time formatter (default: toLocaleTimeString)
|
||||||
*/
|
*/
|
||||||
const ChatMessage = ({ message, isOwn, formatTime }) => {
|
const ChatMessage = ({ message, user, participant, isOwn, formatTime }) => {
|
||||||
const defaultFormatTime = (timestamp) => {
|
const defaultFormatTime = (timestamp) => {
|
||||||
return new Date(timestamp).toLocaleTimeString('en-US', {
|
return new Date(timestamp).toLocaleTimeString('en-US', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
@@ -16,6 +85,12 @@ const ChatMessage = ({ message, isOwn, formatTime }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const timeFormatter = formatTime || defaultFormatTime;
|
const timeFormatter = formatTime || defaultFormatTime;
|
||||||
|
const countryFlag = getCountryFlag(user?.country);
|
||||||
|
|
||||||
|
// Fallback for missing user data
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex ${isOwn ? 'justify-end' : 'justify-start'}`}>
|
<div className={`flex ${isOwn ? 'justify-end' : 'justify-start'}`}>
|
||||||
@@ -25,15 +100,29 @@ const ChatMessage = ({ message, isOwn, formatTime }) => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={message.avatar}
|
src={user.avatar}
|
||||||
username={message.username}
|
username={user.username}
|
||||||
size={32}
|
size={32}
|
||||||
title={message.username}
|
title={user.username}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-baseline space-x-2 mb-1">
|
<div className="flex items-baseline space-x-2 mb-1">
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900 flex items-center gap-1.5">
|
||||||
{message.username}
|
{countryFlag && (
|
||||||
|
<span
|
||||||
|
className="text-base leading-none"
|
||||||
|
style={{ fontFamily: 'Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, sans-serif' }}
|
||||||
|
title={user.country}
|
||||||
|
>
|
||||||
|
{countryFlag}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>{user.username}</span>
|
||||||
|
{participant?.competitorNumber && (
|
||||||
|
<span className="text-xs font-normal text-gray-600">
|
||||||
|
#{participant.competitorNumber}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
{timeFormatter(message.createdAt)}
|
{timeFormatter(message.createdAt)}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import ChatMessage from './ChatMessage';
|
|||||||
* Chat Message List component with infinite scroll support
|
* Chat Message List component with infinite scroll support
|
||||||
*
|
*
|
||||||
* @param {Array} messages - Array of message objects
|
* @param {Array} messages - Array of message objects
|
||||||
|
* @param {Object} userCache - Map of userId to user data
|
||||||
|
* @param {Object} participantCache - Map of userId to participant data
|
||||||
* @param {number} currentUserId - Current user's ID to determine message ownership
|
* @param {number} currentUserId - Current user's ID to determine message ownership
|
||||||
* @param {React.Ref} messagesEndRef - Ref for scroll-to-bottom functionality
|
* @param {React.Ref} messagesEndRef - Ref for scroll-to-bottom functionality
|
||||||
* @param {React.Ref} messagesContainerRef - Ref for the scrollable container
|
* @param {React.Ref} messagesContainerRef - Ref for the scrollable container
|
||||||
@@ -15,6 +17,8 @@ import ChatMessage from './ChatMessage';
|
|||||||
*/
|
*/
|
||||||
const ChatMessageList = ({
|
const ChatMessageList = ({
|
||||||
messages = [],
|
messages = [],
|
||||||
|
userCache = {},
|
||||||
|
participantCache = {},
|
||||||
currentUserId,
|
currentUserId,
|
||||||
messagesEndRef,
|
messagesEndRef,
|
||||||
messagesContainerRef,
|
messagesContainerRef,
|
||||||
@@ -48,10 +52,16 @@ const ChatMessageList = ({
|
|||||||
const messageUserId = message.userId ?? message.user_id;
|
const messageUserId = message.userId ?? message.user_id;
|
||||||
const isOwnMessage = messageUserId === currentUserId;
|
const isOwnMessage = messageUserId === currentUserId;
|
||||||
|
|
||||||
|
// Get user and participant data from caches
|
||||||
|
const user = userCache[messageUserId];
|
||||||
|
const participant = participantCache[messageUserId];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
|
user={user}
|
||||||
|
participant={participant}
|
||||||
isOwn={isOwnMessage}
|
isOwn={isOwnMessage}
|
||||||
formatTime={formatTime}
|
formatTime={formatTime}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -34,6 +34,26 @@ const useEventChat = (slug, userId, event, messagesContainerRef) => {
|
|||||||
const [loadingOlder, setLoadingOlder] = useState(false);
|
const [loadingOlder, setLoadingOlder] = useState(false);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
|
||||||
|
// User and participant caches (normalized data)
|
||||||
|
const [userCache, setUserCache] = useState({});
|
||||||
|
const [participantCache, setParticipantCache] = useState({});
|
||||||
|
|
||||||
|
// Helper to update caches from message data
|
||||||
|
const updateCaches = (messageData) => {
|
||||||
|
if (messageData.user) {
|
||||||
|
setUserCache(prev => ({
|
||||||
|
...prev,
|
||||||
|
[messageData.userId]: messageData.user
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (messageData.participant) {
|
||||||
|
setParticipantCache(prev => ({
|
||||||
|
...prev,
|
||||||
|
[messageData.userId]: messageData.participant
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Socket.IO connection and event listeners
|
// Socket.IO connection and event listeners
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
@@ -81,13 +101,44 @@ const useEventChat = (slug, userId, event, messagesContainerRef) => {
|
|||||||
|
|
||||||
// Receive message history (initial 20 messages)
|
// Receive message history (initial 20 messages)
|
||||||
socket.on('message_history', (history) => {
|
socket.on('message_history', (history) => {
|
||||||
setMessages(history);
|
// Update caches from all messages
|
||||||
|
history.forEach(msg => updateCaches(msg));
|
||||||
|
|
||||||
|
// Store only core message data
|
||||||
|
const coreMessages = history.map(msg => ({
|
||||||
|
id: msg.id,
|
||||||
|
userId: msg.userId,
|
||||||
|
content: msg.content,
|
||||||
|
createdAt: msg.createdAt,
|
||||||
|
roomId: msg.roomId,
|
||||||
|
type: msg.type,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setMessages(coreMessages);
|
||||||
setHasMore(history.length === 20);
|
setHasMore(history.length === 20);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Receive new messages
|
// Receive new messages
|
||||||
socket.on('event_message', (message) => {
|
socket.on('event_message', (message) => {
|
||||||
setMessages((prev) => [...prev, message]);
|
// Update caches with user/participant data
|
||||||
|
updateCaches(message);
|
||||||
|
|
||||||
|
setMessages((prev) => {
|
||||||
|
// Prevent duplicates - check if message ID already exists
|
||||||
|
if (prev.some(m => m.id === message.id)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store only core message data
|
||||||
|
return [...prev, {
|
||||||
|
id: message.id,
|
||||||
|
userId: message.userId,
|
||||||
|
content: message.content,
|
||||||
|
createdAt: message.createdAt,
|
||||||
|
roomId: message.roomId,
|
||||||
|
type: message.type,
|
||||||
|
}];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Receive active users list
|
// Receive active users list
|
||||||
@@ -161,13 +212,29 @@ const useEventChat = (slug, userId, event, messagesContainerRef) => {
|
|||||||
const response = await eventsAPI.getMessages(slug, oldestMessageId, 20);
|
const response = await eventsAPI.getMessages(slug, oldestMessageId, 20);
|
||||||
|
|
||||||
if (response.data.length > 0) {
|
if (response.data.length > 0) {
|
||||||
|
// Update caches from all loaded messages
|
||||||
|
response.data.forEach(msg => updateCaches(msg));
|
||||||
|
|
||||||
// Save current scroll position
|
// Save current scroll position
|
||||||
const container = messagesContainerRef.current;
|
const container = messagesContainerRef.current;
|
||||||
const oldScrollHeight = container?.scrollHeight || 0;
|
const oldScrollHeight = container?.scrollHeight || 0;
|
||||||
const oldScrollTop = container?.scrollTop || 0;
|
const oldScrollTop = container?.scrollTop || 0;
|
||||||
|
|
||||||
// Prepend older messages
|
// Prepend older messages (filter out any duplicates) - store only core data
|
||||||
setMessages((prev) => [...response.data, ...prev]);
|
setMessages((prev) => {
|
||||||
|
const existingIds = new Set(prev.map(m => m.id));
|
||||||
|
const newMessages = response.data
|
||||||
|
.filter(m => !existingIds.has(m.id))
|
||||||
|
.map(msg => ({
|
||||||
|
id: msg.id,
|
||||||
|
userId: msg.userId,
|
||||||
|
content: msg.content,
|
||||||
|
createdAt: msg.createdAt,
|
||||||
|
roomId: msg.roomId,
|
||||||
|
type: msg.type,
|
||||||
|
}));
|
||||||
|
return [...newMessages, ...prev];
|
||||||
|
});
|
||||||
setHasMore(response.hasMore);
|
setHasMore(response.hasMore);
|
||||||
|
|
||||||
// Restore scroll position (adjust for new content)
|
// Restore scroll position (adjust for new content)
|
||||||
@@ -196,6 +263,9 @@ const useEventChat = (slug, userId, event, messagesContainerRef) => {
|
|||||||
isConnected,
|
isConnected,
|
||||||
loadingOlder,
|
loadingOlder,
|
||||||
hasMore,
|
hasMore,
|
||||||
|
// Caches for normalized data
|
||||||
|
userCache,
|
||||||
|
participantCache,
|
||||||
// Actions
|
// Actions
|
||||||
sendMessage,
|
sendMessage,
|
||||||
loadOlderMessages
|
loadOlderMessages
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const EventChatPage = () => {
|
|||||||
isConnected,
|
isConnected,
|
||||||
loadingOlder,
|
loadingOlder,
|
||||||
hasMore,
|
hasMore,
|
||||||
|
userCache,
|
||||||
|
participantCache,
|
||||||
sendMessage: handleSendMessage,
|
sendMessage: handleSendMessage,
|
||||||
loadOlderMessages
|
loadOlderMessages
|
||||||
} = useEventChat(slug, user?.id, event, messagesContainerRef);
|
} = useEventChat(slug, user?.id, event, messagesContainerRef);
|
||||||
@@ -478,6 +480,8 @@ const EventChatPage = () => {
|
|||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
<ChatMessageList
|
<ChatMessageList
|
||||||
messages={messages}
|
messages={messages}
|
||||||
|
userCache={userCache}
|
||||||
|
participantCache={participantCache}
|
||||||
currentUserId={user.id}
|
currentUserId={user.id}
|
||||||
messagesEndRef={messagesEndRef}
|
messagesEndRef={messagesEndRef}
|
||||||
messagesContainerRef={messagesContainerRef}
|
messagesContainerRef={messagesContainerRef}
|
||||||
|
|||||||
Reference in New Issue
Block a user