@pubuduth-aplicy/chat-ui 2.1.71 → 2.1.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/components/Chat.tsx +30 -17
- package/src/components/common/CollapsibleSection.tsx +40 -0
- package/src/components/common/VirtualizedChatList.tsx +57 -0
- package/src/components/messages/Message.tsx +46 -45
- package/src/components/messages/MessageContainer.tsx +116 -59
- package/src/components/messages/MessageInput.tsx +59 -18
- package/src/components/messages/Messages.tsx +97 -45
- package/src/components/sidebar/Conversation.tsx +113 -100
- package/src/components/sidebar/Conversations.tsx +270 -35
- package/src/components/sidebar/SearchInput.tsx +16 -54
- package/src/hooks/useMessageStatus.ts +97 -0
- package/src/providers/ChatProvider.tsx +46 -26
- package/src/service/messageService.ts +86 -61
- package/src/stores/Zustant.ts +28 -21
- package/src/style/style.css +200 -24
- package/src/types/type.ts +25 -28
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
3
|
import { useMessageMutation } from "../../hooks/mutations/useSendMessage";
|
|
3
4
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
4
5
|
import useChatUIStore from "../../stores/Zustant";
|
|
5
|
-
import paperplane from "../../assets/icons8-send-50.png";
|
|
6
6
|
import { FilePreview, FileType } from "../common/FilePreview";
|
|
7
7
|
import { getApiClient } from "../../lib/api/apiClient";
|
|
8
8
|
import { MessageStatus } from "../../types/type";
|
|
@@ -44,6 +44,7 @@ const MessageInput = () => {
|
|
|
44
44
|
const mutation = useMessageMutation();
|
|
45
45
|
const [typingUser, setTypingUser] = useState<string | null>(null);
|
|
46
46
|
const [isSending, setIsSending] = useState(false);
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
47
48
|
const [isTyping, setIsTyping] = useState(false);
|
|
48
49
|
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
|
49
50
|
const [showAttachmentOptions, setShowAttachmentOptions] = useState(false);
|
|
@@ -55,7 +56,6 @@ const MessageInput = () => {
|
|
|
55
56
|
const typingTimeoutRef = useRef<number | null>(null);
|
|
56
57
|
const generateTempId = () =>
|
|
57
58
|
`temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
58
|
-
|
|
59
59
|
// Join chat room when conversation is selected
|
|
60
60
|
useEffect(() => {
|
|
61
61
|
if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
|
|
@@ -68,6 +68,20 @@ const MessageInput = () => {
|
|
|
68
68
|
}
|
|
69
69
|
}, [selectedConversation?._id, socket, sendMessage]);
|
|
70
70
|
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
// Clear all input state when conversation changes
|
|
73
|
+
setMessage("");
|
|
74
|
+
setMessage1("");
|
|
75
|
+
setAttachments([]);
|
|
76
|
+
setInputError(null);
|
|
77
|
+
setShowAttachmentOptions(false);
|
|
78
|
+
|
|
79
|
+
// Clean up any existing file input
|
|
80
|
+
if (fileInputRef.current) {
|
|
81
|
+
fileInputRef.current.value = "";
|
|
82
|
+
}
|
|
83
|
+
}, [selectedConversation?._id]);
|
|
84
|
+
|
|
71
85
|
// Typing indicator logic
|
|
72
86
|
useEffect(() => {
|
|
73
87
|
if (!socket || !selectedConversation?._id) return;
|
|
@@ -211,6 +225,18 @@ const MessageInput = () => {
|
|
|
211
225
|
});
|
|
212
226
|
};
|
|
213
227
|
|
|
228
|
+
// const participantDetails = Array.isArray(selectedConversation?.participantDetails)
|
|
229
|
+
// ? selectedConversation?.participantDetails
|
|
230
|
+
// : [selectedConversation?.participantDetails].filter(Boolean);
|
|
231
|
+
|
|
232
|
+
// const otherParticipant = participantDetails.find(
|
|
233
|
+
// (p: any) => p._id !== userId
|
|
234
|
+
// );
|
|
235
|
+
|
|
236
|
+
const otherParticipant = selectedConversation?.participantDetails?.find(
|
|
237
|
+
(p:any) => p._id !== userId
|
|
238
|
+
);
|
|
239
|
+
|
|
214
240
|
const handleSubmit = useCallback(
|
|
215
241
|
async (e: React.FormEvent) => {
|
|
216
242
|
e.preventDefault();
|
|
@@ -227,7 +253,7 @@ const MessageInput = () => {
|
|
|
227
253
|
text: message1,
|
|
228
254
|
message: message1,
|
|
229
255
|
senderId: userId,
|
|
230
|
-
status: "
|
|
256
|
+
status: "pending" as MessageStatus,
|
|
231
257
|
createdAt: new Date().toISOString(),
|
|
232
258
|
media: attachmentsRef.current.map((att) => ({
|
|
233
259
|
type: att.type,
|
|
@@ -294,14 +320,31 @@ const MessageInput = () => {
|
|
|
294
320
|
|
|
295
321
|
const successfulUploads = uploadedFiles.filter((file) => file !== null);
|
|
296
322
|
|
|
323
|
+
if (!otherParticipant?._id) {
|
|
324
|
+
console.error("Cannot send message: receiver ID is missing.");
|
|
325
|
+
setIsSending(false);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
297
329
|
mutation.mutate(
|
|
298
330
|
{
|
|
299
|
-
|
|
300
|
-
!Array.isArray(selectedConversation?.participantDetails) &&
|
|
301
|
-
selectedConversation?.participantDetails._id,
|
|
331
|
+
receiverId: otherParticipant._id,
|
|
302
332
|
senderId: userId,
|
|
303
333
|
message: message1,
|
|
304
334
|
attachments: successfulUploads,
|
|
335
|
+
bookingId:
|
|
336
|
+
selectedConversation?.type === "service"
|
|
337
|
+
? selectedConversation?.bookingId
|
|
338
|
+
: undefined,
|
|
339
|
+
serviceTitle:
|
|
340
|
+
selectedConversation?.type === "service"
|
|
341
|
+
? selectedConversation?.title
|
|
342
|
+
: undefined,
|
|
343
|
+
type: selectedConversation?.type,
|
|
344
|
+
serviceId:
|
|
345
|
+
selectedConversation?.type === "service"
|
|
346
|
+
? selectedConversation?.serviceId
|
|
347
|
+
: undefined,
|
|
305
348
|
},
|
|
306
349
|
{
|
|
307
350
|
onSuccess: (data) => {
|
|
@@ -321,15 +364,15 @@ const MessageInput = () => {
|
|
|
321
364
|
|
|
322
365
|
// Send message via WebSocket
|
|
323
366
|
sendMessage({
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
367
|
+
event: "sendMessage",
|
|
368
|
+
data: {
|
|
369
|
+
chatId: selectedConversation?._id,
|
|
370
|
+
message: message1,
|
|
371
|
+
messageId: data[1]._id,
|
|
372
|
+
attachments: successfulUploads,
|
|
373
|
+
senderId: userId,
|
|
374
|
+
receiverId: otherParticipant._id,
|
|
375
|
+
},
|
|
333
376
|
});
|
|
334
377
|
},
|
|
335
378
|
onError: (error) => {
|
|
@@ -675,9 +718,7 @@ const MessageInput = () => {
|
|
|
675
718
|
|
|
676
719
|
{typingUser &&
|
|
677
720
|
typingUser !== userId &&
|
|
678
|
-
typingUser ===
|
|
679
|
-
(!Array.isArray(selectedConversation?.participantDetails) &&
|
|
680
|
-
selectedConversation?.participantDetails?._id) &&
|
|
721
|
+
typingUser === otherParticipant?._id &&
|
|
681
722
|
!isSending && (
|
|
682
723
|
<div className="typingIndicator">
|
|
683
724
|
<div className="typing-loader">
|
|
@@ -21,6 +21,7 @@ const Messages = () => {
|
|
|
21
21
|
fetchMessages(selectedConversation?._id, userId, pageParam),
|
|
22
22
|
getNextPageParam: (lastPage) => lastPage.nextPage,
|
|
23
23
|
initialPageParam: 1,
|
|
24
|
+
enabled: !!selectedConversation?._id, // Prevent fetching if no conversation is selected
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
// Handle infinite scroll
|
|
@@ -42,12 +43,20 @@ const Messages = () => {
|
|
|
42
43
|
|
|
43
44
|
const handleMessage = (event: MessageEvent) => {
|
|
44
45
|
try {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
const newMessage = data
|
|
46
|
+
const parsed = JSON.parse(event.data);
|
|
47
|
+
console.log("Parsed WebSocket message1:", parsed);
|
|
48
|
+
if (parsed.type === "newMessage" || parsed.event === "newMessage") {
|
|
49
|
+
const newMessage = parsed.data;
|
|
50
|
+
if (!newMessage) {
|
|
51
|
+
console.warn(
|
|
52
|
+
"Received newMessage event without a message payload",
|
|
53
|
+
parsed
|
|
54
|
+
);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
newMessage.shouldShake = true;
|
|
50
|
-
|
|
59
|
+
|
|
51
60
|
setMessages((prevMessages) => {
|
|
52
61
|
const isDuplicate = prevMessages.some(
|
|
53
62
|
(msg) =>
|
|
@@ -61,10 +70,63 @@ const Messages = () => {
|
|
|
61
70
|
});
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
const statusOrder = ["sent", "delivered", "read", "edited", "deleted"];
|
|
74
|
+
if (parsed.event === "messageStatusUpdated") {
|
|
75
|
+
const { messageId, status } = parsed.data || {};
|
|
76
|
+
if (!messageId) {
|
|
77
|
+
console.error("Missing messageId in status update", parsed);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
65
81
|
setMessages((prev) =>
|
|
66
|
-
prev.map((msg) =>
|
|
67
|
-
msg._id
|
|
82
|
+
prev.map((msg) => {
|
|
83
|
+
if (msg._id !== messageId) return msg;
|
|
84
|
+
|
|
85
|
+
const currentIdx = statusOrder.indexOf(msg.status);
|
|
86
|
+
const newIdx = statusOrder.indexOf(status);
|
|
87
|
+
if (newIdx === -1 || currentIdx === -1 || newIdx <= currentIdx)
|
|
88
|
+
return msg;
|
|
89
|
+
|
|
90
|
+
if (newIdx > currentIdx) {
|
|
91
|
+
console.log(`Updating status for ${messageId} to ${status}`);
|
|
92
|
+
return { ...msg, status };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return msg; // No update if new status isn't higher
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (parsed.event === "messageEdited") {
|
|
101
|
+
console.log("Received messageEdited event:", parsed);
|
|
102
|
+
|
|
103
|
+
const updatedMessage = parsed.data;
|
|
104
|
+
if (!updatedMessage || !updatedMessage.messageId) return;
|
|
105
|
+
|
|
106
|
+
setMessages((prevMessages) =>
|
|
107
|
+
prevMessages.map((msg) =>
|
|
108
|
+
msg._id === updatedMessage.messageId
|
|
109
|
+
? { ...msg, message: updatedMessage.message, status: "edited" }
|
|
110
|
+
: msg
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (parsed.event === "messageDeleted") {
|
|
116
|
+
console.log("Received messageDeleted event:", parsed);
|
|
117
|
+
|
|
118
|
+
const { messageId } = parsed.data || {};
|
|
119
|
+
if (!messageId) return;
|
|
120
|
+
|
|
121
|
+
setMessages((prevMessages) =>
|
|
122
|
+
prevMessages.map((msg) =>
|
|
123
|
+
msg._id === messageId
|
|
124
|
+
? {
|
|
125
|
+
...msg,
|
|
126
|
+
message: "This message was deleted",
|
|
127
|
+
status: "deleted",
|
|
128
|
+
}
|
|
129
|
+
: msg
|
|
68
130
|
)
|
|
69
131
|
);
|
|
70
132
|
}
|
|
@@ -84,47 +146,37 @@ const Messages = () => {
|
|
|
84
146
|
useEffect(() => {
|
|
85
147
|
if (messages.length > 0) {
|
|
86
148
|
setTimeout(() => {
|
|
87
|
-
lastMessageRef.current?.scrollIntoView({
|
|
149
|
+
lastMessageRef.current?.scrollIntoView({
|
|
150
|
+
behavior: "smooth",
|
|
151
|
+
block: "nearest",
|
|
152
|
+
});
|
|
88
153
|
}, 100);
|
|
89
154
|
}
|
|
90
155
|
}, [messages.length]);
|
|
91
156
|
|
|
92
157
|
// Track message visibility for read receipts
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}, [messages, socket]);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const sendDeliveryConfirmation = (messageId: string) => {
|
|
118
|
-
if (!socket) return;
|
|
119
|
-
|
|
120
|
-
const message = {
|
|
121
|
-
type: "confirmDelivery",
|
|
122
|
-
messageId,
|
|
123
|
-
// timestamp: Date.now()
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
socket.send(JSON.stringify(message));
|
|
127
|
-
};
|
|
158
|
+
// useEffect(() => {
|
|
159
|
+
// if (!socket || !messages.length) return;
|
|
160
|
+
|
|
161
|
+
// const observer = new IntersectionObserver(
|
|
162
|
+
// (entries) => {
|
|
163
|
+
// entries.forEach((entry) => {
|
|
164
|
+
// if (entry.isIntersecting) {
|
|
165
|
+
// const messageId = entry.target.getAttribute("data-message-id");
|
|
166
|
+
// if (messageId) {
|
|
167
|
+
// sendDeliveryConfirmation(messageId);
|
|
168
|
+
// }
|
|
169
|
+
// }
|
|
170
|
+
// });
|
|
171
|
+
// },
|
|
172
|
+
// { threshold: 0.5 }
|
|
173
|
+
// );
|
|
174
|
+
|
|
175
|
+
// const messageElements = document.querySelectorAll("[data-message-id]");
|
|
176
|
+
// messageElements.forEach((el) => observer.observe(el));
|
|
177
|
+
|
|
178
|
+
// return () => observer.disconnect();
|
|
179
|
+
// }, [messages, socket]);
|
|
128
180
|
|
|
129
181
|
return (
|
|
130
182
|
<div
|
|
@@ -157,4 +209,4 @@ const Messages = () => {
|
|
|
157
209
|
);
|
|
158
210
|
};
|
|
159
211
|
|
|
160
|
-
export default Messages;
|
|
212
|
+
export default Messages;
|
|
@@ -1,36 +1,71 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { MessageCircle } from "lucide-react";
|
|
1
3
|
import { useEffect } from "react";
|
|
2
4
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
3
5
|
import useChatUIStore from "../../stores/Zustant";
|
|
4
6
|
import { ConversationProps } from "../../types/type";
|
|
5
|
-
import { getChatConfig } from "@pubuduth-aplicy/chat-ui";
|
|
6
7
|
|
|
7
8
|
const Conversation = ({ conversation }: ConversationProps) => {
|
|
8
9
|
const {
|
|
9
10
|
setSelectedConversation,
|
|
10
11
|
setOnlineUsers,
|
|
11
|
-
onlineUsers,
|
|
12
|
-
selectedConversation,
|
|
13
12
|
} = useChatUIStore();
|
|
14
|
-
const { socket,
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const { socket, isUserOnline } = useChatContext();
|
|
14
|
+
const selectedConversation = useChatUIStore(
|
|
15
|
+
(state) => state.selectedConversation
|
|
16
|
+
);
|
|
17
|
+
const { userId } = useChatContext();
|
|
18
|
+
|
|
19
|
+
// This should return the other participant
|
|
20
|
+
const participant = conversation.participantDetails?.find(
|
|
21
|
+
(p:any) => p._id !== userId
|
|
22
|
+
);
|
|
23
|
+
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
// const handleSelectConversation = () => {
|
|
26
|
+
// console.log("Selected Conversation Data:", JSON.stringify(conversation, null, 2));
|
|
27
|
+
// setSelectedConversation(conversation);
|
|
28
|
+
|
|
29
|
+
// const unreadMessages = conversation.unreadMessageIds || [];
|
|
30
|
+
// if (unreadMessages.length > 0 && socket?.readyState === WebSocket.OPEN) {
|
|
31
|
+
// console.log('unread meassge',unreadMessages);
|
|
32
|
+
|
|
33
|
+
// const message = {
|
|
34
|
+
// event: "messageRead",
|
|
35
|
+
// data: {
|
|
36
|
+
// messageIds: unreadMessages,
|
|
37
|
+
// senderId: participant?._id,
|
|
38
|
+
// chatId: conversation._id,
|
|
39
|
+
// },
|
|
40
|
+
// };
|
|
41
|
+
// socket.send(JSON.stringify(message));
|
|
42
|
+
// }
|
|
43
|
+
// };
|
|
44
|
+
|
|
45
|
+
const handleSelectConversation = () => {
|
|
46
|
+
console.log(
|
|
47
|
+
"Selected Conversation Data:",
|
|
48
|
+
JSON.stringify(conversation, null, 2)
|
|
49
|
+
);
|
|
50
|
+
setSelectedConversation(conversation);
|
|
51
|
+
const unreadMessageIds = conversation.unreadMessageIds || [];
|
|
52
|
+
if (unreadMessageIds.length > 0 && socket?.readyState === WebSocket.OPEN) {
|
|
53
|
+
console.log("unread messages", unreadMessageIds);
|
|
22
54
|
|
|
23
|
-
|
|
55
|
+
const message = {
|
|
24
56
|
event: "messageRead",
|
|
25
57
|
data: {
|
|
26
|
-
messageIds:
|
|
58
|
+
messageIds: unreadMessageIds,
|
|
59
|
+
senderId: participant?._id,
|
|
60
|
+
receiverId: userId,
|
|
27
61
|
chatId: conversation._id,
|
|
28
62
|
},
|
|
29
|
-
}
|
|
63
|
+
};
|
|
64
|
+
socket.send(JSON.stringify(message));
|
|
65
|
+
// updateConversationReadStatus(conversation._id, unreadMessageIds);
|
|
30
66
|
}
|
|
31
67
|
};
|
|
32
68
|
|
|
33
|
-
// Listen for online users updates
|
|
34
69
|
useEffect(() => {
|
|
35
70
|
if (!socket) return;
|
|
36
71
|
|
|
@@ -38,10 +73,10 @@ const Conversation = ({ conversation }: ConversationProps) => {
|
|
|
38
73
|
try {
|
|
39
74
|
const data = JSON.parse(event.data);
|
|
40
75
|
if (data.event === "getOnlineUsers") {
|
|
41
|
-
setOnlineUsers(data.
|
|
76
|
+
setOnlineUsers(data.payload);
|
|
42
77
|
}
|
|
43
78
|
} catch (error) {
|
|
44
|
-
console.error("
|
|
79
|
+
console.error("Failed to parse WebSocket message:", error);
|
|
45
80
|
}
|
|
46
81
|
};
|
|
47
82
|
|
|
@@ -52,100 +87,78 @@ const Conversation = ({ conversation }: ConversationProps) => {
|
|
|
52
87
|
};
|
|
53
88
|
}, [socket, setOnlineUsers]);
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
90
|
+
// Temporary debug in your component
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
console.log("Current conversation state:", conversation);
|
|
93
|
+
}, [conversation]);
|
|
58
94
|
|
|
95
|
+
const isOnline = isUserOnline(participant?._id || "");
|
|
59
96
|
const isSelected = selectedConversation?._id === conversation._id;
|
|
97
|
+
const unreadCount = conversation.unreadMessageCount || 0;
|
|
98
|
+
const conversationName =
|
|
99
|
+
conversation.type === "service" && conversation.bookingId
|
|
100
|
+
? `Booking #${conversation.bookingId}`
|
|
101
|
+
: participant?.firstname || "Conversation";
|
|
102
|
+
|
|
103
|
+
const lastMessageTimestamp = new Date(
|
|
104
|
+
conversation.lastMessage?.updatedAt || conversation.lastMessage?.createdAt
|
|
105
|
+
).toLocaleTimeString([], {
|
|
106
|
+
hour: "2-digit",
|
|
107
|
+
minute: "2-digit",
|
|
108
|
+
});
|
|
60
109
|
|
|
61
110
|
return (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
<div
|
|
112
|
+
className={`flex items-center p-2 cursor-pointer rounded-md hover:bg-gray-100 ${
|
|
113
|
+
isSelected ? "bg-gray-200" : ""
|
|
114
|
+
}`}
|
|
115
|
+
onClick={handleSelectConversation}
|
|
116
|
+
>
|
|
117
|
+
<div className="relative">
|
|
118
|
+
{conversation.type === "service" ? (
|
|
119
|
+
<div className="gap-2 flex relative">
|
|
120
|
+
<MessageCircle className="text-gray-600 w-4 h-4 mt-.5" />
|
|
121
|
+
</div>
|
|
122
|
+
) : (
|
|
123
|
+
<>
|
|
124
|
+
<img
|
|
125
|
+
className="w-10 h-10 rounded-full"
|
|
126
|
+
src={participant?.profilePic}
|
|
127
|
+
alt="User Avatar"
|
|
128
|
+
/>
|
|
129
|
+
<span
|
|
130
|
+
className={`chatSidebarStatusDot ${isOnline && "online"}`}
|
|
131
|
+
></span>
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
84
135
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<span className="conversation-time">
|
|
96
|
-
{conversation.lastMessage.status === 'deleted' ? (
|
|
97
|
-
// Show deleted timestamp if message is deleted
|
|
98
|
-
new Date(conversation.lastMessage.updatedAt).toLocaleTimeString([], {
|
|
99
|
-
hour: "2-digit",
|
|
100
|
-
minute: "2-digit",
|
|
101
|
-
})
|
|
102
|
-
) : conversation.lastMessage.status === 'edited' ? (
|
|
103
|
-
// Show updated timestamp if message was edited
|
|
104
|
-
new Date(conversation.lastMessage.updatedAt).toLocaleTimeString([], {
|
|
105
|
-
hour: "2-digit",
|
|
106
|
-
minute: "2-digit",
|
|
107
|
-
})
|
|
108
|
-
) : (
|
|
109
|
-
// Default to created timestamp
|
|
110
|
-
new Date(conversation.lastMessage.createdAt).toLocaleTimeString([], {
|
|
111
|
-
hour: "2-digit",
|
|
112
|
-
minute: "2-digit",
|
|
113
|
-
})
|
|
136
|
+
<div className="flex-1 ml-3">
|
|
137
|
+
<div className="flex justify-between items-center">
|
|
138
|
+
<p className="text-sm font-semibold text-gray-800">
|
|
139
|
+
{conversationName}
|
|
140
|
+
</p>
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
{unreadCount > 0 && (
|
|
143
|
+
<span className="bg-blue-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
|
|
144
|
+
{unreadCount}
|
|
145
|
+
</span>
|
|
114
146
|
)}
|
|
147
|
+
<span className="text-xs text-gray-500">
|
|
148
|
+
{lastMessageTimestamp}
|
|
115
149
|
</span>
|
|
116
150
|
</div>
|
|
117
|
-
<
|
|
118
|
-
{conversation.lastMessage.status === "deleted" ? (
|
|
119
|
-
"This message was deleted"
|
|
120
|
-
) : conversation.lastMessage.type !== "system" &&
|
|
121
|
-
conversation.lastMessage.message.length > 50 ? (
|
|
122
|
-
conversation.lastMessage.message.slice(0, 50) + "..."
|
|
123
|
-
) : conversation.lastMessage.media.length > 0 ? (
|
|
124
|
-
<div
|
|
125
|
-
style={{ display: "flex", alignItems: "center", gap: "5px" }}
|
|
126
|
-
>
|
|
127
|
-
<svg
|
|
128
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
-
width="18"
|
|
130
|
-
height="18"
|
|
131
|
-
viewBox="0 0 24 24"
|
|
132
|
-
fill="none"
|
|
133
|
-
stroke="currentColor"
|
|
134
|
-
strokeWidth="2"
|
|
135
|
-
strokeLinecap="round"
|
|
136
|
-
strokeLinejoin="round"
|
|
137
|
-
>
|
|
138
|
-
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
|
|
139
|
-
</svg>
|
|
140
|
-
attachment
|
|
141
|
-
</div>
|
|
142
|
-
) : (
|
|
143
|
-
conversation.lastMessage.message
|
|
144
|
-
)}
|
|
145
|
-
</p>
|
|
151
|
+
{/* <span className="text-xs text-gray-500">{lastMessageTimestamp}</span> */}
|
|
146
152
|
</div>
|
|
153
|
+
<p className="text-xs text-gray-600 truncate">
|
|
154
|
+
{conversation.lastMessage?.status === "deleted"
|
|
155
|
+
? "This message was deleted"
|
|
156
|
+
: conversation.lastMessage?.media?.length > 0
|
|
157
|
+
? "Attachment"
|
|
158
|
+
: conversation.lastMessage?.message}
|
|
159
|
+
</p>
|
|
147
160
|
</div>
|
|
148
|
-
|
|
161
|
+
</div>
|
|
149
162
|
);
|
|
150
163
|
};
|
|
151
164
|
|