@pubuduth-aplicy/chat-ui 2.1.33 → 2.1.35
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 +30 -1
- package/src/components/messages/MessageContainer.tsx +7 -0
- package/src/components/messages/MessageInput.tsx +68 -54
- package/src/components/messages/Messages.tsx +60 -16
- package/src/components/sidebar/Conversation.tsx +18 -0
- package/src/service/sidebarApi.ts +13 -8
- package/src/stores/Zustant.ts +24 -12
- package/src/style/style.css +36 -36
- package/src/types/type.ts +13 -2
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
3
3
|
|
|
4
|
+
import { MessageStatus } from "../../types/type";
|
|
4
5
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
5
6
|
import useChatUIStore from "../../stores/Zustant";
|
|
6
7
|
|
|
@@ -11,6 +12,7 @@ interface MessageProps {
|
|
|
11
12
|
message: {
|
|
12
13
|
senderId: string;
|
|
13
14
|
message: string;
|
|
15
|
+
status: MessageStatus;
|
|
14
16
|
createdAt:any;
|
|
15
17
|
};
|
|
16
18
|
}
|
|
@@ -29,16 +31,43 @@ const seconds = date.getUTCSeconds();
|
|
|
29
31
|
|
|
30
32
|
// Format the time as HH:mm:ss (24-hour format)
|
|
31
33
|
const time = `${hours}.${minutes}`;
|
|
34
|
+
|
|
35
|
+
const getStatusIcon = () => {
|
|
36
|
+
if (!fromMe) return null;
|
|
37
|
+
|
|
38
|
+
switch (message.status) {
|
|
39
|
+
case 'sent':
|
|
40
|
+
return <span className="message-status sent">✓</span>;
|
|
41
|
+
case 'delivered':
|
|
42
|
+
return <span className="message-status delivered">✓✓</span>;
|
|
43
|
+
case 'read':
|
|
44
|
+
return <span className="message-status read">✓✓ (blue)</span>;
|
|
45
|
+
default:
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
32
50
|
return (
|
|
33
51
|
<>
|
|
52
|
+
{/* <div className="w-max grid">
|
|
53
|
+
<div className="px-3.5 py-2 bg-gray-100 rounded-3xl rounded-tl-none justify-start items-center gap-3 inline-flex">
|
|
54
|
+
<h5 className="text-gray-900 text-sm font-normal leading-snug">{message.message}</h5>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="justify-end items-center inline-flex mb-2.5">
|
|
57
|
+
<h6 className="text-gray-500 text-xs font-normal leading-4 py-1">05:14 PM</h6>
|
|
58
|
+
</div>
|
|
59
|
+
</div> */}
|
|
60
|
+
|
|
34
61
|
<div className='chatMessage'>
|
|
35
62
|
<div className="chatMessagesContainer">
|
|
36
63
|
<div style={{color:"#374151"}}>
|
|
37
64
|
<div className={`chatMessagesBubble_inner ${alignItems} ${bubbleBgColor} `}>
|
|
38
65
|
<p style={{fontSize:"14px"}}>{message.message}</p>
|
|
39
|
-
<div className='chatMessagesBubble_Time'>{
|
|
66
|
+
<div className='chatMessagesBubble_Time'> {new Date(message.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
|
|
67
|
+
{getStatusIcon()}
|
|
40
68
|
</div>
|
|
41
69
|
</div>
|
|
70
|
+
|
|
42
71
|
{/* <div className="clear-both flex text-gray-700" /> */}
|
|
43
72
|
</div>
|
|
44
73
|
</div>
|
|
@@ -12,6 +12,13 @@ const MessageContainer = () => {
|
|
|
12
12
|
const { selectedConversation, setSelectedConversation ,onlineUsers, setOnlineUsers } = useChatUIStore();
|
|
13
13
|
const {socket} = useChatContext();
|
|
14
14
|
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (selectedConversation?._id && socket) {
|
|
17
|
+
socket.emit("joinChat", selectedConversation._id); // Join chat room
|
|
18
|
+
}
|
|
19
|
+
}, [selectedConversation?._id, socket]);
|
|
20
|
+
|
|
21
|
+
|
|
15
22
|
useEffect(() => {
|
|
16
23
|
if (!socket) return;
|
|
17
24
|
|
|
@@ -12,7 +12,8 @@ const MessageInput = () => {
|
|
|
12
12
|
const { userId } = useChatContext();
|
|
13
13
|
const { selectedConversation } = useChatUIStore();
|
|
14
14
|
const [message, setMessage] = useState(""); // State for storing the message input
|
|
15
|
-
const { mutate: sendMessage } = useMessageMutation();
|
|
15
|
+
// const { mutate: sendMessage } = useMessageMutation();
|
|
16
|
+
const mutation = useMessageMutation(); // useMutation hook to send message
|
|
16
17
|
const [typingUser, setTypingUser] = useState<string | null>(null);
|
|
17
18
|
const [isSending, setIsSending] = useState(false);
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -40,20 +41,25 @@ const MessageInput = () => {
|
|
|
40
41
|
userId,
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
},
|
|
44
|
+
}, 200);
|
|
44
45
|
|
|
45
46
|
return () => clearTimeout(typingTimeout);
|
|
46
47
|
}, [message, socket, selectedConversation?._id, userId]);
|
|
47
48
|
|
|
49
|
+
|
|
48
50
|
useEffect(() => {
|
|
49
|
-
if (!socket) return;
|
|
51
|
+
if (!socket || !selectedConversation?._id) return;
|
|
50
52
|
|
|
51
|
-
const handleTyping = ({ userId }: { userId: string }) => {
|
|
52
|
-
|
|
53
|
+
const handleTyping = ({ userId, chatId }: { userId: string; chatId: string }) => {
|
|
54
|
+
if (chatId === selectedConversation._id) {
|
|
55
|
+
setTypingUser(userId);
|
|
56
|
+
}
|
|
53
57
|
};
|
|
54
|
-
|
|
55
|
-
const handleStopTyping = ({ userId }: { userId: string }) => {
|
|
56
|
-
|
|
58
|
+
|
|
59
|
+
const handleStopTyping = ({ userId, chatId }: { userId: string; chatId: string }) => {
|
|
60
|
+
if (chatId === selectedConversation._id) {
|
|
61
|
+
setTypingUser((prev) => (prev === userId ? null : prev));
|
|
62
|
+
}
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
socket.on("typing", handleTyping);
|
|
@@ -63,27 +69,7 @@ const MessageInput = () => {
|
|
|
63
69
|
socket.off("typing", handleTyping);
|
|
64
70
|
socket.off("stopTyping", handleStopTyping);
|
|
65
71
|
};
|
|
66
|
-
}, [socket]);
|
|
67
|
-
|
|
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]);
|
|
72
|
+
}, [socket, selectedConversation?._id]);
|
|
87
73
|
|
|
88
74
|
const handleSubmit = useCallback(
|
|
89
75
|
async (e: any) => {
|
|
@@ -94,18 +80,48 @@ const MessageInput = () => {
|
|
|
94
80
|
try {
|
|
95
81
|
console.log("📤 Sending message:", message);
|
|
96
82
|
|
|
97
|
-
if (selectedConversation?._id) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
mutation.mutate({
|
|
105
|
+
chatId: selectedConversation?.participantDetails._id,
|
|
106
|
+
senderId: userId,
|
|
107
|
+
message,
|
|
108
|
+
}, {
|
|
109
|
+
onSuccess: (data) => {
|
|
110
|
+
console.log('Response from sendMessage:', data);
|
|
111
|
+
// After successfully sending the message, emit the socket event
|
|
112
|
+
socket.emit("sendMessage", {
|
|
113
|
+
chatId: selectedConversation?._id,
|
|
114
|
+
message,
|
|
115
|
+
messageId:data[1]._id,
|
|
116
|
+
senderId: userId,
|
|
117
|
+
receiverId: selectedConversation?.participantDetails._id,
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
onError: (error) => {
|
|
121
|
+
console.error("❌ Error in sending message:", error);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
109
125
|
} catch (error) {
|
|
110
126
|
console.error("❌ Error sending message:", error);
|
|
111
127
|
} finally {
|
|
@@ -116,7 +132,6 @@ const MessageInput = () => {
|
|
|
116
132
|
[message, selectedConversation, userId, isSending]
|
|
117
133
|
);
|
|
118
134
|
|
|
119
|
-
|
|
120
135
|
return (
|
|
121
136
|
<form className="chatMessageInputform" onSubmit={handleSubmit}>
|
|
122
137
|
<div className="chatMessageInputdiv">
|
|
@@ -133,18 +148,17 @@ const MessageInput = () => {
|
|
|
133
148
|
</button>
|
|
134
149
|
</div>
|
|
135
150
|
|
|
136
|
-
{typingUser && typingUser !== userId && !isSending && (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
)}
|
|
151
|
+
{typingUser && typingUser !== userId && typingUser === selectedConversation?.participantDetails?._id && !isSending && (
|
|
152
|
+
<div className="typingIndicator">
|
|
153
|
+
<div className="loader">
|
|
154
|
+
<div className="ball" />
|
|
155
|
+
<div className="ball" />
|
|
156
|
+
<div className="ball" />
|
|
157
|
+
typing
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
148
162
|
</form>
|
|
149
163
|
);
|
|
150
164
|
};
|
|
@@ -29,13 +29,22 @@ const Messages = () => {
|
|
|
29
29
|
const handleNewMessage = (newMessage:any) => {
|
|
30
30
|
newMessage.shouldShake = true;
|
|
31
31
|
console.log("📩 New message received:", newMessage);
|
|
32
|
-
|
|
32
|
+
// setMessages([...messages, newMessage[1]]);
|
|
33
|
+
setMessages((prevMessages) => [...prevMessages, newMessage]);
|
|
33
34
|
};
|
|
35
|
+
|
|
36
|
+
const handleStatusUpdate = ({ messageId, status }) => {
|
|
37
|
+
setMessages(prev => prev.map(msg =>
|
|
38
|
+
msg._id === messageId ? { ...msg, status } : msg
|
|
39
|
+
));
|
|
40
|
+
};
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
socket.on("newMessage", handleNewMessage);
|
|
43
|
+
socket.on("messageStatusUpdated", handleStatusUpdate);
|
|
36
44
|
|
|
37
45
|
return () => {
|
|
38
|
-
|
|
46
|
+
socket.off("newMessage", handleNewMessage);
|
|
47
|
+
socket.off("messageStatusUpdated", handleStatusUpdate);
|
|
39
48
|
};
|
|
40
49
|
}, [socket,setMessages, messages]);
|
|
41
50
|
|
|
@@ -43,7 +52,27 @@ const Messages = () => {
|
|
|
43
52
|
setTimeout(() => {
|
|
44
53
|
lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
45
54
|
}, 100);
|
|
46
|
-
}, [messages]);
|
|
55
|
+
}, [ messages]);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!socket || !messages.length) return;
|
|
59
|
+
|
|
60
|
+
const observer = new IntersectionObserver((entries) => {
|
|
61
|
+
entries.forEach(entry => {
|
|
62
|
+
if (entry.isIntersecting) {
|
|
63
|
+
const messageId = entry.target.getAttribute('data-message-id');
|
|
64
|
+
if (messageId) {
|
|
65
|
+
socket.emit('confirmDelivery', { messageId });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}, { threshold: 0.5 });
|
|
70
|
+
|
|
71
|
+
const messageElements = document.querySelectorAll('[data-message-id]');
|
|
72
|
+
messageElements.forEach(el => observer.observe(el));
|
|
73
|
+
|
|
74
|
+
return () => observer.disconnect();
|
|
75
|
+
}, [messages, socket]);
|
|
47
76
|
|
|
48
77
|
if (isLoading) {
|
|
49
78
|
return <p>Loading messages...</p>;
|
|
@@ -57,18 +86,33 @@ const Messages = () => {
|
|
|
57
86
|
console.log("📩 Messages Length:", messages?.length);
|
|
58
87
|
|
|
59
88
|
return (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
// <div className="chatMessages">
|
|
90
|
+
// {messages?.length > 0 ? (
|
|
91
|
+
// messages?.map((message: any) => (
|
|
92
|
+
// <div key={message._id} ref={lastMessageRef}>
|
|
93
|
+
// <Message message={message} />
|
|
94
|
+
// </div>
|
|
95
|
+
// ))
|
|
96
|
+
// ) : (
|
|
97
|
+
// <p style={{ textAlign: "center" }}>Send a message to start the conversation</p>
|
|
98
|
+
// )}
|
|
99
|
+
// </div>
|
|
100
|
+
<div className="chatMessages">
|
|
101
|
+
{messages?.length > 0 ? (
|
|
102
|
+
messages?.map((message: any) =>
|
|
103
|
+
// Check if the message object is valid and has an _id before rendering
|
|
104
|
+
message ? (
|
|
105
|
+
<div key={message._id} ref={lastMessageRef}>
|
|
106
|
+
<Message message={message} />
|
|
107
|
+
</div>
|
|
108
|
+
) : null
|
|
109
|
+
)
|
|
110
|
+
) : (
|
|
111
|
+
<p style={{ textAlign: "center" }}>
|
|
112
|
+
Send a message to start the conversation
|
|
113
|
+
</p>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
72
116
|
);
|
|
73
117
|
};
|
|
74
118
|
export default Messages;
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
// import { useSocketContext } from "../../context/SocketContext";
|
|
2
2
|
// import useConversation from "../../zustand/useConversation";
|
|
3
3
|
|
|
4
|
+
import { useChatContext } from "../../providers/ChatProvider";
|
|
4
5
|
import useChatUIStore from "../../stores/Zustant";
|
|
5
6
|
import { ConversationProps } from "../../types/type";
|
|
6
7
|
|
|
7
8
|
const Conversation = ({ conversation, lastIdx }: ConversationProps) => {
|
|
8
9
|
const { setSelectedConversation } = useChatUIStore();
|
|
10
|
+
const { socket } = useChatContext();
|
|
9
11
|
console.log(conversation);
|
|
10
12
|
|
|
13
|
+
const handleSelectConversation = async () => {
|
|
14
|
+
setSelectedConversation(conversation);
|
|
15
|
+
|
|
16
|
+
const lastMessageId = conversation.lastMessageId; // You should have this in conversation data
|
|
17
|
+
|
|
18
|
+
if (lastMessageId) {
|
|
19
|
+
// ✅ Notify server via socket
|
|
20
|
+
socket.emit("messageRead", {
|
|
21
|
+
messageId: lastMessageId,
|
|
22
|
+
receiverId: conversation.participantDetails._id, // or receiverId
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
// const isSelected = selectedConversation?._id === conversation._id;
|
|
12
30
|
// const { onlineUsers } = useSocketContext();
|
|
13
31
|
// const isOnline = onlineUsers.includes(conversation._id);
|
|
@@ -7,18 +7,23 @@ import { ApiResponse } from "../types/type";
|
|
|
7
7
|
export const getAllConversationData = async (userid: string) => {
|
|
8
8
|
try {
|
|
9
9
|
const res = await apiClient.get<ApiResponse>(`${Path.getconversation}/${userid}`);
|
|
10
|
-
|
|
10
|
+
if (res.data) {
|
|
11
|
+
console.log("API Response: ", res.data);
|
|
12
|
+
|
|
13
|
+
}
|
|
11
14
|
// Access conversations with participant details from the API response
|
|
12
|
-
const
|
|
13
|
-
|
|
15
|
+
const conversationsWithParticipantDetails = res.data.serviceInfo;
|
|
16
|
+
console.log("conversationsWithParticipantDetails", res.data.serviceInfo);
|
|
17
|
+
|
|
14
18
|
// If needed, you can map the conversations in the specific structure
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
// const formattedConversations = conversationsWithParticipantDetails?.map((conversation) => ({
|
|
20
|
+
// _id: conversation._id,
|
|
21
|
+
// participantDetails: conversation.participantDetails,
|
|
22
|
+
// }));
|
|
23
|
+
// console.log("formattedConversations", formattedConversations);
|
|
19
24
|
|
|
20
25
|
// Return the formatted conversations
|
|
21
|
-
return
|
|
26
|
+
return conversationsWithParticipantDetails;
|
|
22
27
|
} catch (error: any) {
|
|
23
28
|
console.error("ERROR: ", error);
|
|
24
29
|
// Optionally log the error to an external logger
|
package/src/stores/Zustant.ts
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import {create} from
|
|
2
|
+
import { create } from "zustand";
|
|
3
3
|
|
|
4
4
|
interface ChatUIState {
|
|
5
5
|
isChatOpen: boolean;
|
|
6
6
|
unreadCount: number;
|
|
7
7
|
selectedConversation: {
|
|
8
|
-
participantDetails:{
|
|
8
|
+
participantDetails: {
|
|
9
9
|
_id: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
_id:string;
|
|
10
|
+
profilePic: string;
|
|
11
|
+
firstname: string;
|
|
12
|
+
idpic: string;
|
|
13
|
+
};
|
|
14
|
+
_id: string;
|
|
15
15
|
} | null;
|
|
16
|
-
setSelectedConversation: (
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
setSelectedConversation: (
|
|
17
|
+
selectedConversation: ChatUIState["selectedConversation"]
|
|
18
|
+
) => void;
|
|
19
|
+
messages: { id: string; text: string; sender: string; status: string }[];
|
|
20
|
+
setMessages: (messages: ChatUIState["messages"] | ((prev: ChatUIState["messages"]) => ChatUIState["messages"])) => void;
|
|
21
|
+
updateMessageStatus: (messageId: string, status: string) => void;
|
|
19
22
|
toggleChat: () => void;
|
|
20
23
|
incrementUnreadCount: () => void;
|
|
21
24
|
onlineUsers: string[];
|
|
@@ -28,7 +31,16 @@ const useChatUIStore = create<ChatUIState>((set) => ({
|
|
|
28
31
|
unreadCount: 0,
|
|
29
32
|
selectedConversation: null,
|
|
30
33
|
messages: [],
|
|
31
|
-
|
|
34
|
+
setMessages: (updater: any) =>
|
|
35
|
+
set((state) => ({
|
|
36
|
+
messages: typeof updater === "function" ? updater(state.messages) : updater,
|
|
37
|
+
})),
|
|
38
|
+
updateMessageStatus: (messageId, status) =>
|
|
39
|
+
set((state) => ({
|
|
40
|
+
messages: state.messages.map((msg) =>
|
|
41
|
+
msg.id === messageId ? { ...msg, status } : msg
|
|
42
|
+
),
|
|
43
|
+
})),
|
|
32
44
|
setSelectedConversation: (selectedConversation) => set({ selectedConversation }),
|
|
33
45
|
toggleChat: () => set((state) => ({ isChatOpen: !state.isChatOpen })),
|
|
34
46
|
onlineUsers: [],
|
|
@@ -37,4 +49,4 @@ const useChatUIStore = create<ChatUIState>((set) => ({
|
|
|
37
49
|
resetUnreadCount: () => set({ unreadCount: 0 }),
|
|
38
50
|
}));
|
|
39
51
|
|
|
40
|
-
export default useChatUIStore;
|
|
52
|
+
export default useChatUIStore;
|
package/src/style/style.css
CHANGED
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
.chatMessageInputform {
|
|
300
300
|
position: sticky;
|
|
301
301
|
bottom: 0;
|
|
302
|
-
background: #
|
|
302
|
+
background: #eee7e7;
|
|
303
303
|
padding-left: 1rem;
|
|
304
304
|
padding-right: 1rem;
|
|
305
305
|
padding-top: 0.25rem;
|
|
@@ -389,57 +389,57 @@
|
|
|
389
389
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
.typingIndicator{
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
gap: 0.5rem;
|
|
396
|
+
}
|
|
392
397
|
|
|
393
|
-
/* From Uiverse.io by
|
|
394
|
-
.
|
|
398
|
+
/* From Uiverse.io by ashish-yadv */
|
|
399
|
+
.loader {
|
|
400
|
+
width: 60px;
|
|
395
401
|
display: flex;
|
|
396
402
|
align-items: center;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
403
|
+
height: 100%;
|
|
404
|
+
margin-right: 10px;
|
|
405
|
+
width: 100%;
|
|
406
|
+
font-size: smaller;
|
|
407
|
+
gap: 0.5rem;
|
|
400
408
|
}
|
|
401
409
|
|
|
402
|
-
.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
background-color: #b3d4fc;
|
|
408
|
-
animation: pulse 1.5s infinite ease-in-out;
|
|
410
|
+
.ball {
|
|
411
|
+
width: 6px;
|
|
412
|
+
height: 6px;
|
|
413
|
+
border-radius: 50%;
|
|
414
|
+
background-color: #3b2dfd;
|
|
409
415
|
}
|
|
410
416
|
|
|
411
|
-
.
|
|
412
|
-
|
|
417
|
+
.ball:nth-child(1) {
|
|
418
|
+
animation: bounce-1 2.1s ease-in-out infinite;
|
|
413
419
|
}
|
|
414
420
|
|
|
415
|
-
|
|
416
|
-
|
|
421
|
+
@keyframes bounce-1 {
|
|
422
|
+
50% {
|
|
423
|
+
transform: translateY(-3px);
|
|
424
|
+
}
|
|
417
425
|
}
|
|
418
426
|
|
|
419
|
-
.
|
|
420
|
-
animation
|
|
427
|
+
.ball:nth-child(2) {
|
|
428
|
+
animation: bounce-3 2.1s ease-in-out 0.3s infinite;
|
|
421
429
|
}
|
|
422
430
|
|
|
423
|
-
|
|
424
|
-
|
|
431
|
+
@keyframes bounce-2 {
|
|
432
|
+
50% {
|
|
433
|
+
transform: translateY(-3px);
|
|
434
|
+
}
|
|
425
435
|
}
|
|
426
436
|
|
|
427
|
-
|
|
428
|
-
0
|
|
429
|
-
|
|
430
|
-
background-color: #b3d4fc;
|
|
431
|
-
box-shadow: 0 0 0 0 rgba(178, 212, 252, 0.7);
|
|
432
|
-
}
|
|
437
|
+
.ball:nth-child(3) {
|
|
438
|
+
animation: bounce-3 2.1s ease-in-out 0.6s infinite;
|
|
439
|
+
}
|
|
433
440
|
|
|
441
|
+
@keyframes bounce-3 {
|
|
434
442
|
50% {
|
|
435
|
-
transform:
|
|
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);
|
|
443
|
+
transform: translateY(-3px);
|
|
444
444
|
}
|
|
445
445
|
}
|
package/src/types/type.ts
CHANGED
|
@@ -21,6 +21,15 @@ export interface ParticipantDetails {
|
|
|
21
21
|
export interface Conversation {
|
|
22
22
|
_id: string;
|
|
23
23
|
createdAt: string;
|
|
24
|
+
lastMessage:{
|
|
25
|
+
_id: string;
|
|
26
|
+
senderId: string;
|
|
27
|
+
message: string;
|
|
28
|
+
chatId: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
__v: number;
|
|
32
|
+
},
|
|
24
33
|
updatedAt: string;
|
|
25
34
|
__v: number;
|
|
26
35
|
participantDetails: ParticipantDetails;
|
|
@@ -33,7 +42,7 @@ export interface ServiceInfo {
|
|
|
33
42
|
export interface ApiResponse {
|
|
34
43
|
success: boolean;
|
|
35
44
|
message: string;
|
|
36
|
-
serviceInfo:
|
|
45
|
+
serviceInfo: Conversation[];
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export interface ConversationProps {
|
|
@@ -48,4 +57,6 @@ export interface ConversationProps {
|
|
|
48
57
|
_id: string;
|
|
49
58
|
};
|
|
50
59
|
lastIdx: boolean;
|
|
51
|
-
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type MessageStatus = 'sent' | 'delivered' | 'read';
|