@pubuduth-aplicy/chat-ui 2.1.38 → 2.1.40

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.38",
3
+ "version": "2.1.40",
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": "",
@@ -0,0 +1,17 @@
1
+ const Loader = () => {
2
+ return (
3
+
4
+ <div className="dot-spinner">
5
+ <div className="dot-spinner__dot" />
6
+ <div className="dot-spinner__dot" />
7
+ <div className="dot-spinner__dot" />
8
+ <div className="dot-spinner__dot" />
9
+ <div className="dot-spinner__dot" />
10
+ <div className="dot-spinner__dot" />
11
+ <div className="dot-spinner__dot" />
12
+ <div className="dot-spinner__dot" />
13
+ </div>
14
+ )
15
+ }
16
+
17
+ export default Loader
@@ -1,23 +1,26 @@
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";
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { useCallback, useEffect, useRef, useState } 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
+ import Loader from "../loader";
11
+
12
+ const Messages = () => {
13
+ const { selectedConversation, setMessages, messages } = useChatUIStore();
14
+ const { userId, socket } = useChatContext();
15
+ const { ref, inView } = useInView();
16
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
17
+ const [isInitialLoad, setIsInitialLoad] = useState(true);
18
+ const [isAutoScrolling, setIsAutoScrolling] = useState(false);
10
19
 
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
20
 
18
- // // const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
21
+ // const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
19
22
 
20
- // const lastMessageRef = useRef<HTMLDivElement>(null);
23
+ const lastMessageRef = useRef<HTMLDivElement>(null);
21
24
 
22
25
  // const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
23
26
  // useInfiniteQuery({
@@ -30,183 +33,8 @@
30
33
  // },
31
34
  // });
32
35
 
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
36
 
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
-
193
- /* eslint-disable @typescript-eslint/no-explicit-any */
194
- import { useCallback, useEffect, useRef, useState } from "react";
195
- import Message from "./Message";
196
- import { useChatContext } from "../../providers/ChatProvider";
197
- import { useMessages } from "../../hooks/queries/useChatApi";
198
- import useChatUIStore from "../../stores/Zustant";
199
- import { useInfiniteQuery } from "@tanstack/react-query";
200
- import { fetchMessages } from "../../service/messageService";// Assuming you have a loading spinner component
201
-
202
- const Messages = () => {
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 {
37
+ const {
210
38
  data,
211
39
  fetchNextPage,
212
40
  hasNextPage,
@@ -216,76 +44,81 @@ const Messages = () => {
216
44
  queryKey: ["messages", selectedConversation?._id, userId],
217
45
  queryFn: ({ pageParam = 1 }) =>
218
46
  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) {
47
+ getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
48
+ onSuccess: (data) => {
228
49
  const allMessages = data.pages.flatMap(page => page.messages);
229
50
  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
51
 
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]);
52
+ // Scroll to bottom on initial load
53
+ if (isInitialLoad && scrollContainerRef.current) {
54
+ setTimeout(() => {
55
+ scrollContainerRef.current?.scrollTo({
56
+ top: scrollContainerRef.current.scrollHeight,
57
+ behavior: 'auto'
58
+ });
59
+ setIsInitialLoad(false);
60
+ }, 100);
61
+ }
62
+ },
63
+ });
257
64
 
258
- // Auto-scroll to bottom when new message arrives and user is near bottom
259
- useEffect(() => {
260
- if (!scrollContainerRef.current || isAutoScrolling) return;
65
+ // Auto-scroll to bottom when new message arrives and user is near bottom
66
+ useEffect(() => {
67
+ if (!scrollContainerRef.current || isAutoScrolling) return;
68
+
69
+ const container = scrollContainerRef.current;
70
+ const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
71
+
72
+ if (isNearBottom) {
73
+ setIsAutoScrolling(true);
74
+ container.scrollTo({
75
+ top: container.scrollHeight,
76
+ behavior: "smooth"
77
+ });
78
+ setTimeout(() => setIsAutoScrolling(false), 500);
79
+ }
80
+ }, [messages.length, isAutoScrolling]);
81
+
82
+ useEffect(() => {
83
+ if (inView) {
84
+ fetchNextPage();
85
+ }
86
+ }, [fetchNextPage, inView]);
261
87
 
262
- const container = scrollContainerRef.current;
263
- const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
88
+ // useEffect(() => {
89
+ // if (data) {
90
+ // console.log(data,'message data');
91
+
92
+ // setMessages(data.messages);
93
+ // }
94
+ // }, [selectedConversation?._id, data]);
264
95
 
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]);
96
+ useEffect(() => {
97
+ if (data) {
98
+ const allMessages = data.pages.flatMap(page => page.messages);
99
+ setMessages(allMessages);
100
+ }
101
+ }, [data]);
274
102
 
275
- // Socket event handlers
103
+ // Listen for new messages from the server
276
104
  useEffect(() => {
277
105
  if (!socket || !selectedConversation?._id) return;
278
106
 
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
- };
107
+ // const handleNewMessage = (newMessage: any) => {
108
+ // newMessage.shouldShake = true;
109
+ // console.log("📩 New message received:", newMessage);
110
+ // setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
111
+ // };
112
+
113
+ const handleNewMessage = (newMessage: any) => {
114
+ newMessage.shouldShake = true;
115
+ console.log("📩 New message received:", newMessage);
116
+ setMessages((prevMessages) => {
117
+ const updatedMessages = [...prevMessages, newMessage];
118
+ return [...new Map(updatedMessages.map(m => [m._id, m])).values()]; // Prevent duplicates
119
+ });
120
+ };
121
+
289
122
 
290
123
  const handleStatusUpdate = ({
291
124
  messageId,
@@ -306,9 +139,16 @@ const Messages = () => {
306
139
  socket.off("newMessage", handleNewMessage);
307
140
  socket.off("messageStatusUpdated", handleStatusUpdate);
308
141
  };
309
- }, [socket, setMessages, selectedConversation?._id]);
142
+ }, [socket, setMessages, messages]);
143
+
144
+ useEffect(() => {
145
+ if (messages.length > 0) {
146
+ setTimeout(() => {
147
+ lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
148
+ }, 100);
149
+ }
150
+ }, [messages.length]);
310
151
 
