@pubuduth-aplicy/chat-ui 2.1.71 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubuduth-aplicy/chat-ui",
3
- "version": "2.1.71",
3
+ "version": "2.1.73",
4
4
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -5,35 +5,48 @@ import { Sidebar } from "./sidebar/Sidebar";
5
5
  import { useChatContext } from "../providers/ChatProvider";
6
6
 
7
7
  export const Chat = () => {
8
- const { messages, setMessages, updateMessageStatus } =
9
- useChatUIStore();
10
- const { socket } = useChatContext();
8
+ const { setMessages, updateMessageStatus } = useChatUIStore();
9
+
10
+ const { socket, sendMessage } = useChatContext();
11
11
 
12
12
  useEffect(() => {
13
13
  if (!socket) return;
14
14
 
15
15
  const handleMessage = (event: MessageEvent) => {
16
16
  try {
17
- const data = JSON.parse(event.data);
18
-
19
- if (data.type === "receiveMessage") {
20
- setMessages([...messages, { ...data.message, status: "sent" }]);
21
- }
17
+ const parsed = JSON.parse(event.data);
18
+ console.log("Parsed WebSocket message:", parsed);
19
+
20
+ if (parsed.event === "newMessage") {
21
+ const message = parsed.data;
22
+ console.log(
23
+ "📨 Message received at:",
24
+ message.createdAt,
25
+ "from:",
26
+ message
27
+ );
28
+ // Send delivery confirmation
29
+ // Update UI immediately
30
+ setMessages((prev) => [...prev, message]);
31
+ sendMessage({
32
+ event: "confirmDelivery",
33
+ data: {
34
+ messageIds: [message._id],
35
+ chatId: message.conversationId, // make sure this is sent from backend
36
+ },
37
+ });
22
38
 
23
- if (data.type === "messageDelivered") {
24
- updateMessageStatus(data.messageId, "delivered");
39
+ // Optional: update UI
40
+ updateMessageStatus(message._id, "delivered");
25
41
  }
26
42
  } catch (error) {
27
- console.error("Error parsing WebSocket message:", error);
43
+ console.error("WebSocket message parse error:", error);
28
44
  }
29
45
  };
30
46
 
31
47
  socket.addEventListener("message", handleMessage);
32
-
33
- return () => {
34
- socket.removeEventListener("message", handleMessage);
35
- };
36
- }, [socket, messages, setMessages, updateMessageStatus]);
48
+ return () => socket.removeEventListener("message", handleMessage);
49
+ }, [socket, setMessages, sendMessage, updateMessageStatus]);
37
50
 
38
51
  return (
39
52
  <div className="container mx-auto mb-5">
@@ -47,4 +60,4 @@ export const Chat = () => {
47
60
  </div>
48
61
  </div>
49
62
  );
50
- };
63
+ };
@@ -11,27 +11,27 @@ import { useDeleteMessageMutation } from "../../hooks/mutations/useDeleteMessage
11
11
  import { useEffect, useRef, useState } from "react";
12
12
 
13
13
  interface MessageProps {
14
- message: {
15
- _id?: string
16
- senderId: string
17
- message: string
18
- status: MessageStatus
19
- createdAt: any
20
- updatedAt: any
14
+ message: {
15
+ _id?: string;
16
+ senderId: string;
17
+ message: string;
18
+ status: MessageStatus;
19
+ createdAt: any;
20
+ updatedAt: any;
21
21
  media?: {
22
- type: FileType
23
- url: string
24
- name: string
25
- size: number
26
- uploadProgress?: number
27
- uploadError: string | null
28
- }[]
29
- isUploading?: boolean
30
- isEdited?: boolean
31
- isDeleted?: boolean
32
- onEdit?: (messageId: string, newMessage: string) => void
33
- onDelete?: (messageId: string) => void
34
- type?: 'user' | 'system' | 'system-completion';
22
+ type: FileType;
23
+ url: string;
24
+ name: string;
25
+ size: number;
26
+ uploadProgress?: number;
27
+ uploadError: string | null;
28
+ }[];
29
+ isUploading?: boolean;
30
+ isEdited?: boolean;
31
+ isDeleted?: boolean;
32
+ onEdit?: (messageId: string, newMessage: string) => void;
33
+ onDelete?: (messageId: string) => void;
34
+ type?: "user" | "system" | "system-completion";
35
35
  meta?: {
36
36
  bookingDetails?: {
37
37
  serviceId: string;
@@ -41,8 +41,8 @@ interface MessageProps {
41
41
  // Add other booking details as needed
42
42
  };
43
43
  reviewLink?: string;
44
- }
45
- }
44
+ };
45
+ };
46
46
  }
