@pubuduth-aplicy/chat-ui 2.1.70 → 2.1.73

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.
@@ -11,58 +11,97 @@ const MessageContainer = () => {
11
11
  setSelectedConversation,
12
12
  onlineUsers,
13
13
  setOnlineUsers,
14
+ setMessages,
14
15
  } = useChatUIStore();
15
- const { socket, sendMessage } = useChatContext();
16
- const {role}= getChatConfig()
16
+ const { socket, sendMessage, isUserOnline } = useChatContext();
17
+ const { role } = getChatConfig();
17
18
  const [joinedChats, setJoinedChats] = useState<Set<string>>(new Set());
18
19
 
20
+ // useEffect(() => {
21
+ // if (!socket) return;
22
+
23
+ // const handleMessage = (event) => {
24
+ // try {
25
+ // const parsed = JSON.parse(event.data);
26
+
27
+ // if (parsed.event === 'newMessage') {
28
+ // const message = parsed.data;
29
+ // console.log('Received message:', message);
30
+
31
+ // if (selectedConversation?._id !== message.chatId) return;
32
+
33
+ // const messageId = message._id || message.messageId;
34
+ // console.log('Message ID for unread:', messageId);
35
+
36
+ // if (!messageId) {
37
+ // console.warn('Message has no _id or messageId, skipping unread tracking');
38
+ // return;
39
+ // }
40
+
41
+ // const updatedUnread = [
42
+ // ...(selectedConversation?.unreadMessageIds || []),
43
+ // messageId,
44
+ // ];
45
+
46
+ // console.log('Updated unreadMessageIds:', updatedUnread);
47
+
48
+ // setSelectedConversation({
49
+ // ...selectedConversation,
50
+ // unreadMessageIds: updatedUnread,
51
+ // });
52
+ // }
53
+
54
+ // // Handle other events...
55
+
56
+ // } catch (error) {
57
+ // console.error("WebSocket message parse error:", error);
58
+ // }
59
+ // };
60
+
61
+ // socket.addEventListener("message", handleMessage);
62
+ // return () => socket.removeEventListener("message", handleMessage);
63
+ // }, [socket, setMessages, selectedConversation, setSelectedConversation]);
64
+
19
65
  // Join chat room when conversation is selected
66
+
20
67
  useEffect(() => {
21
68
  if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
22
69
  const chatId = selectedConversation._id;
23
- if (!joinedChats.has(chatId)) {
70
+ const unreadMessages = selectedConversation.unreadMessageIds || [];
71
+
72
+ console.log("Unread messages:", unreadMessages);
73
+
74
+ if (unreadMessages.length > 0) {
24
75
  sendMessage({
25
- type: "joinChat",
26
- chatId: chatId,
76
+ event: "messageRead",
77
+ data: {
78
+ messageIds: unreadMessages,
79
+ chatId: chatId,
80
+ },
27
81
  });
28
- setJoinedChats(new Set(joinedChats).add(chatId));
29
82
  }
83
+ // }
30
84
  }
31
- }, [selectedConversation?._id, socket, sendMessage, joinedChats]);
85
+ }, [
86
+ selectedConversation?._id,
87
+ socket,
88
+ selectedConversation?.unreadMessageIds,
89
+ sendMessage,
90
+ joinedChats,
91
+ ]);
32
92
 
33
93
  // Listen for online users updates
34
- useEffect(() => {
35
- if (!socket) return;
36
-
37
- const handleMessage = (event: MessageEvent) => {
38
- try {
39
- const data = JSON.parse(event.data);
40
- if (data.type === "getOnlineUsers") {
41
- setOnlineUsers(data.users);
42
- }
43
- } catch (error) {
44
- console.error("Error parsing message:", error);
45
- }
46
- };
47
94
 
48
- socket.addEventListener("message", handleMessage);
49
-
50
- return () => {
51
- socket.removeEventListener("message", handleMessage);
52
- };
53
- }, [socket, setOnlineUsers]);
54
-
55
- role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
95
+ role === "admin" && Array.isArray(selectedConversation?.participantDetails)
56
96
  ? selectedConversation.participantDetails[1]?.profilePic
57
97
  : !Array.isArray(selectedConversation?.participantDetails)
58
- ? selectedConversation?.participantDetails?.profilePic
59
- : undefined;
60
-
61
- const isUserOnline =
62
- !Array.isArray(selectedConversation?.participantDetails) &&
63
- !!selectedConversation?.participantDetails?._id &&
64
- onlineUsers?.includes(selectedConversation.participantDetails._id);
98
+ ? selectedConversation?.participantDetails?.profilePic
99
+ : undefined;
65
100
 
