Files
spotlightcam/frontend/src/hooks/useEventChat.js

206 lines
5.6 KiB
JavaScript
Raw Normal View History

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;