47
47
 
48
48
  const Message = ({ message }: MessageProps) => {
@@ -91,8 +91,8 @@ const Message = ({ message }: MessageProps) => {
91
91
  const { mutate: editMessage } = useEditMessageMutation();
92
92
  const [editedMessage, setEditedMessage] = useState("");
93
93
  const [isEditingMode, setIsEditingMode] = useState(false);
94
- const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutation();
95
-
94
+ const { mutate: deleteMessage, isPending: isDeleting } =
95
+ useDeleteMessageMutation();
96
96
 
97
97
  useEffect(() => {
98
98
  setLocalStatus(message.status);
@@ -565,7 +565,9 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
565
565
  onClick={handleDeleteClick}
566
566
  >
567
567
  <Trash2 size={16} />
568
- <span>{isDeleting ? 'Deleting...' : 'Delete'}</span>
568
+ <span>
569
+ {isDeleting ? "Deleting..." : "Delete"}
570
+ </span>
569
571
  </button>
570
572
  </div>
571
573
  )}
@@ -576,25 +578,23 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
576
578
  )
577
579
  )}
578
580
  <div className={`${timestamp}`}>
579
- {message.status === 'deleted' ? (
580
- // Show deleted timestamp if message is deleted
581
- new Date(message.updatedAt).toLocaleTimeString([], {
582
- hour: "2-digit",
583
- minute: "2-digit",
584
- })
585
- ) : message.status === 'edited' ? (
586
- // Show updated timestamp if message was edited
587
- new Date(message.updatedAt).toLocaleTimeString([], {
588
- hour: "2-digit",
589
- minute: "2-digit",
590
- })
591
- ) : (
592
- // Default to created timestamp
593
- new Date(message.createdAt).toLocaleTimeString([], {
594
- hour: "2-digit",
595
- minute: "2-digit",
596
- })
597
- )}
581
+ {message.status === "deleted"
582
+ ? // Show deleted timestamp if message is deleted
583
+ new Date(message.updatedAt).toLocaleTimeString([], {
584
+ hour: "2-digit",
585
+ minute: "2-digit",
586
+ })
587
+ : message.status === "edited"
588
+ ? // Show updated timestamp if message was edited
589
+ new Date(message.updatedAt).toLocaleTimeString([], {
590
+ hour: "2-digit",
591
+ minute: "2-digit",
592
+ })
593
+ : // Default to created timestamp
594
+ new Date(message.createdAt).toLocaleTimeString([], {
595
+ hour: "2-digit",
596
+ minute: "2-digit",
597
+ })}
598
598
  <span className="status-icon">{getStatusIcon()}</span>
599
599
  </div>
600
600
  </div>
@@ -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,4 +1,4 @@
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";
@@ -9,53 +9,103 @@ const Conversation = ({ conversation }: ConversationProps) => {
9
9
  setSelectedConversation,
10
10
  setOnlineUsers,
11
11
  onlineUsers,
12
+ setMessages,
12
13
  selectedConversation,
14
+ updateMessageStatus,
13
15
  } = useChatUIStore();
