@pubuduth-aplicy/chat-ui 2.1.56 → 2.1.58
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/Chat.tsx +50 -50
- package/src/components/Loader.tsx +4 -2
- package/src/components/common/FilePreview.tsx +6 -3
- package/src/components/messages/Message.tsx +143 -29
- package/src/components/messages/MessageContainer.tsx +158 -139
- package/src/components/messages/MessageInput.tsx +66 -11
- package/src/components/messages/Messages.tsx +30 -33
- package/src/components/sidebar/Conversation.tsx +20 -7
- package/src/components/sidebar/Conversations.tsx +26 -26
- package/src/components/sidebar/SearchInput.tsx +60 -54
- package/src/components/sidebar/Sidebar.tsx +7 -7
- package/src/lib/api/endpoint.ts +6 -4
- package/src/style/style.css +121 -27
|
@@ -7,69 +7,78 @@ import { useChatContext } from "../../providers/ChatProvider";
|
|
|
7
7
|
// import { Chat, CaretLeft } from "@phosphor-icons/react";
|
|
8
8
|
// import { useAuthContext } from "../../context/AuthContext";
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
const MessageContainer = () => {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const {
|
|
12
|
+
selectedConversation,
|
|
13
|
+
setSelectedConversation,
|
|
14
|
+
onlineUsers,
|
|
15
|
+
setOnlineUsers,
|
|
16
|
+
} = useChatUIStore();
|
|
17
|
+
const { socket } = useChatContext();
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (selectedConversation?._id && socket) {
|
|
21
|
+
socket.emit("joinChat", selectedConversation._id); // Join chat room
|
|
22
|
+
}
|
|
23
|
+
}, [selectedConversation?._id, socket]);
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!socket) return;
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const handleOnlineUsers = (users: string[]) => {
|
|
29
|
+
setOnlineUsers(users);
|
|
30
|
+
};
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
socket.on("getOnlineUsers", handleOnlineUsers);
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
return () => {
|
|
35
|
+
socket.off("getOnlineUsers", handleOnlineUsers);
|
|
36
|
+
};
|
|
37
|
+
}, [socket, setOnlineUsers]);
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
const isUserOnline =
|
|
40
|
+
selectedConversation?.participantDetails?._id &&
|
|
37
41
|
onlineUsers?.includes(selectedConversation.participantDetails._id);
|
|
38
42
|
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
// cleanup function (unmounts)
|
|
45
|
+
return () => setSelectedConversation(null);
|
|
46
|
+
}, [setSelectedConversation]);
|
|
39
47
|
|
|
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
|
-
|
|
48
|
+
return (
|
|
49
|
+
<div className="chatMessageContainer">
|
|
50
|
+
{!selectedConversation ? (
|
|
51
|
+
<EmptyInbox />
|
|
52
|
+
) : (
|
|
53
|
+
<>
|
|
54
|
+
<div className="chatMessageContainerInner">
|
|
55
|
+
<div className="chatMessageContainerInnerDiv">
|
|
56
|
+
<button className="chatMessageContainerInnerDiv_button">
|
|
57
|
+
{/* <CaretLeft size={25} /> */}
|
|
58
|
+
</button>
|
|
59
|
+
<img
|
|
60
|
+
className="chatMessageContainerInnerImg"
|
|
61
|
+
alt="Profile"
|
|
62
|
+
src={
|
|
63
|
+
selectedConversation.participantDetails?.profilePic ||
|
|
64
|
+
selectedConversation.participantDetails?.idpic
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
<div className="chatMessageContainerOutter">
|
|
68
|
+
<div className="chatMessageContainerOutterDiv">
|
|
69
|
+
<p className="chatMessageContainerOutterDiv_name">
|
|
70
|
+
{selectedConversation.participantDetails.firstname}
|
|
71
|
+
</p>
|
|
72
|
+
<p className="text-sm ">
|
|
73
|
+
{isUserOnline ? "Online" : "Offline"}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
{/* <h4 className=" inline-block py-2 text-left font-sans font-semibold normal-case">Lara Abegnale</h4> */}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
{/* <div className="h-14 overflow-x-hidden">
|
|
73
82
|
<div className="top-0 h-14 px-4 py-4 w-full border-b border-gray-300 justify-start items-start gap-2 inline-flex sticky z-10">
|
|
74
83
|
<div className="grow shrink basis-0 self-stretch py-2 justify-start items-center gap-4 flex">
|
|
75
84
|
<button onClick={() => setSelectedConversation(null)} className="text-blue-500 md:hidden">
|
|
@@ -87,96 +96,106 @@ const MessageContainer = () => {
|
|
|
87
96
|
</div>
|
|
88
97
|
</div> */}
|
|
89
98
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
99
|
+
<Messages />
|
|
100
|
+
<MessageInput />
|
|
101
|
+
</>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
97
106
|
|
|
98
107
|
export default MessageContainer;
|
|
99
108
|
|
|
100
109
|
interface EmptyInboxProps {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
title?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const EmptyInbox: React.FC<EmptyInboxProps> = ({
|
|
115
|
+
title = "Ah, a fresh new inbox",
|
|
116
|
+
description = "You haven't started any conversations yet, but when you do, you'll find them here.",
|
|
117
|
+
}) => {
|
|
118
|
+
return (
|
|
119
|
+
<div className="flex flex-col items-center justify-center h-full p-6 text-center">
|
|
120
|
+
<div className="w-48 h-48 mb-6 relative">
|
|
121
|
+
<svg
|
|
122
|
+
viewBox="0 0 200 200"
|
|
123
|
+
fill="none"
|
|
124
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
125
|
+
className="w-full h-full"
|
|
126
|
+
>
|
|
127
|
+
<line
|
|
128
|
+
x1="100"
|
|
129
|
+
y1="100"
|
|
130
|
+
x2="100"
|
|
131
|
+
y2="160"
|
|
132
|
+
stroke="black"
|
|
133
|
+
strokeWidth="2"
|
|
134
|
+
/>
|
|
135
|
+
<line
|
|
136
|
+
x1="40"
|
|
137
|
+
y1="160"
|
|
138
|
+
x2="160"
|
|
139
|
+
y2="160"
|
|
140
|
+
stroke="black"
|
|
141
|
+
strokeWidth="1"
|
|
142
|
+
/>
|
|
143
|
+
<path
|
|
144
|
+
d="M70 160C75 150 80 155 85 160"
|
|
145
|
+
stroke="black"
|
|
146
|
+
strokeWidth="1"
|
|
147
|
+
fill="none"
|
|
148
|
+
/>
|
|
149
|
+
<path
|
|
150
|
+
d="M115 160C120 150 125 155 130 160"
|
|
151
|
+
stroke="black"
|
|
152
|
+
strokeWidth="1"
|
|
153
|
+
fill="none"
|
|
154
|
+
/>
|
|
155
|
+
<rect
|
|
156
|
+
x="70"
|
|
157
|
+
y="80"
|
|
158
|
+
width="60"
|
|
159
|
+
height="30"
|
|
160
|
+
stroke="black"
|
|
161
|
+
strokeWidth="1"
|
|
162
|
+
fill="white"
|
|
163
|
+
/>
|
|
164
|
+
<path
|
|
165
|
+
d="M70 80C70 65 130 65 130 80"
|
|
166
|
+
stroke="black"
|
|
167
|
+
strokeWidth="1"
|
|
168
|
+
fill="none"
|
|
169
|
+
/>
|
|
170
|
+
<rect
|
|
171
|
+
x="70"
|
|
172
|
+
y="80"
|
|
173
|
+
width="60"
|
|
174
|
+
height="20"
|
|
175
|
+
stroke="black"
|
|
176
|
+
strokeWidth="1"
|
|
177
|
+
fill="white"
|
|
178
|
+
/>
|
|
179
|
+
<path d="M120 90H125V95H120V90Z" fill="#10B981" />
|
|
180
|
+
<path d="M120 90H125V95H120V90Z" stroke="black" strokeWidth="0.5" />
|
|
181
|
+
<path d="M125 92L130 87" stroke="#10B981" strokeWidth="1" />
|
|
182
|
+
<path d="M125 92L130 97" stroke="#10B981" strokeWidth="1" />
|
|
183
|
+
<path
|
|
184
|
+
d="M130 60C140 55 150 65 140 70"
|
|
185
|
+
stroke="black"
|
|
186
|
+
strokeWidth="1"
|
|
187
|
+
strokeDasharray="2"
|
|
114
188
|
fill="none"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
stroke="black"
|
|
123
|
-
strokeWidth="1"
|
|
124
|
-
fill="none"
|
|
125
|
-
/>
|
|
126
|
-
<path
|
|
127
|
-
d="M115 160C120 150 125 155 130 160"
|
|
128
|
-
stroke="black"
|
|
129
|
-
strokeWidth="1"
|
|
130
|
-
fill="none"
|
|
131
|
-
/>
|
|
132
|
-
<rect
|
|
133
|
-
x="70"
|
|
134
|
-
y="80"
|
|
135
|
-
width="60"
|
|
136
|
-
height="30"
|
|
137
|
-
stroke="black"
|
|
138
|
-
strokeWidth="1"
|
|
139
|
-
fill="white"
|
|
140
|
-
/>
|
|
141
|
-
<path
|
|
142
|
-
d="M70 80C70 65 130 65 130 80"
|
|
143
|
-
stroke="black"
|
|
144
|
-
strokeWidth="1"
|
|
145
|
-
fill="none"
|
|
146
|
-
/>
|
|
147
|
-
<rect
|
|
148
|
-
x="70"
|
|
149
|
-
y="80"
|
|
150
|
-
width="60"
|
|
151
|
-
height="20"
|
|
152
|
-
stroke="black"
|
|
153
|
-
strokeWidth="1"
|
|
154
|
-
fill="white"
|
|
155
|
-
/>
|
|
156
|
-
<path d="M120 90H125V95H120V90Z" fill="#10B981" />
|
|
157
|
-
<path
|
|
158
|
-
d="M120 90H125V95H120V90Z"
|
|
159
|
-
stroke="black"
|
|
160
|
-
strokeWidth="0.5"
|
|
161
|
-
/>
|
|
162
|
-
<path d="M125 92L130 87" stroke="#10B981" strokeWidth="1" />
|
|
163
|
-
<path d="M125 92L130 97" stroke="#10B981" strokeWidth="1" />
|
|
164
|
-
<path
|
|
165
|
-
d="M130 60C140 55 150 65 140 70"
|
|
166
|
-
stroke="black"
|
|
167
|
-
strokeWidth="1"
|
|
168
|
-
strokeDasharray="2"
|
|
169
|
-
fill="none"
|
|
170
|
-
/>
|
|
171
|
-
<text x="140" y="60" fontSize="12" fill="black">
|
|
172
|
-
✉
|
|
173
|
-
</text>
|
|
174
|
-
<circle cx="85" cy="175" r="5" fill="#10B981" />
|
|
175
|
-
<circle cx="115" cy="175" r="5" fill="#10B981" />
|
|
176
|
-
</svg>
|
|
177
|
-
</div>
|
|
178
|
-
<h3 className="text-xl font-medium text-gray-800 mb-2">{title}</h3>
|
|
179
|
-
<p className="text-gray-500 max-w-sm">{description}</p>
|
|
189
|
+
/>
|
|
190
|
+
<text x="140" y="60" fontSize="12" fill="black">
|
|
191
|
+
✉
|
|
192
|
+
</text>
|
|
193
|
+
<circle cx="85" cy="175" r="5" fill="#10B981" />
|
|
194
|
+
<circle cx="115" cy="175" r="5" fill="#10B981" />
|
|
195
|
+
</svg>
|
|
180
196
|
</div>
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
<h3 className="text-xl font-medium text-gray-800 mb-2">{title}</h3>
|
|
198
|
+
<p className="text-gray-500 max-w-sm">{description}</p>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
};
|
|
@@ -9,7 +9,9 @@ import paperplane from "../../assets/icons8-send-50.png";
|
|
|
9
9
|
import { FilePreview, FileType } from "../common/FilePreview";
|
|
10
10
|
import { getApiClient } from "../../lib/api/apiClient";
|
|
11
11
|
import { MessageStatus } from "../../types/type";
|
|
12
|
-
|
|
12
|
+
import { Path } from "../../lib/api/endpoint";
|
|
13
|
+
const MAX_FILE_SIZE_MB = 5; // 5MB max file size
|
|
14
|
+
const MAX_FILE_COUNT = 5;
|
|
13
15
|
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp','video/mp4', 'video/webm', 'video/ogg'];
|
|
14
16
|
const ACCEPTED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg'];
|
|
15
17
|
const ACCEPTED_DOCUMENT_TYPES = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain'];
|
|
@@ -28,6 +30,7 @@ const MessageInput = () => {
|
|
|
28
30
|
const { userId } = useChatContext();
|
|
29
31
|
const { selectedConversation,setMessages } = useChatUIStore();
|
|
30
32
|
const [message, setMessage] = useState("");
|
|
33
|
+
const [message1, setMessage1] = useState("");
|
|
31
34
|
const mutation = useMessageMutation();
|
|
32
35
|
const [typingUser, setTypingUser] = useState<string | null>(null);
|
|
33
36
|
const [isSending, setIsSending] = useState(false);
|
|
@@ -39,7 +42,7 @@ const MessageInput = () => {
|
|
|
39
42
|
const attachmentsRef = useRef<Attachment[]>([]);
|
|
40
43
|
const [tempMessageId, setTempMessageId] = useState<string | null>(null);
|
|
41
44
|
const [inputError, setInputError] = useState<string | null>(null);
|
|
42
|
-
|
|
45
|
+
const attachmentsContainerRef = useRef<HTMLDivElement>(null);
|
|
43
46
|
const generateTempId = () => `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
44
47
|
|
|
45
48
|
// Function to auto-resize the textarea
|
|
@@ -108,6 +111,8 @@ const MessageInput = () => {
|
|
|
108
111
|
} else {
|
|
109
112
|
setInputError(null);
|
|
110
113
|
setMessage(newValue);
|
|
114
|
+
setMessage1(newValue);
|
|
115
|
+
|
|
111
116
|
}
|
|
112
117
|
};
|
|
113
118
|
|
|
@@ -144,7 +149,7 @@ const MessageInput = () => {
|
|
|
144
149
|
};
|
|
145
150
|
|
|
146
151
|
const uploadToS3 = async (file: File, onProgress?:(progress:number)=> void): Promise<{ url: string, name: string, size: number, type: FileType }> => {
|
|
147
|
-
const response = await apiClient.post(
|
|
152
|
+
const response = await apiClient.post(`${Path.preSignUrl}`, {
|
|
148
153
|
fileName: file.name,
|
|
149
154
|
fileType: file.type,
|
|
150
155
|
}
|
|
@@ -207,14 +212,15 @@ const MessageInput = () => {
|
|
|
207
212
|
e.preventDefault();
|
|
208
213
|
if (!message && attachmentsRef.current.length === 0 || isSending) return;
|
|
209
214
|
setIsSending(true);
|
|
210
|
-
|
|
215
|
+
setAttachments([]);
|
|
216
|
+
setMessage("");
|
|
211
217
|
const tempId = generateTempId();
|
|
212
218
|
setTempMessageId(tempId);
|
|
213
219
|
|
|
214
220
|
const optimisticMessage = {
|
|
215
221
|
_id: tempId,
|
|
216
|
-
text:
|
|
217
|
-
message:
|
|
222
|
+
text: message1, // Added text property to match the expected type
|
|
223
|
+
message: message1,
|
|
218
224
|
senderId: userId,
|
|
219
225
|
status: 'sending' as MessageStatus,
|
|
220
226
|
createdAt: new Date().toISOString(),
|
|
@@ -289,7 +295,7 @@ const MessageInput = () => {
|
|
|
289
295
|
mutation.mutate({
|
|
290
296
|
chatId: selectedConversation?.participantDetails._id,
|
|
291
297
|
senderId: userId,
|
|
292
|
-
message,
|
|
298
|
+
message: message1,
|
|
293
299
|
attachments: successfulUploads,
|
|
294
300
|
}, {
|
|
295
301
|
onSuccess: (data) => {
|
|
@@ -297,7 +303,7 @@ const MessageInput = () => {
|
|
|
297
303
|
|
|
298
304
|
socket.emit("sendMessage", {
|
|
299
305
|
chatId: selectedConversation?._id,
|
|
300
|
-
message,
|
|
306
|
+
message: message1,
|
|
301
307
|
messageId: data[1]._id,
|
|
302
308
|
attachments: successfulUploads,
|
|
303
309
|
senderId: userId,
|
|
@@ -333,7 +339,7 @@ const MessageInput = () => {
|
|
|
333
339
|
));
|
|
334
340
|
} finally {
|
|
335
341
|
setIsSending(false);
|
|
336
|
-
|
|
342
|
+
setMessage1("");
|
|
337
343
|
setAttachments([]);
|
|
338
344
|
setTempMessageId(null);
|
|
339
345
|
}
|
|
@@ -383,6 +389,22 @@ const MessageInput = () => {
|
|
|
383
389
|
const files = e.target.files;
|
|
384
390
|
if (!files || files.length === 0) return;
|
|
385
391
|
|
|
392
|
+
// Check if adding these files would exceed the maximum count
|
|
393
|
+
if (attachments.length + files.length > MAX_FILE_COUNT) {
|
|
394
|
+
alert(`You can only attach up to ${MAX_FILE_COUNT} files`);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Calculate total size of new files
|
|
399
|
+
const newFilesSize = Array.from(files).reduce((total, file) => total + file.size, 0);
|
|
400
|
+
const currentAttachmentsSize = attachments.reduce((total, att) => total + att.file.size, 0);
|
|
401
|
+
|
|
402
|
+
// Check if total size would exceed the limit
|
|
403
|
+
if (currentAttachmentsSize + newFilesSize > MAX_FILE_SIZE_MB * 1024 * 1024) {
|
|
404
|
+
alert(`Total file size cannot exceed ${MAX_FILE_SIZE_MB}MB`);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
386
408
|
const newAttachments: Attachment[] = [];
|
|
387
409
|
|
|
388
410
|
for (let i = 0; i < files.length; i++) {
|
|
@@ -399,7 +421,6 @@ const MessageInput = () => {
|
|
|
399
421
|
continue;
|
|
400
422
|
}
|
|
401
423
|
|
|
402
|
-
|
|
403
424
|
const previewUrl = fileType === 'document'
|
|
404
425
|
? URL.createObjectURL(new Blob([''], { type: 'application/pdf' })) // Placeholder for documents
|
|
405
426
|
: URL.createObjectURL(file);
|
|
@@ -417,6 +438,15 @@ const MessageInput = () => {
|
|
|
417
438
|
}
|
|
418
439
|
};
|
|
419
440
|
|
|
441
|
+
const scrollAttachments = (direction: 'left' | 'right') => {
|
|
442
|
+
if (attachmentsContainerRef.current) {
|
|
443
|
+
const scrollAmount = direction === 'right' ? 200 : -200;
|
|
444
|
+
attachmentsContainerRef.current.scrollBy({
|
|
445
|
+
left: scrollAmount,
|
|
446
|
+
behavior: 'smooth'
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
};
|
|
420
450
|
|
|
421
451
|
const removeAttachment = (index: number) => {
|
|
422
452
|
setAttachments(prev => {
|
|
@@ -431,7 +461,16 @@ const MessageInput = () => {
|
|
|
431
461
|
<div className="message-input-container">
|
|
432
462
|
{/* Preview area for attachments */}
|
|
433
463
|
{attachments.length > 0 && (
|
|
434
|
-
<div className="attachments-preview">
|
|
464
|
+
<div className="attachments-preview-container">
|
|
465
|
+
<button
|
|
466
|
+
className="scroll-button left"
|
|
467
|
+
onClick={() => scrollAttachments('left')}
|
|
468
|
+
disabled={attachments.length <= 3}
|
|
469
|
+
>
|
|
470
|
+
<
|
|
471
|
+
</button>
|
|
472
|
+
|
|
473
|
+
<div className="attachments-preview" ref={attachmentsContainerRef}>
|
|
435
474
|
{attachments.map((attachment, index) => (
|
|
436
475
|
<FilePreview
|
|
437
476
|
key={index}
|
|
@@ -441,6 +480,22 @@ const MessageInput = () => {
|
|
|
441
480
|
onRemove={() => removeAttachment(index)}
|
|
442
481
|
/>
|
|
443
482
|
))}
|
|
483
|
+
|
|
484
|
+
{attachments.length < MAX_FILE_COUNT && (
|
|
485
|
+
<div className="add-more-files" onClick={() => fileInputRef.current?.click()}>
|
|
486
|
+
<div className="plus-icon">+</div>
|
|
487
|
+
<div className="add-more-text">Add more</div>
|
|
488
|
+
</div>
|
|
489
|
+
)}
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<button
|
|
493
|
+
className="scroll-button right"
|
|
494
|
+
onClick={() => scrollAttachments('right')}
|
|
495
|
+
disabled={attachments.length <= 3}
|
|
496
|
+
>
|
|
497
|
+
>
|
|
498
|
+
</button>
|
|
444
499
|
</div>
|
|
445
500
|
)}
|
|
446
501
|
|
|
@@ -16,17 +16,15 @@ const Messages = () => {
|
|
|
16
16
|
|
|
17
17
|
const lastMessageRef = useRef<HTMLDivElement>(null);
|
|
18
18
|
|
|
19
|
-
const { data, fetchNextPage, isFetchingNextPage } =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
|
|
19
|
+
const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery({
|
|
20
|
+
queryKey: ["messages", selectedConversation?._id, userId],
|
|
21
|
+
queryFn: ({ pageParam = 1 }) =>
|
|
22
|
+
fetchMessages(selectedConversation?._id, userId, pageParam),
|
|
23
|
+
getNextPageParam: (lastPage) => {
|
|
24
|
+
return lastPage.nextPage; // Use the nextPage from API response
|
|
25
|
+
},
|
|
26
|
+
initialPageParam: 1,
|
|
27
|
+
});
|
|
30
28
|
|
|
31
29
|
useEffect(() => {
|
|
32
30
|
if (inView) {
|
|
@@ -34,12 +32,11 @@ const Messages = () => {
|
|
|
34
32
|
}
|
|
35
33
|
}, [fetchNextPage, inView]);
|
|
36
34
|
|
|
37
|
-
|
|
38
35
|
useEffect(() => {
|
|
39
36
|
if (data) {
|
|
40
|
-
console.log(
|
|
37
|
+
console.log("message fetching data", data);
|
|
41
38
|
|
|
42
|
-
const allMessages = data.pages.flatMap(page => page.messages).reverse();
|
|
39
|
+
const allMessages = data.pages.flatMap((page) => page.messages).reverse();
|
|
43
40
|
setMessages(allMessages);
|
|
44
41
|
}
|
|
45
42
|
}, [data]);
|
|
@@ -52,19 +49,22 @@ const Messages = () => {
|
|
|
52
49
|
newMessage.shouldShake = true;
|
|
53
50
|
console.log("📩 New message received:", newMessage);
|
|
54
51
|
// setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
|
|
55
|
-
setMessages(prevMessages => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const isDuplicate = prevMessages.some(
|
|
59
|
-
msg
|
|
60
|
-
|
|
52
|
+
setMessages((prevMessages) => {
|
|
53
|
+
console.log("prevMessages", prevMessages);
|
|
54
|
+
|
|
55
|
+
const isDuplicate = prevMessages.some(
|
|
56
|
+
(msg) =>
|
|
57
|
+
msg._id === newMessage[1]._id ||
|
|
58
|
+
(msg.isOptimistic &&
|
|
59
|
+
msg.senderId === userId &&
|
|
60
|
+
msg.message === newMessage[1].message)
|
|
61
61
|
);
|
|
62
|
-
console.log(
|
|
63
|
-
|
|
62
|
+
console.log("isDuplicate", isDuplicate);
|
|
63
|
+
|
|
64
64
|
if (isDuplicate) {
|
|
65
65
|
return prevMessages;
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
return [...prevMessages, newMessage[1]];
|
|
69
69
|
});
|
|
70
70
|
};
|
|
@@ -78,7 +78,6 @@ const Messages = () => {
|
|
|
78
78
|
// });
|
|
79
79
|
// };
|
|
80
80
|
|
|
81
|
-
|
|
82
81
|
const handleStatusUpdate = ({
|
|
83
82
|
messageId,
|
|
84
83
|
status,
|
|
@@ -157,17 +156,14 @@ const Messages = () => {
|
|
|
157
156
|
// }
|
|
158
157
|
// }, [handleScroll]);
|
|
159
158
|
|
|
160
|
-
|
|
161
159
|
console.log("📩 Messages:", messages);
|
|
162
160
|
console.log("📩 Messages Length:", messages?.length);
|
|
163
161
|
|
|
164
|
-
|
|
165
|
-
|
|
166
162
|
return (
|
|
167
|
-
<div
|
|
168
|
-
|
|
163
|
+
<div
|
|
164
|
+
className="chatMessages"
|
|
165
|
+
style={{ overflowY: "auto", height: "100%", position: "relative" }}
|
|
169
166
|
>
|
|
170
|
-
|
|
171
167
|
<div ref={ref} className="my-8">
|
|
172
168
|
{isFetchingNextPage ? <Loader /> : null}
|
|
173
169
|
</div>
|
|
@@ -175,8 +171,10 @@ const Messages = () => {
|
|
|
175
171
|
messages?.map((message: any) =>
|
|
176
172
|
// Check if the message object is valid and has an _id before rendering
|
|
177
173
|
message ? (
|
|
178
|
-
<div
|
|
179
|
-
|
|
174
|
+
<div
|
|
175
|
+
key={message._id}
|
|
176
|
+
ref={lastMessageRef}
|
|
177
|
+
style={{ flex: 1, minHeight: 0, overflowY: "auto" }}
|
|
180
178
|
>
|
|
181
179
|
<Message message={message} />
|
|
182
180
|
</div>
|
|
@@ -187,7 +185,6 @@ const Messages = () => {
|
|
|
187
185
|
Send a message to start the conversation
|
|
188
186
|
</p>
|
|
189
187
|
)}
|
|
190
|
-
|
|
191
188
|
</div>
|
|
192
189
|
);
|
|
193
190
|
};
|
|
@@ -47,14 +47,24 @@ const Conversation = ({ conversation, lastIdx }: ConversationProps) => {
|
|
|
47
47
|
onlineUsers?.includes(conversation.participantDetails._id);
|
|
48
48
|
return (
|
|
49
49
|
<>
|
|
50
|
-
<div
|
|
50
|
+
<div
|
|
51
|
+
className="conversation-container"
|
|
52
|
+
onClick={handleSelectConversation}
|
|
53
|
+
>
|
|
51
54
|
<div className="conversation-avatar">
|
|
52
55
|
<img
|
|
53
56
|
className="conversation-img"
|
|
54
|
-
src={
|
|
57
|
+
src={
|
|
58
|
+
conversation.participantDetails?.profilePic ||
|
|
59
|
+
conversation.participantDetails?.idpic
|
|
60
|
+
}
|
|
55
61
|
alt="User Avatar"
|
|
56
62
|
/>
|
|
57
|
-
<span
|
|
63
|
+
<span
|
|
64
|
+
className={`chatSidebarStatusDot ${
|
|
65
|
+
isUserOnline ? "online" : "offline"
|
|
66
|
+
}`}
|
|
67
|
+
></span>
|
|
58
68
|
</div>
|
|
59
69
|
|
|
60
70
|
<div className="conversation-info">
|
|
@@ -63,10 +73,13 @@ const Conversation = ({ conversation, lastIdx }: ConversationProps) => {
|
|
|
63
73
|
{conversation.participantDetails?.firstname}
|
|
64
74
|
</p>
|
|
65
75
|
<span className="conversation-time">
|
|
66
|
-
{new Date(conversation.lastMessage.createdAt).toLocaleTimeString(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
{new Date(conversation.lastMessage.createdAt).toLocaleTimeString(
|
|
77
|
+
[],
|
|
78
|
+
{
|
|
79
|
+
hour: "2-digit",
|
|
80
|
+
minute: "2-digit",
|
|
81
|
+
}
|
|
82
|
+
)}
|
|
70
83
|
</span>
|
|
71
84
|
</div>
|
|
72
85
|
<p className="conversation-message">
|