@pubuduth-aplicy/chat-ui 2.1.35 → 2.1.37
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 +2 -1
- package/src/components/Chat.tsx +19 -1
- package/src/components/messages/MessageInput.tsx +0 -22
- package/src/components/messages/Messages.tsx +121 -91
- package/src/components/sidebar/Conversation.tsx +7 -8
- package/src/declarations.d.ts +11 -10
- package/src/hooks/queries/useChatApi.ts +10 -10
- package/src/service/messageService.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubuduth-aplicy/chat-ui",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.37",
|
|
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
|
},
|
package/src/components/Chat.tsx
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
1
2
|
import useChatUIStore from '../stores/Zustant';
|
|
2
3
|
import MessageContainer from './messages/MessageContainer';
|
|
3
4
|
import { Sidebar } from './sidebar/Sidebar'
|
|
5
|
+
import { useChatContext } from '../providers/ChatProvider';
|
|
6
|
+
|
|
4
7
|
// import MessageContainer from './components/messages/MessageContainer'
|
|
5
8
|
// import useConversation from '../../zustand/useConversation';
|
|
6
9
|
|
|
7
10
|
export const Chat = () => {
|
|
8
|
-
const { selectedConversation } = useChatUIStore();
|
|
11
|
+
const { selectedConversation, messages, setMessages, updateMessageStatus } = useChatUIStore();
|
|
12
|
+
const {socket}=useChatContext()
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
socket.on("receiveMessage", (messageData) => {
|
|
15
|
+
setMessages([...messages, { ...messageData, status: "sent" }]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
socket.on("messageDelivered", ({ messageId }) => {
|
|
19
|
+
updateMessageStatus(messageId, "delivered");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
socket.off("receiveMessage");
|
|
24
|
+
socket.off("messageDelivered");
|
|
25
|
+
};
|
|
26
|
+
}, [messages, setMessages, updateMessageStatus]);
|
|
9
27
|
return (
|
|
10
28
|
<>
|
|
11
29
|
<div className='container mx-auto mb-5'>
|
|
@@ -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,103 +1,130 @@
|
|
|
1
1
|
/* 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";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
5
3
|
import Message from "./Message";
|
|
6
4
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
7
5
|
import { useMessages } from "../../hooks/queries/useChatApi";
|
|
8
6
|
import useChatUIStore from "../../stores/Zustant";
|
|
9
|
-
|
|
7
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
8
|
+
import { fetchMessages } from "../../service/messageService";
|
|
9
|
+
import { useInView } from "react-intersection-observer";
|
|
10
10
|
|
|
11
11
|
const Messages = () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const { selectedConversation, setMessages, messages } = useChatUIStore();
|
|
13
|
+
const { userId, socket } = useChatContext();
|
|
14
|
+
const { ref, inView } = useInView();
|
|
15
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
// const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
|
|
17
18
|
|
|
19
|
+
const lastMessageRef = useRef<HTMLDivElement>(null);
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
});
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (inView) {
|
|
34
|
+
fetchNextPage();
|
|
22
35
|
}
|
|
23
|
-
}, [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
36
|
+
}, [fetchNextPage, inView]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (data) {
|
|
40
|
+
console.log(data,'message data');
|
|
41
|
+
|
|
42
|
+
setMessages(data.messages);
|
|
43
|
+
}
|
|
44
|
+
}, [selectedConversation?._id, data]);
|
|
45
|
+
|
|
46
|
+
// Listen for new messages from the server
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!socket || !selectedConversation?._id) return;
|
|
49
|
+
|
|
50
|
+
const handleNewMessage = (newMessage: any) => {
|
|
51
|
+
newMessage.shouldShake = true;
|
|
52
|
+
console.log("📩 New message received:", newMessage);
|
|
53
|
+
setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleStatusUpdate = ({
|
|
57
|
+
messageId,
|
|
58
|
+
status,
|
|
59
|
+
}: {
|
|
60
|
+
messageId: string;
|
|
61
|
+
status: string;
|
|
62
|
+
}) => {
|
|
63
|
+
setMessages((prev) =>
|
|
64
|
+
prev.map((msg) => (msg.id === messageId ? { ...msg, status } : msg))
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
socket.on("newMessage", handleNewMessage);
|
|
69
|
+
socket.on("messageStatusUpdated", handleStatusUpdate);
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
socket.off("newMessage", handleNewMessage);
|
|
73
|
+
socket.off("messageStatusUpdated", handleStatusUpdate);
|
|
74
|
+
};
|
|
75
|
+
}, [socket, setMessages, messages]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
80
|
+
}, 100);
|
|
81
|
+
}, [messages]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!socket || !messages.length) return;
|
|
85
|
+
|
|
86
|
+
const observer = new IntersectionObserver(
|
|
87
|
+
(entries) => {
|
|
88
|
+
entries.forEach((entry) => {
|
|
89
|
+
if (entry.isIntersecting) {
|
|
90
|
+
const messageId = entry.target.getAttribute("data-message-id");
|
|
91
|
+
if (messageId) {
|
|
92
|
+
socket.emit("confirmDelivery", { messageId });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
{ threshold: 0.5 }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const messageElements = document.querySelectorAll("[data-message-id]");
|
|
101
|
+
messageElements.forEach((el) => observer.observe(el));
|
|
102
|
+
|
|
103
|
+
return () => observer.disconnect();
|
|
104
|
+
}, [messages, socket]);
|
|
105
|
+
|
|
106
|
+
const handleScroll = useCallback(() => {
|
|
107
|
+
if (!scrollContainerRef.current || isFetchingNextPage || !hasNextPage) return;
|
|
108
|
+
|
|
109
|
+
const { scrollTop } = scrollContainerRef.current;
|
|
110
|
+
|
|
111
|
+
if (scrollTop < 100) {
|
|
112
|
+
fetchNextPage();
|
|
113
|
+
}
|
|
114
|
+
}, [fetchNextPage, isFetchingNextPage, hasNextPage]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const scrollContainer = scrollContainerRef.current;
|
|
118
|
+
if (scrollContainer) {
|
|
119
|
+
scrollContainer.addEventListener("scroll", handleScroll);
|
|
120
|
+
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
|
121
|
+
}
|
|
122
|
+
}, [handleScroll]);
|
|
123
|
+
console.log("📩 Messages:", messages);
|
|
124
|
+
console.log("📩 Messages Length:", messages?.length);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div className="chatMessages">
|
|
101
128
|
{messages?.length > 0 ? (
|
|
102
129
|
messages?.map((message: any) =>
|
|
103
130
|
// Check if the message object is valid and has an _id before rendering
|
|
@@ -112,8 +139,11 @@ const Messages = () => {
|
|
|
112
139
|
Send a message to start the conversation
|
|
113
140
|
</p>
|
|
114
141
|
)}
|
|
142
|
+
|
|
143
|
+
<div ref={ref} className="my-8">
|
|
144
|
+
{isFetchingNextPage ? '<Loading isLoading={isFetchingNextPage} /> ': null}
|
|
145
|
+
</div>
|
|
115
146
|
</div>
|
|
116
|
-
|
|
147
|
+
);
|
|
117
148
|
};
|
|
118
149
|
export default Messages;
|
|
119
|
-
|
|
@@ -13,16 +13,15 @@ console.log(conversation);
|
|
|
13
13
|
const handleSelectConversation = async () => {
|
|
14
14
|
setSelectedConversation(conversation);
|
|
15
15
|
|
|
16
|
-
const
|
|
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
|
-
|
|
22
|
-
receiverId: conversation.participantDetails._id,
|
|
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={
|
|
36
|
+
onClick={handleSelectConversation}
|
|
38
37
|
>
|
|
39
38
|
<img
|
|
40
39
|
className="chatSidebarConversationImg"
|
package/src/declarations.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// src/declarations.d.ts
|
|
2
2
|
declare module '*.css' {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const content: string;
|
|
4
|
+
export default content;
|
|
5
|
+
}
|
|
6
6
|
|
|
7
7
|
declare module '*.png' {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const content: string;
|
|
9
|
+
export default content;
|
|
10
|
+
}
|
|
11
11
|
|
|
12
12
|
declare module '*.svg' {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const content: string;
|
|
14
|
+
export default content;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module '@pubuduth-aplicy/chat-ui';
|
|
@@ -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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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) {
|