14
- const { socket, sendMessage } = useChatContext();
16
+ const { socket, sendMessage, isUserOnline } = useChatContext();
15
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
+
16
40
  const handleSelectConversation = async () => {
41
+ // Set as selected conversation
17
42
  setSelectedConversation(conversation);
18
43
 
19
- const unreadMessages = conversation.unreadMessageIds || [];
20
- if (unreadMessages.length > 0) {
21
- console.log("Marking messages as read:", unreadMessages);
44
+ // Mark as active chat
45
+ setActiveChatId(conversation._id);
22
46
 
47
+ // Join chat via WebSocket
48
+ if (socket?.readyState === WebSocket.OPEN) {
23
49
  sendMessage({
24
- event: "messageRead",
50
+ event: "joinChat",
25
51
  data: {
26
- messageIds: unreadMessages,
27
52
  chatId: conversation._id,
53
+ // Send any existing unread messages to mark as read
54
+ messageIds: conversation.unreadMessageIds || [],
28
55
  },
29
56
  });
30
57
  }
31
58
  };
32
59
 
33
- // Listen for online users updates
60
+ // // Enhanced message handler
34
61
  useEffect(() => {
35
62
  if (!socket) return;
36
63
 
37
64
  const handleMessage = (event: MessageEvent) => {
38
65
  try {
39
- const data = JSON.parse(event.data);
40
- if (data.event === "getOnlineUsers") {
41
- 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
+ }
42
97
  }
43
98
  } catch (error) {
44
- console.error("Error parsing online users update:", error);
99
+ console.error("Error handling message:", error);
45
100
  }
46
101
  };
47
102
 
48
103
  socket.addEventListener("message", handleMessage);
104
+ return () => socket.removeEventListener("message", handleMessage);
105
+ }, [socket, activeChatId, setMessages, updateMessageStatus]);
49
106
 
50
- return () => {
51
- socket.removeEventListener("message", handleMessage);
52
- };
53
- }, [socket, setOnlineUsers]);
54
-
55
- const isUserOnline =
56
- conversation?.participantDetails?._id &&
57
- onlineUsers?.includes(conversation.participantDetails._id);
58
-
107
+ const isOnline = isUserOnline(conversation?.participantDetails?._id || "");
108
+ console.log("Online status:", isOnline);
59
109
  const isSelected = selectedConversation?._id === conversation._id;
60
110
 
61
111
  return (
@@ -69,16 +119,16 @@ const Conversation = ({ conversation }: ConversationProps) => {
69
119
  className="conversation-img"
70
120
  src={
71
121
  role === "admin" &&
72
- Array.isArray(selectedConversation?.participantDetails)
73
- ? selectedConversation.participantDetails[1]?.profilePic
74
- : !Array.isArray(selectedConversation?.participantDetails)
75
- ? selectedConversation?.participantDetails?.profilePic
122
+ Array.isArray(conversation?.participantDetails)
123
+ ? conversation.participantDetails[1]?.profilePic
124
+ : !Array.isArray(conversation?.participantDetails)
125
+ ? conversation?.participantDetails?.profilePic
76
126
  : undefined
77
127
  }
78
128
  alt="User Avatar"
79
129
  />
80
130
  <span
81
- className={`chatSidebarStatusDot ${isUserOnline && "online"}`}
131
+ className={`chatSidebarStatusDot ${isOnline && "online"}`}
82
132
  ></span>
83
133
  </div>
84
134
 
@@ -86,32 +136,36 @@ const Conversation = ({ conversation }: ConversationProps) => {
86
136
  <div className="conversation-header">
87
137
  <p className="conversation-name">
88
138
  {role === "admin" &&
89
- Array.isArray(selectedConversation?.participantDetails)
90
- ? selectedConversation.participantDetails[1]?.firstname
91
- : !Array.isArray(selectedConversation?.participantDetails)
92
- ? selectedConversation?.participantDetails?.firstname
139
+ Array.isArray(conversation?.participantDetails)
140
+ ? conversation.participantDetails[1]?.firstname
141
+ : !Array.isArray(conversation?.participantDetails)
142
+ ? conversation?.participantDetails?.firstname
93
143
  : undefined}
94
144
  </p>
95
145
  <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
- })
114
- )}
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
+ })}
115
169
  </span>
116
170
  </div>
117
171
  <p className="conversation-message">
