@pubuduth-aplicy/chat-ui 2.1.73 → 2.1.75

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.
@@ -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
@@ -45,7 +46,15 @@ const Messages = () => {
45
46
  const parsed = JSON.parse(event.data);
46
47
  console.log("Parsed WebSocket message1:", parsed);
47
48
  if (parsed.type === "newMessage" || parsed.event === "newMessage") {
48
- const newMessage = parsed.message;
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) => {
@@ -61,7 +70,7 @@ const Messages = () => {
61
70
  });
62
71
  }
63
72
 
64
- const statusOrder = ["sent", "delivered", "read"];
73
+ const statusOrder = ["sent", "delivered", "read", "edited", "deleted"];
65
74
  if (parsed.event === "messageStatusUpdated") {
66
75
  const { messageId, status } = parsed.data || {};
67
76
  if (!messageId) {
@@ -69,22 +78,58 @@ const Messages = () => {
69
78
  return;
70
79
  }
71
80
 
72
- console.log(`Updating status for ${messageId} to ${status}`);
73
81
  setMessages((prev) =>
74
82
  prev.map((msg) => {
75
83
  if (msg._id !== messageId) return msg;
76
84
 
77
- // Only update if new status is higher than current status
78
85
  const currentIdx = statusOrder.indexOf(msg.status);
79
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
+ }
80
94
 
81
- return {
82
- ...msg,
83
- status: newIdx > currentIdx ? status : msg.status,
84
- };
95
+ return msg; // No update if new status isn't higher
85
96
  })
86
97
  );
87
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
130
+ )
131
+ );
132
+ }
88
133
  } catch (error) {
89
134
  console.error("Error parsing WebSocket message:", error);
90
135
  }
@@ -97,56 +142,41 @@ const Messages = () => {
97
142
  };
98
143
  }, [socket, selectedConversation?._id, setMessages, userId]);
99
144
 
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
-
115
145
  // Scroll to bottom when messages change
116
146
  useEffect(() => {
117
147
  if (messages.length > 0) {
118
148
  setTimeout(() => {
119
149
  lastMessageRef.current?.scrollIntoView({
120
150
  behavior: "smooth",
121
- block: "end",
151
+ block: "nearest",
122
152
  });
123
153
  }, 100);
124
154
  }
125
155
  }, [messages.length]);
126
156
 
127
157
  // Track message visibility for read receipts
128
- useEffect(() => {
129
- if (!socket || !messages.length) return;
130
-
131
- const observer = new IntersectionObserver(
132
- (entries) => {
133
- entries.forEach((entry) => {
134
- if (entry.isIntersecting) {
135
- const messageId = entry.target.getAttribute("data-message-id");
136
- if (messageId) {
137
- sendDeliveryConfirmation(messageId);
138
- }
139
- }
140
- });
141
- },
142
- { threshold: 0.5 }
143
- );
144
-
145
- const messageElements = document.querySelectorAll("[data-message-id]");
146
- messageElements.forEach((el) => observer.observe(el));
147
-
148
- return () => observer.disconnect();
149
- }, [messages, socket]);
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]);
150
180
 
151
181
  return (
152
182
  <div
@@ -1,205 +1,140 @@
1
- import { useEffect, useState } from "react";
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { MessageCircle } from "lucide-react";
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
- setMessages,
13
- selectedConversation,
14
- updateMessageStatus,
15
12
  } = 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
- // };
13
+ const { socket, isUserOnline } = useChatContext();
14
+ const selectedConversation = useChatUIStore(
15
+ (state) => state.selectedConversation
16
+ );
17
+ const { userId } = useChatContext();
37
18
 
38
- const [activeChatId, setActiveChatId] = useState(null);
19
+ const participant = conversation.participantDetails?.find(
20
+ (p:any) => p._id !== userId
21
+ );
39
22
 
40
- const handleSelectConversation = async () => {
41
- // Set as selected conversation
23
+ const handleSelectConversation = () => {
24
+ console.log(
25
+ "Selected Conversation Data:",
26
+ JSON.stringify(conversation, null, 2)
27
+ );
42
28
  setSelectedConversation(conversation);
29
+ const unreadMessageIds = conversation.unreadMessageIds || [];
30
+ if (unreadMessageIds.length > 0 && socket?.readyState === WebSocket.OPEN) {
31
+ console.log("unread messages", unreadMessageIds);
43
32
 
44
- // Mark as active chat
45
- setActiveChatId(conversation._id);
46
-
47
- // Join chat via WebSocket
48
- if (socket?.readyState === WebSocket.OPEN) {
49
- sendMessage({
50
- event: "joinChat",
33
+ const message = {
34
+ event: "messageRead",
51
35
  data: {
36
+ messageIds: unreadMessageIds,
37
+ senderId: participant?._id,
38
+ receiverId: userId,
52
39
  chatId: conversation._id,
53
- // Send any existing unread messages to mark as read
54
- messageIds: conversation.unreadMessageIds || [],
55
40
  },
56
- });
41
+ };
42
+ socket.send(JSON.stringify(message));
57
43
  }
58
44
  };
59
45
 
60
- // // Enhanced message handler
61
46
  useEffect(() => {
62
47
  if (!socket) return;
63
48
 
64
49
  const handleMessage = (event: MessageEvent) => {
65
50
  try {
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
- }
51
+ const data = JSON.parse(event.data);
52
+ if (data.event === "getOnlineUsers") {
53
+ setOnlineUsers(data.payload);
97
54
  }
98
55
  } catch (error) {
99
- console.error("Error handling message:", error);
56
+ console.error("Failed to parse WebSocket message:", error);
100
57
  }
101
58
  };
102
59
 
103
60
  socket.addEventListener("message", handleMessage);
104
- return () => socket.removeEventListener("message", handleMessage);
105
- }, [socket, activeChatId, setMessages, updateMessageStatus]);
106
61
 
107
- const isOnline = isUserOnline(conversation?.participantDetails?._id || "");
108
- console.log("Online status:", isOnline);
62
+ return () => {
63
+ socket.removeEventListener("message", handleMessage);
64
+ };
65
+ }, [socket, setOnlineUsers]);
66
+
67
+ useEffect(() => {
68
+ console.log("Current conversation state:", conversation);
69
+ }, [conversation]);
70
+
71
+ const isOnline = isUserOnline(participant?._id || "");
109
72
  const isSelected = selectedConversation?._id === conversation._id;
