removed console logs from frontend and a logs from locationService

This commit is contained in:
jackiettran
2025-11-26 15:01:00 -05:00
parent 8b10103ae4
commit fab79e64ee
6 changed files with 344 additions and 275 deletions

View File

@@ -1,9 +1,15 @@
import React, { useState, useEffect, useLayoutEffect, useRef, useCallback } from 'react';
import { messageAPI, getMessageImageUrl } from '../services/api';
import { User, Message } from '../types';
import { useAuth } from '../contexts/AuthContext';
import { useSocket } from '../contexts/SocketContext';
import TypingIndicator from './TypingIndicator';
import React, {
useState,
useEffect,
useLayoutEffect,
useRef,
useCallback,
} from "react";
import { messageAPI, getMessageImageUrl } from "../services/api";
import { User, Message } from "../types";
import { useAuth } from "../contexts/AuthContext";
import { useSocket } from "../contexts/SocketContext";
import TypingIndicator from "./TypingIndicator";
interface ChatWindowProps {
show: boolean;
@@ -12,15 +18,30 @@ interface ChatWindowProps {
onMessagesRead?: (partnerId: string, count: number) => void;
}
const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMessagesRead }) => {
const ChatWindow: React.FC<ChatWindowProps> = ({
show,
onClose,
recipient,
onMessagesRead,
}) => {
const { user: currentUser } = useAuth();
const { isConnected, joinConversation, leaveConversation, onNewMessage, onUserTyping, emitTypingStart, emitTypingStop } = useSocket();
const {
isConnected,
joinConversation,
leaveConversation,
onNewMessage,
onUserTyping,
emitTypingStart,
emitTypingStop,
} = useSocket();
const [messages, setMessages] = useState<Message[]>([]);
const [newMessage, setNewMessage] = useState('');
const [newMessage, setNewMessage] = useState("");
const [sending, setSending] = useState(false);
const [loading, setLoading] = useState(true);
const [isRecipientTyping, setIsRecipientTyping] = useState(false);
const [initialUnreadMessageIds, setInitialUnreadMessageIds] = useState<Set<string>>(new Set());
const [initialUnreadMessageIds, setInitialUnreadMessageIds] = useState<
Set<string>
>(new Set());
const [isAtBottom, setIsAtBottom] = useState(true);
const [hasScrolledToUnread, setHasScrolledToUnread] = useState(false);
const [selectedImage, setSelectedImage] = useState<File | null>(null);
@@ -52,59 +73,57 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
}, [show, recipient.id, isConnected]);
// Create a stable callback for handling new messages
const handleNewMessage = useCallback(async (message: Message) => {
console.log('[ChatWindow] Received new_message event:', message);
// Only add messages that are part of this conversation
if (
(message.senderId === recipient.id && message.receiverId === currentUser?.id) ||
(message.senderId === currentUser?.id && message.receiverId === recipient.id)
) {
console.log('[ChatWindow] Message is for this conversation, adding to chat');
setMessages((prevMessages) => {
// Check if message already exists (avoid duplicates)
if (prevMessages.some(m => m.id === message.id)) {
console.log('[ChatWindow] Message already exists, skipping');
return prevMessages;
}
console.log('[ChatWindow] Adding new message to chat');
return [...prevMessages, message];
});
// Mark incoming messages from recipient as read
if (message.senderId === recipient.id && message.receiverId === currentUser?.id && !message.isRead) {
console.log('[ChatWindow] Marking new incoming message as read');
try {
await messageAPI.markAsRead(message.id);
// Notify parent component that message was marked read
if (onMessagesRead) {
onMessagesRead(recipient.id, 1);
const handleNewMessage = useCallback(
async (message: Message) => {
// Only add messages that are part of this conversation
if (
(message.senderId === recipient.id &&
message.receiverId === currentUser?.id) ||
(message.senderId === currentUser?.id &&
message.receiverId === recipient.id)
) {
setMessages((prevMessages) => {
// Check if message already exists (avoid duplicates)
if (prevMessages.some((m) => m.id === message.id)) {
return prevMessages;
}
return [...prevMessages, message];
});
// Mark incoming messages from recipient as read
if (
message.senderId === recipient.id &&
message.receiverId === currentUser?.id &&
!message.isRead
) {
try {
await messageAPI.markAsRead(message.id);
// Notify parent component that message was marked read
if (onMessagesRead) {
onMessagesRead(recipient.id, 1);
}
} catch (error) {
console.error(
`Failed to mark message ${message.id} as read:`,
error
);
}
} catch (error) {
console.error(`Failed to mark message ${message.id} as read:`, error);
}
}
} else {
console.log('[ChatWindow] Message not for this conversation, ignoring');
}
}, [recipient.id, currentUser?.id, onMessagesRead]);
},
[recipient.id, currentUser?.id, onMessagesRead]
);
// Listen for new messages in real-time
useEffect(() => {
console.log('[ChatWindow] Message listener useEffect running', { isConnected, show, recipientId: recipient.id });
if (!isConnected || !show) {
console.log('[ChatWindow] Skipping listener setup:', { isConnected, show });
return;
}
console.log('[ChatWindow] Setting up message listener for recipient:', recipient.id);
const cleanup = onNewMessage(handleNewMessage);
return () => {
console.log('[ChatWindow] Cleaning up message listener for recipient:', recipient.id);
cleanup();
};
}, [isConnected, show, onNewMessage, handleNewMessage]);
@@ -143,21 +162,20 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
if (!loading && !hasScrolledToUnread && messages.length > 0) {
if (initialUnreadMessageIds.size > 0) {
// Find the oldest unread message
const oldestUnread = messages.find(m => initialUnreadMessageIds.has(m.id));
const oldestUnread = messages.find((m) =>
initialUnreadMessageIds.has(m.id)
);
if (oldestUnread && messageRefs.current.has(oldestUnread.id)) {
console.log(`[ChatWindow] Scrolling to oldest unread message: ${oldestUnread.id}`);
messageRefs.current.get(oldestUnread.id)?.scrollIntoView({
behavior: 'smooth',
block: 'start'
behavior: "smooth",
block: "start",
});
} else {
console.log('[ChatWindow] Unread message ref not found, scrolling to bottom');
scrollToBottom();
}
} else {
// No unread messages, scroll to bottom
console.log('[ChatWindow] No unread messages, scrolling to bottom');
scrollToBottom();
}
setHasScrolledToUnread(true);
@@ -176,26 +194,33 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
// Fetch all messages between current user and recipient
const [sentRes, receivedRes] = await Promise.all([
messageAPI.getSentMessages(),
messageAPI.getMessages()
messageAPI.getMessages(),
]);
const sentToRecipient = sentRes.data.filter((msg: Message) => msg.receiverId === recipient.id);
const receivedFromRecipient = receivedRes.data.filter((msg: Message) => msg.senderId === recipient.id);
const sentToRecipient = sentRes.data.filter(
(msg: Message) => msg.receiverId === recipient.id
);
const receivedFromRecipient = receivedRes.data.filter(
(msg: Message) => msg.senderId === recipient.id
);
// Combine and sort by date
const allMessages = [...sentToRecipient, ...receivedFromRecipient].sort(
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
(a, b) =>
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
);
setMessages(allMessages);
// Mark all unread messages from recipient as read
const unreadMessages = receivedFromRecipient.filter((msg: Message) => !msg.isRead);
const unreadMessages = receivedFromRecipient.filter(
(msg: Message) => !msg.isRead
);
if (unreadMessages.length > 0) {
console.log(`[ChatWindow] Marking ${unreadMessages.length} messages as read`);
// Save unread message IDs for scrolling purposes
setInitialUnreadMessageIds(new Set(unreadMessages.map((m: Message) => m.id)));
setInitialUnreadMessageIds(
new Set(unreadMessages.map((m: Message) => m.id))
);
// Mark each message as read
const markReadPromises = unreadMessages.map((message: Message) =>
@@ -212,28 +237,32 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
}
}
} catch (error) {
console.error('Failed to fetch messages:', error);
console.error("Failed to fetch messages:", error);
} finally {
setLoading(false);
}
};
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
const setMessageRef = useCallback((id: string) => (el: HTMLDivElement | null) => {
if (el) {
messageRefs.current.set(id, el);
} else {
messageRefs.current.delete(id);
}
}, []);
const setMessageRef = useCallback(
(id: string) => (el: HTMLDivElement | null) => {
if (el) {
messageRefs.current.set(id, el);
} else {
messageRefs.current.delete(id);
}
},
[]
);
const handleScroll = () => {
if (!messagesContainerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
const { scrollTop, scrollHeight, clientHeight } =
messagesContainerRef.current;
const isBottom = scrollHeight - scrollTop - clientHeight < 50; // 50px threshold
setIsAtBottom(isBottom);
};
@@ -265,14 +294,14 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
const file = e.target.files?.[0];
if (file) {
// Validate file type
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
if (!file.type.startsWith("image/")) {
alert("Please select an image file");
return;
}
// Validate file size (5MB)
if (file.size > 5 * 1024 * 1024) {
alert('Image size must be less than 5MB');
alert("Image size must be less than 5MB");
return;
}
@@ -291,7 +320,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
setSelectedImage(null);
setImagePreview(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
fileInputRef.current.value = "";
}
};
@@ -311,20 +340,20 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
setSending(true);
const messageContent = newMessage;
const imageToSend = selectedImage;
setNewMessage(''); // Clear input immediately for better UX
setNewMessage(""); // Clear input immediately for better UX
setSelectedImage(null);
setImagePreview(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
fileInputRef.current.value = "";
}
try {
// Build FormData for message (with or without image)
const formData = new FormData();
formData.append('receiverId', recipient.id);
formData.append('content', messageContent || ' '); // Send space if only image
formData.append("receiverId", recipient.id);
formData.append("content", messageContent || " "); // Send space if only image
if (imageToSend) {
formData.append('image', imageToSend);
formData.append("image", imageToSend);
}
const response = await messageAPI.sendMessage(formData);
@@ -333,13 +362,13 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
// Socket will handle updating the receiver's chat
setMessages((prevMessages) => {
// Avoid duplicates
if (prevMessages.some(m => m.id === response.data.id)) {
if (prevMessages.some((m) => m.id === response.data.id)) {
return prevMessages;
}
return [...prevMessages, response.data];
});
} catch (error) {
console.error('Failed to send message:', error);
console.error("Failed to send message:", error);
setNewMessage(messageContent); // Restore message on error
setSelectedImage(imageToSend);
if (imageToSend) {
@@ -362,47 +391,47 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
const formatTime = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
return date.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
hour12: true,
});
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const today = new Date();
if (date.toDateString() === today.toDateString()) {
return 'Today';
return "Today";
}
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === yesterday.toDateString()) {
return 'Yesterday';
return "Yesterday";
}
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: date.getFullYear() !== today.getFullYear() ? 'numeric' : undefined
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: date.getFullYear() !== today.getFullYear() ? "numeric" : undefined,
});
};
if (!show) return null;
return (
<div
className="position-fixed bottom-0 end-0 m-3 shadow-lg d-flex flex-column"
style={{
width: '350px',
height: '500px',
maxHeight: 'calc(100vh - 100px)',
<div
className="position-fixed bottom-0 end-0 m-3 shadow-lg d-flex flex-column"
style={{
width: "350px",
height: "500px",
maxHeight: "calc(100vh - 100px)",
zIndex: 1050,
borderRadius: '12px',
overflow: 'hidden',
backgroundColor: 'white'
borderRadius: "12px",
overflow: "hidden",
backgroundColor: "white",
}}
>
{/* Header */}
@@ -413,23 +442,25 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
src={recipient.profileImage}
alt={`${recipient.firstName} ${recipient.lastName}`}
className="rounded-circle me-2"
style={{ width: '35px', height: '35px', objectFit: 'cover' }}
style={{ width: "35px", height: "35px", objectFit: "cover" }}
/>
) : (
<div
<div
className="rounded-circle bg-white bg-opacity-25 d-flex align-items-center justify-content-center me-2"
style={{ width: '35px', height: '35px' }}
style={{ width: "35px", height: "35px" }}
>
<i className="bi bi-person-fill text-white"></i>
</div>
)}
<div>
<h6 className="mb-0">{recipient.firstName} {recipient.lastName}</h6>
<h6 className="mb-0">
{recipient.firstName} {recipient.lastName}
</h6>
</div>
</div>
<button
type="button"
className="btn-close btn-close-white"
<button
type="button"
className="btn-close btn-close-white"
onClick={onClose}
></button>
</div>
@@ -440,8 +471,8 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
onScroll={handleScroll}
className="p-3 overflow-auto flex-grow-1"
style={{
backgroundColor: '#f8f9fa',
minHeight: 0
backgroundColor: "#f8f9fa",
minHeight: 0,
}}
>
{loading ? (
@@ -452,15 +483,22 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
</div>
) : messages.length === 0 ? (
<div className="text-center py-5">
<i className="bi bi-chat-dots" style={{ fontSize: '3rem', color: '#dee2e6' }}></i>
<p className="text-muted mt-2">Start a conversation with {recipient.firstName}</p>
<i
className="bi bi-chat-dots"
style={{ fontSize: "3rem", color: "#dee2e6" }}
></i>
<p className="text-muted mt-2">
Start a conversation with {recipient.firstName}
</p>
</div>
) : (
<>
{messages.map((message, index) => {
const isCurrentUser = message.senderId === currentUser?.id;
const showDate = index === 0 ||
formatDate(message.createdAt) !== formatDate(messages[index - 1].createdAt);
const showDate =
index === 0 ||
formatDate(message.createdAt) !==
formatDate(messages[index - 1].createdAt);
return (
<div key={message.id} ref={setMessageRef(message.id)}>
@@ -471,16 +509,20 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
</small>
</div>
)}
<div className={`d-flex mb-2 ${isCurrentUser ? 'justify-content-end' : ''}`}>
<div
className={`d-flex mb-2 ${
isCurrentUser ? "justify-content-end" : ""
}`}
>
<div
className={`px-3 py-2 rounded-3 ${
isCurrentUser
? 'bg-primary text-white'
: 'bg-white border'
? "bg-primary text-white"
: "bg-white border"
}`}
style={{
maxWidth: '75%',
wordBreak: 'break-word'
maxWidth: "75%",
wordBreak: "break-word",
}}
>
{message.imagePath && (
@@ -489,24 +531,29 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
src={getMessageImageUrl(message.imagePath)}
alt="Shared image"
style={{
width: '100%',
borderRadius: '8px',
cursor: 'pointer',
maxHeight: '300px',
objectFit: 'cover'
width: "100%",
borderRadius: "8px",
cursor: "pointer",
maxHeight: "300px",
objectFit: "cover",
}}
onClick={() => window.open(getMessageImageUrl(message.imagePath!), '_blank')}
onClick={() =>
window.open(
getMessageImageUrl(message.imagePath!),
"_blank"
)
}
/>
</div>
)}
{message.content.trim() && (
<p className="mb-1" style={{ fontSize: '0.95rem' }}>
<p className="mb-1" style={{ fontSize: "0.95rem" }}>
{message.content}
</p>
)}
<small
className={isCurrentUser ? 'opacity-75' : 'text-muted'}
style={{ fontSize: '0.75rem' }}
className={isCurrentUser ? "opacity-75" : "text-muted"}
style={{ fontSize: "0.75rem" }}
>
{formatTime(message.createdAt)}
</small>
@@ -536,17 +583,22 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
src={imagePreview}
alt="Preview"
style={{
maxWidth: '150px',
maxHeight: '150px',
borderRadius: '8px',
objectFit: 'cover'
maxWidth: "150px",
maxHeight: "150px",
borderRadius: "8px",
objectFit: "cover",
}}
/>
<button
type="button"
className="btn btn-sm btn-danger position-absolute top-0 end-0 m-1 rounded-circle"
onClick={handleRemoveImage}
style={{ width: '24px', height: '24px', padding: '0', fontSize: '0.7rem' }}
style={{
width: "24px",
height: "24px",
padding: "0",
fontSize: "0.7rem",
}}
>
<i className="bi bi-x"></i>
</button>
@@ -558,7 +610,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
type="file"
accept="image/*"
onChange={handleImageSelect}
style={{ display: 'none' }}
style={{ display: "none" }}
/>
<button
type="button"
@@ -584,7 +636,11 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
disabled={sending || (!newMessage.trim() && !selectedImage)}
>
{sending ? (
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
></span>
) : (
<i className="bi bi-send-fill"></i>
)}
@@ -595,4 +651,4 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ show, onClose, recipient, onMes
);
};
export default ChatWindow;
export default ChatWindow;