@@ -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">
@@ -18,9 +18,11 @@ interface ChatProviderProps {
18
18
  interface ChatContextType {
19
19
  socket: WebSocket | null;
20
20
  userId: string;
21
- onlineUsers: any[];
21
+ // onlineUsers: any[];
22
22
  sendMessage: (data: any) => void;
23
23
  isConnected: boolean;
24
+ onlineUsers: Set<string>; // Change to Set for better performance
25
+ isUserOnline: (userId: string) => boolean; // Add helper function
24
26
  }
25
27
 
26
28
  const ChatContext = createContext<ChatContextType | null>(null);
@@ -31,7 +33,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
31
33
  }) => {
32
34
  const socketRef = useRef<WebSocket | null>(null);
33
35
  const [socket, setSocket] = useState<WebSocket | null>(null);
34
- const [onlineUsers, setOnlineUsers] = useState<any[]>([]);
36
+ // const [onlineUsers, setOnlineUsers] = useState<any[]>([]);
37
+ const [onlineUsers, setOnlineUsers] = useState<Set<string>>(new Set());
35
38
  const [isConnected, setIsConnected] = useState(false);
36
39
  const reconnectAttempts = useRef(0);
37
40
  const maxReconnectAttempts = 5;
@@ -40,35 +43,40 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
40
43
 
41
44
  const connectWebSocket = useCallback(() => {
42
45
  console.log("🔌 Creating new WebSocket connection...");
43
-
46
+
44
47
  // Convert HTTP URL to WebSocket URL
45
- const wsUrl = apiUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');
46
- const socketInstance = new WebSocket(`${wsUrl}?userId=${userId}`);
48
+ const wsUrl = apiUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
49
+ const socketInstance = new WebSocket(
50
+ `${wsUrl}?userId=${encodeURIComponent(userId)}`
51
+ );
47
52
 
48
53
  socketInstance.onopen = () => {
49
54
  console.log("✅ WebSocket connected");
50
55
  setIsConnected(true);
51
56
  reconnectAttempts.current = 0;
52
-
57
+
53
58
  // Send initial handshake if needed
54
- socketInstance.send(JSON.stringify({
55
- type: 'handshake',
56
- userId: userId
57
- }));
59
+ socketInstance.send(
60
+ JSON.stringify({
61
+ event: "handshake",
62
+ userId: userId,
63
+ })
64
+ );
58
65
  };
59
66
 
60
67
  socketInstance.onmessage = (event) => {
61
68
  try {
62
69
  const data = JSON.parse(event.data);
63
-
64
- if (data.type === 'getOnlineUsers') {
65
- setOnlineUsers(data.users);
70
+
71
+ if (data.event === "getOnlineUsers") {
72
+ console.log("Online users update:", data.data);
73
+ setOnlineUsers(new Set(data.data));
66
74
  }
67
-
75
+
68
76
  // Handle other message types here
69
- console.log('Received message:', data);
77
+ console.log("Received message:", data);
70
78
  } catch (error) {
71
- console.error('Error parsing message:', error);
79
+ console.error("Error parsing message:", error);
72
80
  }
73
81
  };
74
82
 
@@ -80,11 +88,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
80
88
  console.log("🔌 WebSocket connection closed", event);
81
89
  console.log("❌ WebSocket disconnected:", event.code, event.reason);
82
90
  setIsConnected(false);
83
-
91
+
84
92
  // Attempt reconnection
85
93
  if (reconnectAttempts.current < maxReconnectAttempts) {
86
94
  reconnectAttempts.current += 1;
87
- console.log(`Attempting to reconnect (${reconnectAttempts.current}/${maxReconnectAttempts})...`);
95
+ console.log(
96
+ `Attempting to reconnect (${reconnectAttempts.current}/${maxReconnectAttempts})...`
97
+ );
88
98
  setTimeout(connectWebSocket, reconnectInterval);
89
99
  }
90
100
  };
@@ -113,14 +123,24 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
113
123
  };
114
124
  }, [connectWebSocket]);
115
125
 
