@pubuduth-aplicy/chat-ui 2.1.68 → 2.1.70

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.
@@ -1,20 +1,31 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import React, { useCallback, useEffect, useRef, useState } from "react";
3
2
  import { useMessageMutation } from "../../hooks/mutations/useSendMessage";
4
3
  import { useChatContext } from "../../providers/ChatProvider";
5
4
  import useChatUIStore from "../../stores/Zustant";
6
5
  import paperplane from "../../assets/icons8-send-50.png";
7
- // import { PaperPlaneRight } from '@phosphor-icons/react'; // Assuming you're using icons from Phosphor Icons library
8
- // import useSendMessage from '../../hooks/useSendMessage'; // Importing the useSendMessage hook
9
6
  import { FilePreview, FileType } from "../common/FilePreview";
10
7
  import { getApiClient } from "../../lib/api/apiClient";
11
8
  import { MessageStatus } from "../../types/type";
12
9
  import { Path } from "../../lib/api/endpoint";
13
- const MAX_FILE_SIZE_MB = 5; // 5MB max file size
10
+
11
+ const MAX_FILE_SIZE_MB = 5;
14
12
  const MAX_FILE_COUNT = 5;
15
- const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4', 'video/webm', 'video/ogg'];
16
- const ACCEPTED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg'];
17
- const ACCEPTED_DOCUMENT_TYPES = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain'];
13
+ const ACCEPTED_IMAGE_TYPES = [
14
+ "image/jpeg",
15
+ "image/png",
16
+ "image/gif",
17
+ "image/webp",
18
+ "video/mp4",
19
+ "video/webm",
20
+ "video/ogg",
21
+ ];
22
+ const ACCEPTED_VIDEO_TYPES = ["video/mp4", "video/webm", "video/ogg"];
23
+ const ACCEPTED_DOCUMENT_TYPES = [
24
+ "application/pdf",
25
+ "application/msword",
26
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
27
+ "text/plain",
28
+ ];
18
29
 
