181 lines
4.8 KiB
JavaScript
181 lines
4.8 KiB
JavaScript
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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 !== 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');
|
||
|
|
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, 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;
|