real time messaging

This commit is contained in:
jackiettran
2025-11-08 18:20:02 -05:00
parent de32b68ec4
commit 7a5bff8f2b
19 changed files with 2046 additions and 20 deletions

View File

@@ -3,11 +3,13 @@ import { useParams, useNavigate } from 'react-router-dom';
import { Message } from '../types';
import { messageAPI } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
import { useSocket } from '../contexts/SocketContext';
const MessageDetail: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { user } = useAuth();
const { isConnected, onNewMessage } = useSocket();
const [message, setMessage] = useState<Message | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -18,6 +20,34 @@ const MessageDetail: React.FC = () => {
fetchMessage();
}, [id]);
// Listen for new replies in real-time
useEffect(() => {
if (!isConnected || !message) return;
const cleanup = onNewMessage((newMessage: Message) => {
// Check if this is a reply to the current thread
if (newMessage.parentMessageId === message.id) {
setMessage((prevMessage) => {
if (!prevMessage) return prevMessage;
// Check if reply already exists (avoid duplicates)
const replies = prevMessage.replies || [];
if (replies.some(r => r.id === newMessage.id)) {
return prevMessage;
}
// Add new reply to the thread
return {
...prevMessage,
replies: [...replies, newMessage]
};
});
}
});
return cleanup;
}, [isConnected, message?.id, onNewMessage]);
const fetchMessage = async () => {
try {
const response = await messageAPI.getMessage(id!);
@@ -38,7 +68,7 @@ const MessageDetail: React.FC = () => {
try {
const recipientId = message.senderId === user?.id ? message.receiverId : message.senderId;
await messageAPI.sendMessage({
const response = await messageAPI.sendMessage({
receiverId: recipientId,
subject: `Re: ${message.subject}`,
content: replyContent,
@@ -46,7 +76,20 @@ const MessageDetail: React.FC = () => {
});
setReplyContent('');
fetchMessage(); // Refresh to show the new reply
// Note: Socket will automatically add the reply to the thread
// But we add it manually for immediate feedback if socket is disconnected
if (!isConnected) {
setMessage((prevMessage) => {
if (!prevMessage) return prevMessage;
const replies = prevMessage.replies || [];
return {
...prevMessage,
replies: [...replies, response.data]
};
});
}
alert('Reply sent successfully!');
} catch (err: any) {
setError(err.response?.data?.error || 'Failed to send reply');

View File

@@ -3,11 +3,13 @@ import { useNavigate } from 'react-router-dom';
import { Message, User } from '../types';
import { messageAPI } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
import { useSocket } from '../contexts/SocketContext';
import ChatWindow from '../components/ChatWindow';
const Messages: React.FC = () => {
const navigate = useNavigate();
const { user } = useAuth();
const { isConnected, onNewMessage } = useSocket();
const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -18,6 +20,27 @@ const Messages: React.FC = () => {
fetchMessages();
}, []);
// Listen for new messages in real-time
useEffect(() => {
if (!isConnected) return;
const cleanup = onNewMessage((newMessage: Message) => {
// Only add if this is a received message (user is the receiver)
if (newMessage.receiverId === user?.id) {
setMessages((prevMessages) => {
// Check if message already exists (avoid duplicates)
if (prevMessages.some(m => m.id === newMessage.id)) {
return prevMessages;
}
// Add new message to the top of the inbox
return [newMessage, ...prevMessages];
});
}
});
return cleanup;
}, [isConnected, user?.id, onNewMessage]);
const fetchMessages = async () => {
try {
const response = await messageAPI.getMessages();

View File

@@ -3,6 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom';
import { User, Item } from '../types';
import { userAPI, itemAPI } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
import ChatWindow from '../components/ChatWindow';
const PublicProfile: React.FC = () => {
const { id } = useParams<{ id: string }>();
@@ -12,6 +13,7 @@ const PublicProfile: React.FC = () => {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showChat, setShowChat] = useState(false);
useEffect(() => {
fetchUserProfile();
@@ -85,11 +87,10 @@ const PublicProfile: React.FC = () => {
</div>
)}
<h3>{user.firstName} {user.lastName}</h3>
<p className="text-muted">@{user.username}</p>
{currentUser && currentUser.id !== user.id && (
<button
className="btn btn-primary mt-3"
onClick={() => navigate('/messages')}
<button
className="btn btn-primary mt-3"
onClick={() => setShowChat(true)}
>
<i className="bi bi-chat-dots-fill me-2"></i>Message
</button>
@@ -148,6 +149,15 @@ const PublicProfile: React.FC = () => {
</div>
</div>
</div>
{/* ChatWindow popup */}
{user && (
<ChatWindow
show={showChat}
onClose={() => setShowChat(false)}
recipient={user}
/>
)}
</div>
);
};