weewoo
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Metadata } from "next";
|
import { useParams } from "next/navigation";
|
||||||
import ChatDetail from "@/components/dashboard/ChatDetail";
|
import ChatDetail from "@/components/dashboard/ChatDetail";
|
||||||
import Dashboard from "@/components/dashboard/dashboard";
|
import Dashboard from "@/components/dashboard/dashboard";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export default function ChatDetailPage() {
|
||||||
title: "Chat Conversation",
|
const params = useParams();
|
||||||
description: "View and respond to customer messages",
|
const chatId = params.id as string;
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatDetailPage({ params }: { params: { id: string } }) {
|
|
||||||
return (
|
return (
|
||||||
<Dashboard>
|
<Dashboard>
|
||||||
<ChatDetail chatId={params.id} />
|
<ChatDetail chatId={chatId} />
|
||||||
</Dashboard>
|
</Dashboard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -174,19 +174,10 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
|
|||||||
// Function to mark messages as read
|
// Function to mark messages as read
|
||||||
const markMessagesAsRead = async () => {
|
const markMessagesAsRead = async () => {
|
||||||
try {
|
try {
|
||||||
const authToken = getCookie("Authorization");
|
// Use clientFetch instead of direct axios
|
||||||
|
await clientFetch(`/chats/${chatId}/mark-read`, {
|
||||||
if (!authToken) return;
|
method: 'POST'
|
||||||
|
|
||||||
const authAxios = axios.create({
|
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${authToken}`
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use dedicated endpoint to mark messages as read
|
|
||||||
await authAxios.post(`/chats/${chatId}/mark-read`);
|
|
||||||
console.log("Marked messages as read");
|
console.log("Marked messages as read");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error marking messages as read:", error);
|
console.error("Error marking messages as read:", error);
|
||||||
@@ -226,7 +217,25 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
|
|||||||
|
|
||||||
setChatData(response);
|
setChatData(response);
|
||||||
setChat(response); // Set chat data to maintain compatibility
|
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
|
// Scroll to bottom on initial load
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -240,61 +249,76 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch new messages periodically
|
// Setup polling for new messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chatId || !chatData) return;
|
// Set up a polling interval to check for new messages
|
||||||
|
const pollInterval = setInterval(() => {
|
||||||
const checkForNewMessages = async () => {
|
if (chatId && !isPollingRef.current) {
|
||||||
if (isPollingRef.current) return;
|
pollNewMessages();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
};
|
}, 3000); // Poll every 3 seconds
|
||||||
|
|
||||||
// Check for new messages every 3 seconds
|
|
||||||
const intervalId = setInterval(checkForNewMessages, 3000);
|
|
||||||
|
|
||||||
return () => {
|
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
|
// Handle form submit for sending messages
|
||||||
const handleSendMessage = (e: React.FormEvent) => {
|
const handleSendMessage = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!message.trim()) return;
|
if (!message.trim()) return;
|
||||||
sendMessage();
|
sendMessage(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle keyboard shortcuts for sending message
|
// Handle keyboard shortcuts for sending message
|
||||||
@@ -302,65 +326,97 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
|
|||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (message.trim()) {
|
if (message.trim()) {
|
||||||
sendMessage();
|
sendMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send a message
|
// Function to send a new message
|
||||||
const sendMessage = async () => {
|
const sendMessage = async (newMessage: string, file?: File | null) => {
|
||||||
if (!message.trim() || !chatId) return;
|
// Don't send empty messages
|
||||||
|
if (!newMessage.trim() && !file) return;
|
||||||
|
|
||||||
// Save the message text and clear input immediately
|
if (!chatId || !chatData) return;
|
||||||
const messageText = message.trim();
|
|
||||||
setMessage("");
|
|
||||||
|
|
||||||
// Create temporary message to show immediately
|
// Create a temporary message with a unique temporary ID
|
||||||
const tempId = `temp-${Date.now()}`;
|
const tempId = `temp-${Date.now()}`;
|
||||||
const tempMessage: Message = {
|
const tempMessage: Message = {
|
||||||
_id: tempId,
|
_id: tempId,
|
||||||
sender: 'vendor',
|
sender: 'vendor',
|
||||||
content: messageText,
|
content: newMessage,
|
||||||
attachments: [],
|
attachments: file ? [URL.createObjectURL(file)] : [],
|
||||||
read: true,
|
read: true,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
buyerId: chat?.buyerId || '',
|
buyerId: chatData.buyerId || '',
|
||||||
vendorId: chat?.vendorId || '',
|
vendorId: chatData.vendorId || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update messages array with temp message
|
// Optimistically add the temp message to the UI
|
||||||
setMessages(prev => [...prev, tempMessage]);
|
setMessages(prev => [...prev, tempMessage]);
|
||||||
|
|
||||||
// Scroll to bottom
|
// Scroll to bottom to show the new message
|
||||||
setTimeout(scrollToBottom, 50);
|
setTimeout(scrollToBottom, 50);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use clientFetch to send message to server
|
setSending(true);
|
||||||
const response = await clientFetch(`/chats/${chatId}/message`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
content: messageText,
|
|
||||||
attachments: []
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace temp message with server response
|
let response;
|
||||||
setMessages(prevMessages => {
|
|
||||||
const updatedMessages = prevMessages.filter(m => m._id !== tempId);
|
if (file) {
|
||||||
return [...updatedMessages, response];
|
// 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) {
|
} catch (error) {
|
||||||
console.error("Error sending message:", error);
|
console.error('Error sending message:', error);
|
||||||
toast.error("Failed to send message");
|
toast.error('Failed to send message');
|
||||||
|
|
||||||
// Remove temp message on error
|
// Remove the temporary message if sending failed
|
||||||
setMessages(prev => prev.filter(m => m._id !== tempId));
|
setMessages(prev => prev.filter(msg => msg._id !== tempId));
|
||||||
|
} finally {
|
||||||
|
setSending(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user