@pubuduth-aplicy/chat-ui 2.1.70 → 2.1.71

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubuduth-aplicy/chat-ui",
3
- "version": "2.1.70",
3
+ "version": "2.1.71",
4
4
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -5,32 +5,32 @@ import { useChatContext } from "../../providers/ChatProvider";
5
5
  import { FileType } from "../common/FilePreview";
6
6
  import { getChatConfig } from "../../Chat.config";
7
7
  import { Path } from "../../lib/api/endpoint";
8
- import { MoreHorizontal, Pencil, Trash2 } from "lucide-react"
8
+ import { MoreHorizontal, Pencil, Trash2 } from "lucide-react";
9
9
  import { useEditMessageMutation } from "../../hooks/mutations/useEditMessage";
10
- import { useDeleteMessageMutation } from "../../hooks/mutations/useDeleteMessage"
10
+ import { useDeleteMessageMutation } from "../../hooks/mutations/useDeleteMessage";
11
11
  import { useEffect, useRef, useState } from "react";
12
12
 
13
-
14
13
  interface MessageProps {
15
- message: {
16
- _id?: string;
17
- senderId: string;
18
- message: string;
19
- status: MessageStatus;
20
- createdAt: any;
14
+ message: {
15
+ _id?: string
16
+ senderId: string
17
+ message: string
18
+ status: MessageStatus
19
+ createdAt: any
20
+ updatedAt: any
21
21
  media?: {
22
- type: FileType;
23
- url: string;
24
- name: string;
25
- size: number;
26
- uploadProgress?: number;
27
- uploadError: string | null;
28
- }[];
29
- isUploading?: boolean;
22
+ type: FileType
23
+ url: string
24
+ name: string
25
+ size: number
26
+ uploadProgress?: number
27
+ uploadError: string | null
28
+ }[]
29
+ isUploading?: boolean
30
30
  isEdited?: boolean
31
31
  isDeleted?: boolean
32
- onEdit?: (messageId: string, newMessage: string) => void
33
- onDelete?: (messageId: string) => void
32
+ onEdit?: (messageId: string, newMessage: string) => void
33
+ onDelete?: (messageId: string) => void
34
34
  type?: 'user' | 'system' | 'system-completion';
35
35
  meta?: {
36
36
  bookingDetails?: {
@@ -41,15 +41,15 @@ interface MessageProps {
41
41
  // Add other booking details as needed
42
42
  };
43
43
  reviewLink?: string;
44
- };
45
- };
44
+ }
45
+ }
46
46
  }
47
47
 
