import { useState, useRef, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import Layout from '../components/layout/Layout'; import { useAuth } from '../contexts/AuthContext'; import { Send, UserPlus, Loader2 } from 'lucide-react'; import { connectSocket, disconnectSocket, getSocket } from '../services/socket'; import { eventsAPI } from '../services/api'; const EventChatPage = () => { const { slug } = useParams(); const { user } = useAuth(); const navigate = useNavigate(); const [event, setEvent] = useState(null); const [loading, setLoading] = useState(true); const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(''); const [activeUsers, setActiveUsers] = useState([]); const [isConnected, setIsConnected] = useState(false); const [loadingOlder, setLoadingOlder] = useState(false); const [hasMore, setHasMore] = useState(true); const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; // Fetch event data useEffect(() => { const fetchEvent = async () => { try { setLoading(true); const data = await eventsAPI.getBySlug(slug); setEvent(data); } catch (err) { console.error('Error loading event:', err); setEvent(null); } finally { setLoading(false); } }; fetchEvent(); }, [slug]); useEffect(() => { scrollToBottom(); }, [messages]); useEffect(() => { if (!event) return; // Connect to Socket.IO const socket = connectSocket(); if (!socket) { console.error('Failed to connect to socket'); return; } // Socket event listeners socket.on('connect', () => { setIsConnected(true); // Join event room socket.emit('join_event_room', { slug }); }); socket.on('disconnect', () => { setIsConnected(false); }); // Receive message history (initial 20 messages) socket.on('message_history', (history) => { setMessages(history); setHasMore(history.length === 20); }); // Receive new messages socket.on('event_message', (message) => { setMessages((prev) => [...prev, message]); }); // Receive active users list socket.on('active_users', (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 !== user.id); setActiveUsers(uniqueUsers); }); // User joined notification socket.on('user_joined', (userData) => { console.log(`${userData.username} joined the room`); }); // User left notification socket.on('user_left', (userData) => { console.log(`${userData.username} left the room`); }); // Cleanup return () => { socket.emit('leave_event_room'); socket.off('connect'); socket.off('disconnect'); socket.off('message_history'); socket.off('event_message'); socket.off('active_users'); socket.off('user_joined'); socket.off('user_left'); }; }, [event, slug, user.id]); const handleSendMessage = (e) => { e.preventDefault(); if (!newMessage.trim()) return; const socket = getSocket(); if (!socket || !socket.connected) { alert('Not connected to chat server'); return; } // Send message via Socket.IO socket.emit('send_event_message', { content: newMessage, }); setNewMessage(''); }; const loadOlderMessages = async () => { if (loadingOlder || !hasMore || messages.length === 0) return; setLoadingOlder(true); try { const oldestMessageId = messages[0].id; const response = await eventsAPI.getMessages(slug, oldestMessageId, 20); if (response.data.length > 0) { // Save current scroll position const container = messagesContainerRef.current; const oldScrollHeight = container.scrollHeight; const oldScrollTop = container.scrollTop; // Prepend older messages setMessages((prev) => [...response.data, ...prev]); setHasMore(response.hasMore); // Restore scroll position (adjust for new content) setTimeout(() => { const newScrollHeight = container.scrollHeight; container.scrollTop = oldScrollTop + (newScrollHeight - oldScrollHeight); }, 0); } else { setHasMore(false); } } catch (error) { console.error('Failed to load older messages:', error); } finally { setLoadingOlder(false); } }; const handleMatchWith = (userId) => { // TODO: Implement match request alert(`Match request sent to user!`); setTimeout(() => { navigate(`/matches/1/chat`); }, 1000); }; // Infinite scroll - detect scroll to top useEffect(() => { const container = messagesContainerRef.current; if (!container) return; const handleScroll = () => { if (container.scrollTop < 100 && !loadingOlder && hasMore) { loadOlderMessages(); } }; container.addEventListener('scroll', handleScroll); return () => container.removeEventListener('scroll', handleScroll); }, [loadingOlder, hasMore, messages]); if (loading) { return ( Loading event... ); } if (!event) { return ( Event not found ); } return ( {/* Header */} {event.name} {event.location} {isConnected ? '● Connected' : '● Disconnected'} {/* Active Users Sidebar */} Active users ({activeUsers.length}) {activeUsers.length === 0 && ( No other users online )} {activeUsers.map((activeUser) => ( {activeUser.username} handleMatchWith(activeUser.userId)} className="p-1 text-primary-600 hover:bg-primary-50 rounded" title="Connect" > ))} {/* Chat Area */} {/* Messages */} {/* Loading older messages indicator */} {loadingOlder && ( )} {messages.length === 0 && ( No messages yet. Start the conversation! )} {messages.map((message) => { const isOwnMessage = message.userId === user.id; return ( {message.username} {new Date(message.createdAt).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })} {message.content} ); })} {/* Message Input */} setNewMessage(e.target.value)} placeholder="Write a message..." disabled={!isConnected} className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-primary-500 focus:border-primary-500 disabled:opacity-50" /> ); }; export default EventChatPage;
Loading event...
{event.location}
No other users online
{activeUser.username}