189 lines
5.9 KiB
JavaScript
189 lines
5.9 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react'
|
|
import { chatAPI, API_BASE_URL } from '../api'
|
|
import '../styles/chat.css'
|
|
|
|
export default function Chat({ conversationId, onNotificationsUpdate }) {
|
|
const [conversations, setConversations] = useState([])
|
|
const [selectedConversation, setSelectedConversation] = useState(conversationId || null)
|
|
const [messages, setMessages] = useState([])
|
|
const [newMessage, setNewMessage] = useState('')
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [error, setError] = useState('')
|
|
const [isSending, setIsSending] = useState(false)
|
|
const messageInputRef = useRef(null)
|
|
|
|
const currentUserId = localStorage.getItem('user_id')
|
|
|
|
useEffect(() => {
|
|
loadConversations()
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (selectedConversation) {
|
|
loadMessages()
|
|
// Poll for new messages every 2 seconds
|
|
const interval = setInterval(loadMessages, 2000)
|
|
return () => clearInterval(interval)
|
|
}
|
|
}, [selectedConversation])
|
|
|
|
const loadConversations = async () => {
|
|
try {
|
|
setIsLoading(true)
|
|
const response = await chatAPI.getConversations()
|
|
setConversations(response.data || [])
|
|
// Notify parent about notifications update
|
|
if (onNotificationsUpdate) {
|
|
onNotificationsUpdate()
|
|
}
|
|
} catch (err) {
|
|
setError('Failed to load conversations')
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
const loadMessages = async () => {
|
|
if (!selectedConversation) return
|
|
|
|
try {
|
|
const response = await chatAPI.getMessages(selectedConversation)
|
|
setMessages(response.data || [])
|
|
|
|
// Mark messages as read
|
|
await chatAPI.markAsRead(selectedConversation)
|
|
|
|
// Update unread_count to 0 for this conversation immediately
|
|
setConversations(prev =>
|
|
prev.map(conv =>
|
|
conv.id === selectedConversation
|
|
? { ...conv, unread_count: 0 }
|
|
: conv
|
|
)
|
|
)
|
|
|
|
// Update parent notifications
|
|
if (onNotificationsUpdate) {
|
|
onNotificationsUpdate()
|
|
}
|
|
} catch (err) {
|
|
setError('Failed to load messages')
|
|
}
|
|
}
|
|
|
|
const handleSendMessage = async (e) => {
|
|
e.preventDefault()
|
|
if (!newMessage.trim() || !selectedConversation) return
|
|
|
|
setIsSending(true)
|
|
try {
|
|
const response = await chatAPI.sendMessage(selectedConversation, newMessage)
|
|
setMessages((prev) => [...prev, response.data])
|
|
setNewMessage('')
|
|
// Auto-focus back to input field after sending
|
|
setTimeout(() => messageInputRef.current?.focus(), 0)
|
|
} catch (err) {
|
|
setError('Failed to send message')
|
|
} finally {
|
|
setIsSending(false)
|
|
}
|
|
}
|
|
|
|
if (isLoading) {
|
|
return <div className="chat">Loading conversations...</div>
|
|
}
|
|
|
|
if (conversations.length === 0) {
|
|
return (
|
|
<div className="chat">
|
|
<div className="empty-state">
|
|
<h2>No conversations yet</h2>
|
|
<p>Match with someone to start chatting!</p>
|
|
<a href="/discover">Discover</a>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="chat">
|
|
<div className="chat-container">
|
|
<div className="conversations-list">
|
|
<h2>Conversations</h2>
|
|
{conversations.map((conv) => (
|
|
<div
|
|
key={conv.id}
|
|
className={`conversation-item ${selectedConversation === conv.id ? 'active' : ''}`}
|
|
onClick={() => setSelectedConversation(conv.id)}
|
|
>
|
|
<div className="conversation-header">
|
|
{conv.other_user_photo ? (
|
|
<img
|
|
src={`${API_BASE_URL}/media/${conv.other_user_photo}`}
|
|
alt={conv.other_user_display_name}
|
|
className="conversation-avatar"
|
|
/>
|
|
) : (
|
|
<div className="conversation-avatar-placeholder">
|
|
{conv.other_user_display_name.charAt(0).toUpperCase()}
|
|
</div>
|
|
)}
|
|
<div className="conversation-info">
|
|
<h4>{conv.other_user_display_name}</h4>
|
|
<p className="latest-msg">{conv.latest_message || 'No messages yet'}</p>
|
|
</div>
|
|
{conv.unread_count > 0 && (
|
|
<span className="unread-badge">{conv.unread_count}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="messages-pane">
|
|
{selectedConversation ? (
|
|
<>
|
|
<div className="messages-header">
|
|
<h3>
|
|
{conversations.find((c) => c.id === selectedConversation)?.other_user_display_name}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="messages-list">
|
|
{messages.map((msg) => (
|
|
<div
|
|
key={msg.id}
|
|
className={`message ${msg.sender_id == currentUserId ? 'sent' : 'received'}`}
|
|
>
|
|
<p>{msg.content}</p>
|
|
<span className="timestamp">
|
|
{new Date(msg.created_at).toLocaleTimeString()}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<form onSubmit={handleSendMessage} className="message-form">
|
|
<input
|
|
ref={messageInputRef}
|
|
type="text"
|
|
value={newMessage}
|
|
onChange={(e) => setNewMessage(e.target.value)}
|
|
placeholder="Type a message..."
|
|
disabled={isSending}
|
|
autoFocus
|
|
/>
|
|
<button type="submit" disabled={isSending || !newMessage.trim()}>
|
|
Send
|
|
</button>
|
|
</form>
|
|
</>
|
|
) : (
|
|
<div className="no-conversation">Select a conversation to start messaging</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|