126
+ const isUserOnline = useCallback(
127
+ (userId: string) => {
128
+ return onlineUsers.has(userId);
129
+ },
130
+ [onlineUsers]
131
+ );
132
+
116
133
  return (
117
- <ChatContext.Provider value={{
118
- socket,
119
- userId,
120
- onlineUsers,
121
- sendMessage,
122
- isConnected
123
- }}>
134
+ <ChatContext.Provider
135
+ value={{
136
+ socket,
137
+ userId,
138
+ onlineUsers,
139
+ sendMessage,
140
+ isConnected,
141
+ isUserOnline,
142
+ }}
143
+ >
124
144
  {children}
125
145
  </ChatContext.Provider>
126
146
  );
@@ -132,4 +152,4 @@ export const useChatContext = () => {
132
152
  throw new Error("useChatContext must be used within a ChatProvider");
133
153
  }
134
154
  return context;
135
- };
155
+ };
@@ -12,28 +12,30 @@ interface ChatUIState {
12
12
  firstname: string;
13
13
  idpic: string;
14
14
  };
15
+ unreadMessageIds?: string[];
15
16
  _id: string;
16
17
  } | null;
17
18
  setSelectedConversation: (
18
19
  selectedConversation: ChatUIState["selectedConversation"]
19
20
  ) => void;
20
- messages: {
21
- _id: string;
22
- text: string;
23
- senderId: string;
24
- status: string ;
25
- isOptimistic:boolean;
26
- message:string;
27
- createdAt: string;
28
- media: {
29
- type: FileType;
30
- url: string;
31
- name: string;
32
- size: number;
33
- uploadProgress: number;
34
- uploadError:string
35
- }[];
36
- isUploading: boolean;}[];
21
+ messages: {
22
+ _id: string;
23
+ text: string;
24
+ senderId: string;
25
+ status: string;
26
+ isOptimistic: boolean;
27
+ message: string;
28
+ createdAt: string;
29
+ media: {
30
+ type: FileType;
31
+ url: string;
32
+ name: string;
33
+ size: number;
34
+ uploadProgress: number;
35
+ uploadError: string
36
+ }[];
37
+ isUploading: boolean;
38
+ }[];
37
39
  setMessages: (messages: ChatUIState["messages"] | ((prev: ChatUIState["messages"]) => ChatUIState["messages"])) => void;
38
40
  updateMessageStatus: (messageId: string, status: string) => void;
39
41
  toggleChat: () => void;
@@ -51,13 +51,15 @@
51
51
  display: flex;
52
52
  padding: 0.5rem 1rem;
53
53
  width: 100%;
54
- border: 1px solid #ccc; /* Consistent border color */
54
+ border: 1px solid #ccc;
55
+ /* Consistent border color */
55
56
  border-radius: 4px;
56
57
  gap: 1rem;
57
58
  justify-content: flex-start;
58
59
  align-items: center;
59
60
  height: 2.5rem;
60
- background-color: rgb(248, 249, 249); /* Move bg color here */
61
+ background-color: rgb(248, 249, 249);
62
+ /* Move bg color here */
61
63
  }
62
64
 
63
65
  .chatSidebarSearchbarImg {
@@ -77,9 +79,12 @@
77
79
  font-weight: 400;
78
80
  width: 100%;
79
81
  max-width: 600px;
80
- border: none !important; /* Force no border */
81
- background: transparent; /* Inherit from container */
82
- box-shadow: none !important; /* Remove any shadow */
82
+ border: none !important;
83
+ /* Force no border */
84
+ background: transparent;
85
+ /* Inherit from container */
86
+ box-shadow: none !important;
87
+ /* Remove any shadow */
83
88
  }
84
89
 
85
90
  .chatSidebarSearchbarContainer:focus-within {
@@ -379,7 +384,8 @@
379
384
  width: 40px;
380
385
  height: 40px;
381
386
  padding: 0;
382
- border-radius: 6px; /* or 0px for sharp corners */
387
+ border-radius: 6px;
388
+ /* or 0px for sharp corners */
383
389
  cursor: pointer;
384
390
  display: inline-flex;
385
391
  align-items: center;
@@ -435,23 +441,23 @@
435
441
  position: relative;
436
442
  }
