@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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 (
|
|
40
|
-
|
|
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
|
-
}, [
|
|
256
|
+
}, [messages.length, initialLoad]);
|
|
45
257
|
|
|
46
|
-
//
|
|
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
|
-
|
|
53
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
355
|
+
|
|
356
|
+
if (isLoading && !messages.length) {
|
|
357
|
+
return <LoadingSpinner />;
|
|
358
|
+
}
|
|
125
359
|
|
|
126
360
|
return (
|
|
127
|
-
<div
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
)
|
|
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
|
-
|
|
392
|
+
|
|
393
|
+
export default Messages;
|
package/src/declarations.d.ts
CHANGED
|
@@ -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
|
+
}
|