@pubuduth-aplicy/chat-ui 2.1.37 → 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.37",
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": "",
@@ -1,56 +1,290 @@
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 { useCallback, useEffect, useRef } from "react";
194
+ import { useCallback, useEffect, useRef, useState } from "react";
3
195
  import Message from "./Message";
4
196
  import { useChatContext } from "../../providers/ChatProvider";
5
197
  import { useMessages } from "../../hooks/queries/useChatApi";
6
198
  import useChatUIStore from "../../stores/Zustant";
7
199
  import { useInfiniteQuery } from "@tanstack/react-query";
8
- import { fetchMessages } from "../../service/messageService";
9
- import { useInView } from "react-intersection-observer";
200
+ import { fetchMessages } from "../../service/messageService";// Assuming you have a loading spinner component
10
201
 
11
202
  const Messages = () => {
12
203
  const { selectedConversation, setMessages, messages } = useChatUIStore();
13
204
  const { userId, socket } = useChatContext();
14
- const { ref, inView } = useInView();
15
205
  const scrollContainerRef = useRef<HTMLDivElement>(null);
206
+ const [isAutoScrolling, setIsAutoScrolling] = useState(false);
207
+ const [initialLoad, setInitialLoad] = useState(true);
16
208
 
17
- // const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
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
+ });
18
224
 
19
- const lastMessageRef = useRef<HTMLDivElement>(null);
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]);
20
232
 
21
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
22
- useInfiniteQuery({
23
- queryKey: ["messages", selectedConversation?._id, userId],
24
- queryFn: ({ pageParam = 1 }) =>
25
- fetchMessages(selectedConversation?._id, userId, pageParam),
26
- initialPageParam: 1,
27
- getNextPageParam: (lastPage, allPages) => {
28
- return lastPage.length ? allPages.length + 1 : undefined;
29
- },
30
- });
233
+ // Handle scroll position when new messages are loaded
234
+ useEffect(() => {
235
+ if (!scrollContainerRef.current || initialLoad) return;
31
236
 
32
- useEffect(() => {
33
- if (inView) {
34
- fetchNextPage();
35
- }
36
- }, [fetchNextPage, inView]);
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]);
37
249
 
250
+ // Handle initial scroll to bottom
38
251
  useEffect(() => {
39
- if (data) {
40
- console.log(data,'message data');
41
-
42
- setMessages(data.messages);
252
+ if (scrollContainerRef.current && messages.length > 0 && initialLoad) {
253
+ scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
254
+ setInitialLoad(false);
43
255
  }
44
- }, [selectedConversation?._id, data]);
256
+ }, [messages.length, initialLoad]);
45
257
 
46
- // Listen for new messages from the server
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
47
276
  useEffect(() => {
48
277
  if (!socket || !selectedConversation?._id) return;
49
278
 
50
279
  const handleNewMessage = (newMessage: any) => {
51
280
  newMessage.shouldShake = true;
52
- console.log("📩 New message received:", newMessage);
53
- setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
281
+ setMessages((prevMessages) => {
282
+ // Prevent duplicates
283
+ if (prevMessages.some(msg => msg._id === newMessage._id)) {
284
+ return prevMessages;
285
+ }
286
+ return [...prevMessages, newMessage];
287
+ });
54
288
  };
55
289
 
56
290
  const handleStatusUpdate = ({
@@ -61,7 +295,7 @@ const Messages = () => {
61
295
  status: string;
62
296
  }) => {
63
297
  setMessages((prev) =>
64
- prev.map((msg) => (msg.id === messageId ? { ...msg, status } : msg))
298
+ prev.map((msg) => (msg._id === messageId ? { ...msg, status } : msg))
65
299
  );
66
300
  };
67
301
 
@@ -72,14 +306,9 @@ const Messages = () => {
72
306
  socket.off("newMessage", handleNewMessage);
73
307
  socket.off("messageStatusUpdated", handleStatusUpdate);
74
308
  };
75
- }, [socket, setMessages, messages]);
76
-
77
- useEffect(() => {
78
- setTimeout(() => {
79
- lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
80
- }, 100);
81
- }, [messages]);
309
+ }, [socket, setMessages, selectedConversation?._id]);
82
310
 
311
+ // Delivery confirmation observer
83
312
  useEffect(() => {
84
313
  if (!socket || !messages.length) return;
85
314
 
@@ -103,12 +332,15 @@ const Messages = () => {
103
332
  return () => observer.disconnect();
104
333
  }, [messages, socket]);
105
334
 
335
+ // Scroll handler for infinite loading
106
336
  const handleScroll = useCallback(() => {
107
337
  if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
338
+
339
+ const container = scrollContainerRef.current;
340
+ const scrollTop = container.scrollTop;
108
341
 
109
- const { scrollTop } = scrollContainerRef.current;
110
-
111
- if (scrollTop < 100) {
342
+ // Load more messages when scrolled near the top
343
+ if (scrollTop < 100 && !isFetchingNextPage) {
112
344
  fetchNextPage();
113
345
  }
114
346
  }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
@@ -120,30 +352,42 @@ const Messages = () => {
120
352
  return () => scrollContainer.removeEventListener("scroll", handleScroll);
121
353
  }
122
354
  }, [handleScroll]);
123
- console.log("📩 Messages:", messages);
124
- console.log("📩 Messages Length:", messages?.length);
355
+
356
+ if (isLoading && !messages.length) {
357
+ return <LoadingSpinner />;
358
+ }
125
359
 
126
360
  return (
127
- <div className="chatMessages">
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
+
128
372
  {messages?.length > 0 ? (
129
- messages?.map((message: any) =>
130
- // Check if the message object is valid and has an _id before rendering
131
- message ? (
132
- <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
+ >
133
380
  <Message message={message} />
134
381
  </div>
135
- ) : null
136
- )
382
+ )
383
+ ))
137
384
  ) : (
138
385
  <p style={{ textAlign: "center" }}>
139
386
  Send a message to start the conversation
140
387
  </p>
141
388
  )}
142
-
143
- <div ref={ref} className="my-8">
144
- {isFetchingNextPage ? '<Loading isLoading={isFetchingNextPage} /> ': null}
145
- </div>
146
389
  </div>
147
390
  );
148
391
  };
149
- export default Messages;
392
+
393
+ export default Messages;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  // src/declarations.d.ts
2
3
  declare module '*.css' {
3
4
  const content: string;
@@ -15,3 +16,7 @@ declare module '*.svg' {
15
16
  }
16
17
 
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
+ }