48
48
  const Message = ({ message }: MessageProps) => {
49
49
  const { userId } = useChatContext();
50
- const { apiUrl } = getChatConfig();
50
+ const { apiUrl } = getChatConfig();
51
51
 
52
- if (message.type === 'system') {
52
+ if (message.type === "system") {
53
53
  return (
54
54
  <div className="system-message booking-details">
55
55
  <h4>Booking Confirmed</h4>
@@ -63,12 +63,14 @@ const Message = ({ message }: MessageProps) => {
63
63
  );
64
64
  }
65
65
 
66
- if (message.type === 'system-completion') {
66
+ if (message.type === "system-completion") {
67
67
  return (
68
68
  <div className="system-message completion-notice">
69
69
  <p>Service completed successfully!</p>
70
- <button
71
- onClick={() => window.location.href = message.meta?.reviewLink || '#'}
70
+ <button
71
+ onClick={() =>
72
+ (window.location.href = message.meta?.reviewLink || "#")
73
+ }
72
74
  className="review-button"
73
75
  >
74
76
  Leave a Review
@@ -77,21 +79,19 @@ const Message = ({ message }: MessageProps) => {
77
79
  );
78
80
  }
79
81
 
80
-
81
-
82
82
  const fromMe = message.senderId === userId;
83
83
  const timestamp = fromMe ? "timestamp_outgoing" : "timestamp_incomeing";
84
84
  const alignItems = fromMe ? "outgoing" : "incoming";
85
85
  const [localStatus, setLocalStatus] = useState(message.status);
86
86
 
87
- const [showOptions, setShowOptions] = useState(false)
88
- const [showDeleteOption, setShowDeleteOption] = useState(false)
89
- const editInputRef = useRef<HTMLInputElement>(null)
90
- const optionsRef = useRef<HTMLDivElement>(null)
91
- const { mutate: editMessage} = useEditMessageMutation();
92
- const [editedMessage, setEditedMessage] = useState('');
87
+ const [showOptions, setShowOptions] = useState(false);
88
+ const [showDeleteOption, setShowDeleteOption] = useState(false);
89
+ const editInputRef = useRef<HTMLInputElement>(null);
90
+ const optionsRef = useRef<HTMLDivElement>(null);
91
+ const { mutate: editMessage } = useEditMessageMutation();
92
+ const [editedMessage, setEditedMessage] = useState("");
93
93
  const [isEditingMode, setIsEditingMode] = useState(false);
94
- const { mutate: deleteMessage} = useDeleteMessageMutation();
94
+ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutation();
95
95
 
96
96
 
97
97
  useEffect(() => {
@@ -102,9 +102,10 @@ const [showOptions, setShowOptions] = useState(false)
102
102
  // saveAs(url, name);
103
103
  // };
104
104
 
105
- const [downloadingIndex, setDownloadingIndex] = useState<number | null>(null)
106
- const [downloadProgress, setDownloadProgress] = useState<number>(0)
107
- const [downloadController, setDownloadController] = useState<AbortController | null>(null)
105
+ const [downloadingIndex, setDownloadingIndex] = useState<number | null>(null);
106
+ const [downloadProgress, setDownloadProgress] = useState<number>(0);
107
+ const [downloadController, setDownloadController] =
108
+ useState<AbortController | null>(null);
108
109
 
109
110
  const getStatusIcon = () => {
110
111
  if (!fromMe) return null;
@@ -121,31 +122,76 @@ const [showOptions, setShowOptions] = useState(false)
121
122
  case "sending":
122
123
  return <span className="message-status sending">🔄</span>;
123
124
  case "sent":
124
- return <span className="message-status sent">
125
- <svg viewBox="0 0 12 11" height="11" width="16" preserveAspectRatio="xMidYMid meet" fill="none"><title>msg-check</title><path d="M11.1549 0.652832C11.0745 0.585124 10.9729 0.55127 10.8502 0.55127C10.7021 0.55127 10.5751 0.610514 10.4693 0.729004L4.28038 8.36523L1.87461 6.09277C1.8323 6.04622 1.78151 6.01025 1.72227 5.98486C1.66303 5.95947 1.60166 5.94678 1.53819 5.94678C1.407 5.94678 1.29275 5.99544 1.19541 6.09277L0.884379 6.40381C0.79128 6.49268 0.744731 6.60482 0.744731 6.74023C0.744731 6.87565 0.79128 6.98991 0.884379 7.08301L3.88047 10.0791C4.02859 10.2145 4.19574 10.2822 4.38194 10.2822C4.48773 10.2822 4.58929 10.259 4.68663 10.2124C4.78396 10.1659 4.86436 10.1003 4.92784 10.0156L11.5738 1.59863C11.6458 1.5013 11.6817 1.40186 11.6817 1.30029C11.6817 1.14372 11.6183 1.01888 11.4913 0.925781L11.1549 0.652832Z" fill="currentcolor"></path></svg>
126
- </span>;
125
+ return (
126
+ <span className="message-status sent">
127
+ <svg
128
+ viewBox="0 0 12 11"
129
+ height="11"
130
+ width="16"
131
+ preserveAspectRatio="xMidYMid meet"
132
+ fill="none"
133
+ >
134
+ <title>msg-check</title>
135
+ <path
136
+ d="M11.1549 0.652832C11.0745 0.585124 10.9729 0.55127 10.8502 0.55127C10.7021 0.55127 10.5751 0.610514 10.4693 0.729004L4.28038 8.36523L1.87461 6.09277C1.8323 6.04622 1.78151 6.01025 1.72227 5.98486C1.66303 5.95947 1.60166 5.94678 1.53819 5.94678C1.407 5.94678 1.29275 5.99544 1.19541 6.09277L0.884379 6.40381C0.79128 6.49268 0.744731 6.60482 0.744731 6.74023C0.744731 6.87565 0.79128 6.98991 0.884379 7.08301L3.88047 10.0791C4.02859 10.2145 4.19574 10.2822 4.38194 10.2822C4.48773 10.2822 4.58929 10.259 4.68663 10.2124C4.78396 10.1659 4.86436 10.1003 4.92784 10.0156L11.5738 1.59863C11.6458 1.5013 11.6817 1.40186 11.6817 1.30029C11.6817 1.14372 11.6183 1.01888 11.4913 0.925781L11.1549 0.652832Z"
137
+ fill="currentcolor"
138
+ ></path>
139
+ </svg>
140
+ </span>
141
+ );
127
142
  case "delivered":
128
- return <span className="message-status delivered">
129
- <svg viewBox="0 0 16 11" height="11" width="16" preserveAspectRatio="xMidYMid meet" fill="none"><title>msg-dblcheck</title><path d="M11.0714 0.652832C10.991 0.585124 10.8894 0.55127 10.7667 0.55127C10.6186 0.55127 10.4916 0.610514 10.3858 0.729004L4.19688 8.36523L1.79112 6.09277C1.7488 6.04622 1.69802 6.01025 1.63877 5.98486C1.57953 5.95947 1.51817 5.94678 1.45469 5.94678C1.32351 5.94678 1.20925 5.99544 1.11192 6.09277L0.800883 6.40381C0.707784 6.49268 0.661235 6.60482 0.661235 6.74023C0.661235 6.87565 0.707784 6.98991 0.800883 7.08301L3.79698 10.0791C3.94509 10.2145 4.11224 10.2822 4.29844 10.2822C4.40424 10.2822 4.5058 10.259 4.60313 10.2124C4.70046 10.1659 4.78086 10.1003 4.84434 10.0156L11.4903 1.59863C11.5623 1.5013 11.5982 1.40186 11.5982 1.30029C11.5982 1.14372 11.5348 1.01888 11.4078 0.925781L11.0714 0.652832ZM8.6212 8.32715C8.43077 8.20866 8.2488 8.09017 8.0753 7.97168C7.99489 7.89128 7.8891 7.85107 7.75791 7.85107C7.6098 7.85107 7.4892 7.90397 7.3961 8.00977L7.10411 8.33984C7.01947 8.43717 6.97715 8.54508 6.97715 8.66357C6.97715 8.79476 7.0237 8.90902 7.1168 9.00635L8.1959 10.0791C8.33132 10.2145 8.49636 10.2822 8.69102 10.2822C8.79681 10.2822 8.89838 10.259 8.99571 10.2124C9.09304 10.1659 9.17556 10.1003 9.24327 10.0156L15.8639 1.62402C15.9358 1.53939 15.9718 1.43994 15.9718 1.32568C15.9718 1.1818 15.9125 1.05697 15.794 0.951172L15.4386 0.678223C15.3582 0.610514 15.2587 0.57666 15.1402 0.57666C14.9964 0.57666 14.8715 0.635905 14.7657 0.754395L8.6212 8.32715Z" fill="currentColor"></path></svg>
130
- </span>;
143
+ return (
144
+ <span className="message-status delivered">
145
+ <svg
146
+ viewBox="0 0 16 11"
147
+ height="11"
148
+ width="16"
149
+ preserveAspectRatio="xMidYMid meet"
150
+ fill="none"
151
+ >
152
+ <title>msg-dblcheck</title>
153
+ <path
154
+ d="M11.0714 0.652832C10.991 0.585124 10.8894 0.55127 10.7667 0.55127C10.6186 0.55127 10.4916 0.610514 10.3858 0.729004L4.19688 8.36523L1.79112 6.09277C1.7488 6.04622 1.69802 6.01025 1.63877 5.98486C1.57953 5.95947 1.51817 5.94678 1.45469 5.94678C1.32351 5.94678 1.20925 5.99544 1.11192 6.09277L0.800883 6.40381C0.707784 6.49268 0.661235 6.60482 0.661235 6.74023C0.661235 6.87565 0.707784 6.98991 0.800883 7.08301L3.79698 10.0791C3.94509 10.2145 4.11224 10.2822 4.29844 10.2822C4.40424 10.2822 4.5058 10.259 4.60313 10.2124C4.70046 10.1659 4.78086 10.1003 4.84434 10.0156L11.4903 1.59863C11.5623 1.5013 11.5982 1.40186 11.5982 1.30029C11.5982 1.14372 11.5348 1.01888 11.4078 0.925781L11.0714 0.652832ZM8.6212 8.32715C8.43077 8.20866 8.2488 8.09017 8.0753 7.97168C7.99489 7.89128 7.8891 7.85107 7.75791 7.85107C7.6098 7.85107 7.4892 7.90397 7.3961 8.00977L7.10411 8.33984C7.01947 8.43717 6.97715 8.54508 6.97715 8.66357C6.97715 8.79476 7.0237 8.90902 7.1168 9.00635L8.1959 10.0791C8.33132 10.2145 8.49636 10.2822 8.69102 10.2822C8.79681 10.2822 8.89838 10.259 8.99571 10.2124C9.09304 10.1659 9.17556 10.1003 9.24327 10.0156L15.8639 1.62402C15.9358 1.53939 15.9718 1.43994 15.9718 1.32568C15.9718 1.1818 15.9125 1.05697 15.794 0.951172L15.4386 0.678223C15.3582 0.610514 15.2587 0.57666 15.1402 0.57666C14.9964 0.57666 14.8715 0.635905 14.7657 0.754395L8.6212 8.32715Z"
155
+ fill="currentColor"
156
+ ></path>
157
+ </svg>
158
+ </span>
159
+ );
131
160
  case "read":
132
- return <span className="message-status read">
133
- <svg viewBox="0 0 16 11" height="11" width="16" preserveAspectRatio="xMidYMid meet" fill="none"><title>msg-dblcheck</title><path d="M11.0714 0.652832C10.991 0.585124 10.8894 0.55127 10.7667 0.55127C10.6186 0.55127 10.4916 0.610514 10.3858 0.729004L4.19688 8.36523L1.79112 6.09277C1.7488 6.04622 1.69802 6.01025 1.63877 5.98486C1.57953 5.95947 1.51817 5.94678 1.45469 5.94678C1.32351 5.94678 1.20925 5.99544 1.11192 6.09277L0.800883 6.40381C0.707784 6.49268 0.661235 6.60482 0.661235 6.74023C0.661235 6.87565 0.707784 6.98991 0.800883 7.08301L3.79698 10.0791C3.94509 10.2145 4.11224 10.2822 4.29844 10.2822C4.40424 10.2822 4.5058 10.259 4.60313 10.2124C4.70046 10.1659 4.78086 10.1003 4.84434 10.0156L11.4903 1.59863C11.5623 1.5013 11.5982 1.40186 11.5982 1.30029C11.5982 1.14372 11.5348 1.01888 11.4078 0.925781L11.0714 0.652832ZM8.6212 8.32715C8.43077 8.20866 8.2488 8.09017 8.0753 7.97168C7.99489 7.89128 7.8891 7.85107 7.75791 7.85107C7.6098 7.85107 7.4892 7.90397 7.3961 8.00977L7.10411 8.33984C7.01947 8.43717 6.97715 8.54508 6.97715 8.66357C6.97715 8.79476 7.0237 8.90902 7.1168 9.00635L8.1959 10.0791C8.33132 10.2145 8.49636 10.2822 8.69102 10.2822C8.79681 10.2822 8.89838 10.259 8.99571 10.2124C9.09304 10.1659 9.17556 10.1003 9.24327 10.0156L15.8639 1.62402C15.9358 1.53939 15.9718 1.43994 15.9718 1.32568C15.9718 1.1818 15.9125 1.05697 15.794 0.951172L15.4386 0.678223C15.3582 0.610514 15.2587 0.57666 15.1402 0.57666C14.9964 0.57666 14.8715 0.635905 14.7657 0.754395L8.6212 8.32715Z" fill="currentColor"></path></svg>
134
- </span>;
161
+ return (
162
+ <span className="message-status read">
163
+ <svg
164
+ viewBox="0 0 16 11"
165
+ height="11"
166
+ width="16"
167
+ preserveAspectRatio="xMidYMid meet"
168
+ fill="none"
169
+ >
170
+ <title>msg-dblcheck</title>
171
+ <path
172
+ d="M11.0714 0.652832C10.991 0.585124 10.8894 0.55127 10.7667 0.55127C10.6186 0.55127 10.4916 0.610514 10.3858 0.729004L4.19688 8.36523L1.79112 6.09277C1.7488 6.04622 1.69802 6.01025 1.63877 5.98486C1.57953 5.95947 1.51817 5.94678 1.45469 5.94678C1.32351 5.94678 1.20925 5.99544 1.11192 6.09277L0.800883 6.40381C0.707784 6.49268 0.661235 6.60482 0.661235 6.74023C0.661235 6.87565 0.707784 6.98991 0.800883 7.08301L3.79698 10.0791C3.94509 10.2145 4.11224 10.2822 4.29844 10.2822C4.40424 10.2822 4.5058 10.259 4.60313 10.2124C4.70046 10.1659 4.78086 10.1003 4.84434 10.0156L11.4903 1.59863C11.5623 1.5013 11.5982 1.40186 11.5982 1.30029C11.5982 1.14372 11.5348 1.01888 11.4078 0.925781L11.0714 0.652832ZM8.6212 8.32715C8.43077 8.20866 8.2488 8.09017 8.0753 7.97168C7.99489 7.89128 7.8891 7.85107 7.75791 7.85107C7.6098 7.85107 7.4892 7.90397 7.3961 8.00977L7.10411 8.33984C7.01947 8.43717 6.97715 8.54508 6.97715 8.66357C6.97715 8.79476 7.0237 8.90902 7.1168 9.00635L8.1959 10.0791C8.33132 10.2145 8.49636 10.2822 8.69102 10.2822C8.79681 10.2822 8.89838 10.259 8.99571 10.2124C9.09304 10.1659 9.17556 10.1003 9.24327 10.0156L15.8639 1.62402C15.9358 1.53939 15.9718 1.43994 15.9718 1.32568C15.9718 1.1818 15.9125 1.05697 15.794 0.951172L15.4386 0.678223C15.3582 0.610514 15.2587 0.57666 15.1402 0.57666C14.9964 0.57666 14.8715 0.635905 14.7657 0.754395L8.6212 8.32715Z"
173
+ fill="currentColor"
174
+ ></path>
175
+ </svg>
176
+ </span>
177
+ );
178
+ case "edited":
179
+ return <span className="message-status edited">Edited</span>;
180
+ case "deleted":
181
+ return <span className="message-status deleted">Deleted</span>;
135
182
  default:
136
183
  return null;
137
184
  }
138
185
  };
139
186
 
140
-
141
187
  const cancelDownload = () => {
142
188
  if (downloadController) {
143
- downloadController.abort()
144
- setDownloadingIndex(null)
145
- setDownloadProgress(0)
146
- setDownloadController(null)
189
+ downloadController.abort();
190
+ setDownloadingIndex(null);
191
+ setDownloadProgress(0);
192
+ setDownloadController(null);
147
193
  }
148
- }
194
+ };
149
195
 
150
196
  // const handleDownload = async (url: string, name: string, index: number) => {
151
197
  // setDownloadingIndex(index);
@@ -169,51 +215,53 @@ const [showOptions, setShowOptions] = useState(false)
169
215
  // const renderMedia = () => {
170
216
  // if (!message.media || message.media.length === 0) return null;
171
217
 
172
-
173
218
  const handleDownload = async (url: string, name: string, index: number) => {
174
219
  setDownloadingIndex(index);
175
220
  setDownloadProgress(0);
176
-
221
+
177
222
  const controller = new AbortController();
178
223
  setDownloadController(controller);
179
-
224
+
180
225
  try {
181
226
  // First try to download directly
182
227
  try {
183
- const response = await fetch(`${apiUrl}${Path.apiProxy}?url=${encodeURIComponent(url)}&name=${encodeURIComponent(name)}`,
228
+ const response = await fetch(
229
+ `${apiUrl}${Path.apiProxy}?url=${encodeURIComponent(
230
+ url
231
+ )}&name=${encodeURIComponent(name)}`
184
232
  );
185
-
186
- if (!response.ok) throw new Error('Network response was not ok');
187
-
233
+
234
+ if (!response.ok) throw new Error("Network response was not ok");
235
+
188
236
  const contentLength = response.headers.get("content-length");
189
237
  const total = contentLength ? Number.parseInt(contentLength, 10) : 0;
190
-
238
+
191
239
  const reader = response.body?.getReader();
192
240
  if (!reader) throw new Error("Failed to get response reader");
193
-
241
+
194
242
  let receivedLength = 0;
195
243
  const chunks: Uint8Array[] = [];
196
-
244
+
197
245
  while (true) {
198
246
  const { done, value } = await reader.read();
199
247
  if (done) break;
200
-
248
+
201
249
  chunks.push(value);
202
250
  receivedLength += value.length;
203
-
251
+
204
252
  if (total) {
205
253
  const progress = Math.round((receivedLength / total) * 100);
206
254
  setDownloadProgress(progress);
207
255
  }
208
256
  }
209
-
257
+
210
258
  const chunksAll = new Uint8Array(receivedLength);
211
259
  let position = 0;
212
260
  for (const chunk of chunks) {
213
261
  chunksAll.set(chunk, position);
214
262
  position += chunk.length;
215
263
  }
216
-
264
+
217
265
  const blob = new Blob([chunksAll]);
218
266
  const link = document.createElement("a");
219
267
  link.href = URL.createObjectURL(blob);
@@ -223,10 +271,13 @@ const [showOptions, setShowOptions] = useState(false)
223
271
  document.body.removeChild(link);
224
272
  URL.revokeObjectURL(link.href);
225
273
  } catch (directDownloadError) {
226
- console.log('Direct download failed, trying alternative method', directDownloadError);
227
-
274
+ console.log(
275
+ "Direct download failed, trying alternative method",
276
+ directDownloadError
277
+ );
278
+
228
279
  // Fallback: Open in new tab if download fails
229
- window.open(url, '_blank');
280
+ window.open(url, "_blank");
230
281
  }
231
282
  } catch (error) {
232
283
  if ((error as Error).name === "AbortError") {
@@ -234,10 +285,10 @@ const [showOptions, setShowOptions] = useState(false)
234
285
  } else {
235
286
  console.error("Download failed:", error);
236
287
  // Final fallback - create a temporary link
237
- const link = document.createElement('a');
288
+ const link = document.createElement("a");
238
289
  link.href = url;
239
- link.target = '_blank';
240
- link.rel = 'noopener noreferrer';
290
+ link.target = "_blank";
291
+ link.rel = "noopener noreferrer";
241
292
  document.body.appendChild(link);
242
293
  link.click();
243
294
  document.body.removeChild(link);
@@ -248,11 +299,9 @@ const [showOptions, setShowOptions] = useState(false)
248
299
  setDownloadController(null);
249
300
  }
250
301
  };
251
-
252
302
 
253
-
254
303
  const renderMedia = () => {
255
- if (!message.media || message.media.length === 0) return null
304
+ if (!message.media || message.media.length === 0) return null;
256
305
 
257
306
  return (
258
307
  <div
@@ -341,26 +390,29 @@ const [showOptions, setShowOptions] = useState(false)
341
390
  </div>
342
391
  )}
343
392
  <button
344
- className="download-btn"
345
- onClick={(e) => {
346
- e.stopPropagation()
347
- if (downloadingIndex === index) {
348
- cancelDownload()
349
- } else {
350
- handleDownload(media.url, media.name, index)
351
- }
352
- }}
353
- title={downloadingIndex === index ? "Cancel" : "Download"}
354
- >
355
- {downloadingIndex === index ? (
356
- <div className="download-progress-container">
357
- <div className="download-progress" style={{ width: `${downloadProgress}%` }} />
358
- <span className="cancel-download">✕</span>
359
- </div>
360
- ) : (
361
- "⬇️"
362
- )}
363
- </button>
393
+ className="download-btn"
394
+ onClick={(e) => {
395
+ e.stopPropagation();
396
+ if (downloadingIndex === index) {
397
+ cancelDownload();
398
+ } else {
399
+ handleDownload(media.url, media.name, index);
400
+ }
401
+ }}
402
+ title={downloadingIndex === index ? "Cancel" : "Download"}
403
+ >
404
+ {downloadingIndex === index ? (
405
+ <div className="download-progress-container">
406
+ <div
407
+ className="download-progress"
408
+ style={{ width: `${downloadProgress}%` }}
409
+ />
410
+ <span className="cancel-download">✕</span>
411
+ </div>
412
+ ) : (
413
+ "⬇️"
414
+ )}
415
+ </button>
364
416
  </>
365
417
  )}
366
418
  </div>
@@ -369,129 +421,180 @@ const [showOptions, setShowOptions] = useState(false)
369
421
  );
370
422
  };
371
423
 
372
-
373
- const handleEditClick = () => {
374
- setIsEditingMode(true)
375
- setShowOptions(false)
424
+ const handleEditClick = (message: string) => {
425
+ setEditedMessage(message);
426
+ setIsEditingMode(true);
427
+ setShowOptions(false);
376
428
  setTimeout(() => {
377
- editInputRef.current?.focus()
378
- }, 0)
379
- }
429
+ editInputRef.current?.focus();
430
+ }, 0);
431
+ };
380
432
 
381
433
  const handleSaveEdit = () => {
382
434
  if (message._id && editedMessage.trim() !== message.message) {
383
- editMessage({
384
- messageId: message._id ?? '',
385
- userId: userId, // Using userId from useChatContext
386
- newMessage: editedMessage.trim()
387
- }, {
388
- onSuccess: () => {
389
- setIsEditingMode(false);
390
- // Any additional success handling
435
+ editMessage(
436
+ {
437
+ messageId: message._id ?? "",
438
+ userId: userId, // Using userId from useChatContext
439
+ newMessage: editedMessage.trim(),
391
440
  },
392
- onError: (error:any) => {
393
- // Handle error specifically for this edit
394
- console.error("Edit failed:", error);
441
+ {
442
+ onSuccess: () => {
443
+ setIsEditingMode(false);
444
+ setShowOptions(false);
445
+ setEditedMessage(""); // Clear the input after saving
446
+ // Any additional success handling
447
+ },
448
+ onError: (error) => {
449
+ // Handle error specifically for this edit
450
+ console.error("Edit failed:", error);
451
+ },
395
452
  }
396
- });
453
+ );
397
454
  } else {
398
455
  setIsEditingMode(false);
399
456
  }
400
- }
457
+ };
401
458
 
402
459
  const handleCancelEdit = () => {
403
- setEditedMessage(message.message)
404
- setIsEditingMode(false)
405
- }
460
+ setEditedMessage(message.message);
461
+ setIsEditingMode(false);
462
+ };
406
463
 
407
464
  const handleKeyDown = (e: React.KeyboardEvent) => {
408
465
  if (e.key === "Enter") {
409
- handleSaveEdit()
466
+ handleSaveEdit();
410
467
  } else if (e.key === "Escape") {
411
- handleCancelEdit()
468
+ handleCancelEdit();
412
469
  }
413
- }
470
+ };
414
471
 
