@pubuduth-aplicy/chat-ui 2.1.39 → 2.1.41

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.39",
3
+ "version": "2.1.41",
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,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useCallback, useEffect, useRef } from "react";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import Message from "./Message";
4
4
  import { useChatContext } from "../../providers/ChatProvider";
5
5
  import { useMessages } from "../../hooks/queries/useChatApi";
@@ -7,27 +7,144 @@ import useChatUIStore from "../../stores/Zustant";
7
7
  import { useInfiniteQuery } from "@tanstack/react-query";
8
8
  import { fetchMessages } from "../../service/messageService";
9
9
  import { useInView } from "react-intersection-observer";
10
+ import Loader from "../Loader";
10
11
 
11
12
  const Messages = () => {
12
13
  const { selectedConversation, setMessages, messages } = useChatUIStore();
13
14
  const { userId, socket } = useChatContext();
14
15
  const { ref, inView } = useInView();
15
16
  const scrollContainerRef = useRef<HTMLDivElement>(null);
16
-
17
+ const [isInitialLoad, setIsInitialLoad] = useState(true);
18
+ const [isAutoScrolling, setIsAutoScrolling] = useState(false);
19
+ const [prevMessagesLength, setPrevMessagesLength] = useState(0);
20
+ const loadingRef = useRef(false);
17
21
  // const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
18
22
 
19
23
  const lastMessageRef = useRef<HTMLDivElement>(null);
20
24
 
21
25
  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.messages.length ? allPages.length + 1: undefined;
29
- },
30
- });
26
+ useInfiniteQuery({
27
+ queryKey: ["messages", selectedConversation?._id, userId],
28
+ queryFn: ({ pageParam = 1 }) =>
29
+ fetchMessages(selectedConversation?._id, userId, pageParam),
30
+ getNextPageParam: (lastPage) => {
31
+ return lastPage.nextPage; // Use the nextPage from API response
32
+ },
33
+ initialPageParam: 1,
34
+ });
35
+
36
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
37
+ const scrollHeightBeforeLoad = useRef(0);
38
+
39
+ const handleScroll = useCallback(() => {
40
+ if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
41
+
42
+ const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
43
+
44
+ // Load more when scrolled near the top (100px threshold)
45
+ if (scrollTop < 100) {
46
+ // Save current scroll height before loading
47
+ scrollHeightBeforeLoad.current = scrollHeight;
48
+ setIsLoadingMore(true);
49
+ fetchNextPage().finally(() => setIsLoadingMore(false));
50
+ }
51
+ }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
52
+
53
+ useEffect(() => {
54
+ if (isLoadingMore && scrollContainerRef.current && scrollHeightBeforeLoad.current) {
55
+ // After new messages are loaded, adjust scroll position to maintain the same view
56
+ const container = scrollContainerRef.current;
57
+ const newScrollHeight = container.scrollHeight;
58
+ container.scrollTop = newScrollHeight - scrollHeightBeforeLoad.current;
59
+ }
60
+ }, [messages.length, isLoadingMore]);
61
+
62
+ // Scroll to bottom for new messages
63
+ useEffect(() => {
64
+ if (messages.length > 0 && !isLoadingMore) {
65
+ setTimeout(() => {
66
+ lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
67
+ }, 100);
68
+ }
69
+ }, [messages.length, isLoadingMore]);
70
+
71
+
72
+ // const {
73
+ // data,
74
+ // fetchNextPage,
75
+ // hasNextPage,
76
+ // isFetchingNextPage,
77
+ // isLoading,
78
+ // } = useInfiniteQuery({
79
+ // queryKey: ["messages", selectedConversation?._id, userId],
80
+ // queryFn: ({ pageParam = 1 }) =>
81
+ // fetchMessages(selectedConversation?._id, userId, pageParam),
82
+ // getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
83
+ // onSuccess: (data) => {
84
+ // const allMessages = data.pages.flatMap(page => page.messages);
85
+ // setMessages(allMessages);
86
+
87
+ // // Scroll to bottom on initial load
88
+ // if (isInitialLoad && scrollContainerRef.current) {
89
+ // setTimeout(() => {
90
+ // scrollContainerRef.current?.scrollTo({
91
+ // top: scrollContainerRef.current.scrollHeight,
92
+ // behavior: 'auto'
93
+ // });
94
+ // setIsInitialLoad(false);
95
+ // }, 100);
96
+ // }
97
+ // },
98
+ // });
99
+
100
+ // Maintain scroll position when loading older messages
101
+
102
+
103
+ // useEffect(() => {
104
+ // console.log('scrollContainerRef',scrollContainerRef.current);
105
+
106
+ // if (!scrollContainerRef.current || isInitialLoad) return;
107
+
108
+ // if (isFetchingNextPage) {
109
+ // // Store current scroll position before loading
110
+ // const container = scrollContainerRef.current;
111
+ // const scrollTopBefore = container.scrollTop;
112
+ // const scrollHeightBefore = container.scrollHeight;
113
+ // console.log(container,'scrollHeightBefore',scrollHeightBefore,'container',scrollTopBefore);
114
+
115
+ // loadingRef.current = true;
116
+
117
+ // return () => {
118
+ // // After messages are loaded, adjust scroll position
119
+ // requestAnimationFrame(() => {
120
+ // container.scrollTop = container.scrollHeight - scrollHeightBefore + scrollTopBefore;
121
+ // loadingRef.current = false;
122
+ // });
123
+ // };
124
+ // }
125
+ // }, [isFetchingNextPage, isInitialLoad]);
126
+
127
+ // Add this useEffect to handle new messages
128
+ // useEffect(() => {
129
+ // if (!scrollContainerRef.current || loadingRef.current) return;
130
+
131
+ // const container = scrollContainerRef.current;
132
+ // console.log("container",container);
133
+
134
+ // const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
135
+
136
+ // // Only auto-scroll if we're near bottom or it's a new message
137
+ // if (isNearBottom || messages.length > prevMessagesLength) {
138
+ // setIsAutoScrolling(true);
139
+ // container.scrollTo({
140
+ // top: container.scrollHeight,
141
+ // behavior: messages.length > prevMessagesLength ? 'smooth' : 'auto'
142
+ // });
143
+ // setTimeout(() => setIsAutoScrolling(false), 500);
144
+ // }
145
+
146
+ // setPrevMessagesLength(messages.length);
147
+ // }, [messages.length]);
31
148
 
32
149
  useEffect(() => {
33
150
  if (inView) {
@@ -45,7 +162,9 @@ const Messages = () => {
45
162
 
46
163
  useEffect(() => {
47
164
  if (data) {
48
- const allMessages = data.pages.flatMap(page => page.messages);
165
+ console.log('message fetching data',data);
166
+
167
+ const allMessages = data.pages.flatMap(page => page.messages).reverse();
49
168
  setMessages(allMessages);
50
169
  }
51
170
  }, [data]);
@@ -122,15 +241,23 @@ useEffect(() => {
122
241
  return () => observer.disconnect();
123
242
  }, [messages, socket]);
124
243
 
125
- const handleScroll = useCallback(() => {
126
- if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
127
-
128
- const { scrollTop } = scrollContainerRef.current;
129
-
130
- if (scrollTop < 100) {
131
- fetchNextPage();
132
- }
133
- }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
244
+ // const handleScroll = useCallback(() => {
245
+ // if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
246
+
247
+ // const { scrollTop } = scrollContainerRef.current;
248
+ // // Load more when scrolled near the top (10% threshold)
249
+ // if (scrollTop < 100) {
250
+ // fetchNextPage();
251
+ // }
252
+ // }, [fetchNextPage, isFetchingNextPage, hasNextPage]);
253
+
254
+ // useEffect(() => {
255
+ // const container = scrollContainerRef.current;
256
+ // if (container) {
257
+ // container.addEventListener('scroll', handleScroll);
258
+ // return () => container.removeEventListener('scroll', handleScroll);
259
+ // }
260
+ // }, [handleScroll]);
134
261
 
135
262
  // useEffect(() => {
136
263
  // const scrollContainer = scrollContainerRef.current;
@@ -139,20 +266,29 @@ useEffect(() => {
139
266
  // return () => scrollContainer.removeEventListener("scroll", handleScroll);
140
267
  // }
141
268
  // }, [handleScroll]);
142
- console.log("📩 Messages:", messages);
269
+
270
+
271
+ console.log("📩 Messages:", messages);
143
272
  console.log("📩 Messages Length:", messages?.length);
144
273
 
274
+
275
+
145
276
  return (
146
- <div className="chatMessages">
147
-
277
+ <div className="chatMessages"
278
+ ref={scrollContainerRef}
279
+ style={{ overflowY: 'auto', height: '100%', position: 'relative' }}
280
+ >
281
+
148
282
  <div ref={ref} className="my-8">
149
- {isFetchingNextPage ? '<Loading isLoading={isFetchingNextPage} /> ': null}
283
+ {isFetchingNextPage ? <Loader /> : null}
150
284
  </div>
151
285
  {messages?.length > 0 ? (
152
286
  messages?.map((message: any) =>
153
287
  // Check if the message object is valid and has an _id before rendering
154
288
  message ? (
155
- <div key={message._id} ref={lastMessageRef}>
289
+ <div key={message._id} ref={lastMessageRef}
290
+ style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}
291
+ >
156
292
  <Message message={message} />
157
293
  </div>
158
294
  ) : null
@@ -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
+