tweaks to chat
This commit is contained in:
parent
c59ef43710
commit
7273a28aee
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Socket | null>(null);
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [userData, setUserData] = useState<Record<string, UserData>>({});
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
const [notification, setNotification] = useState<{ message: string; username: string } | null>(null);
|
||||
const notificationTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(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 (
|
||||
<div className="fixed bottom-4 right-4 flex items-center gap-2">
|
||||
{notification && (
|
||||
<div className="bg-[rgb(40,40,40)] text-white px-4 py-2 rounded-lg shadow-lg animate-fade-in-out">
|
||||
<div className="text-sm font-semibold">{notification.username}</div>
|
||||
<div className="text-sm text-gray-300">{notification.message}</div>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setIsCollapsed(false)}
|
||||
className="bg-[rgb(248,144,22)] text-black p-4 rounded-full shadow-lg hover:scale-110 transition-all duration-300"
|
||||
>
|
||||
<ChatBubbleLeftIcon className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 w-80 bg-[rgb(30,30,30)] rounded-lg shadow-lg">
|
||||
<div className="p-4 border-b border-gray-700">
|
||||
<h3 className="text-lg font-semibold text-white">Global Chat</h3>
|
||||
<div className="text-sm text-gray-400">
|
||||
{isConnected ? 'Connected' : 'Disconnected'}
|
||||
<div className="p-4 border-b border-gray-700 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white">Global Chat</h3>
|
||||
<div className="text-sm text-gray-400">
|
||||
{isConnected ? 'Connected' : 'Disconnected'}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(true)}
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<XMarkIcon className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="h-96 overflow-y-auto p-4 space-y-2">
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id} className="bg-[rgb(40,40,40)] p-2 rounded">
|
||||
<div className="text-sm text-gray-400">
|
||||
{msg.user === user?.id ? 'You' : `User ${msg.user.slice(0, 6)}`}
|
||||
<div
|
||||
key={msg.id}
|
||||
className={`p-2 rounded ${
|
||||
msg.user === user?.id
|
||||
? 'bg-[rgb(100,60,10)] bg-opacity-20'
|
||||
: 'bg-[rgb(40,40,40)]'
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm text-gray-400 flex justify-between items-center">
|
||||
<span>
|
||||
{msg.user === user?.id ? 'You' : userData[msg.user]?.username || `User ${msg.user.slice(0, 6)}`}
|
||||
</span>
|
||||
<span className="text-xs">
|
||||
{new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-white">{msg.message}</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<form onSubmit={sendMessage} className="p-4 border-t border-gray-700">
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user