437
443
 
438
- .amk7{
439
- position: absolute;
440
- top: 0;
441
- display: block;
442
- width: 8px;
443
- height: 13px;
444
- right: -8px;
444
+ .amk7 {
445
+ position: absolute;
446
+ top: 0;
447
+ display: block;
448
+ width: 8px;
449
+ height: 13px;
450
+ right: -8px;
445
451
  }
446
452
 
447
- :root{
448
- font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
453
+ :root {
454
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
449
455
  }
450
456
 
451
457
  .chat-bubble {
452
458
 
453
459
  background-color: #f3f4f6;
454
- border-radius: 0.5rem ;
460
+ border-radius: 0.5rem;
455
461
  /* padding: 0.5rem; */
456
462
  font-size: 14px;
457
463
  max-width: 20rem;
@@ -1191,17 +1197,17 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
1191
1197
  /* margin-top: 0.5rem; */
1192
1198
  }
1193
1199
 
1194
- .media-item {
1200
+ .media-item {
1195
1201
  background: #f3f4f6;
1196
1202
  border-radius: 0.375rem;
1197
1203
  /* reduced from 0.75rem */
1198
- padding: 0.5rem;
1204
+ padding: 0.5rem;
1199
1205
  overflow: hidden;
1200
1206
  display: flex;
1201
1207
  flex-direction: column;
1202
1208
  align-items: start;
1203
1209
  width: 246px;
1204
- }
1210
+ }
1205
1211
 
1206
1212
  /* .media-grid {
1207
1213
  display: grid;
@@ -1243,15 +1249,15 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
1243
1249
  position: relative;
1244
1250
  width: 246px;
1245
1251
  height: 100%; */
1246
- /* aspect-ratio: 1; */
1247
- /* overflow: hidden;
1252
+ /* aspect-ratio: 1; */
1253
+ /* overflow: hidden;
1248
1254
  } */
1249
1255
 
1250
1256
  /* .media-item img {
1251
1257
  width: 100%;
1252
1258
  height: 100%;
1253
1259
  object-fit: cover; */
1254
- /* display: block; */
1260
+ /* display: block; */
1255
1261
  /* } */
1256
1262
 
1257
1263
  /* Adjust aspect ratio for single media */
@@ -1277,7 +1283,8 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
1277
1283
  .four-media-item
1278
1284
  {
1279
1285
  width: 100%;
1280
- } */ */
1286
+ } */
1287
+ */
1281
1288
 
1282
1289
  /* Optional: Add overlay for additional items count */
1283
1290
  .media-item.count-overlay {
@@ -1351,7 +1358,7 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
1351
1358
 
1352
1359
  .document-preview {
1353
1360
  position: relative;
1354
- width: 226px;
1361
+ width: 226px;
1355
1362
  height: 60px;
1356
1363
  /* Increased height to accommodate filename */
1357
1364
  border-radius: 12px;
@@ -1560,6 +1567,7 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
1560
1567
  background-color: #f44336;
1561
1568
  color: white;
1562
1569
  }
1570
+
1563
1571
  .system-message.booking-details {
1564
1572
  display: flex;
1565
1573
  flex-direction: column;
package/src/types/type.ts CHANGED
@@ -21,7 +21,7 @@ export interface ParticipantDetails {
21
21
  export interface Conversation {
22
22
  _id: string;
23
23
  createdAt: string;
24
- lastMessage:{
24
+ lastMessage: {
25
25
  _id: string;
26
26
  senderId: string;
27
27
  message: string;
@@ -47,12 +47,12 @@ export interface ApiResponse {
47
47
 
48
48
  export interface ConversationProps {
49
49
  conversation: {
50
- lastMessage:{
50
+ lastMessage: {
51
51
  _id: string;
52
52
  senderId: string;
53
53
  message: string;
54
- media:string[];
55
- type?:'user' | 'system' | 'system-completion';
54
+ media: string[];
55
+ type?: 'user' | 'system' | 'system-completion';
56
56
  status: MessageStatus;
57
57
  chatId: string;
58
58
  createdAt: string;