Spaces:
Configuration error
Configuration error
import { useState, useRef, useEffect } from 'react' | |
import { useChatStore } from '@/store/chatStore' | |
import { useAuthStore } from '@/store/authStore' | |
import { Button } from '@/components/ui/button' | |
import { Input } from '@/components/ui/input' | |
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' | |
import { ScrollArea } from '@/components/ui/scroll-area' | |
import { | |
Send, | |
Paperclip, | |
Smile, | |
Phone, | |
Video, | |
MoreVertical, | |
Users, | |
Info | |
} from 'lucide-react' | |
import { formatTime, getInitials } from '@/lib/utils' | |
export default function ChatWindow() { | |
const { user } = useAuthStore() | |
const { currentChat, messages, sendMessage, loading } = useChatStore() | |
const [messageText, setMessageText] = useState('') | |
const messagesEndRef = useRef<HTMLDivElement>(null) | |
const inputRef = useRef<HTMLInputElement>(null) | |
const chatMessages = currentChat ? messages[currentChat.id] || [] : [] | |
useEffect(() => { | |
scrollToBottom() | |
}, [chatMessages]) | |
const scrollToBottom = () => { | |
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) | |
} | |
const handleSendMessage = async () => { | |
if (!messageText.trim() || !currentChat) return | |
try { | |
await sendMessage(currentChat.id, messageText.trim()) | |
setMessageText('') | |
inputRef.current?.focus() | |
} catch (error) { | |
console.error('Failed to send message:', error) | |
} | |
} | |
const handleKeyPress = (e: React.KeyboardEvent) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault() | |
handleSendMessage() | |
} | |
} | |
const getChatDisplayName = () => { | |
if (!currentChat) return '' | |
if (currentChat.type === 'group') { | |
return currentChat.name || 'Unnamed Group' | |
} else { | |
const otherParticipant = currentChat.participants.find(p => p.userId !== user?.id) | |
return otherParticipant?.user.displayName || 'Unknown User' | |
} | |
} | |
const getChatAvatar = () => { | |
if (!currentChat) return undefined | |
if (currentChat.type === 'group') { | |
return currentChat.avatar | |
} else { | |
const otherParticipant = currentChat.participants.find(p => p.userId !== user?.id) | |
return otherParticipant?.user.avatar | |
} | |
} | |
const isMessageFromCurrentUser = (message: any) => { | |
return message.senderId === user?.id | |
} | |
if (!currentChat) { | |
return ( | |
<div className="flex-1 flex items-center justify-center"> | |
<div className="text-center"> | |
<div className="text-6xl mb-4">π¬</div> | |
<h2 className="text-2xl font-semibold mb-2">Welcome to ChatApp</h2> | |
<p className="text-muted-foreground"> | |
Select a chat to start messaging | |
</p> | |
</div> | |
</div> | |
) | |
} | |
return ( | |
<div className="flex flex-col h-full"> | |
{/* Header */} | |
<div className="flex items-center justify-between p-4 border-b border-border"> | |
<div className="flex items-center space-x-3"> | |
<Avatar className="w-10 h-10"> | |
<AvatarImage src={getChatAvatar()} /> | |
<AvatarFallback> | |
{currentChat.type === 'group' ? ( | |
<Users className="w-5 h-5" /> | |
) : ( | |
getInitials(getChatDisplayName()) | |
)} | |
</AvatarFallback> | |
</Avatar> | |
<div> | |
<h2 className="font-semibold">{getChatDisplayName()}</h2> | |
<p className="text-sm text-muted-foreground"> | |
{currentChat.type === 'group' | |
? `${currentChat.participants.length} members` | |
: 'Online' | |
} | |
</p> | |
</div> | |
</div> | |
<div className="flex items-center space-x-2"> | |
<Button variant="ghost" size="icon"> | |
<Phone className="w-4 h-4" /> | |
</Button> | |
<Button variant="ghost" size="icon"> | |
<Video className="w-4 h-4" /> | |
</Button> | |
<Button variant="ghost" size="icon"> | |
<Info className="w-4 h-4" /> | |
</Button> | |
<Button variant="ghost" size="icon"> | |
<MoreVertical className="w-4 h-4" /> | |
</Button> | |
</div> | |
</div> | |
{/* Messages */} | |
<ScrollArea className="flex-1 p-4"> | |
<div className="space-y-4"> | |
{loading ? ( | |
<div className="flex items-center justify-center py-8"> | |
<div className="text-muted-foreground">Loading messages...</div> | |
</div> | |
) : chatMessages.length === 0 ? ( | |
<div className="flex items-center justify-center py-8"> | |
<div className="text-center"> | |
<div className="text-4xl mb-4">π</div> | |
<p className="text-muted-foreground"> | |
No messages yet. Start the conversation! | |
</p> | |
</div> | |
</div> | |
) : ( | |
chatMessages.map((message, index) => { | |
const isFromCurrentUser = isMessageFromCurrentUser(message) | |
const showAvatar = !isFromCurrentUser && ( | |
index === 0 || | |
chatMessages[index - 1]?.senderId !== message.senderId | |
) | |
return ( | |
<div | |
key={message.id} | |
className={`flex ${isFromCurrentUser ? 'justify-end' : 'justify-start'}`} | |
> | |
<div className={`flex max-w-[70%] ${isFromCurrentUser ? 'flex-row-reverse' : 'flex-row'}`}> | |
{showAvatar && !isFromCurrentUser && ( | |
<Avatar className="w-8 h-8 mr-2"> | |
<AvatarImage src={message.sender?.avatar} /> | |
<AvatarFallback> | |
{getInitials(message.sender?.displayName || 'U')} | |
</AvatarFallback> | |
</Avatar> | |
)} | |
<div className={`${!showAvatar && !isFromCurrentUser ? 'ml-10' : ''}`}> | |
<div | |
className={` | |
px-4 py-2 rounded-lg | |
${isFromCurrentUser | |
? 'bg-primary text-primary-foreground' | |
: 'bg-muted' | |
} | |
`} | |
> | |
{!isFromCurrentUser && showAvatar && currentChat.type === 'group' && ( | |
<p className="text-xs font-medium mb-1 opacity-70"> | |
{message.sender?.displayName} | |
</p> | |
)} | |
<p className="text-sm">{message.content}</p> | |
<div className={`flex items-center justify-end mt-1 space-x-1`}> | |
<span className="text-xs opacity-70"> | |
{formatTime(message.createdAt)} | |
</span> | |
{isFromCurrentUser && ( | |
<span className="text-xs opacity-70"> | |
{message.status === 'sent' && 'β'} | |
{message.status === 'delivered' && 'ββ'} | |
{message.status === 'read' && 'ββ'} | |
</span> | |
)} | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
) | |
}) | |
)} | |
<div ref={messagesEndRef} /> | |
</div> | |
</ScrollArea> | |
{/* Message Input */} | |
<div className="p-4 border-t border-border"> | |
<div className="flex items-center space-x-2"> | |
<Button variant="ghost" size="icon"> | |
<Paperclip className="w-4 h-4" /> | |
</Button> | |
<div className="flex-1 relative"> | |
<Input | |
ref={inputRef} | |
placeholder="Type a message..." | |
value={messageText} | |
onChange={(e) => setMessageText(e.target.value)} | |
onKeyPress={handleKeyPress} | |
className="pr-10" | |
/> | |
<Button | |
variant="ghost" | |
size="icon" | |
className="absolute right-1 top-1/2 transform -translate-y-1/2" | |
> | |
<Smile className="w-4 h-4" /> | |
</Button> | |
</div> | |
<Button | |
onClick={handleSendMessage} | |
disabled={!messageText.trim()} | |
size="icon" | |
> | |
<Send className="w-4 h-4" /> | |
</Button> | |
</div> | |
</div> | |
</div> | |
) | |
} | |