import { useState, useEffect } from 'react'; import { connectSocket, getSocket } from '../services/socket'; import { eventsAPI } from '../services/api'; /** * Custom hook for Event Chat functionality * Extracts Socket.IO logic and chat state management from EventChatPage * * @param {string} slug - Event slug * @param {number} userId - Current user ID * @param {object} event - Event object (needed to check if event exists) * @param {object} messagesContainerRef - Ref to messages container for scroll management * @returns {object} Chat state and handlers * * @example * const { * messages, * isConnected, * activeUsers, * sendMessage, * newMessage, * setNewMessage, * loadOlderMessages, * loadingOlder, * hasMore * } = useEventChat(slug, user.id, event, messagesContainerRef); */ const useEventChat = (slug, userId, event, messagesContainerRef) => { // Chat state 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); // Socket.IO connection and event listeners useEffect(() => { if (!event) return; // Connect to Socket.IO const socket = connectSocket(); if (!socket) { console.error('Failed to connect to socket'); return; } // Function to join room (used on connect and reconnect) const joinRoom = () => { setIsConnected(true); socket.emit('join_event_room', { slug }); }; // Check if already connected (socket instance may already be connected) if (socket.connected) { joinRoom(); } // Socket event listeners socket.on('connect', joinRoom); socket.on('disconnect', (reason) => { setIsConnected(false); console.log('🔌 Disconnected:', reason); }); // Handle reconnection - rejoin room automatically socket.on('reconnect', (attemptNumber) => { console.log('🔄 Reconnected after', attemptNumber, 'attempts'); // Room will be joined via 'connect' event }); socket.on('reconnect_attempt', (attemptNumber) => { console.log('🔄 Reconnection attempt', attemptNumber); }); socket.on('reconnect_error', (error) => { console.error('🔄 Reconnection error:', error); }); // 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 !== userId); 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', joinRoom); socket.off('disconnect'); socket.off('reconnect'); socket.off('reconnect_attempt'); socket.off('reconnect_error'); socket.off('message_history'); socket.off('event_message'); socket.off('active_users'); socket.off('user_joined'); socket.off('user_left'); }; }, [event, slug, userId]); /** * Send a message to the event chat */ const sendMessage = (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(''); }; /** * Load older messages (pagination) * Preserves scroll position when loading older messages */ 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 || 0; const oldScrollTop = container?.scrollTop || 0; // Prepend older messages setMessages((prev) => [...response.data, ...prev]); setHasMore(response.hasMore); // Restore scroll position (adjust for new content) setTimeout(() => { if (container) { 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); } }; return { // State messages, newMessage, setNewMessage, activeUsers, isConnected, loadingOlder, hasMore, // Actions sendMessage, loadOlderMessages }; }; export default useEventChat;