This commit is contained in:
NotII
2025-03-24 00:44:19 +00:00
parent 37d4d0930c
commit be0f9aa3af
2 changed files with 158 additions and 102 deletions

View File

@@ -1,17 +1,17 @@
"use client";
import React from "react";
import { Metadata } from "next";
import { useParams } from "next/navigation";
import ChatDetail from "@/components/dashboard/ChatDetail";
import Dashboard from "@/components/dashboard/dashboard";
export const metadata: Metadata = {
title: "Chat Conversation",
description: "View and respond to customer messages",
};
export default function ChatDetailPage() {
const params = useParams();
const chatId = params.id as string;
export default function ChatDetailPage({ params }: { params: { id: string } }) {
return (
<Dashboard>
<ChatDetail chatId={params.id} />
<ChatDetail chatId={chatId} />
</Dashboard>
);
}

View File

@@ -174,19 +174,10 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
// Function to mark messages as read
const markMessagesAsRead = async () => {
try {
const authToken = getCookie("Authorization");
if (!authToken) return;
const authAxios = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
Authorization: `Bearer ${authToken}`
}
// Use clientFetch instead of direct axios
await clientFetch(`/chats/${chatId}/mark-read`, {
method: 'POST'
});
// Use dedicated endpoint to mark messages as read
await authAxios.post(`/chats/${chatId}/mark-read`);
console.log("Marked messages as read");
} catch (error) {
console.error("Error marking messages as read:", error);
@@ -226,7 +217,25 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
setChatData(response);
setChat(response); // Set chat data to maintain compatibility
setMessages(Array.isArray(response.messages) ? response.messages : []);
// Set messages with a transition effect
// If we already have messages, append new ones to avoid jumpiness
if (messages.length > 0) {
const existingMessageIds = new Set(messages.map(m => m._id));
const newMessages = response.messages.filter(
(msg: Message) => !existingMessageIds.has(msg._id)
);
if (newMessages.length > 0) {
setMessages(prev => [...prev, ...newMessages]);
} else {
// If we need to replace all messages (e.g., first load or refresh)
setMessages(Array.isArray(response.messages) ? response.messages : []);
}
} else {
// Initial load
setMessages(Array.isArray(response.messages) ? response.messages : []);
}
// Scroll to bottom on initial load
setTimeout(() => {
@@ -240,61 +249,76 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
}
};
// Fetch new messages periodically
// Setup polling for new messages
useEffect(() => {
if (!chatId || !chatData) return;
const checkForNewMessages = async () => {
if (isPollingRef.current) return;
isPollingRef.current = true;
try {
// Use clientFetch instead of direct axios calls
const response = await clientFetch(`/chats/${chatId}?markAsRead=true`);
if (response && Array.isArray(response.messages)) {
// Check if there are new messages
const hasNewMessages = response.messages.length > messages.length;
// Always update messages to ensure we have the latest state
setMessages(response.messages);
// If there are new messages and we're near the bottom, scroll down
if (hasNewMessages && isNearBottom()) {
setTimeout(scrollToBottom, 100);
}
// Play notification sound if there are new buyer messages
if (hasNewMessages) {
const newMessages = response.messages.slice(messages.length);
const hasNewBuyerMessages = newMessages.some((msg: Message) => msg.sender === 'buyer');
if (hasNewBuyerMessages) {
playNotificationSound();
}
}
}
} catch (error) {
console.error("Error checking for new messages:", error);
} finally {
isPollingRef.current = false;
// Set up a polling interval to check for new messages
const pollInterval = setInterval(() => {
if (chatId && !isPollingRef.current) {
pollNewMessages();
}
};
// Check for new messages every 3 seconds
const intervalId = setInterval(checkForNewMessages, 3000);
}, 3000); // Poll every 3 seconds
return () => {
clearInterval(intervalId);
clearInterval(pollInterval);
};
}, [chatId, chatData, messages.length]);
}, [chatId]);
// Poll for new messages without replacing existing ones
const pollNewMessages = async () => {
if (!chatId || isPollingRef.current) return;
isPollingRef.current = true;
try {
const response = await clientFetch(`/chats/${chatId}`);
// Update chat metadata
setChatData(response);
setChat(response);
// Check if there are new messages
if (Array.isArray(response.messages) && response.messages.length > 0) {
// Get existing message IDs to avoid duplicates
const existingIds = new Set(messages.map(m => m._id));
const newMessages = response.messages.filter((msg: Message) => !existingIds.has(msg._id));
if (newMessages.length > 0) {
// Add only new messages to avoid re-rendering all messages
setMessages(prev => [...prev, ...newMessages]);
// Play notification sound for new buyer messages
const hasBuyerMessage = newMessages.some((msg: Message) => msg.sender === 'buyer');
if (hasBuyerMessage) {
playNotificationSound();
}
// If near bottom, scroll to new messages
if (isNearBottom()) {
setTimeout(scrollToBottom, 50);
}
// Set timeout to mark new messages as read
if (markReadTimeoutRef.current) {
clearTimeout(markReadTimeoutRef.current);
}
markReadTimeoutRef.current = setTimeout(() => {
markMessagesAsRead();
}, 1000);
}
}
} catch (error) {
console.error("Error polling new messages:", error);
} finally {
isPollingRef.current = false;
}
};
// Handle form submit for sending messages
const handleSendMessage = (e: React.FormEvent) => {
e.preventDefault();
if (!message.trim()) return;
sendMessage();
sendMessage(message);
};
// Handle keyboard shortcuts for sending message
@@ -302,65 +326,97 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (message.trim()) {
sendMessage();
sendMessage(message);
}
}
};
// Send a message
const sendMessage = async () => {
if (!message.trim() || !chatId) return;
// Function to send a new message
const sendMessage = async (newMessage: string, file?: File | null) => {
// Don't send empty messages
if (!newMessage.trim() && !file) return;
// Save the message text and clear input immediately
const messageText = message.trim();
setMessage("");
if (!chatId || !chatData) return;
// Create temporary message to show immediately
// Create a temporary message with a unique temporary ID
const tempId = `temp-${Date.now()}`;
const tempMessage: Message = {
_id: tempId,
sender: 'vendor',
content: messageText,
attachments: [],
content: newMessage,
attachments: file ? [URL.createObjectURL(file)] : [],
read: true,
createdAt: new Date().toISOString(),
buyerId: chat?.buyerId || '',
vendorId: chat?.vendorId || '',
buyerId: chatData.buyerId || '',
vendorId: chatData.vendorId || ''
};
// Update messages array with temp message
// Optimistically add the temp message to the UI
setMessages(prev => [...prev, tempMessage]);
// Scroll to bottom
// Scroll to bottom to show the new message
setTimeout(scrollToBottom, 50);
try {
// Use clientFetch to send message to server
const response = await clientFetch(`/chats/${chatId}/message`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: messageText,
attachments: []
}),
});
setSending(true);
// Replace temp message with server response
setMessages(prevMessages => {
const updatedMessages = prevMessages.filter(m => m._id !== tempId);
return [...updatedMessages, response];
});
let response;
if (file) {
// Use FormData for file uploads
const formData = new FormData();
formData.append('content', newMessage);
formData.append('attachment', file);
response = await clientFetch(`/chats/${chatId}/message`, {
method: 'POST',
body: formData,
// Don't set Content-Type with FormData - browser will set it with boundary
});
} else {
// Use JSON for text-only messages
response = await clientFetch(`/chats/${chatId}/message`, {
method: 'POST',
body: JSON.stringify({
content: newMessage
}),
headers: {
'Content-Type': 'application/json'
}
});
}
// Replace the temporary message with the real one from the server
setMessages(prev => prev.map(msg =>
msg._id === tempId ? response : msg
));
// Update the textarea value to empty
setMessage('');
// Clear the file if there was one
if (file) {
setSelectedImage(null);
setSelectedMessageIndex(null);
setSelectedAttachmentIndex(null);
}
// Update the chat's last message
if (chatData) {
setChatData({
...chatData,
lastUpdated: new Date().toISOString()
});
}
// Force a refresh of the entire chat to ensure consistency
fetchChatData();
} catch (error) {
console.error("Error sending message:", error);
toast.error("Failed to send message");
console.error('Error sending message:', error);
toast.error('Failed to send message');
// Remove temp message on error
setMessages(prev => prev.filter(m => m._id !== tempId));
// Remove the temporary message if sending failed
setMessages(prev => prev.filter(msg => msg._id !== tempId));
} finally {
setSending(false);
}
};