101
+ const isOnline =
102
+ !Array.isArray(selectedConversation?.participantDetails) &&
103
+ !!selectedConversation?.participantDetails?._id &&
104
+ isUserOnline(selectedConversation.participantDetails._id);
66
105
 
67
106
  // Cleanup on unmount
68
107
  useEffect(() => {
@@ -84,34 +123,32 @@ const MessageContainer = () => {
84
123
  className="chatMessageContainerInnerImg"
85
124
  alt="Profile"
86
125
  src={
87
- role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
88
- ? selectedConversation.participantDetails[1]?.profilePic
89
- : !Array.isArray(selectedConversation?.participantDetails)
90
- ? selectedConversation?.participantDetails?.profilePic
91
- : undefined
126
+ role === "admin" &&
127
+ Array.isArray(selectedConversation?.participantDetails)
128
+ ? selectedConversation.participantDetails[1]?.profilePic
129
+ : !Array.isArray(selectedConversation?.participantDetails)
130
+ ? selectedConversation?.participantDetails?.profilePic
131
+ : undefined
92
132
  }
93
133
  />
94
134
  <div className="chatMessageContainerOutter">
95
135
  <div className="chatMessageContainerOutterDiv">
96
136
  <p className="chatMessageContainerOutterDiv_name">
97
- {role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
98
- ? selectedConversation.participantDetails[1]?.firstname
99
- : !Array.isArray(selectedConversation?.participantDetails)
100
- ? selectedConversation?.participantDetails?.firstname
101
- : undefined
102
- }
103
- </p>
104
- <p className="text-sm">
105
- {isUserOnline ? "Online" : "Offline"}
137
+ {role === "admin" &&
138
+ Array.isArray(selectedConversation?.participantDetails)
139
+ ? selectedConversation.participantDetails[1]?.firstname
140
+ : !Array.isArray(selectedConversation?.participantDetails)
141
+ ? selectedConversation?.participantDetails?.firstname
142
+ : undefined}
106
143
  </p>
144
+ <p className="text-sm">{isOnline ? "Online" : "Offline"}</p>
107
145
  </div>
108
146
  </div>
109
147
  </div>
110
148
  </div>
111
149
 
112
150
  <Messages />
113
- {role !== 'admin'&& (<MessageInput />)}
114
-
151
+ {role !== "admin" && <MessageInput />}
115
152
  </>
116
153
  )}
117
154
  </div>
@@ -213,4 +250,4 @@ const EmptyInbox: React.FC<EmptyInboxProps> = ({
213
250
  <p className="text-gray-500 max-w-sm">{description}</p>
214
251
  </div>
215
252
  );
216
- };
253
+ };
@@ -227,7 +227,7 @@ const MessageInput = () => {
227
227
  text: message1,
228
228
  message: message1,
229
229
  senderId: userId,
230
- status: "sending" as MessageStatus,
230
+ status: "pending" as MessageStatus,
231
231
  createdAt: new Date().toISOString(),
232
232
  media: attachmentsRef.current.map((att) => ({
233
233
  type: att.type,
@@ -321,15 +321,17 @@ const MessageInput = () => {
321
321
 
322
322
  // Send message via WebSocket
323
323
  sendMessage({
324
- type: "sendMessage",
325
- chatId: selectedConversation?._id,
326
- message: message1,
327
- messageId: data[1]._id,
328
- attachments: successfulUploads,
329
- senderId: userId,
330
- receiverId:
331
- !Array.isArray(selectedConversation?.participantDetails) &&
332
- selectedConversation?.participantDetails._id,
324
+ event: "sendMessage",
325
+ data: {
326
+ chatId: selectedConversation?._id,
327
+ message: message1,
328
+ messageId: data[1]._id,
329
+ attachments: successfulUploads,
330
+ senderId: userId,
331
+ receiverId:
332
+ !Array.isArray(selectedConversation?.participantDetails) &&
333
+ selectedConversation?.participantDetails._id,
334
+ },
333
335
  });
334
336
  },
335
337
  onError: (error) => {
@@ -42,12 +42,12 @@ const Messages = () => {
42
42
 
43
43
  const handleMessage = (event: MessageEvent) => {
44
44
  try {
45
- const data = JSON.parse(event.data);
46
-
47
- if (data.type === "newMessage") {
48
- const newMessage = data.message;
45
+ const parsed = JSON.parse(event.data);
46
+ console.log("Parsed WebSocket message1:", parsed);
47
+ if (parsed.type === "newMessage" || parsed.event === "newMessage") {
48
+ const newMessage = parsed.message;
49
49
  newMessage.shouldShake = true;
50
-
50
+
51
51
  setMessages((prevMessages) => {
52
52
  const isDuplicate = prevMessages.some(
53
53
  (msg) =>
@@ -61,11 +61,28 @@ const Messages = () => {
61
61
  });
62
62
  }
63
63
 
64
- if (data.type === "messageStatusUpdated") {
64
+ const statusOrder = ["sent", "delivered", "read"];
65
+ if (parsed.event === "messageStatusUpdated") {
66
+ const { messageId, status } = parsed.data || {};
67
+ if (!messageId) {
68
+ console.error("Missing messageId in status update", parsed);
69
+ return;
70
+ }
71
+
72
+ console.log(`Updating status for ${messageId} to ${status}`);
65
73
  setMessages((prev) =>
66
- prev.map((msg) =>
67
- msg._id === data.messageId ? { ...msg, status: data.status } : msg
68
- )
74
+ prev.map((msg) => {
75
+ if (msg._id !== messageId) return msg;
76
+
77
+ // Only update if new status is higher than current status
78
+ const currentIdx = statusOrder.indexOf(msg.status);
79
+ const newIdx = statusOrder.indexOf(status);
80
+
81
+ return {
82
+ ...msg,
83
+ status: newIdx > currentIdx ? status : msg.status,
84
+ };
85
+ })
69
86
  );
70
87
  }
71
88
  } catch (error) {
@@ -80,11 +97,29 @@ const Messages = () => {
80
97
  };
81
98
  }, [socket, selectedConversation?._id, setMessages, userId]);
82
99
 
100
+ const sendDeliveryConfirmation = (messageId: string) => {
101
+ if (!socket) return;
102
+
103
+ const message = {
104
+ event: "confirmDelivery",
105
+ data: {
106
+ messageId,
107
+ },
108
+
109
+ // timestamp: Date.now()
110
+ };
111
+
112
+ socket.send(JSON.stringify(message));
113
+ };
114
+
83
115
  // Scroll to bottom when messages change
84
116
  useEffect(() => {
85
117
  if (messages.length > 0) {
86
118
  setTimeout(() => {
87
- lastMessageRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
119
+ lastMessageRef.current?.scrollIntoView({
120
+ behavior: "smooth",
121
+ block: "end",
122
+ });
88
123
  }, 100);
89
124
  }
90
125
  }, [messages.length]);
@@ -113,19 +148,6 @@ const Messages = () => {
113
148
  return () => observer.disconnect();
114
149
  }, [messages, socket]);
115
150
 
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
- };
128
-
129
151
  return (
130
152
  <div
131
153
  className="chatMessages"
@@ -157,4 +179,4 @@ const Messages = () => {
157
179
  );
158
180
  };
159
181
 
160
- export default Messages;
182
+ export default Messages;
@@ -1,59 +1,112 @@
1
- import { useEffect } from "react";
1
+ import { useEffect, useState } from "react";
2
2
  import { useChatContext } from "../../providers/ChatProvider";
3
3
  import useChatUIStore from "../../stores/Zustant";
4
4
  import { ConversationProps } from "../../types/type";
5
5
  import { getChatConfig } from "@pubuduth-aplicy/chat-ui";
6
6
 
7
7
  const Conversation = ({ conversation }: ConversationProps) => {
8
- const { setSelectedConversation, setOnlineUsers, onlineUsers,selectedConversation } =
9
- useChatUIStore();
10
- const { socket, sendMessage } = useChatContext();
11
- const {role} =getChatConfig()
8
+ const {
9
+ setSelectedConversation,
10
+ setOnlineUsers,
11
+ onlineUsers,
12
+ setMessages,
13
+ selectedConversation,
14
+ updateMessageStatus,
15
+ } = useChatUIStore();
16
+ const { socket, sendMessage, isUserOnline } = useChatContext();
17
+ const { role } = getChatConfig();
18
+ // const handleSelectConversation = async () => {
19
+ // setSelectedConversation(conversation);
20
+
21
+ // // Mark unread messages as read
22
+ // const unreadMessages = conversation.unreadMessageIds || [];
23
+ // if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
24
+ // sendMessage({
25
+ // event: "joinChat",
26
+ // data: {
27
+ // chatId: conversation._id,
28
+ // },
29
+ // // event: "messageRead",
30
+ // // data: {
31
+ // // messageIds: unreadMessages,
32
+ // // chatId: conversation._id,
33
+ // // },
34
+ // });
35
+ // }
36
+ // };
37
+
38
+ const [activeChatId, setActiveChatId] = useState(null);
39
+
12
40
  const handleSelectConversation = async () => {
41
+ // Set as selected conversation
13
42
  setSelectedConversation(conversation);
14
43
 
15
- const unreadMessages = conversation.unreadMessageIds || [];
16
- if (unreadMessages.length > 0) {
17
- console.log("Marking messages as read:", unreadMessages);
18
-
44
+ // Mark as active chat
45
+ setActiveChatId(conversation._id);
46
+
47
+ // Join chat via WebSocket
48
+ if (socket?.readyState === WebSocket.OPEN) {
19
49
  sendMessage({
20
- event: "messageRead",
21
- data:{
22
- messageIds: unreadMessages,
23
- chatId: conversation._id
24
- }
25
-
50
+ event: "joinChat",
51
+ data: {
52
+ chatId: conversation._id,
53
+ // Send any existing unread messages to mark as read
54
+ messageIds: conversation.unreadMessageIds || [],
55
+ },
26
56
  });
27
57
  }
28
58
  };
29
59
 
30
- // Listen for online users updates
60
+ // // Enhanced message handler
31
61
  useEffect(() => {
32
62
  if (!socket) return;
33
63
 
34
64
  const handleMessage = (event: MessageEvent) => {
35
65
  try {
36
- const data = JSON.parse(event.data);
37
- if (data.event === "getOnlineUsers") {
38
- setOnlineUsers(data.data);
66
+ const message = JSON.parse(event.data);
67
+ console.log("fdgfd", message);
68
+
69
+ if (message.event === "newMessage") {
70
+ const newMessage = message.data;
71
+
72
+ // If this is the active chat, mark as read immediately
73
+ if (activeChatId === newMessage.conversationId) {
74
+ console.log("rtrtr");
75
+
76
+ sendMessage({
77
+ event: "messageRead",
78
+ data: {
79
+ messageIds: [newMessage._id],
80
+ chatId: newMessage.conversationId,
81
+ senderId: newMessage.senderId,
82
+ },
83
+ });
84
+
85
+ // Optimistic UI update
86
+ // updateMessageStatus(newMessage._id, "read");
87
+ } else {
88
+ // Otherwise mark as delivered
89
+ sendMessage({
90
+ event: "confirmDelivery",
91
+ data: {
92
+ messageIds: [newMessage._id],
93
+ chatId: newMessage.conversationId,
94
+ },
95
+ });
96
+ }
39
97
  }
40
98
  } catch (error) {
41
- console.error("Error parsing online users update:", error);
99
+ console.error("Error handling message:", error);
42
100
  }
43
101
  };
44
102
 
45
103
  socket.addEventListener("message", handleMessage);
104
+ return () => socket.removeEventListener("message", handleMessage);
105
+ }, [socket, activeChatId, setMessages, updateMessageStatus]);
46
106
 
47
- return () => {
48
- socket.removeEventListener("message", handleMessage);
49
- };
50
- }, [socket, setOnlineUsers]);
51
-
52
- const isUserOnline =
53
- conversation?.participantDetails?._id &&
54
- onlineUsers?.includes(conversation.participantDetails._id);
55
-
56
- const isSelected = selectedConversation?._id === conversation._id;
107
+ const isOnline = isUserOnline(conversation?.participantDetails?._id || "");
108
+ console.log("Online status:", isOnline);
109
+ const isSelected = selectedConversation?._id === conversation._id;
57
110
 
58
111
  return (
59
112
  <>
@@ -65,54 +118,84 @@ const Conversation = ({ conversation }: ConversationProps) => {
65
118
  <img
66
119
  className="conversation-img"
67
120
  src={
68
- role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
69
- ? selectedConversation.participantDetails[1]?.profilePic
70
- : !Array.isArray(selectedConversation?.participantDetails)
71
- ? selectedConversation?.participantDetails?.profilePic
72
- : undefined
121
+ role === "admin" &&
122
+ Array.isArray(conversation?.participantDetails)
123
+ ? conversation.participantDetails[1]?.profilePic
124
+ : !Array.isArray(conversation?.participantDetails)
125
+ ? conversation?.participantDetails?.profilePic
126
+ : undefined
73
127
  }
74
-
75
128
  alt="User Avatar"
76
129
  />
77
130
  <span
78
- className={`chatSidebarStatusDot ${
79
- isUserOnline && "online"
80
- }`}
131
+ className={`chatSidebarStatusDot ${isOnline && "online"}`}
81
132
  ></span>
82
133
  </div>
83
134
 
84
135
  <div className="conversation-info">
85
136
  <div className="conversation-header">
86
137
  <p className="conversation-name">
87
- {
88
- role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
89
- ? selectedConversation.participantDetails[1]?.firstname
90
- : !Array.isArray(selectedConversation?.participantDetails)
91
- ? selectedConversation?.participantDetails?.firstname
92
- : undefined
93
- }
138
+ {role === "admin" &&
139
+ Array.isArray(conversation?.participantDetails)
140
+ ? conversation.participantDetails[1]?.firstname
141
+ : !Array.isArray(conversation?.participantDetails)
142
+ ? conversation?.participantDetails?.firstname
143
+ : undefined}
94
144
  </p>
95
145
  <span className="conversation-time">
96
- {new Date(conversation.lastMessage.createdAt).toLocaleTimeString(
97
- [],
98
- {
99
- hour: "2-digit",
100
- minute: "2-digit",
101
- }
102
- )}
146
+ {conversation.lastMessage.status === "deleted"
147
+ ? // Show deleted timestamp if message is deleted
148
+ new Date(
149
+ conversation.lastMessage.updatedAt
150
+ ).toLocaleTimeString([], {
151
+ hour: "2-digit",
152
+ minute: "2-digit",
153
+ })
154
+ : conversation.lastMessage.status === "edited"
155
+ ? // Show updated timestamp if message was edited
156
+ new Date(
157
+ conversation.lastMessage.updatedAt
158
+ ).toLocaleTimeString([], {
159
+ hour: "2-digit",
160
+ minute: "2-digit",
161
+ })
162
+ : // Default to created timestamp
163
+ new Date(
164
+ conversation.lastMessage.createdAt
165
+ ).toLocaleTimeString([], {
166
+ hour: "2-digit",
167
+ minute: "2-digit",
168
+ })}
103
169
  </span>
104
170
  </div>
105
171
  <p className="conversation-message">
106
- {conversation.lastMessage.message.length > 50
107
- ? conversation.lastMessage.message.slice(0, 50) + "..."
108
- : conversation.lastMessage.media.length > 0 ? (
109
- <div style={{display:"flex",alignItems:"center", gap:"5px"}}>
110
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
111
- <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>
112
- </svg>
113
- attachment
114
- </div>
115
- ) : conversation.lastMessage.message}
172
+ {conversation.lastMessage.status === "deleted" ? (
173
+ "This message was deleted"
174
+ ) : conversation.lastMessage.type !== "system" &&
175
+ conversation.lastMessage.message.length > 50 ? (
176
+ conversation.lastMessage.message.slice(0, 50) + "..."
177
+ ) : conversation.lastMessage.media.length > 0 ? (
178
+ <div
179
+ style={{ display: "flex", alignItems: "center", gap: "5px" }}
180
+ >
181
+ <svg
182
+ xmlns="http://www.w3.org/2000/svg"
183
+ width="18"
184
+ height="18"
185
+ viewBox="0 0 24 24"
186
+ fill="none"
187
+ stroke="currentColor"
188
+ strokeWidth="2"
189
+ strokeLinecap="round"
190
+ strokeLinejoin="round"
191
+ >
192
+ <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>
193
+ </svg>
194
+ attachment
195
+ </div>
196
+ ) : (
197
+ conversation.lastMessage.message
198
+ )}
116
199
  </p>
117
200
  </div>
118
201
  </div>
@@ -120,4 +203,4 @@ const Conversation = ({ conversation }: ConversationProps) => {
120
203
  );
121
204
  };
122
205
 
123
- export default Conversation;
206
+ export default Conversation;
@@ -12,7 +12,12 @@ const Conversations = () => {
12
12
  // const { loading, conversations } = useGetConversations();
13
13
  return (
14
14
  <div className="chatSidebarConversations">
15
- <h2 className="text-lg font-semibold text-gray-700" style={{paddingLeft:'1rem'}}>All Messages</h2>
15
+ <h2
16
+ className="text-lg font-semibold text-gray-700"
17
+ style={{ paddingLeft: "1rem" }}
18
+ >
19
+ All Messages
20
+ </h2>
16
21
  {(!conversations || conversations.length === 0) && (
17
22
  <div className="flex flex-col items-center justify-center p-8 text-center">
18
23
  <div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-100 mb-4">