@pubuduth-aplicy/chat-ui 2.1.31 → 2.1.33
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 +1 -1
- package/src/components/messages/Message.tsx +0 -1
- package/src/components/messages/MessageContainer.tsx +24 -2
- package/src/components/messages/MessageInput.tsx +62 -36
- package/src/components/messages/Messages.tsx +22 -17
- package/src/providers/ChatProvider.tsx +31 -3
- package/src/stores/Zustant.ts +10 -9
- package/src/style/style.css +56 -1
package/package.json
CHANGED
|
@@ -21,7 +21,6 @@ const Message = ({ message }: MessageProps) => {
|
|
|
21
21
|
const fromMe = message.senderId === userId;
|
|
22
22
|
const bubbleBgColor = fromMe ? "chatMessagesBubble_me" : "chatMessagesBubble_Other";
|
|
23
23
|
const alignItems = fromMe ? "chatMessagesBubble_me" : "chatMessagesBubble_Other";
|
|
24
|
-
console.log('messagedis',message);
|
|
25
24
|
|
|
26
25
|
const date = new Date(message.createdAt);
|
|
27
26
|
const hours = date.getUTCHours();
|
|
@@ -3,12 +3,32 @@ import { useEffect } from "react";
|
|
|
3
3
|
import MessageInput from "./MessageInput";
|
|
4
4
|
import Messages from "./Messages";
|
|
5
5
|
import useChatUIStore from "../../stores/Zustant";
|
|
6
|
+
import { useChatContext } from "../../providers/ChatProvider";
|
|
6
7
|
// import { Chat, CaretLeft } from "@phosphor-icons/react";
|
|
7
8
|
// import { useAuthContext } from "../../context/AuthContext";
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
const MessageContainer = () => {
|
|
11
|
-
const { selectedConversation, setSelectedConversation } = useChatUIStore();
|
|
12
|
+
const { selectedConversation, setSelectedConversation ,onlineUsers, setOnlineUsers } = useChatUIStore();
|
|
13
|
+
const {socket} = useChatContext();
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!socket) return;
|
|
17
|
+
|
|
18
|
+
const handleOnlineUsers = (users: string[]) => {
|
|
19
|
+
setOnlineUsers(users);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
socket.on("getOnlineUsers", handleOnlineUsers);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
socket.off("getOnlineUsers", handleOnlineUsers);
|
|
26
|
+
};
|
|
27
|
+
}, [socket, setOnlineUsers]);
|
|
28
|
+
|
|
29
|
+
const isUserOnline = selectedConversation?.participantDetails?._id &&
|
|
30
|
+
onlineUsers?.includes(selectedConversation.participantDetails._id);
|
|
31
|
+
|
|
12
32
|
|
|
13
33
|
useEffect(() => {
|
|
14
34
|
// cleanup function (unmounts)
|
|
@@ -33,7 +53,9 @@ const MessageContainer = () => {
|
|
|
33
53
|
<p className="chatMessageContainerOutterDiv_name">
|
|
34
54
|
{selectedConversation.participantDetails.firstname}
|
|
35
55
|
</p>
|
|
36
|
-
<p className="text-sm text-[#12bbb5]">
|
|
56
|
+
<p className="text-sm text-[#12bbb5]">
|
|
57
|
+
{isUserOnline ? "Online" : "Offline"}
|
|
58
|
+
</p>
|
|
37
59
|
</div>
|
|
38
60
|
</div>
|
|
39
61
|
{/* <h4 className=" inline-block py-2 text-left font-sans font-semibold normal-case">Lara Abegnale</h4> */}
|
|
@@ -15,6 +15,7 @@ const MessageInput = () => {
|
|
|
15
15
|
const { mutate: sendMessage } = useMessageMutation();
|
|
16
16
|
const [typingUser, setTypingUser] = useState<string | null>(null);
|
|
17
17
|
const [isSending, setIsSending] = useState(false);
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
19
|
const [isTyping, setIsTyping] = useState(false); // Track if the user is typing
|
|
19
20
|
|
|
20
21
|
useEffect(() => {
|
|
@@ -25,48 +26,64 @@ const MessageInput = () => {
|
|
|
25
26
|
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
if (!socket) return;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
|
|
30
|
+
if (message.trim() !== "") {
|
|
31
|
+
setIsTyping(true);
|
|
32
|
+
socket.emit("typing", { chatId: selectedConversation?._id, userId });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const typingTimeout = setTimeout(() => {
|
|
36
|
+
if (message.trim() === "") {
|
|
37
|
+
setIsTyping(false);
|
|
38
|
+
socket.emit("stopTyping", {
|
|
39
|
+
chatId: selectedConversation?._id,
|
|
40
|
+
userId,
|
|
41
|
+
});
|
|
32
42
|
}
|
|
43
|
+
}, 2000);
|
|
44
|
+
|
|
45
|
+
return () => clearTimeout(typingTimeout);
|
|
46
|
+
}, [message, socket, selectedConversation?._id, userId]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!socket) return;
|
|
50
|
+
|
|
51
|
+
const handleTyping = ({ userId }: { userId: string }) => {
|
|
52
|
+
setTypingUser(userId);
|
|
33
53
|
};
|
|
34
|
-
|
|
35
|
-
const handleStopTyping = ({
|
|
36
|
-
|
|
37
|
-
setTypingUser(null);
|
|
38
|
-
}
|
|
54
|
+
|
|
55
|
+
const handleStopTyping = ({ userId }: { userId: string }) => {
|
|
56
|
+
setTypingUser((prev) => (prev === userId ? null : prev));
|
|
39
57
|
};
|
|
40
|
-
|
|
58
|
+
|
|
41
59
|
socket.on("typing", handleTyping);
|
|
42
60
|
socket.on("stopTyping", handleStopTyping);
|
|
43
|
-
|
|
61
|
+
|
|
44
62
|
return () => {
|
|
45
63
|
socket.off("typing", handleTyping);
|
|
46
64
|
socket.off("stopTyping", handleStopTyping);
|
|
47
65
|
};
|
|
48
|
-
}, [socket
|
|
66
|
+
}, [socket]);
|
|
49
67
|
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}, [message, socket, selectedConversation?._id, userId]);
|
|
69
|
-
|
|
68
|
+
// useEffect(() => {
|
|
69
|
+
// if (message) {
|
|
70
|
+
// setIsTyping(true);
|
|
71
|
+
// socket.emit("typing", {
|
|
72
|
+
// chatId: selectedConversation?._id,
|
|
73
|
+
// userId,
|
|
74
|
+
// });
|
|
75
|
+
// }
|
|
76
|
+
|
|
77
|
+
// // Clear typing indicator when user stops typing
|
|
78
|
+
// const typingTimeout = setTimeout(() => {
|
|
79
|
+
// if (message === "") {
|
|
80
|
+
// setIsTyping(false);
|
|
81
|
+
// socket.emit("stopTyping", { chatId: selectedConversation?._id, userId });
|
|
82
|
+
// }
|
|
83
|
+
// }, 2000);
|
|
84
|
+
|
|
85
|
+
// return () => clearTimeout(typingTimeout);
|
|
86
|
+
// }, [message, socket, selectedConversation?._id, userId]);
|
|
70
87
|
|
|
71
88
|
const handleSubmit = useCallback(
|
|
72
89
|
async (e: any) => {
|
|
@@ -78,7 +95,7 @@ const MessageInput = () => {
|
|
|
78
95
|
console.log("📤 Sending message:", message);
|
|
79
96
|
|
|
80
97
|
if (selectedConversation?._id) {
|
|
81
|
-
|
|
98
|
+
sendMessage({
|
|
82
99
|
chatId: selectedConversation.participantDetails._id,
|
|
83
100
|
senderId: userId,
|
|
84
101
|
message,
|
|
@@ -99,6 +116,7 @@ const MessageInput = () => {
|
|
|
99
116
|
[message, selectedConversation, userId, isSending]
|
|
100
117
|
);
|
|
101
118
|
|
|
119
|
+
|
|
102
120
|
return (
|
|
103
121
|
<form className="chatMessageInputform" onSubmit={handleSubmit}>
|
|
104
122
|
<div className="chatMessageInputdiv">
|
|
@@ -115,10 +133,18 @@ const MessageInput = () => {
|
|
|
115
133
|
</button>
|
|
116
134
|
</div>
|
|
117
135
|
|
|
118
|
-
{
|
|
119
|
-
<div className="typingIndicator">
|
|
136
|
+
{typingUser && typingUser !== userId && !isSending && (
|
|
137
|
+
<div className="typingIndicator">
|
|
138
|
+
<section className="dots-container">
|
|
139
|
+
<div className="dot" />
|
|
140
|
+
<div className="dot" />
|
|
141
|
+
<div className="dot" />
|
|
142
|
+
<div className="dot" />
|
|
143
|
+
<div className="dot" />
|
|
144
|
+
</section>
|
|
145
|
+
typing...
|
|
146
|
+
</div>
|
|
120
147
|
)}
|
|
121
|
-
|
|
122
148
|
</form>
|
|
123
149
|
);
|
|
124
150
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useEffect, useRef
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
3
|
// import useGetMessages from "../../hooks/useGetMessages";
|
|
4
4
|
// import MessageSkeleton from "../skeletons/MessageSkeleton";
|
|
5
5
|
import Message from "./Message";
|
|
@@ -9,25 +9,27 @@ import useChatUIStore from "../../stores/Zustant";
|
|
|
9
9
|
// import useListenMessages from "../../hooks/useListenMessages";
|
|
10
10
|
|
|
11
11
|
const Messages = () => {
|
|
12
|
-
const { selectedConversation } = useChatUIStore()
|
|
12
|
+
const { selectedConversation,setMessages,messages } = useChatUIStore()
|
|
13
13
|
const {userId,socket}=useChatContext()
|
|
14
|
-
const { data
|
|
15
|
-
const [localMessages, setLocalMessages] = useState<any[]>([]);
|
|
14
|
+
const { data, isLoading, isError, error } = useMessages(selectedConversation?._id, userId);
|
|
16
15
|
|
|
17
16
|
const lastMessageRef = useRef<HTMLDivElement>(null);
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (data) {
|
|
21
|
+
setMessages(data.messages);
|
|
22
22
|
}
|
|
23
|
-
}, [
|
|
23
|
+
}, [selectedConversation?._id,data]);
|
|
24
24
|
|
|
25
25
|
// Listen for new messages from the server
|
|
26
26
|
useEffect(() => {
|
|
27
|
-
if (!socket) return;
|
|
27
|
+
if (!socket || !selectedConversation?._id) return;
|
|
28
28
|
|
|
29
|
-
const handleNewMessage = (newMessage:
|
|
30
|
-
|
|
29
|
+
const handleNewMessage = (newMessage:any) => {
|
|
30
|
+
newMessage.shouldShake = true;
|
|
31
|
+
console.log("📩 New message received:", newMessage);
|
|
32
|
+
setMessages([...messages, newMessage[1]]);
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
socket.on("newMessage", handleNewMessage);
|
|
@@ -35,13 +37,13 @@ const Messages = () => {
|
|
|
35
37
|
return () => {
|
|
36
38
|
socket.off("newMessage", handleNewMessage);
|
|
37
39
|
};
|
|
38
|
-
}, [socket]);
|
|
40
|
+
}, [socket,setMessages, messages]);
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
useEffect(() => {
|
|
41
43
|
setTimeout(() => {
|
|
42
|
-
|
|
44
|
+
lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
43
45
|
}, 100);
|
|
44
|
-
|
|
46
|
+
}, [messages]);
|
|
45
47
|
|
|
46
48
|
if (isLoading) {
|
|
47
49
|
return <p>Loading messages...</p>;
|
|
@@ -51,10 +53,13 @@ const Messages = () => {
|
|
|
51
53
|
return <p>Error: {error?.message}</p>;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
console.log("📩 Messages:", messages);
|
|
57
|
+
console.log("📩 Messages Length:", messages?.length);
|
|
58
|
+
|
|
54
59
|
return (
|
|
55
60
|
<div className="chatMessages">
|
|
56
|
-
{
|
|
57
|
-
|
|
61
|
+
{messages?.length > 0 ? (
|
|
62
|
+
messages?.map((message: any) => (
|
|
58
63
|
<div key={message._id} ref={lastMessageRef}>
|
|
59
64
|
<Message message={message} />
|
|
60
65
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import React, {
|
|
2
3
|
createContext,
|
|
3
4
|
useContext,
|
|
@@ -28,6 +29,7 @@ interface ChatContextType {
|
|
|
28
29
|
socket: Socket;
|
|
29
30
|
// cryptoUtils: CryptoUtils;
|
|
30
31
|
userId: string;
|
|
32
|
+
onlineUsers: any;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const ChatContext = createContext<ChatContextType | null>(null);
|
|
@@ -38,16 +40,41 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
38
40
|
}) => {
|
|
39
41
|
const socketRef = useRef<Socket | null>(null);
|
|
40
42
|
const [socket, setSocket] = useState<Socket | null>(null);
|
|
43
|
+
const [onlineUsers, setOnlineUsers] = useState([]);
|
|
41
44
|
const apiUrl = import.meta.env.VITE_APP_BACKEND_PORT;
|
|
45
|
+
console.log("API URL:", apiUrl);
|
|
42
46
|
|
|
43
47
|
useEffect(() => {
|
|
44
48
|
if (!socketRef.current) {
|
|
45
49
|
console.log("🔌 Creating new socket connection...");
|
|
46
|
-
const socketInstance = io(
|
|
50
|
+
const socketInstance = io('http://localhost:3001', {
|
|
51
|
+
query: {
|
|
52
|
+
userId: userId,
|
|
53
|
+
},
|
|
54
|
+
transports: ["websocket"],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Log connection events
|
|
58
|
+
socketInstance.on("connect", () => {
|
|
59
|
+
console.log("✅ Connected to server with socket ID:", socketInstance.id);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
socketInstance.on("connect_error", (error) => {
|
|
63
|
+
console.error("❌ Connection error:", error);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
socketInstance.on("disconnect", () => {
|
|
67
|
+
console.log("❌ Disconnected from server");
|
|
68
|
+
});
|
|
69
|
+
|
|
47
70
|
socketRef.current = socketInstance;
|
|
48
71
|
setSocket(socketInstance);
|
|
49
72
|
}
|
|
50
|
-
|
|
73
|
+
|
|
74
|
+
socket?.on("getOnlineUsers", (users) => {
|
|
75
|
+
setOnlineUsers(users);
|
|
76
|
+
});
|
|
77
|
+
|
|
51
78
|
return () => {
|
|
52
79
|
console.log("❌ Disconnecting socket...");
|
|
53
80
|
socketRef.current?.disconnect();
|
|
@@ -57,12 +84,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
57
84
|
|
|
58
85
|
if (!socket) return null;
|
|
59
86
|
|
|
87
|
+
|
|
60
88
|
// const apiClient = new ApiClient(apiUrl);
|
|
61
89
|
// const s3Client = new S3Client(s3Config);
|
|
62
90
|
// const cryptoUtils = new CryptoUtils();
|
|
63
91
|
|
|
64
92
|
return (
|
|
65
|
-
<ChatContext.Provider value={{ socket, userId }}>
|
|
93
|
+
<ChatContext.Provider value={{ socket, userId,onlineUsers }}>
|
|
66
94
|
{children}
|
|
67
95
|
</ChatContext.Provider>
|
|
68
96
|
);
|
package/src/stores/Zustant.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import {create} from 'zustand';
|
|
2
3
|
|
|
3
|
-
// interface ChatUIState {
|
|
4
|
-
// isChatOpen: boolean;
|
|
5
|
-
// unreadCount: number;
|
|
6
|
-
// toggleChat: () => void;
|
|
7
|
-
// incrementUnreadCount: () => void;
|
|
8
|
-
// resetUnreadCount: () => void;
|
|
9
|
-
// }
|
|
10
|
-
|
|
11
4
|
interface ChatUIState {
|
|
12
5
|
isChatOpen: boolean;
|
|
13
6
|
unreadCount: number;
|
|
@@ -20,9 +13,13 @@ interface ChatUIState {
|
|
|
20
13
|
}
|
|
21
14
|
_id:string;
|
|
22
15
|
} | null;
|
|
23
|
-
setSelectedConversation: (selectedConversation: { _id: string; profilePic: string;
|
|
16
|
+
setSelectedConversation: (selectedConversation: { participantDetails: { _id: string; profilePic: string; firstname: string; idpic: string; }; _id: string; } | null) => void;
|
|
17
|
+
messages: any[];
|
|
18
|
+
setMessages: (messages: any[]) => void;
|
|
24
19
|
toggleChat: () => void;
|
|
25
20
|
incrementUnreadCount: () => void;
|
|
21
|
+
onlineUsers: string[];
|
|
22
|
+
setOnlineUsers: (users: string[]) => void;
|
|
26
23
|
resetUnreadCount: () => void;
|
|
27
24
|
}
|
|
28
25
|
|
|
@@ -30,8 +27,12 @@ const useChatUIStore = create<ChatUIState>((set) => ({
|
|
|
30
27
|
isChatOpen: false,
|
|
31
28
|
unreadCount: 0,
|
|
32
29
|
selectedConversation: null,
|
|
30
|
+
messages: [],
|
|
31
|
+
setMessages: (messages: any) => set({ messages }),
|
|
33
32
|
setSelectedConversation: (selectedConversation) => set({ selectedConversation }),
|
|
34
33
|
toggleChat: () => set((state) => ({ isChatOpen: !state.isChatOpen })),
|
|
34
|
+
onlineUsers: [],
|
|
35
|
+
setOnlineUsers: (users) => set({ onlineUsers: users }),
|
|
35
36
|
incrementUnreadCount: () => set((state) => ({ unreadCount: state.unreadCount + 1 })),
|
|
36
37
|
resetUnreadCount: () => set({ unreadCount: 0 }),
|
|
37
38
|
}));
|
package/src/style/style.css
CHANGED
|
@@ -387,4 +387,59 @@
|
|
|
387
387
|
padding-right: 2rem;
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
/* From Uiverse.io by adamgiebl */
|
|
394
|
+
.dots-container {
|
|
395
|
+
display: flex;
|
|
396
|
+
align-items: center;
|
|
397
|
+
justify-content: center;
|
|
398
|
+
height: 100%;
|
|
399
|
+
width: 100%;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.dot {
|
|
403
|
+
height: 20px;
|
|
404
|
+
width: 20px;
|
|
405
|
+
margin-right: 10px;
|
|
406
|
+
border-radius: 10px;
|
|
407
|
+
background-color: #b3d4fc;
|
|
408
|
+
animation: pulse 1.5s infinite ease-in-out;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.dot:last-child {
|
|
412
|
+
margin-right: 0;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.dot:nth-child(1) {
|
|
416
|
+
animation-delay: -0.3s;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.dot:nth-child(2) {
|
|
420
|
+
animation-delay: -0.1s;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.dot:nth-child(3) {
|
|
424
|
+
animation-delay: 0.1s;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@keyframes pulse {
|
|
428
|
+
0% {
|
|
429
|
+
transform: scale(0.8);
|
|
430
|
+
background-color: #b3d4fc;
|
|
431
|
+
box-shadow: 0 0 0 0 rgba(178, 212, 252, 0.7);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
50% {
|
|
435
|
+
transform: scale(1.2);
|
|
436
|
+
background-color: #6793fb;
|
|
437
|
+
box-shadow: 0 0 0 10px rgba(178, 212, 252, 0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
100% {
|
|
441
|
+
transform: scale(0.8);
|
|
442
|
+
background-color: #b3d4fc;
|
|
443
|
+
box-shadow: 0 0 0 0 rgba(178, 212, 252, 0.7);
|
|
444
|
+
}
|
|
445
|
+
}
|