415
472
  const handleDeleteClick = () => {
416
- deleteMessage({
417
- messageId: message._id ?? '',
418
- userId: userId // Get this from your auth context
419
- });
473
+ if (message._id) {
474
+ deleteMessage(
475
+ {
476
+ messageId: message._id,
477
+ userId: userId,
478
+ },
479
+ {
480
+ onSuccess: () => {
481
+ setShowDeleteOption(false);
482
+ setShowOptions(false);
483
+ },
484
+ onError: (error) => {
485
+ console.error("Delete failed:", error);
486
+ },
487
+ }
488
+ );
489
+ }
420
490
  };
421
491
 
422
-
423
492
  return (
424
493
  <div className="chat-container">
425
494
  <div className={`message-row ${alignItems}`}>
426
495
  <div
427
496
  className="bubble-container"
428
497
  onMouseEnter={() => fromMe && setShowOptions(true)}
429
- onMouseLeave={() => fromMe && !showDeleteOption && setShowOptions(false)}
498
+ onMouseLeave={() =>
499
+ fromMe && !showDeleteOption && setShowOptions(false)
500
+ }
430
501
  >
431
502
  {message.isDeleted ? (
432
503
  <div className="chat-bubble compact-bubble deleted-message">
433
504
  <div className="message-text">This message was deleted</div>
434
505
  </div>
435
- ) : (message.message || (message.media && message.media.length > 0)) && (
436
- <div className="chat-bubble compact-bubble">
437
- {renderMedia()}
438
- {isEditingMode ? (
439
- <div className="edit-message-container">
440
- <input
441
- ref={editInputRef}
442
- type="text"
443
- value={editedMessage}
444
- onChange={(e) => setEditedMessage(e.target.value)}
445
- onKeyDown={handleKeyDown}
446
- className="edit-message-input"
447
- />
448
- <div className="edit-actions">
449
- <button className="save-edit" onClick={handleSaveEdit}>
450
- Save
451
- </button>
452
- <button className="cancel-edit" onClick={handleCancelEdit}>
453
- Cancel
454
- </button>
506
+ ) : (
507
+ (message.message ||
508
+ (message.media && message.media.length > 0)) && (
509
+ <div className="chat-bubble compact-bubble">
510
+ {renderMedia()}
511
+ {isEditingMode ? (
512
+ <div className="edit-message-container">
513
+ <input
514
+ ref={editInputRef}
515
+ type="text"
516
+ value={editedMessage}
517
+ onChange={(e) => setEditedMessage(e.target.value)}
518
+ onKeyDown={handleKeyDown}
519
+ className="edit-message-input"
520
+ />
521
+ <div className="edit-actions">
522
+ <button className="save-edit" onClick={handleSaveEdit}>
523
+ Save
524
+ </button>
525
+ <button
526
+ className="cancel-edit"
527
+ onClick={handleCancelEdit}
528
+ >
529
+ Cancel
530
+ </button>
531
+ </div>
455
532
  </div>
456
- </div>
457
- ) : (
458
- message.message && <div className="message-text">{message.message}</div>
459
- )}
460
-
461
- {/* Message options for outgoing messages */}
462
- {fromMe && showOptions && !isEditingMode && !message.isDeleted && (
463
- <div className="message-options">
464
- <button className="message-option-btn edit-btn" onClick={handleEditClick} title="Edit">
465
- <Pencil size={16} />
466
- </button>
467
- <div className="more-options-container" ref={optionsRef}>
468
- <button
469
- className="message-option-btn more-btn"
470
- onClick={() => setShowDeleteOption(!showDeleteOption)}
471
- title="More options"
472
- >
473
- <MoreHorizontal size={16} />
474
- </button>
475
-
476
- {showDeleteOption && (
477
- <div className="delete-option">
478
- <button className="delete-btn" onClick={handleDeleteClick}>
479
- <Trash2 size={16} />
480
- <span>Delete</span>
533
+ ) : (
534
+ message.message && (
535
+ <div className="message-text">{message.message}</div>
536
+ )
537
+ )}
538
+
539
+ {/* Message options for outgoing messages */}
540
+ {fromMe &&
541
+ showOptions &&
542
+ !isEditingMode &&
543
+ !message.isDeleted && (
544
+ <div className="message-options">
545
+ <button
546
+ className="message-option-btn edit-btn"
547
+ onClick={() => handleEditClick(message.message)}
548
+ title="Edit"
549
+ >
550
+ <Pencil size={16} />
551
+ </button>
552
+ <div className="more-options-container" ref={optionsRef}>
553
+ <button
554
+ className="message-option-btn more-btn"
555
+ onClick={() => setShowDeleteOption(!showDeleteOption)}
556
+ title="More options"
557
+ >
558
+ <MoreHorizontal size={16} />
481
559
  </button>
560
+
561
+ {showDeleteOption && (
562
+ <div className="delete-option">
563
+ <button
564
+ className="delete-btn"
565
+ onClick={handleDeleteClick}
566
+ >
567
+ <Trash2 size={16} />
568
+ <span>{isDeleting ? 'Deleting...' : 'Delete'}</span>
569
+ </button>
570
+ </div>
571
+ )}
482
572
  </div>
483
- )}
484
- </div>
485
- </div>
486
- )}
487
- </div>
573
+ </div>
574
+ )}
575
+ </div>
576
+ )
488
577
  )}
489
578
  <div className={`${timestamp}`}>
490
- {new Date(message.createdAt).toLocaleTimeString([], {
491
- hour: "2-digit",
492
- minute: "2-digit",
493
- })}
494
- {message.isEdited && !message.isDeleted && <span className="edited-label">edited</span>}
579
+ {message.status === 'deleted' ? (
580
+ // Show deleted timestamp if message is deleted
581
+ new Date(message.updatedAt).toLocaleTimeString([], {
582
+ hour: "2-digit",
583
+ minute: "2-digit",
584
+ })
585
+ ) : message.status === 'edited' ? (
586
+ // Show updated timestamp if message was edited
587
+ new Date(message.updatedAt).toLocaleTimeString([], {
588
+ hour: "2-digit",
589
+ minute: "2-digit",
590
+ })
591
+ ) : (
592
+ // Default to created timestamp
593
+ new Date(message.createdAt).toLocaleTimeString([], {
594
+ hour: "2-digit",
595
+ minute: "2-digit",
596
+ })
597
+ )}
495
598
  <span className="status-icon">{getStatusIcon()}</span>
496
599
  </div>
497
600
  </div>
@@ -500,4 +603,4 @@ const [showOptions, setShowOptions] = useState(false)
500
603
  );
