Spaces:
Running
Running
| 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> | |
| ); | |
| } |