anycoder-ed280f1c / components /ChatInterface.js
samirerty's picture
Upload components/ChatInterface.js with huggingface_hub
4d277b2 verified
import { useState, useEffect, useRef } from 'react';
import { ArrowRight, Send, Trash2, Reply, MoreVertical } from 'lucide-react';
export default function ChatInterface({ room, user, onBack }) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [replyTo, setReplyTo] = useState(null);
const [selectedMessage, setSelectedMessage] = useState(null);
const messagesEndRef = useRef(null);
const longPressTimer = useRef(null);
// بارگذاری پیام‌ها
useEffect(() => {
const saved = localStorage.getItem(`chat_msgs_${room.id}`);
if (saved) {
setMessages(JSON.parse(saved));
} else {
// پیام خوش‌آمدگویی پیش‌فرض
const welcomeMsg = {
id: 'welcome',
text: `به اتاق "${room.name}" خوش آمدید!`,
sender: 'system',
timestamp: new Date().toISOString(),
isSystem: true
};
setMessages([welcomeMsg]);
localStorage.setItem(`chat_msgs_${room.id}`, JSON.stringify([welcomeMsg]));
}
scrollToBottom();
}, [room.id]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
const saveMessages = (newMessages) => {
setMessages(newMessages);
localStorage.setItem(`chat_msgs_${room.id}`, JSON.stringify(newMessages));
};
const handleSend = (e) => {
e.preventDefault();
if (!input.trim()) return;
const newMessage = {
id: Date.now().toString(),
text: input,
sender: user,
timestamp: new Date().toISOString(),
replyTo: replyTo,
reactions: []
};
saveMessages([...messages, newMessage]);
setInput('');
setReplyTo(null);
scrollToBottom();
};
const handleDelete = (msgId) => {
const newMessages = messages.filter(m => m.id !== msgId);
saveMessages(newMessages);
setSelectedMessage(null);
};
const handleReply = (msg) => {
setReplyTo(msg);
setSelectedMessage(null);
document.getElementById('chat-input')?.focus();
};
const handleReaction = (msgId, emoji) => {
const newMessages = messages.map(m => {
if (m.id === msgId) {
const exists = m.reactions?.find(r => r.user === user);
if (exists) {
return { ...m, reactions: m.reactions.filter(r => r.user !== user).concat({ user, emoji }) };
}
return { ...m, reactions: [...(m.reactions || []), { user, emoji }] };
}
return m;
});
saveMessages(newMessages);
setSelectedMessage(null);
};
// هندل کردن لمس طولانی برای نمایش منو
const startLongPress = (msg) => {
longPressTimer.current = setTimeout(() => {
setSelectedMessage(msg);
}, 500);
};
const cancelLongPress = () => {
clearTimeout(longPressTimer.current);
};
const findReplyMessage = (replyId) => messages.find(m => m.id === replyId);
return (
<div className="flex flex-col h-screen pt-20 bg-gradient-to-b from-transparent to-black/20">
{/* هدر چت */}
<div className="glass-panel px-4 py-3 flex items-center gap-3 border-b border-glassBorder">
<button onClick={onBack} className="p-2 -mr-2 text-white/70 hover:text-white">
<ArrowRight className="w-6 h-6 rotate-180" />
</button>
<div className="flex-1">
<h2 className="text-white font-bold">{room.name}</h2>
<p className="text-primary text-xs">در حال گفتگو...</p>
</div>
</div>
{/* لیست پیام‌ها */}
<div className="flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar">
{messages.map((msg) => {
const isMe = msg.sender === user;
const replyMsg = msg.replyTo ? findReplyMessage(msg.replyTo) : null;
return (
<div
key={msg.id}
onContextMenu={(e) => { e.preventDefault(); setSelectedMessage(msg); }}
onTouchStart={() => !msg.isSystem && startLongPress(msg)}
onTouchEnd={cancelLongPress}
onMouseDown={() => !msg.isSystem && startLongPress(msg)}
onMouseUp={cancelLongPress}
className={`flex ${isMe ? 'justify-end' : 'justify-start'} group relative animate-slide-in`}
>
<div className={`max-w-[80%] ${isMe ? 'order-2' : 'order-1'}`}>
{/* پاسخ به پیام */}
{replyMsg && (
<div className={`flex items-center gap-2 mb-1 text-xs px-2 py-1 rounded-t-lg border-b ${
isMe
? 'bg-primary/20 border-primary/30 text-primary/80 text-right'
: 'bg-white/5 border-white/10 text-white/50 text-right'
}`}>
<Reply className="w-3 h-3" />
<span className="truncate max-w-[150px]">{replyMsg.text}</span>
</div>
)}
{/* بدنه پیام */}
<div className={`relative px-4 py-2 rounded-2xl ${
msg.isSystem
? 'bg-transparent text-center text-white/40 text-sm w-full'
: isMe
? 'bg-primary text-white rounded-tr-none'
: 'glass-panel text-white rounded-tl-none'
}`}>
{!isMe && !msg.isSystem && (
<span className="text-xs text-primary/70 block mb-1">{msg.sender}</span>
)}
<p className="text-sm leading-relaxed whitespace-pre-wrap">{msg.text}</p>
<span className={`text-[10px] opacity-50 block mt-1 ${isMe ? 'text-right' : 'text-left'}`}>
{new Date(msg.timestamp).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' })}
</span>
{/* ری‌اکشن‌ها */}
{msg.reactions && msg.reactions.length > 0 && (
<div className="absolute -bottom-2 left-0 flex -space-x-1 space-x-reverse">
{msg.reactions.map((r, i) => (
<span key={i} className="text-sm bg-black/50 rounded-full w-5 h-5 flex items-center justify-center backdrop-blur-sm">
{r.emoji}
</span>
))}
</div>
)}
</div>
</div>
</div>
);
})}
<div ref={messagesEndRef} />
</div>
{/* منوی عملیات (ری‌اکشن و حذف) */}
{selectedMessage && (
<div className="absolute inset-0 z-50 bg-black/40" onClick={() => setSelectedMessage(null)}>
<div
className={`absolute glass-panel p-2 rounded-xl flex gap-2 shadow-2xl animate-slide-in ${
selectedMessage.sender === user ? 'left-4 bottom-24' : 'right-4 bottom-24'
}`}
onClick={(e) => e.stopPropagation()}
>
{['❤️', '😂', '😮', '😢', '😡', '👍'].map(emoji => (
<button
key={emoji}
onClick={() => handleReaction(selectedMessage.id, emoji)}
className="w-10 h-10 rounded-lg hover:bg-white/20 text-xl transition-transform active:scale-90"
>
{emoji}
</button>
))}
<div className="w-px bg-white/10 mx-1" />
<button
onClick={() => handleReply(selectedMessage)}
className="w-10 h-10 rounded-lg hover:bg-blue-500/30 text-blue-400 flex items-center justify-center"
>
<Reply className="w-5 h-5" />
</button>
{selectedMessage.sender === user && (
<button
onClick={() => handleDelete(selectedMessage.id)}
className="w-10 h-10 rounded-lg hover:bg-red-500/30 text-red-400 flex items-center justify-center"
>
<Trash2 className="w-5 h-5" />
</button>
)}
</div>
</div>
)}
{/* ورودی پیام */}
<div className="p-4 glass-panel border-t border-glassBorder">
{replyTo && (
<div className="flex items-center justify-between bg-white/5 p-2 rounded-t-lg border-b border-white/10 mb-2">
<div className="flex items-center gap-2 text-xs text-white/60">
<Reply className="w-3 h-3" />
<span>پاسخ به: {replyTo.text}</span>
</div>
<button onClick={() => setReplyTo(null)} className="text-white/40 hover:text-white">
</button>
</div>
)}
<form onSubmit={handleSend} className="flex gap-2">
<input
id="chat-input"
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="پیام خود را بنویسید..."
className="glass-input flex-1 p-3 rounded-xl"
/>
<button
type="submit"
disabled={!input.trim()}
className="bg-primary hover:bg-indigo-600 disabled:opacity-50 text-white p-3 rounded-xl transition-colors"
>
<Send className="w-5 h-5 rotate-180" />
</button>
</form>
</div>
</div>
);
}