From 7273a28aeea404db72748bdbd72c8175c7d76fe3 Mon Sep 17 00:00:00 2001 From: Sewmina Date: Mon, 2 Jun 2025 08:26:56 +0530 Subject: [PATCH] tweaks to chat --- src/app/globals.css | 25 +++++++ src/components/GlobalChat.tsx | 132 +++++++++++++++++++++++++++++---- src/components/PrivyButton.tsx | 27 ++++--- 3 files changed, 160 insertions(+), 24 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 0a8ba04..a0c47b9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -31,6 +31,31 @@ body { font-weight: 400; font-style: normal; } + +/* Chat notification animation */ +@keyframes fade-in-out { + 0% { + opacity: 0; + transform: translateX(20px); + } + 10% { + opacity: 1; + transform: translateX(0); + } + 90% { + opacity: 1; + transform: translateX(0); + } + 100% { + opacity: 0; + transform: translateX(20px); + } +} + +.animate-fade-in-out { + animation: fade-in-out 3s ease-in-out forwards; +} + /* Grid animation */ @keyframes scrollGrid { from { diff --git a/src/components/GlobalChat.tsx b/src/components/GlobalChat.tsx index 12bb655..944b90b 100644 --- a/src/components/GlobalChat.tsx +++ b/src/components/GlobalChat.tsx @@ -1,9 +1,10 @@ "use client"; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { usePrivy } from '@privy-io/react-auth'; import { io, Socket } from 'socket.io-client'; -import { toast } from 'sonner'; +import { fetchUserById } from '@/shared/data_fetcher'; +import { ChatBubbleLeftIcon, XMarkIcon } from '@heroicons/react/24/outline'; interface ChatMessage { id: string; @@ -12,18 +13,38 @@ interface ChatMessage { 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(process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3001', { + const socketInstance = io('http://api.duelfi.io:3040', { auth: { token: user?.id // Using Privy user ID as authentication } @@ -31,24 +52,67 @@ export default function GlobalChat() { socketInstance.on('connect', () => { setIsConnected(true); - toast.success('Connected to chat'); }); socketInstance.on('disconnect', () => { setIsConnected(false); - toast.error('Disconnected from chat'); }); - socketInstance.on('chat message', (message: ChatMessage) => { + 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]); + }, [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(); @@ -73,24 +137,64 @@ export default function GlobalChat() { ); } + if (isCollapsed) { + return ( +
+ {notification && ( +
+
{notification.username}
+
{notification.message}
+
+ )} + +
+ ); + } + return (
-
-

Global Chat

-
- {isConnected ? 'Connected' : 'Disconnected'} +
+
+

Global Chat

+
+ {isConnected ? 'Connected' : 'Disconnected'} +
+
{messages.map((msg) => ( -
-
- {msg.user === user?.id ? 'You' : `User ${msg.user.slice(0, 6)}`} +
+
+ + {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' })} +
{msg.message}
))} +
diff --git a/src/components/PrivyButton.tsx b/src/components/PrivyButton.tsx index de67284..856d52d 100644 --- a/src/components/PrivyButton.tsx +++ b/src/components/PrivyButton.tsx @@ -215,13 +215,26 @@ export default function PrivyButton() { }; useEffect(() => { - const intervalId = setInterval(() => { + if (wallets) { fetchSolBalance(); - }, 15000); // 5000 milliseconds = 5 seconds + } + }, [user, wallets]); + + useEffect(() => { + const intervalId = setInterval(() => { + if (wallets && solWallet) { + fetchSolBalance(); + } + }, 15000); // 15000 milliseconds = 15 seconds - // Cleanup function to clear the interval on unmount or when `ready` changes + // Initial fetch + if (wallets && solWallet) { + fetchSolBalance(); + } + + // Cleanup function to clear the interval on unmount or when dependencies change return () => clearInterval(intervalId); - }, [ready]); + }, [wallets, solWallet]); const saveProfileChanges = async () => { if (!user) { @@ -273,12 +286,6 @@ export default function PrivyButton() { } }; - useEffect(() => { - if (wallets) { - fetchSolBalance(); - } - }, [user, wallets]); - useEffect(() => { function handleClickOutside(event: MouseEvent) { if (modalRef.current && !modalRef.current.contains(event.target as Node)) {