@pubuduth-aplicy/chat-ui 2.1.36 → 2.1.38

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.36",
3
+ "version": "2.1.38",
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": "",
@@ -16,6 +16,7 @@
16
16
  "axios": "^1.8.2",
17
17
  "react": "^19.0.0",
18
18
  "react-dom": "^19.0.0",
19
+ "react-intersection-observer": "^9.16.0",
19
20
  "socket.io-client": "^4.8.1",
20
21
  "zustand": "^5.0.3"
21
22
  },
@@ -79,28 +79,6 @@ const MessageInput = () => {
79
79
  setIsSending(true);
80
80
  try {
81
81
  console.log("📤 Sending message:", message);
82
-
83
- // if (selectedConversation?._id) {
84
- // const response = await sendMessage({
85
- // chatId: selectedConversation.participantDetails._id,
86
- // senderId: userId,
87
- // message,
88
- // });
89
-
90
- // // You can log or handle the response here
91
- // console.log('Response from sendMessage:', response);
92
-
93
-
94
- // socket.emit("sendMessage", {
95
- // chatId: selectedConversation._id,
96
- // message,
97
- // senderId: userId,
98
- // receiverId: selectedConversation.participantDetails._id,
99
- // });
100
- // }
101
-
102
-
103
-
104
82
  mutation.mutate({
105
83
  chatId: selectedConversation?.participantDetails._id,
106
84
  senderId: userId,
@@ -1,119 +1,393 @@
1
+ // /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ // import { useCallback, useEffect, useRef } from "react";
3
+ // import Message from "./Message";
4
+ // import { useChatContext } from "../../providers/ChatProvider";
5
+ // import { useMessages } from "../../hooks/queries/useChatApi";
6
+ // import useChatUIStore from "../../stores/Zustant";
7
+ // import { useInfiniteQuery } from "@tanstack/react-query";
8
+ // import { fetchMessages } from "../../service/messageService";
9
+ // import { useInView } from "react-intersection-observer";
10
+
11
+ // const Messages = () => {
12
+ // const { selectedConversation, setMessages, messages } = useChatUIStore();
13
+ // const { userId, socket } = useChatContext();
14
+ // const { ref, inView } = useInView();
15
+ // const previousScrollHeight = useRef(0);
16
+ // const scrollContainerRef = useRef<HTMLDivElement>(null);
17
+
18
+ // // const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
19
+
20
+ // const lastMessageRef = useRef<HTMLDivElement>(null);
21
+
22
+ // const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
23
+ // useInfiniteQuery({
24
+ // queryKey: ["messages", selectedConversation?._id, userId],
25
+ // queryFn: ({ pageParam = 1 }) =>
26
+ // fetchMessages(selectedConversation?._id, userId, pageParam),
27
+ // initialPageParam: 1,
28
+ // getNextPageParam: (lastPage, allPages) => {
29
+ // return lastPage.messages.length ? allPages.length + 1: undefined;
30
+ // },
31
+ // });
32
+
33
+ // useEffect(() => {
34
+ // if (inView) {
35
+ // fetchNextPage();
36
+ // }
37
+ // }, [fetchNextPage, inView]);
38
+
39
+ // // useEffect(() => {
40
+ // // if (data) {
41
+ // // console.log(data,'message data');
42
+
43
+ // // setMessages(data.messages);
44
+ // // }
45
+ // // }, [selectedConversation?._id, data]);
46
+
47
+ // useEffect(() => {
48
+ // if (data) {
49
+ // const allMessages = data.pages.flatMap(page => page.messages);
50
+ // setMessages(allMessages);
51
+
52
+ // if (scrollContainerRef.current) {
53
+ // const newScrollHeight = scrollContainerRef.current.scrollHeight;
54
+ // scrollContainerRef.current.scrollTop = newScrollHeight - previousScrollHeight.current;
55
+ // }
56
+ // }
57
+ // }, [data]);
58
+
59
+ // // Listen for new messages from the server
60
+ // useEffect(() => {
61
+ // if (!socket || !selectedConversation?._id) return;
62
+
63
+ // // const handleNewMessage = (newMessage: any) => {
64
+ // // newMessage.shouldShake = true;
65
+ // // console.log("📩 New message received:", newMessage);
66
+ // // setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
67
+ // // };
68
+
69
+ // const handleNewMessage = (newMessage: any) => {
70
+ // newMessage.shouldShake = true;
71
+ // console.log("📩 New message received:", newMessage);
72
+ // setMessages((prevMessages) => {
73
+ // const updatedMessages = [...prevMessages, newMessage];
74
+ // return [...new Map(updatedMessages.map(m => [m._id, m])).values()]; // Prevent duplicates
75
+ // });
76
+ // };
77
+
78
+
79
+ // const handleStatusUpdate = ({
80
+ // messageId,
81
+ // status,
82
+ // }: {
83
+ // messageId: string;
84
+ // status: string;
85
+ // }) => {
86
+ // setMessages((prev) =>
87
+ // prev.map((msg) => (msg._id === messageId ? { ...msg, status } : msg))
88
+ // );
89
+ // };
90
+
91
+ // socket.on("newMessage", handleNewMessage);
92
+ // socket.on("messageStatusUpdated", handleStatusUpdate);
93
+
94
+ // return () => {
95
+ // socket.off("newMessage", handleNewMessage);
96
+ // socket.off("messageStatusUpdated", handleStatusUpdate);
97
+ // };
98
+ // }, [socket, setMessages, messages]);
99
+
100
+ // // useEffect(() => {
101
+ // // setTimeout(() => {
102
+ // // lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
103
+ // // }, 100);
104
+ // // }, [messages]);
105
+
106
+ // useEffect(() => {
107
+ // if (!scrollContainerRef.current) return;
108
+
109
+ // const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
110
+ // const isAtBottom = scrollHeight - scrollTop <= clientHeight + 100;
111
+
112
+ // if (isAtBottom) {
113
+ // setTimeout(() => {
114
+ // lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
115
+ // }, 100);
116
+ // }
117
+ // }, [messages]);
118
+
119
+
120
+ // useEffect(() => {
121
+ // if (!socket || !messages.length) return;
122
+
123
+ // const observer = new IntersectionObserver(
124
+ // (entries) => {
125
+ // entries.forEach((entry) => {
126
+ // if (entry.isIntersecting) {
127
+ // const messageId = entry.target.getAttribute("data-message-id");
128
+ // if (messageId) {
129
+ // socket.emit("confirmDelivery", { messageId });
130
+ // }
131
+ // }
132
+ // });
133
+ // },
134
+ // { threshold: 0.5 }
135
+ // );
136
+
137
+ // const messageElements = document.querySelectorAll("[data-message-id]");
138
+ // messageElements.forEach((el) => observer.observe(el));
139
+
140
+ // return () => observer.disconnect();
141
+ // }, [messages, socket]);
142
+
143
+ // const handleScroll = useCallback(() => {
144
+ // if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
145
+
146
+ // const { scrollTop } = scrollContainerRef.current;
147
+
148
+ // if (scrollTop < 50) { // Fetch next page only if close to the top
149
+ // fetchNextPage();
150
+ // }
151
+ // }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
152
+
153
+
154
+
155
+ // useEffect(() => {
156
+ // const scrollContainer = scrollContainerRef.current;
157
+ // if (scrollContainer) {
158
+ // scrollContainer.addEventListener("scroll", handleScroll);
159
+ // return () => scrollContainer.removeEventListener("scroll", handleScroll);
160
+ // }
161
+ // }, [handleScroll]);
162
+
163
+ // console.log("📩 Messages:", messages);
164
+ // console.log("📩 Messages Length:", messages?.length);
165
+
166
+ // return (
167
+ // <div className="chatMessages">
168
+
169
+ // <div ref={ref} className="my-8">
170
+ // {isFetchingNextPage ? '<Loading isLoading={isFetchingNextPage} /> ': null}
171
+ // </div>
172
+ // {messages?.length > 0 ? (
173
+ // messages?.map((message: any) =>
174
+ // // Check if the message object is valid and has an _id before rendering
175
+ // message ? (
176
+ // <div key={message._id} ref={lastMessageRef}>
177
+ // <Message message={message} />
178
+ // </div>
179
+ // ) : null
180
+ // )
181
+ // ) : (
182
+ // <p style={{ textAlign: "center" }}>
183
+ // Send a message to start the conversation
184
+ // </p>
185
+ // )}
186
+
187
+ // </div>
188
+ // );
189
+ // };
190
+ // export default Messages;
191
+
192
+
1
193
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useEffect, useRef } from "react";
3
- // import useGetMessages from "../../hooks/useGetMessages";
4
- // import MessageSkeleton from "../skeletons/MessageSkeleton";
194
+ import { useCallback, useEffect, useRef, useState } from "react";
5
195
  import Message from "./Message";
6
196
  import { useChatContext } from "../../providers/ChatProvider";
7
197
  import { useMessages } from "../../hooks/queries/useChatApi";
8
198
  import useChatUIStore from "../../stores/Zustant";
9
- // import useListenMessages from "../../hooks/useListenMessages";
199
+ import { useInfiniteQuery } from "@tanstack/react-query";
200
+ import { fetchMessages } from "../../service/messageService";// Assuming you have a loading spinner component
10
201
 
11
202
  const Messages = () => {
12
- const { selectedConversation,setMessages,messages } = useChatUIStore()
13
- const {userId,socket}=useChatContext()
14
- const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
15
-
16
- const lastMessageRef = useRef<HTMLDivElement>(null);
17
-
18
-
19
- useEffect(() => {
20
- if (data) {
21
- setMessages(data.messages);
22
- }
23
- }, [selectedConversation?._id,data]);
24
-
25
- // Listen for new messages from the server
26
- useEffect(() => {
27
- if (!socket || !selectedConversation?._id) return;
28
-
29
- const handleNewMessage = (newMessage:any) => {
30
- newMessage.shouldShake = true;
31
- console.log("📩 New message received:", newMessage);
32
- // setMessages([...messages, newMessage[1]]);
33
- setMessages((prevMessages) => [...prevMessages, newMessage]);
34
- };
35
-
36
- const handleStatusUpdate = ({ messageId, status }) => {
37
- setMessages(prev => prev.map(msg =>
38
- msg._id === messageId ? { ...msg, status } : msg
39
- ));
40
- };
41
-
42
- socket.on("newMessage", handleNewMessage);
43
- socket.on("messageStatusUpdated", handleStatusUpdate);
44
-
45
- return () => {
46
- socket.off("newMessage", handleNewMessage);
47
- socket.off("messageStatusUpdated", handleStatusUpdate);
48
- };
49
- }, [socket,setMessages, messages]);
50
-
51
- useEffect(() => {
52
- setTimeout(() => {
53
- lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
54
- }, 100);
55
- }, [ messages]);
56
-
57
- useEffect(() => {
58
- if (!socket || !messages.length) return;
59
-
60
- const observer = new IntersectionObserver((entries) => {
61
- entries.forEach(entry => {
62
- if (entry.isIntersecting) {
63
- const messageId = entry.target.getAttribute('data-message-id');
64
- if (messageId) {
65
- socket.emit('confirmDelivery', { messageId });
66
- }
67
- }
68
- });
69
- }, { threshold: 0.5 });
70
-
71
- const messageElements = document.querySelectorAll('[data-message-id]');
72
- messageElements.forEach(el => observer.observe(el));
73
-
74
- return () => observer.disconnect();
75
- }, [messages, socket]);
76
-
77
- if (isLoading) {
78
- return <p>Loading messages...</p>;
79
- }
80
-
81
- if (isError) {
82
- return <p>Error: {error?.message}</p>;
83
- }
84
-
85
- console.log("📩 Messages:", messages);
86
- console.log("📩 Messages Length:", messages?.length);
87
-
88
- return (
89
- // <div className="chatMessages">
90
- // {messages?.length > 0 ? (
91
- // messages?.map((message: any) => (
92
- // <div key={message._id} ref={lastMessageRef}>
93
- // <Message message={message} />
94
- // </div>
95
- // ))
96
- // ) : (
97
- // <p style={{ textAlign: "center" }}>Send a message to start the conversation</p>
98
- // )}
99
- // </div>
100
- <div className="chatMessages">
203
+ const { selectedConversation, setMessages, messages } = useChatUIStore();
204
+ const { userId, socket } = useChatContext();
205
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
206
+ const [isAutoScrolling, setIsAutoScrolling] = useState(false);
207
+ const [initialLoad, setInitialLoad] = useState(true);
208
+
209
+ const {
210
+ data,
211
+ fetchNextPage,
212
+ hasNextPage,
213
+ isFetchingNextPage,
214
+ isLoading,
215
+ } = useInfiniteQuery({
216
+ queryKey: ["messages", selectedConversation?._id, userId],
217
+ queryFn: ({ pageParam = 1 }) =>
218
+ fetchMessages(selectedConversation?._id, userId, pageParam),
219
+ initialPageParam: 1,
220
+ getNextPageParam: (lastPage, allPages) => {
221
+ return lastPage.messages.length ? allPages.length + 1 : undefined;
222
+ },
223
+ });
224
+
225
+ // Handle merging paginated data with messages
226
+ useEffect(() => {
227
+ if (data) {
228
+ const allMessages = data.pages.flatMap(page => page.messages);
229
+ setMessages(allMessages);
230
+ }
231
+ }, [data, setMessages]);
232
+
233
+ // Handle scroll position when new messages are loaded
234
+ useEffect(() => {
235
+ if (!scrollContainerRef.current || initialLoad) return;
236
+
237
+ const container = scrollContainerRef.current;
238
+ if (isFetchingNextPage) {
239
+ // Store current scroll position before loading more messages
240
+ const scrollTopBefore = container.scrollTop;
241
+ const scrollHeightBefore = container.scrollHeight;
242
+
243
+ // After messages are loaded, adjust scroll position to maintain view
244
+ requestAnimationFrame(() => {
245
+ container.scrollTop = container.scrollHeight - scrollHeightBefore + scrollTopBefore;
246
+ });
247
+ }
248
+ }, [isFetchingNextPage, initialLoad]);
249
+
250
+ // Handle initial scroll to bottom
251
+ useEffect(() => {
252
+ if (scrollContainerRef.current && messages.length > 0 && initialLoad) {
253
+ scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
254
+ setInitialLoad(false);
255
+ }
256
+ }, [messages.length, initialLoad]);
257
+
258
+ // Auto-scroll to bottom when new message arrives and user is near bottom
259
+ useEffect(() => {
260
+ if (!scrollContainerRef.current || isAutoScrolling) return;
261
+
262
+ const container = scrollContainerRef.current;
263
+ const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
264
+
265
+ if (isNearBottom) {
266
+ setIsAutoScrolling(true);
267
+ container.scrollTo({
268
+ top: container.scrollHeight,
269
+ behavior: "smooth"
270
+ });
271
+ setTimeout(() => setIsAutoScrolling(false), 500);
272
+ }
273
+ }, [messages.length, isAutoScrolling]);
274
+
275
+ // Socket event handlers
276
+ useEffect(() => {
277
+ if (!socket || !selectedConversation?._id) return;
278
+
279
+ const handleNewMessage = (newMessage: any) => {
280
+ newMessage.shouldShake = true;
281
+ setMessages((prevMessages) => {
282
+ // Prevent duplicates
283
+ if (prevMessages.some(msg => msg._id === newMessage._id)) {
284
+ return prevMessages;
285
+ }
286
+ return [...prevMessages, newMessage];
287
+ });
288
+ };
289
+
290
+ const handleStatusUpdate = ({
291
+ messageId,
292
+ status,
293
+ }: {
294
+ messageId: string;
295
+ status: string;
296
+ }) => {
297
+ setMessages((prev) =>
298
+ prev.map((msg) => (msg._id === messageId ? { ...msg, status } : msg))
299
+ );
300
+ };
301
+
302
+ socket.on("newMessage", handleNewMessage);
303
+ socket.on("messageStatusUpdated", handleStatusUpdate);
304
+
305
+ return () => {
306
+ socket.off("newMessage", handleNewMessage);
307
+ socket.off("messageStatusUpdated", handleStatusUpdate);
308
+ };
309
+ }, [socket, setMessages, selectedConversation?._id]);
310
+
311
+ // Delivery confirmation observer
312
+ useEffect(() => {
313
+ if (!socket || !messages.length) return;
314
+
315
+ const observer = new IntersectionObserver(
316
+ (entries) => {
317
+ entries.forEach((entry) => {
318
+ if (entry.isIntersecting) {
319
+ const messageId = entry.target.getAttribute("data-message-id");
320
+ if (messageId) {
321
+ socket.emit("confirmDelivery", { messageId });
322
+ }
323
+ }
324
+ });
325
+ },
326
+ { threshold: 0.5 }
327
+ );
328
+
329
+ const messageElements = document.querySelectorAll("[data-message-id]");
330
+ messageElements.forEach((el) => observer.observe(el));
331
+
332
+ return () => observer.disconnect();
333
+ }, [messages, socket]);
334
+
335
+ // Scroll handler for infinite loading
336
+ const handleScroll = useCallback(() => {
337
+ if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
338
+
339
+ const container = scrollContainerRef.current;
340
+ const scrollTop = container.scrollTop;
341
+
342
+ // Load more messages when scrolled near the top
343
+ if (scrollTop < 100 && !isFetchingNextPage) {
344
+ fetchNextPage();
345
+ }
346
+ }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
347
+
348
+ useEffect(() => {
349
+ const scrollContainer = scrollContainerRef.current;
350
+ if (scrollContainer) {
351
+ scrollContainer.addEventListener("scroll", handleScroll);
352
+ return () => scrollContainer.removeEventListener("scroll", handleScroll);
353
+ }
354
+ }, [handleScroll]);
355
+
356
+ if (isLoading && !messages.length) {
357
+ return <LoadingSpinner />;
358
+ }
359
+
360
+ return (
361
+ <div
362
+ ref={scrollContainerRef}
363
+ className="chatMessages"
364
+ style={{ overflowY: "auto", height: "100%", position: "relative" }}
365
+ >
366
+ {isFetchingNextPage && (
367
+ <div className="loading-indicator">
368
+ loading
369
+ </div>
370
+ )}
371
+
101
372
  {messages?.length > 0 ? (
102
- messages?.map((message: any) =>
103
- // Check if the message object is valid and has an _id before rendering
104
- message ? (
105
- <div key={message._id} ref={lastMessageRef}>
373
+ messages.map((message: any) => (
374
+ message && (
375
+ <div
376
+ key={message._id}
377
+ data-message-id={message._id}
378
+ ref={messages.indexOf(message) === messages.length - 1 ? lastMessageRef : null}
379
+ >
106
380
  <Message message={message} />
107
381
  </div>
108
- ) : null
109
- )
382
+ )
383
+ ))
110
384
  ) : (
111
385
  <p style={{ textAlign: "center" }}>
112
386
  Send a message to start the conversation
113
387
  </p>
114
388
  )}
115
389
  </div>
116
- );
390
+ );
117
391
  };
118
- export default Messages;
119
392
 
393
+ export default Messages;
@@ -13,16 +13,15 @@ console.log(conversation);
13
13
  const handleSelectConversation = async () => {
14
14
  setSelectedConversation(conversation);
15
15
 
16
- const lastMessageId = conversation.lastMessageId; // You should have this in conversation data
16
+ const unreadMessages = conversation.unreadMessageIds || []; // Get all unread message IDs
17
+
18
+ if (unreadMessages.length > 0) {
19
+ console.log("Emitting messageRead for messages:", unreadMessages);
17
20
 
18
- if (lastMessageId) {
19
- // ✅ Notify server via socket
20
21
  socket.emit("messageRead", {
21
- messageId: lastMessageId,
22
- receiverId: conversation.participantDetails._id, // or receiverId
22
+ messageIds: unreadMessages, // Send all unread message IDs
23
+ receiverId: conversation.participantDetails._id,
23
24
  });
24
-
25
-
26
25
  }
27
26
  };
28
27
 
@@ -34,7 +33,7 @@ const handleSelectConversation = async () => {
34
33
  <div
35
34
  className={` chatSidebarConversationMain
36
35
  `}
37
- onClick={() => setSelectedConversation(conversation)}
36
+ onClick={handleSelectConversation}
38
37
  >
39
38
  <img
40
39
  className="chatSidebarConversationImg"
@@ -1,16 +1,22 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  // src/declarations.d.ts
2
3
  declare module '*.css' {
3
- const content: string;
4
- export default content;
5
- }
4
+ const content: string;
5
+ export default content;
6
+ }
6
7
 
7
8
  declare module '*.png' {
8
- const content: string;
9
- export default content;
10
- }
9
+ const content: string;
10
+ export default content;
11
+ }
11
12
 
12
13
  declare module '*.svg' {
13
- const content: string;
14
- export default content;
15
- }
16
-
14
+ const content: string;
15
+ export default content;
16
+ }
17
+
18
+ declare module '@pubuduth-aplicy/chat-ui';
19
+ declare module '@pubuduth-aplicy/chat-ui' {
20
+ export function ChatUI(props: any): JSX.Element;
21
+ // Define other exports and types as needed
22
+ }
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { getAllConversationData } from "../../service/sidebarApi";
4
- import { fetchMessages } from "../../service/messageService";
4
+ // import { fetchMessages } from "../../service/messageService";
5
5
 
6
6
 
7
7
  export const useGetConversations = (id: any) => {
@@ -15,12 +15,12 @@ export const useGetConversations = (id: any) => {
15
15
  });
16
16
  };
17
17
 
18
- export const useMessages = (chatId: string| undefined, userid: string) => {
19
- return useQuery({
20
- queryKey: ['messages', chatId, userid],
21
- queryFn: () => {
22
- console.log('Fetching messages for:', chatId, userid);
23
- return fetchMessages(chatId, userid);
24
- },
25
- });
26
- };
18
+ // export const useMessages = (chatId: string| undefined, userid: string) => {
19
+ // return useQuery({
20
+ // queryKey: ['messages', chatId, userid],
21
+ // queryFn: () => {
22
+ // console.log('Fetching messages for:', chatId, userid);
23
+ // return fetchMessages(chatId, userid);
24
+ // },
25
+ // });
26
+ // };
@@ -10,9 +10,11 @@ export const sendMessage = async ({ chatId,senderId, message }: { chatId: any; s
10
10
  };
11
11
 
12
12
 
13
- export const fetchMessages = async (chatId: string|undefined, userid: string) => {
13
+ export const fetchMessages = async (chatId: string|undefined, userid: string,pagenum:number) => {
14
14
  try {
15
- const response = await apiClient.get(`${Path.getmessage}/${chatId}/${userid}`);
15
+ const response = await apiClient.get(`${Path.getmessage}/${chatId}/${userid}`,{
16
+ params: { pagenum, limit: 20 },
17
+ });
16
18
  console.log(response); // Check the full response
17
19
  return response.data; // Ensure 'data' exists or adjust accordingly
18
20
  } catch (error) {