19
30
  interface Attachment {
20
31
  file: File;
@@ -26,15 +37,13 @@ interface Attachment {
26
37
 
27
38
  const MessageInput = () => {
28
39
  const apiClient = getApiClient();
29
- const { socket } = useChatContext();
30
- const { userId } = useChatContext();
40
+ const { socket, sendMessage, userId } = useChatContext();
31
41
  const { selectedConversation, setMessages } = useChatUIStore();
32
42
  const [message, setMessage] = useState("");
33
43
  const [message1, setMessage1] = useState("");
34
44
  const mutation = useMessageMutation();
35
45
  const [typingUser, setTypingUser] = useState<string | null>(null);
36
46
  const [isSending, setIsSending] = useState(false);
37
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
47
  const [isTyping, setIsTyping] = useState(false);
39
48
  const [attachments, setAttachments] = useState<Attachment[]>([]);
40
49
  const [showAttachmentOptions, setShowAttachmentOptions] = useState(false);
@@ -43,63 +52,101 @@ const MessageInput = () => {
43
52
  const [tempMessageId, setTempMessageId] = useState<string | null>(null);
44
53
  const [inputError, setInputError] = useState<string | null>(null);
45
54
  const attachmentsContainerRef = useRef<HTMLDivElement>(null);
46
- const generateTempId = () => `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
47
-
48
- // Function to auto-resize the textarea
49
- // const autoResizeTextarea = (element: HTMLTextAreaElement) => {
50
- // element.style.height = "auto"
51
- // element.style.height = Math.min(150, element.scrollHeight) + "px"
52
- // }
55
+ const typingTimeoutRef = useRef<number | null>(null);
56
+ const generateTempId = () =>
57
+ `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
53
58
 
59
+ // Join chat room when conversation is selected
54
60
  useEffect(() => {
55
- if (selectedConversation?._id) {
56
- socket.emit("joinChat", selectedConversation._id);
61
+ if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
62
+ sendMessage({
63
+ event: "joinChat",
64
+ data: {
65
+ chatId: selectedConversation._id,
66
+ },
67
+ });
57
68
  }
58
- }, [selectedConversation, socket]);
69
+ }, [selectedConversation?._id, socket, sendMessage]);
59
70
 
71
+ // Typing indicator logic
60
72
  useEffect(() => {
61
- if (!socket) return;
73
+ if (!socket || !selectedConversation?._id) return;
62
74
 
63
75
  if (message.trim() !== "") {
64
76
  setIsTyping(true);
65
- socket.emit("typing", { chatId: selectedConversation?._id, userId });
77
+ sendMessage({
78
+ event: "typing",
79
+ data: {
80
+ chatId: selectedConversation._id,
81
+ userId,
82
+ },
83
+ });
66
84
  }
67
85
 
68
- const typingTimeout = setTimeout(() => {
86
+ typingTimeoutRef.current = setTimeout(() => {
69
87
  if (message.trim() === "") {
70
88
  setIsTyping(false);
71
- socket.emit("stopTyping", {
72
- chatId: selectedConversation?._id,
73
- userId,
89
+ sendMessage({
90
+ event: "stopTyping",
91
+ data: {
92
+ chatId: selectedConversation._id,
93
+ userId,
94
+ },
74
95
  });
75
96
  }
76
97
  }, 200);
77
98
 
78
- return () => clearTimeout(typingTimeout);
79
- }, [message, socket, selectedConversation?._id, userId]);
99
+ return () => {
100
+ if (typingTimeoutRef.current) {
101
+ clearTimeout(typingTimeoutRef.current);
102
+ }
103
+ };
104
+ }, [message, socket, selectedConversation?._id, userId, sendMessage]);
105
+
106
+ // Listen for typing indicators from others
107
+ useEffect(() => {
108
+ if (!socket || !selectedConversation?._id) return;
80
109
 
110
+ const handleMessage = (event: MessageEvent) => {
111
+ try {
112
+ const data = JSON.parse(event.data);
113
+ console.log("Received WebSocket message:", data);
114
+ if (
115
+ data.event === "typing" &&
116
+ data.data.chatId === selectedConversation._id
117
+ ) {
118
+ console.log("Setting typing user:", data.data.userId);
119
+ setTypingUser(data.data.userId);
120
+ } else if (
121
+ data.event === "stopTyping" &&
122
+ data.data.chatId === selectedConversation._id
123
+ ) {
124
+ setTypingUser((prev) => (prev === data.data.userId ? null : prev));
125
+ }
126
+ } catch (error) {
127
+ console.error("Error parsing typing message:", error);
128
+ }
129
+ };
130
+
131
+ socket.addEventListener("message", handleMessage);
132
+ return () => {
133
+ socket.removeEventListener("message", handleMessage);
134
+ };
135
+ }, [socket, selectedConversation?._id]);
81
136
 
82
137
  const validateInput = (text: string) => {
83
- // Check for email
84
138
  const emailRegex = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
85
139
  if (emailRegex.test(text)) {
86
140
  return "To protect your account, make sure not to share any personal or sensitive information.";
87
141
  }
88
142
 
89
- // Check for phone number (very basic)
90
- const phoneRegex = /(\+?\d{1,4}[\s-]?)?(\(?\d{3}\)?[\s-]?)?\d{3}[\s-]?\d{4}/;
143
+ const phoneRegex =
144
+ /(\+?\d{1,4}[\s-]?)?(\(?\d{3}\)?[\s-]?)?\d{3}[\s-]?\d{4}/;
91
145
  if (phoneRegex.test(text)) {
92
146
  return "To protect your account, make sure not to share any personal or sensitive information.";
93
147
  }
94
148
 
95
- // // Check for bad words
96
- // for (const word of badWords) {
97
- // if (text.toLowerCase().includes(word)) {
98
- // return "Inappropriate language is not allowed.";
99
- // }
100
- // }
101
-
102
- return null; // No errors
149
+ return null;
103
150
  };
104
151
 
105
152
  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
@@ -114,63 +161,29 @@ const MessageInput = () => {
114
161
  setInputError(null);
115
162
  setMessage(newValue);
116
163
  setMessage1(newValue);
117
-
118
164
  }
119
165
  };
120
166
 
121
-
122
- useEffect(() => {
123
- if (!socket || !selectedConversation?._id) return;
124
-
125
- const handleTyping = ({ userId, chatId }: { userId: string; chatId: string }) => {
126
- if (chatId === selectedConversation._id) {
127
- setTypingUser(userId);
128
- }
129
- };
130
-
131
- const handleStopTyping = ({ userId, chatId }: { userId: string; chatId: string }) => {
132
- if (chatId === selectedConversation._id) {
133
- setTypingUser((prev) => (prev === userId ? null : prev));
134
- }
135
- };
136
-
137
- socket.on("typing", handleTyping);
138
- socket.on("stopTyping", handleStopTyping);
139
-
140
- return () => {
141
- socket.off("typing", handleTyping);
142
- socket.off("stopTyping", handleStopTyping);
143
- };
144
- }, [socket, selectedConversation?._id]);
145
-
146
167
  const getFileType = (mimeType: string): FileType | null => {
147
- if (ACCEPTED_IMAGE_TYPES.includes(mimeType)) return 'image';
148
- if (ACCEPTED_VIDEO_TYPES.includes(mimeType)) return 'video';
149
- if (ACCEPTED_DOCUMENT_TYPES.includes(mimeType)) return 'document';
168
+ if (ACCEPTED_IMAGE_TYPES.includes(mimeType)) return "image";
169
+ if (ACCEPTED_VIDEO_TYPES.includes(mimeType)) return "video";
170
+ if (ACCEPTED_DOCUMENT_TYPES.includes(mimeType)) return "document";
150
171
  return null;
151
172
  };
152
173
 
153
- const uploadToS3 = async (file: File, onProgress?: (progress: number) => void): Promise<{ url: string, name: string, size: number, type: FileType }> => {
174
+ const uploadToS3 = async (
175
+ file: File,
176
+ onProgress?: (progress: number) => void
177
+ ): Promise<{ url: string; name: string; size: number; type: FileType }> => {
154
178
  const response = await apiClient.post(`${Path.preSignUrl}`, {
155
179
  fileName: file.name,
156
180
  fileType: file.type,
157
- }
158
- );
181
+ });
159
182
 
160
183
  const { signedUrl, fileUrl } = await response.data;
161
-
162
- // const uploadResponse = await fetch(signedUrl, {
163
- // method: 'PUT',
164
- // body: file,
165
- // headers: {
166
- // 'Content-Type': file.type,
167
- // // 'x-amz-acl': 'public-read'
168
- // },
169
- // });
170
-
171
184
  const xhr = new XMLHttpRequest();
172
- xhr.open('PUT', signedUrl, true);
173
- xhr.setRequestHeader('Content-Type', file.type);
185
+ xhr.open("PUT", signedUrl, true);
186
+ xhr.setRequestHeader("Content-Type", file.type);
174
187
 
175
188
  return new Promise((resolve, reject) => {
176
189
  xhr.upload.onprogress = (event) => {
@@ -189,30 +202,20 @@ const MessageInput = () => {
189
202
  type: getFileType(file.type),
190
203
  });
191
204
  } else {
192
- reject(new Error('Upload failed'));
205
+ reject(new Error("Upload failed"));
193
206
  }
194
207
  };
195
208
 
196
- xhr.onerror = () => reject(new Error('Upload failed'));
209
+ xhr.onerror = () => reject(new Error("Upload failed"));
197
210
  xhr.send(file);
198
211
  });
199
-
200
- // if (!uploadResponse.ok) {
201
- // throw new Error('Upload failed');
202
- // }
203
-
204
- // return {
205
- // url: fileUrl,
206
- // name: file.name,
207
- // size: file.size,
208
- // type: getFileType(file.type),
209
- // };
210
212
  };