501
604
  };
502
605
 
503
- export default Message;
606
+ export default Message;
@@ -5,24 +5,27 @@ import { ConversationProps } from "../../types/type";
5
5
  import { getChatConfig } from "@pubuduth-aplicy/chat-ui";
6
6
 
7
7
  const Conversation = ({ conversation }: ConversationProps) => {
8
- const { setSelectedConversation, setOnlineUsers, onlineUsers,selectedConversation } =
9
- useChatUIStore();
8
+ const {
9
+ setSelectedConversation,
10
+ setOnlineUsers,
11
+ onlineUsers,
12
+ selectedConversation,
13
+ } = useChatUIStore();
10
14
  const { socket, sendMessage } = useChatContext();
11
- const {role} =getChatConfig()
15
+ const { role } = getChatConfig();
12
16
  const handleSelectConversation = async () => {
13
17
  setSelectedConversation(conversation);
14
18
 
15
19
  const unreadMessages = conversation.unreadMessageIds || [];
16
20
  if (unreadMessages.length > 0) {
17
21
  console.log("Marking messages as read:", unreadMessages);
18
-
22
+
19
23
  sendMessage({
20
24
  event: "messageRead",
21
- data:{
25
+ data: {
22
26
  messageIds: unreadMessages,
23
- chatId: conversation._id
24
- }
25
-
27
+ chatId: conversation._id,
28
+ },
26
29
  });
27
30
  }
28
31
  };
@@ -53,7 +56,7 @@ const Conversation = ({ conversation }: ConversationProps) => {
53
56
  conversation?.participantDetails?._id &&
54
57
  onlineUsers?.includes(conversation.participantDetails._id);
55
58
 
56
- const isSelected = selectedConversation?._id === conversation._id;
59
+ const isSelected = selectedConversation?._id === conversation._id;
57
60
 
58
61
  return (
59
62
  <>
@@ -65,54 +68,80 @@ const Conversation = ({ conversation }: ConversationProps) => {
65
68
  <img
66
69
  className="conversation-img"
67
70
  src={
68
- role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
69
- ? selectedConversation.participantDetails[1]?.profilePic
70
- : !Array.isArray(selectedConversation?.participantDetails)
71
- ? selectedConversation?.participantDetails?.profilePic
72
- : undefined
71
+ role === "admin" &&
72
+ Array.isArray(selectedConversation?.participantDetails)
73
+ ? selectedConversation.participantDetails[1]?.profilePic
74
+ : !Array.isArray(selectedConversation?.participantDetails)
75
+ ? selectedConversation?.participantDetails?.profilePic
76
+ : undefined
73
77
  }
74
-
75
78
  alt="User Avatar"
76
79
  />
77
80
  <span
78
- className={`chatSidebarStatusDot ${
79
- isUserOnline && "online"
80
- }`}
81
+ className={`chatSidebarStatusDot ${isUserOnline && "online"}`}
81
82
  ></span>
82
83
  </div>
83
84
 
84
85
  <div className="conversation-info">
85
86
  <div className="conversation-header">
86
87
  <p className="conversation-name">
87
- {
88
- role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
89
- ? selectedConversation.participantDetails[1]?.firstname
90
- : !Array.isArray(selectedConversation?.participantDetails)
91
- ? selectedConversation?.participantDetails?.firstname
92
- : undefined
93
- }
88
+ {role === "admin" &&
89
+ Array.isArray(selectedConversation?.participantDetails)
90
+ ? selectedConversation.participantDetails[1]?.firstname
91
+ : !Array.isArray(selectedConversation?.participantDetails)
92
+ ? selectedConversation?.participantDetails?.firstname
93
+ : undefined}
94
94
  </p>
95
95
  <span className="conversation-time">
96
- {new Date(conversation.lastMessage.createdAt).toLocaleTimeString(
97
- [],
98
- {
99
- hour: "2-digit",
100
- minute: "2-digit",
101
- }
102
- )}
96
+ {conversation.lastMessage.status === 'deleted' ? (
97
+ // Show deleted timestamp if message is deleted
98
+ new Date(conversation.lastMessage.updatedAt).toLocaleTimeString([], {
99
+ hour: "2-digit",
100
+ minute: "2-digit",
101
+ })
102
+ ) : conversation.lastMessage.status === 'edited' ? (
103
+ // Show updated timestamp if message was edited
104
+ new Date(conversation.lastMessage.updatedAt).toLocaleTimeString([], {
105
+ hour: "2-digit",
106
+ minute: "2-digit",
107
+ })
108
+ ) : (
109
+ // Default to created timestamp
110
+ new Date(conversation.lastMessage.createdAt).toLocaleTimeString([], {
111
+ hour: "2-digit",
112
+ minute: "2-digit",
113
+ })
114
+ )}
103
115
  </span>
104
116
  </div>
105
117
  <p className="conversation-message">
106
- {conversation.lastMessage.message.length > 50
107
- ? conversation.lastMessage.message.slice(0, 50) + "..."
108
- : conversation.lastMessage.media.length > 0 ? (
109
- <div style={{display:"flex",alignItems:"center", gap:"5px"}}>
110
- <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">
111
- <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>
112
- </svg>
113
- attachment
114
- </div>
115
- ) : conversation.lastMessage.message}
118
+ {conversation.lastMessage.status === "deleted" ? (
119
+ "This message was deleted"
120
+ ) : conversation.lastMessage.type !== "system" &&
121
+ conversation.lastMessage.message.length > 50 ? (
122
+ conversation.lastMessage.message.slice(0, 50) + "..."
123
+ ) : conversation.lastMessage.media.length > 0 ? (
124
+ <div
125
+ style={{ display: "flex", alignItems: "center", gap: "5px" }}
126
+ >
127
+ <svg
128
+ xmlns="http://www.w3.org/2000/svg"
129
+ width="18"
130
+ height="18"
131
+ viewBox="0 0 24 24"
132
+ fill="none"
133
+ stroke="currentColor"
134
+ strokeWidth="2"
135
+ strokeLinecap="round"
136
+ strokeLinejoin="round"
137
+ >
138
+ <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>
139
+ </svg>
140
+ attachment
141
+ </div>
142
+ ) : (
143
+ conversation.lastMessage.message
144
+ )}
116
145
  </p>
117
146
  </div>
118
147
  </div>
@@ -120,4 +149,4 @@ const Conversation = ({ conversation }: ConversationProps) => {
120
149
  );
121
150
  };
122
151
 
123
- export default Conversation;
152
+ export default Conversation;
@@ -1559,4 +1559,82 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
1559
1559
  .cancel-edit {
1560
1560
  background-color: #f44336;
1561
1561
  color: white;
1562
+ }
1563
+ .system-message.booking-details {
1564
+ display: flex;
1565
+ flex-direction: column;
1566
+ align-items: center;
1567
+ justify-content: center;
1568
+ max-width: 400px;
1569
+ margin: 20px auto;
1570
+ padding: 24px;
1571
+ background: #f8f9fa;
1572
+ border: 1px solid #e9ecef;
1573
+ border-radius: 12px;
1574
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1575
+ text-align: center;
1576
+ }
1577
+
1578
+ .system-message.booking-details h4 {
1579
+ margin: 0 0 16px 0;
1580
+ font-size: 18px;
1581
+ font-weight: 600;
1582
+ color: #2c3e50;
1583
+ display: flex;
1584
+ align-items: center;
1585
+ gap: 8px;
1586
+ }
1587
+
1588
+ .system-message.booking-details h4::before {
1589
+ content: "✓";
1590
+ display: inline-block;
1591
+ width: 24px;
1592
+ height: 24px;
1593
+ background: #2cb1aa;
1594
+ color: white;
1595
+ border-radius: 50%;
1596
+ font-size: 14px;
1597
+ line-height: 24px;
1598
+ text-align: center;
1599
+ font-weight: bold;
1600
+ }
1601
+
1602
+ .system-message.booking-details .details {
1603
+ width: 100%;
1604
+ background: white;
1605
+ border-radius: 8px;
1606
+ padding: 16px;
1607
+ border: 1px solid #dee2e6;
1608
+ }
1609
+
1610
+ .system-message.booking-details .details p {
1611
+ margin: 8px 0;
1612
+ padding: 8px 0;
1613
+ border-bottom: 1px solid #f1f3f4;
1614
+ font-size: 14px;
1615
+ color: #495057;
1616
+ display: flex;
1617
+ justify-content: space-between;
1618
+ align-items: center;
1619
+ }
1620
+
1621
+ .system-message.booking-details .details p:last-child {
1622
+ border-bottom: none;
1623
+ font-weight: 600;
1624
+ color: #2cb1aa;
1625
+ font-size: 16px;
1626
+ }
1627
+
1628
+ .system-message.booking-details .details p strong {
1629
+ color: #2c3e50;
1630
+ font-weight: 500;
1631
+ }
1632
+
1633
+ /* Responsive design */
1634
+ @media (max-width: 480px) {
1635
+ .system-message.booking-details {
1636
+ max-width: 90%;
1637
+ margin: 16px auto;
1638
+ padding: 20px;
1639
+ }
1562
1640
  }
package/src/types/type.ts CHANGED
@@ -52,6 +52,8 @@ export interface ConversationProps {
52
52
  senderId: string;
53
53
  message: string;
54
54
  media:string[];
55
+ type?:'user' | 'system' | 'system-completion';
56
+ status: MessageStatus;
55
57
  chatId: string;
56
58
  createdAt: string;
57
59
  updatedAt: string;
@@ -70,4 +72,4 @@ export interface ConversationProps {
70
72
  lastIdx: boolean;
71
73
  }
72
74
 
73
- export type MessageStatus = 'sent' | 'delivered' | 'read' | 'sending' | 'failed';
75
+ export type MessageStatus = 'sent' | 'delivered' | 'read' | 'sending' | 'failed' | 'edited' | 'deleted';