73
+ const unreadCount = conversation.unreadMessageCount || 0;
74
+ const conversationName =
75
+ conversation.type === "service" && conversation.bookingId
76
+ ? `Booking #${conversation.bookingId}`
77
+ : participant?.name || "Conversation";
78
+
79
+ const lastMessageTimestamp = new Date(
80
+ conversation.lastMessage?.updatedAt || conversation.lastMessage?.createdAt
81
+ ).toLocaleTimeString([], {
82
+ hour: "2-digit",
83
+ minute: "2-digit",
84
+ });
110
85
 
111
86
  return (
112
- <>
113
- <div
114
- className={`conversation-container ${isSelected ? "selected" : ""} `}
115
- onClick={handleSelectConversation}
116
- >
117
- <div className="conversation-avatar">
118
- <img
119
- className="conversation-img"
120
- src={
121
- role === "admin" &&
122
- Array.isArray(conversation?.participantDetails)
123
- ? conversation.participantDetails[1]?.profilePic
124
- : !Array.isArray(conversation?.participantDetails)
125
- ? conversation?.participantDetails?.profilePic
126
- : undefined
127
- }
128
- alt="User Avatar"
129
- />
130
- <span
131
- className={`chatSidebarStatusDot ${isOnline && "online"}`}
132
- ></span>
133
- </div>
87
+ <div
88
+ className={`flex items-center p-2 cursor-pointer rounded-md hover:bg-gray-100 ${
89
+ isSelected ? "bg-gray-200" : ""
90
+ }`}
91
+ onClick={handleSelectConversation}
92
+ >
93
+ <div className="relative">
94
+ {conversation.type === "service" ? (
95
+ <div className="gap-2 flex relative">
96
+ <MessageCircle className="text-gray-600 w-4 h-4 mt-.5" />
97
+ </div>
98
+ ) : (
99
+ <>
100
+ <img
101
+ className="w-10 h-10 rounded-full"
102
+ src={participant?.profilePicture}
103
+ alt="User Avatar"
104
+ />
105
+ <span
106
+ className={`chatSidebarStatusDot ${isOnline && "online"}`}
107
+ ></span>
108
+ </>
109
+ )}
110
+ </div>
134
111
 
135
- <div className="conversation-info">
136
- <div className="conversation-header">
137
- <p className="conversation-name">
138
- {role === "admin" &&
139
- Array.isArray(conversation?.participantDetails)
140
- ? conversation.participantDetails[1]?.firstname
141
- : !Array.isArray(conversation?.participantDetails)
142
- ? conversation?.participantDetails?.firstname
143
- : undefined}
144
- </p>
145
- <span className="conversation-time">
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
- })}
112
+ <div className="flex-1 ml-3">
113
+ <div className="flex justify-between items-center">
114
+ <p className="text-sm font-semibold text-gray-800">
115
+ {conversationName}
116
+ </p>
117
+ <div className="flex items-center gap-2">
118
+ {unreadCount > 0 && (
119
+ <span className="bg-blue-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
120
+ {unreadCount}
121
+ </span>
122
+ )}
123
+ <span className="text-xs text-gray-500">
124
+ {lastMessageTimestamp}
169
125
  </span>
170
126
  </div>
171
- <p className="conversation-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
- )}
199
- </p>
127
+ {/* <span className="text-xs text-gray-500">{lastMessageTimestamp}</span> */}
200
128
  </div>
129
+ <p className="text-xs text-gray-600 truncate dark:text-gray-500">
130
+ {conversation.lastMessage?.status === "deleted"
131
+ ? "This message was deleted"
132
+ : conversation.lastMessage?.media?.length > 0
133
+ ? "Attachment"
134
+ : conversation.lastMessage?.message}
135
+ </p>
201
136
  </div>
202
- </>
137
+ </div>
203
138
  );
204
139
  };
205
140