160 lines
4.8 KiB
TypeScript
160 lines
4.8 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { io, Socket } from 'socket.io-client';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Card } from '@/components/ui/card';
|
|
import { User, fetchUsers } from '@/utils/api';
|
|
|
|
interface ChatMessage {
|
|
id: string;
|
|
user: string;
|
|
message: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
export default function ChatModeration() {
|
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
const [socket, setSocket] = useState<Socket | null>(null);
|
|
const [editingMessage, setEditingMessage] = useState<string | null>(null);
|
|
const [editText, setEditText] = useState('');
|
|
const [users, setUsers] = useState<User[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const loadUsers = async () => {
|
|
try {
|
|
const usersData = await fetchUsers();
|
|
setUsers(usersData);
|
|
} catch (error) {
|
|
console.error('Error loading users:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadUsers();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const newSocket = io('https://wschat.duelfi.io');
|
|
setSocket(newSocket);
|
|
|
|
newSocket.on('recent messages', (recentMessages: ChatMessage[]) => {
|
|
setMessages(recentMessages);
|
|
});
|
|
|
|
newSocket.on('chat message', (message: ChatMessage) => {
|
|
setMessages(prev => [...prev, message]);
|
|
});
|
|
|
|
newSocket.on('message edited', ({ messageId, newMessage }: { messageId: string; newMessage: string }) => {
|
|
setMessages(prev =>
|
|
prev.map(msg =>
|
|
msg.id === messageId ? { ...msg, message: newMessage } : msg
|
|
)
|
|
);
|
|
});
|
|
|
|
newSocket.on('message deleted', (messageId: string) => {
|
|
setMessages(prev => prev.filter(msg => msg.id !== messageId));
|
|
});
|
|
|
|
return () => {
|
|
newSocket.close();
|
|
};
|
|
}, []);
|
|
|
|
const getUserDisplayName = (userId: string): string => {
|
|
const user = users.find(u => u.id === userId);
|
|
if (user?.username) {
|
|
return user.username;
|
|
}
|
|
// If no username found, return a truncated version of the ID
|
|
return userId.startsWith('did:privy:')
|
|
? `User ${userId.slice(-4)}`
|
|
: userId;
|
|
};
|
|
|
|
const handleEdit = (message: ChatMessage) => {
|
|
setEditingMessage(message.id);
|
|
setEditText(message.message);
|
|
};
|
|
|
|
const handleSaveEdit = (messageId: string) => {
|
|
if (socket) {
|
|
socket.emit('edit message', {
|
|
messageId,
|
|
newMessage: editText
|
|
});
|
|
setEditingMessage(null);
|
|
}
|
|
};
|
|
|
|
const handleDelete = (messageId: string) => {
|
|
if (socket && confirm('Are you sure you want to delete this message?')) {
|
|
socket.emit('delete message', messageId);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="text-lg text-[var(--text-primary)]">Loading...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="container mx-auto p-4">
|
|
<h1 className="text-2xl font-bold mb-4">Chat Moderation</h1>
|
|
<div className="space-y-4">
|
|
{messages.map((message) => (
|
|
<Card key={message.id} className="p-4">
|
|
<div className="flex justify-between items-start">
|
|
<div className="flex-1">
|
|
<div className="text-sm text-[var(--text-secondary)]">
|
|
{getUserDisplayName(message.user)} • {new Date(message.timestamp).toLocaleString()}
|
|
</div>
|
|
{editingMessage === message.id ? (
|
|
<div className="mt-2 flex gap-2">
|
|
<Input
|
|
value={editText}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEditText(e.target.value)}
|
|
className="flex-1"
|
|
/>
|
|
<Button onClick={() => handleSaveEdit(message.id)}>Save</Button>
|
|
<Button variant="outline" onClick={() => setEditingMessage(null)}>
|
|
Cancel
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<div className="mt-1 text-[var(--text-primary)]">{message.message}</div>
|
|
)}
|
|
</div>
|
|
{editingMessage !== message.id && (
|
|
<div className="flex gap-2 ml-4">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleEdit(message)}
|
|
>
|
|
Edit
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={() => handleDelete(message.id)}
|
|
>
|
|
Delete
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|