211
213
 
212
214
  const handleSubmit = useCallback(
213
- async (e: any) => {
215
+ async (e: React.FormEvent) => {
214
216
  e.preventDefault();
215
- if (!message && attachmentsRef.current.length === 0 || isSending) return;
217
+ if ((!message && attachmentsRef.current.length === 0) || isSending)
218
+ return;
216
219
  setIsSending(true);
217
220
  setAttachments([]);
218
221
  setMessage("");
@@ -221,12 +224,12 @@ const MessageInput = () => {
221
224
 
222
225
  const optimisticMessage = {
223
226
  _id: tempId,
224
- text: message1, // Added text property to match the expected type
227
+ text: message1,
225
228
  message: message1,
226
229
  senderId: userId,
227
- status: 'sending' as MessageStatus,
230
+ status: "sending" as MessageStatus,
228
231
  createdAt: new Date().toISOString(),
229
- media: attachmentsRef.current.map(att => ({
232
+ media: attachmentsRef.current.map((att) => ({
230
233
  type: att.type,
231
234
  url: att.previewUrl,
232
235
  name: att.file.name,
@@ -235,111 +238,117 @@ const MessageInput = () => {
235
238
  uploadError: "",
236
239
  })),
237
240
  isUploading: true,
238
- isOptimistic: true
241
+ isOptimistic: true,
239
242
  };
240
243
 
241
- setMessages(prev => [...prev, optimisticMessage]);
244
+ setMessages((prev) => [...prev, optimisticMessage]);
242
245
 
243
246
  try {
244
-
245
247
  const uploadedFiles = await Promise.all(
246
248
  attachmentsRef.current.map(async (attachment, index) => {
247
249
  try {
248
250
  const result = await uploadToS3(attachment.file, (progress) => {
249
- setMessages(prev => prev.map(msg => {
251
+ setMessages((prev) =>
252
+ prev.map((msg) => {
253
+ if (msg._id === tempId) {
254
+ const updatedMedia = [...msg.media!];
255
+ updatedMedia[index] = {
256
+ ...updatedMedia[index],
257
+ uploadProgress: progress,
258
+ };
259
+ return {
260
+ ...msg,
261
+ media: updatedMedia,
262
+ };
263
+ }
264
+ return msg;
265
+ })
266
+ );
267
+ });
268
+ return result;
269
+ } catch (error) {
270
+ console.error(
271
+ `Error uploading file ${attachment.file.name}:`,
272
+ error
273
+ );
274
+ setMessages((prev) =>
275
+ prev.map((msg) => {
250
276
  if (msg._id === tempId) {
251
277
  const updatedMedia = [...msg.media!];
252
278
  updatedMedia[index] = {
253
279
  ...updatedMedia[index],
254
- uploadProgress: progress
280
+ uploadError: "Upload failed",
255
281
  };
256
282
  return {
257
283
  ...msg,
258
- media: updatedMedia
284
+ media: updatedMedia,
259
285
  };
260
286
  }
261
287
  return msg;
262
- }))
263
- });
264
- console.log("Uploaded file:", result);
265
- return result;
266
- // return {
267
- // type: attachment.type,
268
- // url: result.url,
269
- // name: result.name,
270
- // size: result.size
271
- // };
272
- } catch (error) {
273
- console.error(`Error uploading file ${attachment.file.name}:`, error);
274
- setMessages(prev => prev.map(msg => {
275
- if (msg._id === tempId) {
276
- const updatedMedia = [...msg.media!];
277
- updatedMedia[index] = {
278
- ...updatedMedia[index],
279
- uploadError: "Upload failed"
280
- };
281
- return {
282
- ...msg,
283
- media: updatedMedia
284
- };
285
- }
286
- return msg;
287
- }))
288
+ })
289
+ );
288
290
  return null;
289
291
  }
290
292
  })
291
293
  );
292
294
 
295
+ const successfulUploads = uploadedFiles.filter((file) => file !== null);
293
296
 
294
- const successfulUploads = uploadedFiles.filter(file => file !== null);
295
-
296
- console.log("📤 Sending message:", successfulUploads);
297
- mutation.mutate({
298
- chatId: selectedConversation?.participantDetails._id,
299
- senderId: userId,
300
- message: message1,
301
- attachments: successfulUploads,
302
- }, {
303
- onSuccess: (data) => {
304
- console.log('Response from sendMessage:', data);
305
- setMessages(prev => {
306
- console.log("Removing optimistic message:", prev);
307
-
308
- // Definitely remove the optimistic message
309
- const filtered = prev.filter(msg => msg._id !== tempMessageId);
310
- // Add the real message from server
311
- console.log("Adding real message:", filtered);
312
-
313
- return [...filtered, {
314
- ...data[1],
315
- isUploading: false,
316
- isOptimistic: false
317
- }
318
- ];
319
- });
320
- socket.emit("sendMessage", {
321
- chatId: selectedConversation?._id,
322
- message: message1,
323
- messageId: data[1]._id,
324
- attachments: successfulUploads,
325
- senderId: userId,
326
- receiverId: selectedConversation?.participantDetails._id,
327
- });
328
-
297
+ mutation.mutate(
298
+ {
299
+ chatId:
300
+ !Array.isArray(selectedConversation?.participantDetails) &&
301
+ selectedConversation?.participantDetails._id,
302
+ senderId: userId,
303
+ message: message1,
304
+ attachments: successfulUploads,
329
305
  },
330
- onError: (error) => {
331
- console.error("❌ Error in sending message:", error);
332
- setMessages(prev => prev.map(msg =>
333
- msg._id === tempId ? { ...msg, status: 'failed' } : msg
334
- ));
335
- },
336
- });
306
+ {
307
+ onSuccess: (data) => {
308
+ setMessages((prev) => {
309
+ const filtered = prev.filter(
310
+ (msg) => msg._id !== tempMessageId
311
+ );
312
+ return [
313
+ ...filtered,
314
+ {
315
+ ...data[1],
316
+ isUploading: false,
317
+ isOptimistic: false,
318
+ },
319
+ ];
320
+ });
337
321
 
322
+ // Send message via WebSocket
323
+ sendMessage({
324
+ type: "sendMessage",
325
+ chatId: selectedConversation?._id,
326
+ message: message1,
327
+ messageId: data[1]._id,
328
+ attachments: successfulUploads,
329
+ senderId: userId,
330
+ receiverId:
331
+ !Array.isArray(selectedConversation?.participantDetails) &&
332
+ selectedConversation?.participantDetails._id,
333
+ });
334
+ },
335
+ onError: (error) => {
336
+ console.error("Error in sending message:", error);
337
+ setMessages((prev) =>
338
+ prev.map((msg) =>
339
+ msg._id === tempId ? { ...msg, status: "failed" } : msg
340
+ )
341
+ );
342
+ },
343
+ }
344
+ );
338
345
  } catch (error) {
339
- console.error("Error sending message:", error);
340
- setMessages(prev => prev.map(msg =>
341
- msg._id === tempId ? { ...msg, status: 'failed' } : msg
342
- ));
346
+ console.error("Error sending message:", error);
347
+ setMessages((prev) =>
348
+ prev.map((msg) =>
349
+ msg._id === tempId ? { ...msg, status: "failed" } : msg
350
+ )
351
+ );
343
352
  } finally {
344
353
  setIsSending(false);
345
354
  setMessage1("");
@@ -347,17 +356,33 @@ const MessageInput = () => {
347
356
  setTempMessageId(null);
348
357
  }
349
358
  },
350
- [message, selectedConversation, userId, isSending]
359
+ [
360
+ message,
361
+ message1,
362
+ selectedConversation,
363
+ userId,
364
+ isSending,
365
+ mutation,
366
+ setMessages,
367
+ sendMessage,
368
+ ]
351
369
  );
352
370
 
371
+ // Clean up object URLs when component unmounts
353
372
  useEffect(() => {
354
373
  return () => {
355
- attachments.forEach(attachment => {
374
+ attachments.forEach((attachment) => {
356
375
  URL.revokeObjectURL(attachment.previewUrl);
357
376
  });
358
377
  };
359
378
  }, [attachments]);
360
379
 
380
+ // Update attachments ref
381
+ useEffect(() => {
382
+ attachmentsRef.current = attachments;
383
+ }, [attachments]);
384
+
385
+ // File attachment handlers
361
386
  const handleAttachmentClick = () => {
362
387
  setShowAttachmentOptions(!showAttachmentOptions);
363
388
  };
@@ -372,38 +397,39 @@ const MessageInput = () => {
372
397
 
373
398
  const getAcceptString = (type: FileType): string => {
374
399
  switch (type) {
375
- case 'image':
376
- return ACCEPTED_IMAGE_TYPES.join(',');
377
- case 'video':
378
- return ACCEPTED_VIDEO_TYPES.join(',');
379
- case 'document':
380
- return ACCEPTED_DOCUMENT_TYPES.join(',');
400
+ case "image":
401
+ return ACCEPTED_IMAGE_TYPES.join(",");
402
+ case "video":
403
+ return ACCEPTED_VIDEO_TYPES.join(",");
404
+ case "document":
405
+ return ACCEPTED_DOCUMENT_TYPES.join(",");
381
406
  default:
382
- return '*';
407
+ return "*";
383
408
  }
384
409
  };
385
410
 
386
-
387
- useEffect(() => {
388
- attachmentsRef.current = attachments;
389
- }, [attachments]);
390
-
391
411
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
392
412
  const files = e.target.files;
393
413
  if (!files || files.length === 0) return;
394
414
 
395
- // Check if adding these files would exceed the maximum count
396
415
  if (attachments.length + files.length > MAX_FILE_COUNT) {
397
416
  alert(`You can only attach up to ${MAX_FILE_COUNT} files`);
398
417
  return;
399
418
  }
400
419
 
401
- // Calculate total size of new files
402
- const newFilesSize = Array.from(files).reduce((total, file) => total + file.size, 0);
403
- const currentAttachmentsSize = attachments.reduce((total, att) => total + att.file.size, 0);
420
+ const newFilesSize = Array.from(files).reduce(
421
+ (total, file) => total + file.size,
422
+ 0
423
+ );
424
+ const currentAttachmentsSize = attachments.reduce(
425
+ (total, att) => total + att.file.size,
426
+ 0
427
+ );
404
428
 
405
- // Check if total size would exceed the limit
406
- if (currentAttachmentsSize + newFilesSize > MAX_FILE_SIZE_MB * 1024 * 1024) {
429
+ if (
430
+ currentAttachmentsSize + newFilesSize >
431
+ MAX_FILE_SIZE_MB * 1024 * 1024
432
+ ) {
407
433
  alert(`Total file size cannot exceed ${MAX_FILE_SIZE_MB}MB`);
408
434
  return;
409
435
  }
@@ -424,35 +450,36 @@ const MessageInput = () => {
424
450
  continue;
425
451
  }
426
452
 
427
- const previewUrl = fileType === 'document'
428
- ? URL.createObjectURL(new Blob([''], { type: 'application/pdf' })) // Placeholder for documents
429
- : URL.createObjectURL(file);
453
+ const previewUrl =
454
+ fileType === "document"
455
+ ? URL.createObjectURL(new Blob([""], { type: "application/pdf" }))
456
+ : URL.createObjectURL(file);
430
457
 
431
458
  newAttachments.push({
432
459
  file,
433
460
  type: fileType,
434
- previewUrl
461
+ previewUrl,
435
462
  });
436
463
  }
437
464
 
438
- setAttachments(prev => [...prev, ...newAttachments]);
465
+ setAttachments((prev) => [...prev, ...newAttachments]);
439
466
  if (fileInputRef.current) {
440
- fileInputRef.current.value = '';
467
+ fileInputRef.current.value = "";
441
468
  }
442
469
  };
443
470
 
444
- const scrollAttachments = (direction: 'left' | 'right') => {
471
+ const scrollAttachments = (direction: "left" | "right") => {
445
472
  if (attachmentsContainerRef.current) {
446
- const scrollAmount = direction === 'right' ? 200 : -200;
473
+ const scrollAmount = direction === "right" ? 200 : -200;
447
474
  attachmentsContainerRef.current.scrollBy({
448
475
  left: scrollAmount,
449
- behavior: 'smooth'
476
+ behavior: "smooth",
450
477
  });
451
478
  }
452
479
  };
453
480
 
454
481
  const removeAttachment = (index: number) => {
455
- setAttachments(prev => {
482
+ setAttachments((prev) => {
456
483
  const newAttachments = [...prev];
457
484
  URL.revokeObjectURL(newAttachments[index].previewUrl);
458
485
  newAttachments.splice(index, 1);
@@ -462,12 +489,11 @@ const MessageInput = () => {
462
489
 
463
490
  return (
464
491
  <div className="message-input-container">
465
- {/* Preview area for attachments */}
466
492
  {attachments.length > 0 && (
467
493
  <div className="attachments-preview-container">
468
494
  <button
469
495
  className="scroll-button left"
470
- onClick={() => scrollAttachments('left')}
496
+ onClick={() => scrollAttachments("left")}
471
497
  disabled={attachments.length <= 3}
472
498
  >
473
499
  &lt;
@@ -485,7 +511,10 @@ const MessageInput = () => {
485
511
  ))}
486
512
 
487
513
  {attachments.length < MAX_FILE_COUNT && (
488
- <div className="add-more-files" onClick={() => fileInputRef.current?.click()}>
514
+ <div
515
+ className="add-more-files"
516
+ onClick={() => fileInputRef.current?.click()}
517
+ >
489
518
  <div className="plus-icon">+</div>
490
519
  <div className="add-more-text">Add more</div>
491
520
  </div>
@@ -494,7 +523,7 @@ const MessageInput = () => {
494
523
 
495
524
  <button
496
525
  className="scroll-button right"
497
- onClick={() => scrollAttachments('right')}
526
+ onClick={() => scrollAttachments("right")}
498
527
  disabled={attachments.length <= 3}
499
528
  >
500
529
  &gt;
@@ -503,33 +532,46 @@ const MessageInput = () => {
503
532
  )}
504
533
 
505
534
  <form className="chatMessageInputform" onSubmit={handleSubmit}>
506
- {inputError && <p style={{ color: 'red', fontSize: '12px' }}>{inputError}</p>}
535
+ {inputError && (
536
+ <p style={{ color: "red", fontSize: "12px" }}>{inputError}</p>
537
+ )}
507
538
 
508
539
  <div className="chatMessageInputdiv">
509
- {/* Hidden file input */}
510
540
  <input
511
541
  type="file"
512
542
  ref={fileInputRef}
513
- style={{ display: 'none' }}
543
+ style={{ display: "none" }}
514
544
  onChange={handleFileChange}
515
545
  multiple
516
546
  />
517
547
 
518
- {/* Attachment button and options */}
519
- <div className="attachment-container" style={{ position: 'relative' }}>
548
+ <div
549
+ className="attachment-container"
550
+ style={{ position: "relative" }}
551
+ >
520
552
  <button
521
553
  type="button"
522
554
  className="attachment-button"
523
555
  onClick={handleAttachmentClick}
524
556
  style={{
525
- background: 'none',
526
- border: 'none',
527
- cursor: 'pointer',
528
- padding: '8px',
557
+ background: "none",
558
+ border: "none",
559
+ cursor: "pointer",
560
+ padding: "8px",
529
561
  }}
530
562
  >
531
563
  <div className="attachment-icon">
532
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
564
+ <svg
565
+ xmlns="http://www.w3.org/2000/svg"
566
+ width="18"
567
+ height="18"
568
+ viewBox="0 0 24 24"
569
+ fill="none"
570
+ stroke="currentColor"
571
+ strokeWidth="2"
572
+ strokeLinecap="round"
573
+ strokeLinejoin="round"
574
+ >
533
575
  <path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
534
576
  </svg>
535
577
  </div>
@@ -537,13 +579,27 @@ const MessageInput = () => {
537
579
 
538
580
  {showAttachmentOptions && (
539
581
  <div className="attachment-options">
540
- <button
541
- type="button"
542
- onClick={() => handleFileSelect('image')}
543
- >
582
+ <button type="button" onClick={() => handleFileSelect("image")}>
544
583
  <div className="icon">
545
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
546
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
584
+ <svg
585
+ xmlns="http://www.w3.org/2000/svg"
586
+ width="18"
587
+ height="18"
588
+ viewBox="0 0 24 24"
589
+ fill="none"
590
+ stroke="currentColor"
591
+ strokeWidth="2"
592
+ strokeLinecap="round"
593
+ strokeLinejoin="round"
594
+ >
595
+ <rect
596
+ x="3"
597
+ y="3"
598
+ width="18"
599
+ height="18"
600
+ rx="2"
601
+ ry="2"
602
+ ></rect>
547
603
  <circle cx="8.5" cy="8.5" r="1.5"></circle>
548
604
  <polyline points="21 15 16 10 5 21"></polyline>
549
605
  </svg>
@@ -552,10 +608,20 @@ const MessageInput = () => {
552
608
  </button>
553
609
  <button
554
610
  type="button"
555
- onClick={() => handleFileSelect('document')}
611
+ onClick={() => handleFileSelect("document")}
556
612
  >
557
613
  <div className="icon">
558
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
614
+ <svg
615
+ xmlns="http://www.w3.org/2000/svg"
616
+ width="18"
617
+ height="18"
618
+ viewBox="0 0 24 24"
619
+ fill="none"
620
+ stroke="currentColor"
621
+ strokeWidth="2"
622
+ strokeLinecap="round"
623
+ strokeLinejoin="round"
624
+ >
559
625
  <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
560
626
  <polyline points="14 2 14 8 20 8"></polyline>
561
627
  <line x1="16" y1="13" x2="8" y2="13"></line>
@@ -573,36 +639,55 @@ const MessageInput = () => {
573
639
  className="chatMessageInput"
574
640
  placeholder="Send a message"
575
641
  value={message}
576
- // onChange={(e) => {
577
- // setMessage(e.target.value)
578
- // autoResizeTextarea(e.target)
579
- // }}
580
642
  onChange={handleChange}
581
643
  rows={1}
582
644
  style={{ resize: "none" }}
583
645
  onKeyDown={(e) => {
584
646
  if (e.key === "Enter" && !e.shiftKey) {
585
- e.preventDefault()
586
- handleSubmit(e)
647
+ e.preventDefault();
648
+ handleSubmit(e);
587
649
  }
588
650
  }}
589
651
  />
590
652
 
591
- <button type="submit" className="chatMessageInputSubmit" disabled={!!inputError || isSending}>
592
- <img width={10} height={10} src={paperplane} alt="send" />
653
+ <button
654
+ type="submit"
655
+ className="chatMessageInputSubmit"
656
+ disabled={!!inputError || isSending}
657
+ >
658
+ <svg
659
+ xmlns="http://www.w3.org/2000/svg"
660
+ className="icon"
661
+ width="14"
662
+ height="14"
663
+ viewBox="0 0 24 24"
664
+ fill="none"
665
+ stroke="#ffffff"
666
+ stroke-width="2"
667
+ stroke-linecap="round"
668
+ stroke-linejoin="round"
669
+ >
670
+ <line x1="22" y1="2" x2="11" y2="13"></line>
671
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
672
+ </svg>
593
673
  </button>
594
674
  </div>
595
675
 
596
- {typingUser && typingUser !== userId && typingUser === selectedConversation?.participantDetails?._id && !isSending && (
597
- <div className="typingIndicator">
598
- <div className="typing-loader">
599
- <div className="ball" />
600
- <div className="ball" />
601
- <div className="ball" />
602
- typing
676
+ {typingUser &&
677
+ typingUser !== userId &&
678
+ typingUser ===
679
+ (!Array.isArray(selectedConversation?.participantDetails) &&
680
+ selectedConversation?.participantDetails?._id) &&
681
+ !isSending && (
682
+ <div className="typingIndicator">
683
+ <div className="typing-loader">
684
+ <div className="ball" />
685
+ <div className="ball" />
686
+ <div className="ball" />
687
+ typing
688
+ </div>
603
689
  </div>
604
- </div>
605
- )}
690
+ )}
606
691
  </form>
607
692
  </div>
608
693
  );