"use client"; import { useEffect, useState, useRef } from 'react'; import { usePrivy } from '@privy-io/react-auth'; import { io, Socket } from 'socket.io-client'; import { fetchUserById } from '@/shared/data_fetcher'; import { ChatBubbleLeftIcon, XMarkIcon } from '@heroicons/react/24/outline'; import { profanity } from '@2toad/profanity'; interface ChatMessage { id: string; user: string; message: string; timestamp: number; } interface UserData { id: string; username: string; bio: string; x_profile_url: string; } export default function GlobalChat() { const { user, authenticated } = usePrivy(); const [socket, setSocket] = useState(null); const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(''); const [isConnected, setIsConnected] = useState(false); const [userData, setUserData] = useState>({}); const [isCollapsed, setIsCollapsed] = useState(true); const [notification, setNotification] = useState<{ message: string; username: string } | null>(null); const notificationTimeoutRef = useRef(undefined); const messagesEndRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(() => { scrollToBottom(); }, [messages]); useEffect(() => { if (!authenticated) return; // Initialize socket connection const socketInstance = io('https://wschat.duelfi.io', { auth: { token: user?.id // Using Privy user ID as authentication } }); socketInstance.on('connect', () => { setIsConnected(true); }); socketInstance.on('disconnect', () => { setIsConnected(false); }); socketInstance.on('chat message', async (message: ChatMessage) => { setMessages(prev => [...prev, message]); // Fetch user data if we haven't already if (!userData[message.user]) { const userInfo = await fetchUserById(message.user); if (userInfo) { setUserData(prev => ({ ...prev, [message.user]: userInfo })); // Show notification if chat is collapsed if (isCollapsed && message.user !== user?.id) { showNotification(message.message, userInfo.username); } } } else if (isCollapsed && message.user !== user?.id) { // Show notification if chat is collapsed and we already have the user data showNotification(message.message, userData[message.user].username); } }); socketInstance.on('recent messages', (messages: ChatMessage[]) => { setMessages(messages); messages.forEach(message => { if (!userData[message.user]) { fetchUserById(message.user).then(userInfo => { setUserData(prev => ({ ...prev, [message.user]: userInfo })); }); } }); }); setSocket(socketInstance); return () => { socketInstance.disconnect(); if (notificationTimeoutRef.current) { clearTimeout(notificationTimeoutRef.current); } }; }, [authenticated, user, isCollapsed]); const showNotification = (message: string, username: string) => { // Clear any existing notification timeout if (notificationTimeoutRef.current) { clearTimeout(notificationTimeoutRef.current); } setNotification({ message, username }); // Set new timeout to clear notification notificationTimeoutRef.current = setTimeout(() => { setNotification(null); }, 3000); }; const sendMessage = (e: React.FormEvent) => { e.preventDefault(); if (!socket || !newMessage.trim() || !user) return; const message: ChatMessage = { id: Date.now().toString(), user: user.id, message: newMessage.trim(), timestamp: Date.now() }; socket.emit('chat message', message); setNewMessage(''); }; if (!authenticated) { return (
Please log in to join the chat
); } if (isCollapsed) { return (
{notification && (
{notification.username}
{notification.message}
)}
); } return (

Global Chat

{isConnected ? 'Connected' : 'Disconnected'}
{messages.map((msg) => (
{msg.user === user?.id ? 'You' : userData[msg.user]?.username || `User ${msg.user.slice(0, 6)}`} {new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{profanity.censor(msg.message)}
))}
setNewMessage(e.target.value)} placeholder="Type a message..." className="flex-1 bg-[rgb(40,40,40)] text-white px-3 py-2 rounded focus:outline-none focus:ring-2 focus:ring-orange-500" />
); }