@pubuduth-aplicy/chat-ui 2.1.50 → 2.1.52

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,23 +1,51 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import React, { useCallback, useEffect, useState } from "react";
2
+ import React, { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { useMessageMutation } from "../../hooks/mutations/useSendMessage";
4
4
  import { useChatContext } from "../../providers/ChatProvider";
5
5
  import useChatUIStore from "../../stores/Zustant";
6
6
  import paperplane from "../../assets/icons8-send-50.png";
7
7
  // import { PaperPlaneRight } from '@phosphor-icons/react'; // Assuming you're using icons from Phosphor Icons library
8
8
  // import useSendMessage from '../../hooks/useSendMessage'; // Importing the useSendMessage hook
9
+ import { FilePreview, FileType } from "../common/FilePreview";
10
+ import { getApiClient } from "../../lib/api/apiClient";
11
+ import { MessageStatus } from "../../types/type";
12
+ const MAX_FILE_SIZE_MB = 25; // 10MB max file size
13
+ const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp','video/mp4', 'video/webm', 'video/ogg'];
14
+ const ACCEPTED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg'];
15
+ const ACCEPTED_DOCUMENT_TYPES = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain'];
16
+
17
+ interface Attachment {
18
+ file: File;
19
+ type: FileType;
20
+ previewUrl: string;
21
+ uploadProgress?: number;
22
+ uploadError?: string;
23
+ }
9
24
 
10
25
  const MessageInput = () => {
26
+ const apiClient = getApiClient();
11
27
  const { socket } = useChatContext();
12
28
  const { userId } = useChatContext();
13
- const { selectedConversation } = useChatUIStore();
14
- const [message, setMessage] = useState(""); // State for storing the message input
15
- // const { mutate: sendMessage } = useMessageMutation();
16
- const mutation = useMessageMutation(); // useMutation hook to send message
29
+ const { selectedConversation,setMessages } = useChatUIStore();
30
+ const [message, setMessage] = useState("");
31
+ const mutation = useMessageMutation();
17
32
  const [typingUser, setTypingUser] = useState<string | null>(null);
18
33
  const [isSending, setIsSending] = useState(false);
19
34
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
- const [isTyping, setIsTyping] = useState(false); // Track if the user is typing
35
+ const [isTyping, setIsTyping] = useState(false);
36
+ const [attachments, setAttachments] = useState<Attachment[]>([]);
37
+ const [showAttachmentOptions, setShowAttachmentOptions] = useState(false);
38
+ const fileInputRef = useRef<HTMLInputElement>(null);
39
+ const attachmentsRef = useRef<Attachment[]>([]);
40
+ const [tempMessageId, setTempMessageId] = useState<string | null>(null);
41
+
42
+ const generateTempId = () => `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
43
+
44
+ // Function to auto-resize the textarea
45
+ const autoResizeTextarea = (element: HTMLTextAreaElement) => {
46
+ element.style.height = "auto"
47
+ element.style.height = Math.min(150, element.scrollHeight) + "px"
48
+ }
21
49
 
22
50
  useEffect(() => {
23
51
  if (selectedConversation?._id) {
@@ -71,58 +99,398 @@ const MessageInput = () => {
71
99
  };
72
100
  }, [socket, selectedConversation?._id]);
73
101
 
102
+ const getFileType = (mimeType: string): FileType | null => {
103
+ if (ACCEPTED_IMAGE_TYPES.includes(mimeType)) return 'image';
104
+ if (ACCEPTED_VIDEO_TYPES.includes(mimeType)) return 'video';
105
+ if (ACCEPTED_DOCUMENT_TYPES.includes(mimeType)) return 'document';
106
+ return null;
107
+ };
108
+
109
+ const uploadToS3 = async (file: File, onProgress?:(progress:number)=> void): Promise<{ url: string, name: string, size: number, type: FileType }> => {
110
+ const response = await apiClient.post('api/chat/generatePresignedUrl', {
111
+ fileName: file.name,
112
+ fileType: file.type,
113
+ }
114
+ );
115
+
116
+ const { signedUrl, fileUrl } = await response.data;
117
+
118
+ // const uploadResponse = await fetch(signedUrl, {
119
+ // method: 'PUT',
120
+ // body: file,
121
+ // headers: {
122
+ // 'Content-Type': file.type,
123
+ // // 'x-amz-acl': 'public-read'
124
+ // },
125
+ // });
126
+
127
+ const xhr = new XMLHttpRequest();
128
+ xhr.open('PUT', signedUrl, true);
129
+ xhr.setRequestHeader('Content-Type', file.type);
130
+
131
+ return new Promise((resolve, reject) => {
132
+ xhr.upload.onprogress = (event) => {
133
+ if (event.lengthComputable && onProgress) {
134
+ const progress = Math.round((event.loaded / event.total) * 100);
135
+ onProgress(progress);
136
+ }
137
+ };
138
+
139
+ xhr.onload = () => {
140
+ if (xhr.status >= 200 && xhr.status < 300) {
141
+ resolve({
142
+ url: fileUrl,
143
+ name: file.name,
144
+ size: file.size,
145
+ type: getFileType(file.type),
146
+ });
147
+ } else {
148
+ reject(new Error('Upload failed'));
149
+ }
150
+ };
151
+
152
+ xhr.onerror = () => reject(new Error('Upload failed'));
153
+ xhr.send(file);
154
+ });
155
+
156
+ // if (!uploadResponse.ok) {
157
+ // throw new Error('Upload failed');
158
+ // }
159
+
160
+ // return {
161
+ // url: fileUrl,
162
+ // name: file.name,
163
+ // size: file.size,
164
+ // type: getFileType(file.type),
165
+ // };
166
+ };
167
+
74
168
  const handleSubmit = useCallback(
75
169
  async (e: any) => {
76
170
  e.preventDefault();
77
- if (!message || isSending) return;
78
-
171
+ if (!message && attachmentsRef.current.length === 0 || isSending) return;
79
172
  setIsSending(true);
173
+
174
+ const tempId = generateTempId();
175
+ setTempMessageId(tempId);
176
+
177
+ const optimisticMessage = {
178
+ _id: tempId,
179
+ text: message, // Added text property to match the expected type
180
+ message: message,
181
+ senderId: userId,
182
+ status: 'sending' as MessageStatus,
183
+ createdAt: new Date().toISOString(),
184
+ media: attachmentsRef.current.map(att => ({
185
+ type: att.type,
186
+ url: att.previewUrl,
187
+ name: att.file.name,
188
+ size: att.file.size,
189
+ uploadProgress: 0,
190
+ uploadError: "",
191
+ })),
192
+ isUploading: true,
193
+ isOptimistic: true
194
+ };
195
+
196
+ setMessages(prev => [...prev, optimisticMessage]);
197
+
80
198
  try {
81
- console.log("📤 Sending message:", message);
199
+
200
+ const uploadedFiles = await Promise.all(
201
+ attachmentsRef.current.map(async (attachment,index) => {
202
+ try {
203
+ const result = await uploadToS3(attachment.file,(progress)=>{
204
+ setMessages(prev => prev.map(msg =>{
205
+ if(msg._id === tempId){
206
+ const updatedMedia = [...msg.media!];
207
+ updatedMedia[index] = {
208
+ ...updatedMedia[index],
209
+ uploadProgress: progress
210
+ };
211
+ return {
212
+ ...msg,
213
+ media: updatedMedia
214
+ };
215
+ }
216
+ return msg;
217
+ }))
218
+ });
219
+ console.log("Uploaded file:", result);
220
+ return result;
221
+ // return {
222
+ // type: attachment.type,
223
+ // url: result.url,
224
+ // name: result.name,
225
+ // size: result.size
226
+ // };
227
+ } catch (error) {
228
+ console.error(`Error uploading file ${attachment.file.name}:`, error);
229
+ setMessages(prev => prev.map(msg => {
230
+ if(msg._id === tempId){
231
+ const updatedMedia = [...msg.media!];
232
+ updatedMedia[index] = {
233
+ ...updatedMedia[index],
234
+ uploadError: "Upload failed"
235
+ };
236
+ return {
237
+ ...msg,
238
+ media: updatedMedia
239
+ };
240
+ }
241
+ return msg;
242
+ }))
243
+ return null;
244
+ }
245
+ })
246
+ );
247
+
248
+
249
+ const successfulUploads = uploadedFiles.filter(file => file !== null);
250
+
251
+ console.log("📤 Sending message:", successfulUploads);
82
252
  mutation.mutate({
83
253
  chatId: selectedConversation?.participantDetails._id,
84
254
  senderId: userId,
85
255
  message,
256
+ attachments: successfulUploads,
86
257
  }, {
87
258
  onSuccess: (data) => {
88
259
  console.log('Response from sendMessage:', data);
89
- // After successfully sending the message, emit the socket event
260
+
90
261
  socket.emit("sendMessage", {
91
262
  chatId: selectedConversation?._id,
92
263
  message,
93
264
  messageId: data[1]._id,
265
+ attachments: successfulUploads,
94
266
  senderId: userId,
95
267
  receiverId: selectedConversation?.participantDetails._id,
96
268
  });
269
+ setMessages(prev => {
270
+ console.log("Removing optimistic message:", prev);
271
+
272
+ // Definitely remove the optimistic message
273
+ const filtered = prev.filter(msg => msg._id !== tempMessageId);
274
+ // Add the real message from server
275
+ console.log("Adding real message:", filtered);
276
+
277
+ return [...filtered, {
278
+ ...data[1],
279
+ isUploading: false,
280
+ isOptimistic: false}
281
+ ];
282
+ });
97
283
  },
98
284
  onError: (error) => {
99
285
  console.error("❌ Error in sending message:", error);
286
+ setMessages(prev => prev.map(msg =>
287
+ msg._id === tempId ? { ...msg, status: 'failed' } : msg
288
+ ));
100
289
  },
101
290
  });
102
291
 
103
292
  } catch (error) {
104
293
  console.error("❌ Error sending message:", error);
294
+ setMessages(prev => prev.map(msg =>
295
+ msg._id === tempId ? { ...msg, status: 'failed' } : msg
296
+ ));
105
297
  } finally {
106
298
  setIsSending(false);
107
299
  setMessage("");
300
+ setAttachments([]);
301
+ setTempMessageId(null);
108
302
  }
109
303
  },
110
304
  [message, selectedConversation, userId, isSending]
111
305
  );
112
306
 
307
+ useEffect(() => {
308
+ return () => {
309
+ attachments.forEach(attachment => {
310
+ URL.revokeObjectURL(attachment.previewUrl);
311
+ });
312
+ };
313
+ }, [attachments]);
314
+
315
+ const handleAttachmentClick = () => {
316
+ setShowAttachmentOptions(!showAttachmentOptions);
317
+ };
318
+
319
+ const handleFileSelect = (type: FileType) => {
320
+ if (fileInputRef.current) {
321
+ fileInputRef.current.accept = getAcceptString(type);
322
+ fileInputRef.current.click();
323
+ }
324
+ setShowAttachmentOptions(false);
325
+ };
326
+
327
+ const getAcceptString = (type: FileType): string => {
328
+ switch (type) {
329
+ case 'image':
330
+ return ACCEPTED_IMAGE_TYPES.join(',');
331
+ case 'video':
332
+ return ACCEPTED_VIDEO_TYPES.join(',');
333
+ case 'document':
334
+ return ACCEPTED_DOCUMENT_TYPES.join(',');
335
+ default:
336
+ return '*';
337
+ }
338
+ };
339
+
340
+
341
+ useEffect(() => {
342
+ attachmentsRef.current = attachments;
343
+ }, [attachments]);
344
+
345
+ const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
346
+ const files = e.target.files;
347
+ if (!files || files.length === 0) return;
348
+
349
+ const newAttachments: Attachment[] = [];
350
+
351
+ for (let i = 0; i < files.length; i++) {
352
+ const file = files[i];
353
+ const fileType = getFileType(file.type);
354
+
355
+ if (!fileType) {
356
+ console.error(`Unsupported file type: ${file.type}`);
357
+ continue;
358
+ }
359
+
360
+ if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
361
+ console.error(`File too large: ${file.name}`);
362
+ continue;
363
+ }
364
+
365
+
366
+ const previewUrl = fileType === 'document'
367
+ ? URL.createObjectURL(new Blob([''], { type: 'application/pdf' })) // Placeholder for documents
368
+ : URL.createObjectURL(file);
369
+
370
+ newAttachments.push({
371
+ file,
372
+ type: fileType,
373
+ previewUrl
374
+ });
375
+ }
376
+
377
+ setAttachments(prev => [...prev, ...newAttachments]);
378
+ if (fileInputRef.current) {
379
+ fileInputRef.current.value = '';
380
+ }
381
+ };
382
+
383
+
384
+ const removeAttachment = (index: number) => {
385
+ setAttachments(prev => {
386
+ const newAttachments = [...prev];
387
+ URL.revokeObjectURL(newAttachments[index].previewUrl);
388
+ newAttachments.splice(index, 1);
389
+ return newAttachments;
390
+ });
391
+ };
392
+
113
393
  return (
394
+ <div className="message-input-container">
395
+ {/* Preview area for attachments */}
396
+ {attachments.length > 0 && (
397
+ <div className="attachments-preview">
398
+ {attachments.map((attachment, index) => (
399
+ <FilePreview
400
+ key={index}
401
+ file={attachment.file}
402
+ type={attachment.type}
403
+ previewUrl={attachment.previewUrl}
404
+ onRemove={() => removeAttachment(index)}
405
+ />
406
+ ))}
407
+ </div>
408
+ )}
409
+
114
410
  <form className="chatMessageInputform" onSubmit={handleSubmit}>
115
411
  <div className="chatMessageInputdiv">
412
+ {/* Hidden file input */}
116
413
  <input
117
- type="text"
414
+ type="file"
415
+ ref={fileInputRef}
416
+ style={{ display: 'none' }}
417
+ onChange={handleFileChange}
418
+ multiple
419
+ />
420
+
421
+ {/* Attachment button and options */}
422
+ <div className="attachment-container" style={{ position: 'relative' }}>
423
+ <button
424
+ type="button"
425
+ className="attachment-button"
426
+ onClick={handleAttachmentClick}
427
+ style={{
428
+ background: 'none',
429
+ border: 'none',
430
+ cursor: 'pointer',
431
+ padding: '8px',
432
+ }}
433
+ >
434
+ <div className="attachment-icon">
435
+ <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">
436
+ <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>
437
+ </svg>
438
+ </div>
439
+ </button>
440
+
441
+ {showAttachmentOptions && (
442
+ <div className="attachment-options">
443
+ <button
444
+ type="button"
445
+ onClick={() => handleFileSelect('image')}
446
+ >
447
+ <div className="icon">
448
+ <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">
449
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
450
+ <circle cx="8.5" cy="8.5" r="1.5"></circle>
451
+ <polyline points="21 15 16 10 5 21"></polyline>
452
+ </svg>
453
+ </div>
454
+ <span>Photos & videos</span>
455
+ </button>
456
+ <button
457
+ type="button"
458
+ onClick={() => handleFileSelect('document')}
459
+ >
460
+ <div className="icon">
461
+ <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">
462
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
463
+ <polyline points="14 2 14 8 20 8"></polyline>
464
+ <line x1="16" y1="13" x2="8" y2="13"></line>
465
+ <line x1="16" y1="17" x2="8" y2="17"></line>
466
+ <polyline points="10 9 9 9 8 9"></polyline>
467
+ </svg>
468
+ </div>
469
+ <span>Document</span>
470
+ </button>
471
+ </div>
472
+ )}
473
+ </div>
474
+
475
+ <textarea
118
476
  className="chatMessageInput"
119
477
  placeholder="Send a message"
120
478
  value={message}
121
- onChange={(e) => setMessage(e.target.value)} // Update message state as the user types
479
+ onChange={(e) => {
480
+ setMessage(e.target.value)
481
+ autoResizeTextarea(e.target)
482
+ }}
483
+ rows={1}
484
+ style={{ resize: "none" }}
485
+ onKeyDown={(e) => {
486
+ if (e.key === "Enter" && !e.shiftKey) {
487
+ e.preventDefault()
488
+ handleSubmit(e)
489
+ }
490
+ }}
122
491
  />
123
- <button type="submit" className="chatMessageInputSubmit">
492
+ <button type="submit" className="chatMessageInputSubmit" disabled={isSending}>
124
493
  <img width={10} height={10} src={paperplane} alt="send" />
125
- {/* {loading ? <div className='loading loading-spinner'></div> : <PaperPlaneRight />} Show loading spinner if loading */}
126
494
  </button>
127
495
  </div>
128
496
 
@@ -136,8 +504,8 @@ const MessageInput = () => {
136
504
  </div>
137
505
  </div>
138
506
  )}
139
-
140
507
  </form>
508
+ </div>
141
509
  );
142
510
  };
143
511
 
@@ -51,7 +51,22 @@ const Messages = () => {
51
51
  const handleNewMessage = (newMessage: any) => {
52
52
  newMessage.shouldShake = true;
53
53
  console.log("📩 New message received:", newMessage);
54
- setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
54
+ // setMessages((prevMessages) => [...prevMessages, newMessage[1]]);
55
+ setMessages(prevMessages => {
56
+ console.log("prevMessages", prevMessages);
57
+
58
+ const isDuplicate = prevMessages.some(msg =>
59
+ msg._id === newMessage[1]._id ||
60
+ (msg.isOptimistic && msg.senderId === userId && msg.message === newMessage[1].message)
61
+ );
62
+ console.log('isDuplicate', isDuplicate);
63
+
64
+ if (isDuplicate) {
65
+ return prevMessages;
66
+ }
67
+
68
+ return [...prevMessages, newMessage[1]];
69
+ });
55
70
  };
56
71
 
57
72
  // const handleNewMessage = (newMessage: any) => {
@@ -72,7 +87,7 @@ const Messages = () => {
72
87
  status: string;
73
88
  }) => {
74
89
  setMessages((prev) =>
75
- prev.map((msg) => (msg.id === messageId ? { ...msg, status } : msg))
90
+ prev.map((msg) => (msg._id === messageId ? { ...msg, status } : msg))
76
91
  );
77
92
  };
78
93
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
1
2
  // import { useSocketContext } from "../../context/SocketContext";
2
3
  // import useConversation from "../../zustand/useConversation";
3
4
 
@@ -76,7 +77,7 @@ const Conversation = ({ conversation, lastIdx }: ConversationProps) => {
76
77
  </div>
77
78
  </div>
78
79
 
79
- {!lastIdx && <div className="divider my-0 py-0 h-1" />}
80
+ {/* {!lastIdx && <div className="divider my-0 py-0 h-1" />} */}
80
81
  </>
81
82
  );
82
83
  };
@@ -12,7 +12,29 @@ const Conversations = () => {
12
12
  // const { loading, conversations } = useGetConversations();
13
13
  return (
14
14
  <div className="chatSidebarConversations">
15
- <h2 className="text-lg font-semibold text-gray-700">All Chats</h2>
15
+ <h2 className="text-lg font-semibold text-gray-700">All Messages</h2>
16
+ {(!conversations || conversations.length === 0) && (
17
+ <div className="flex flex-col items-center justify-center p-8 text-center">
18
+ <div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-100 mb-4">
19
+ <svg
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ width="32"
22
+ height="32"
23
+ viewBox="0 0 24 24"
24
+ fill="none"
25
+ stroke="currentColor"
26
+ strokeWidth="2"
27
+ strokeLinecap="round"
28
+ strokeLinejoin="round"
29
+ className="text-gray-500"
30
+ >
31
+ <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
32
+ </svg>
33
+ </div>
34
+ <h3 className="text-xl font-semibold mb-1">No Conversations</h3>
35
+ <p className="text-gray-500 text-sm mb-4">There are no conversations under &quot;All messages&quot;</p>
36
+ </div>
37
+ )}
16
38
  {conversations?.map((conversation: any, idx: any) => (
17
39
  <Conversation
18
40
  key={conversation._id}
@@ -21,9 +43,11 @@ const Conversations = () => {
21
43
  />
22
44
  ))}
23
45
 
46
+
47
+
24
48
  {/* {loading ? <span className='loading loading-spinner mx-auto'></span> : null} */}
25
49
  </div>
26
50
  );
27
51
  };
28
52
 
29
- export default Conversations;
53
+ export default Conversations;
@@ -22,12 +22,20 @@ const {userId} =useChatContext()
22
22
  return
23
23
  }
24
24
 
25
- const conversation = data?.find((c: { _id: string; participantDetails: { username: string } }) =>
25
+ const conversation = data?.find((c: { _id: string; participantDetails: { username: string; firstname?: string; idpic?: string } }) =>
26
26
  c.participantDetails.username.toLowerCase().includes(search.toLowerCase())
27
27
  );
28
28
 
29
29
  if (conversation) {
30
- setSelectedConversation(conversation);
30
+ const updatedConversation = {
31
+ ...conversation,
32
+ participantDetails: {
33
+ ...conversation.participantDetails,
34
+ firstname: conversation.participantDetails.username || "Unknown",
35
+ idpic: conversation.participantDetails.profilePic || "default-idpic.png",
36
+ },
37
+ };
38
+ setSelectedConversation(updatedConversation);
31
39
  setSearch("");
32
40
  }
33
41
  console.error("No such user found!");
@@ -3,11 +3,10 @@ import SearchInput from "./SearchInput";
3
3
 
4
4
  export const Sidebar = () => {
5
5
  return (
6
- <div className='chatSidebarMain'>
6
+ <div className=''>
7
7
  <SearchInput />
8
8
  <div className='divider px-3'></div>
9
9
  <Conversations />
10
-
11
10
  </div>
12
11
  )
13
12
  }
@@ -1,11 +1,15 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { FileType } from "../components/common/FilePreview";
2
3
  import { getApiClient } from "../lib/api/apiClient";
3
4
  import { Path } from "../lib/api/endpoint";
4
5
 
5
- export const sendMessage = async ({ chatId,senderId, message }: { chatId: any; senderId:any; message: string }) => {
6
+ export const sendMessage = async ({ chatId,senderId, message,attachments }: { chatId: any; senderId:any; message: string,attachments: { type: FileType; url: string; name: string; size: number; }[] }) => {
6
7
  const apiClient = getApiClient();
8
+ console.log("sendMessage", chatId, senderId, message, attachments); // Log the parameters
9
+
7
10
  const response = await apiClient.post(`${Path.sendmessage}/${chatId}/${senderId}`, {
8
- message:message
11
+ message:message,
12
+ attachments:attachments
9
13
  })
10
14
  return response.data;
11
15
  };
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { FileType } from "../components/common/FilePreview";
2
3
  import { create } from "zustand";
3
4
 
4
5
  interface ChatUIState {
@@ -16,7 +17,23 @@ interface ChatUIState {
16
17
  setSelectedConversation: (
17
18
  selectedConversation: ChatUIState["selectedConversation"]
18
19
  ) => void;
19
- messages: { id: string; text: string; sender: string; status: string }[];
20
+ messages: {
21
+ _id: string;
22
+ text: string;
23
+ senderId: string;
24
+ status: string ;
25
+ isOptimistic:boolean;
26
+ message:string;
27
+ createdAt: string;
28
+ media: {
29
+ type: FileType;
30
+ url: string;
31
+ name: string;
32
+ size: number;
33
+ uploadProgress: number;
34
+ uploadError:string
35
+ }[];
36
+ isUploading: boolean;}[];
20
37
  setMessages: (messages: ChatUIState["messages"] | ((prev: ChatUIState["messages"]) => ChatUIState["messages"])) => void;
21
38
  updateMessageStatus: (messageId: string, status: string) => void;
22
39
  toggleChat: () => void;
@@ -38,7 +55,7 @@ const useChatUIStore = create<ChatUIState>((set) => ({
38
55
  updateMessageStatus: (messageId, status) =>
39
56
  set((state) => ({
40
57
  messages: state.messages.map((msg) =>
41
- msg.id === messageId ? { ...msg, status } : msg
58
+ msg._id === messageId ? { ...msg, status } : msg
42
59
  ),
43
60
  })),
44
61
  setSelectedConversation: (selectedConversation) => set({ selectedConversation }),