311
- // Delivery confirmation observer
312
152
  useEffect(() => {
313
153
  if (!socket || !messages.length) return;
314
154
 
@@ -332,62 +172,64 @@ const Messages = () => {
332
172
  return () => observer.disconnect();
333
173
  }, [messages, socket]);
334
174
 
335
- // Scroll handler for infinite loading
336
175
  const handleScroll = useCallback(() => {
337
176
  if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
338
177
 
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) {
178
+ const { scrollTop } = scrollContainerRef.current;
179
+ // Load more when scrolled near the top (10% threshold)
180
+ if (scrollTop < 100) {
344
181
  fetchNextPage();
345
182
  }
346
183
  }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
347
184
 
348
185
  useEffect(() => {
349
- const scrollContainer = scrollContainerRef.current;
350
- if (scrollContainer) {
351
- scrollContainer.addEventListener("scroll", handleScroll);
352
- return () => scrollContainer.removeEventListener("scroll", handleScroll);
186
+ const container = scrollContainerRef.current;
187
+ if (container) {
188
+ container.addEventListener('scroll', handleScroll);
189
+ return () => container.removeEventListener('scroll', handleScroll);
353
190
  }
354
191
  }, [handleScroll]);
355
192
 
193
+ // useEffect(() => {
194
+ // const scrollContainer = scrollContainerRef.current;
195
+ // if (scrollContainer) {
196
+ // scrollContainer.addEventListener("scroll", handleScroll);
197
+ // return () => scrollContainer.removeEventListener("scroll", handleScroll);
198
+ // }
199
+ // }, [handleScroll]);
200
+ console.log("📩 Messages:", messages);
201
+ console.log("📩 Messages Length:", messages?.length);
202
+
356
203
  if (isLoading && !messages.length) {
357
- return <LoadingSpinner />;
204
+ return <Loader />;
358
205
  }
359
206
 
360
207
  return (
361
- <div
362
- ref={scrollContainerRef}
363
- className="chatMessages"
364
- style={{ overflowY: "auto", height: "100%", position: "relative" }}
365
- >
366
- {isFetchingNextPage && (
208
+ <div className="chatMessages"
209
+ ref={scrollContainerRef}
210
+ style={{ overflowY: 'auto', height: '100%', position: 'relative' }}
211
+ >
212
+ {isFetchingNextPage && (
367
213
  <div className="loading-indicator">
368
- loading
214
+ <Loader/>
369
215
  </div>
370
216
  )}
371
-
372
217
  {messages?.length > 0 ? (
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
- >
218
+ messages?.map((message: any) =>
219
+ // Check if the message object is valid and has an _id before rendering
220
+ message ? (
221
+ <div key={message._id} ref={lastMessageRef}>
380
222
  <Message message={message} />
381
223
  </div>
382
- )
383
- ))
224
+ ) : null
225
+ )
384
226
  ) : (
385
227
  <p style={{ textAlign: "center" }}>
386
228
  Send a message to start the conversation
387
229
  </p>
388
230
  )}
231
+
389
232
  </div>
390
233
  );
391
234
  };
392
-
393
- export default Messages;
235
+ export default Messages;
@@ -442,4 +442,110 @@
442
442
  50% {
443
443
  transform: translateY(-3px);
444
444
  }
445
- }
445
+ }
446
+
447
+ /* From Uiverse.io by abrahamcalsin */
448
+ .dot-spinner {
449
+ --uib-size: 2.8rem;
450
+ --uib-speed: .9s;
451
+ --uib-color: #183153;
452
+ position: relative;
453
+ display: flex;
454
+ align-items: center;
455
+ justify-content: flex-start;
456
+ height: var(--uib-size);
457
+ width: var(--uib-size);
458
+ }
459
+
460
+ .dot-spinner__dot {
461
+ position: absolute;
462
+ top: 0;
463
+ left: 0;
464
+ display: flex;
465
+ align-items: center;
466
+ justify-content: flex-start;
467
+ height: 100%;
468
+ width: 100%;
469
+ }
470
+
471
+ .dot-spinner__dot::before {
472
+ content: '';
473
+ height: 20%;
474
+ width: 20%;
475
+ border-radius: 50%;
476
+ background-color: var(--uib-color);
477
+ transform: scale(0);
478
+ opacity: 0.5;
479
+ animation: pulse0112 calc(var(--uib-speed) * 1.111) ease-in-out infinite;
480
+ box-shadow: 0 0 20px rgba(18, 31, 53, 0.3);
481
+ }
482
+
483
+ .dot-spinner__dot:nth-child(2) {
484
+ transform: rotate(45deg);
485
+ }
486
+
487
+ .dot-spinner__dot:nth-child(2)::before {
488
+ animation-delay: calc(var(--uib-speed) * -0.875);
489
+ }
490
+
491
+ .dot-spinner__dot:nth-child(3) {
492
+ transform: rotate(90deg);
493
+ }
494
+
495
+ .dot-spinner__dot:nth-child(3)::before {
496
+ animation-delay: calc(var(--uib-speed) * -0.75);
497
+ }
498
+
499
+ .dot-spinner__dot:nth-child(4) {
500
+ transform: rotate(135deg);
501
+ }
502
+
503
+ .dot-spinner__dot:nth-child(4)::before {
504
+ animation-delay: calc(var(--uib-speed) * -0.625);
505
+ }
506
+
507
+ .dot-spinner__dot:nth-child(5) {
508
+ transform: rotate(180deg);
509
+ }
510
+
511
+ .dot-spinner__dot:nth-child(5)::before {
512
+ animation-delay: calc(var(--uib-speed) * -0.5);
513
+ }
514
+
515
+ .dot-spinner__dot:nth-child(6) {
516
+ transform: rotate(225deg);
517
+ }
518
+
519
+ .dot-spinner__dot:nth-child(6)::before {
520
+ animation-delay: calc(var(--uib-speed) * -0.375);
521
+ }
522
+
523
+ .dot-spinner__dot:nth-child(7) {
524
+ transform: rotate(270deg);
525
+ }
526
+
527
+ .dot-spinner__dot:nth-child(7)::before {
528
+ animation-delay: calc(var(--uib-speed) * -0.25);
529
+ }
530
+
531
+ .dot-spinner__dot:nth-child(8) {
532
+ transform: rotate(315deg);
533
+ }
534
+
535
+ .dot-spinner__dot:nth-child(8)::before {
536
+ animation-delay: calc(var(--uib-speed) * -0.125);
537
+ }
538
+
539
+ @keyframes pulse0112 {
540
+ 0%,
541
+ 100% {
542
+ transform: scale(0);
543
+ opacity: 0.5;
544
+ }
545
+
546
+ 50% {
547
+ transform: scale(1);
548
+ opacity: 1;
549
+ }
550
+ }
551
+