@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubuduth-aplicy/chat-ui",
3
- "version": "2.1.56",
3
+ "version": "2.1.58",
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": "",
@@ -1,60 +1,60 @@
1
- import { useEffect } from 'react';
2
- import useChatUIStore from '../stores/Zustant';
3
- import MessageContainer from './messages/MessageContainer';
4
- import { Sidebar } from './sidebar/Sidebar'
5
- import { useChatContext } from '../providers/ChatProvider';
1
+ import { useEffect } from "react";
2
+ import useChatUIStore from "../stores/Zustant";
3
+ import MessageContainer from "./messages/MessageContainer";
4
+ import { Sidebar } from "./sidebar/Sidebar";
5
+ import { useChatContext } from "../providers/ChatProvider";
6
6
 
7
7
  // import MessageContainer from './components/messages/MessageContainer'
8
8
  // import useConversation from '../../zustand/useConversation';
9
9
 
10
10
  export const Chat = () => {
11
- const { selectedConversation, messages, setMessages, updateMessageStatus } = useChatUIStore();
12
- const {socket}=useChatContext()
13
- useEffect(() => {
14
- socket.on("receiveMessage", (messageData) => {
15
- setMessages([...messages, { ...messageData, status: "sent" }]);
16
- });
17
-
18
- socket.on("messageDelivered", ({ messageId }) => {
19
- updateMessageStatus(messageId, "delivered");
20
- });
21
-
22
- return () => {
23
- socket.off("receiveMessage");
24
- socket.off("messageDelivered");
25
- };
26
- }, [messages, setMessages, updateMessageStatus]);
27
- return (
28
- <>
29
- <div className='container mx-auto mb-5'>
30
- <div className='grid-container'>
31
- {selectedConversation ? (
32
- <>
33
- <div className={`sidebarContainer`}>
34
- <Sidebar />
35
- </div>
36
- <div className='messageContainer'>
37
- <MessageContainer />
38
- </div>
39
- </>
40
- ) : (
41
- <>
42
- <div className='sidebarContainer'>
43
- <Sidebar />
44
- </div>
45
- <div className='messageContainer'>
46
- <MessageContainer />
47
- </div>
48
- </>
49
- )}
50
- </div>
51
- </div>
52
- </>
53
- )
54
- }
11
+ const { selectedConversation, messages, setMessages, updateMessageStatus } =
12
+ useChatUIStore();
13
+ const { socket } = useChatContext();
14
+ useEffect(() => {
15
+ socket.on("receiveMessage", (messageData) => {
16
+ setMessages([...messages, { ...messageData, status: "sent" }]);
17
+ });
55
18
 
56
- // const MessageBody = () => {
19
+ socket.on("messageDelivered", ({ messageId }) => {
20
+ updateMessageStatus(messageId, "delivered");
21
+ });
22
+
23
+ return () => {
24
+ socket.off("receiveMessage");
25
+ socket.off("messageDelivered");
26
+ };
27
+ }, [messages, setMessages, updateMessageStatus]);
28
+ return (
29
+ <>
30
+ <div className="container mx-auto mb-5">
31
+ <div className="grid-container">
32
+ {selectedConversation ? (
33
+ <>
34
+ <div className={`sidebarContainer`}>
35
+ <Sidebar />
36
+ </div>
37
+ <div className="messageContainer">
38
+ <MessageContainer />
39
+ </div>
40
+ </>
41
+ ) : (
42
+ <>
43
+ <div className="sidebarContainer">
44
+ <Sidebar />
45
+ </div>
46
+ <div className="messageContainer">
47
+ <MessageContainer />
48
+ </div>
49
+ </>
50
+ )}
51
+ </div>
52
+ </div>
53
+ </>
54
+ );
55
+ };
57
56
 
57
+ // const MessageBody = () => {
58
58
 
59
59
  // return (
60
60
  // <><div className='flex-grow px-8 pt-8 text-left text-gray-700 overflow-y-auto'>
@@ -1,7 +1,7 @@
1
1
  const Loader = () => {
2
2
  return (
3
-
4
- <div className="dot-spinner">
3
+ <div className="loader-wrapper">
4
+ <div className="dot-spinner">
5
5
  <div className="dot-spinner__dot" />
6
6
  <div className="dot-spinner__dot" />
7
7
  <div className="dot-spinner__dot" />
@@ -11,6 +11,8 @@ const Loader = () => {
11
11
  <div className="dot-spinner__dot" />
12
12
  <div className="dot-spinner__dot" />
13
13
  </div>
14
+ </div>
15
+
14
16
  )
15
17
  }
16
18
 
@@ -42,7 +42,8 @@ export const FilePreview: React.FC<FilePreviewProps> = ({
42
42
  };
43
43
 
44
44
  return (
45
- <div className="file-preview">
45
+ <div className='file-preview-wrapper' style={{ display: 'flex', alignItems: 'center' }}>
46
+ <div className="file-preview">
46
47
  <div className="file-preview-content">
47
48
  {type === 'image' && (
48
49
  <img src={previewUrl} alt={file.name} className="file-preview-image" />
@@ -55,7 +56,6 @@ export const FilePreview: React.FC<FilePreviewProps> = ({
55
56
  {type === 'document' && (
56
57
  <div className="file-preview-document">
57
58
  {getFileIcon()}
58
- <span>{file.name}</span>
59
59
  </div>
60
60
  )}
61
61
 
@@ -87,5 +87,8 @@ export const FilePreview: React.FC<FilePreviewProps> = ({
87
87
  &times;
88
88
  </button>
89
89
  </div>
90
+ </div>
91
+
90
92
  );
91
- };
93
+ };
94
+
@@ -3,7 +3,8 @@ import { MessageStatus } from "../../types/type";
3
3
  import { useChatContext } from "../../providers/ChatProvider";
4
4
  import { useEffect, useState } from "react";
5
5
  import { FileType } from "../common/FilePreview";
6
-
6
+ import { getChatConfig } from "../../Chat.config";
7
+ import { Path } from "../../lib/api/endpoint";
7
8
 
8
9
  interface MessageProps {
9
10
  message: {
@@ -26,6 +27,7 @@ interface MessageProps {
26
27
 
27
28
  const Message = ({ message }: MessageProps) => {
28
29
  const { userId } = useChatContext();
30
+ const { apiUrl } = getChatConfig();
29
31
  const fromMe = message.senderId === userId;
30
32
  const timestamp = fromMe ? "timestamp_outgoing" : "timestamp_incomeing";
31
33
  const alignItems = fromMe ? "outgoing" : "incoming";
@@ -40,6 +42,10 @@ const Message = ({ message }: MessageProps) => {
40
42
  // saveAs(url, name);
41
43
  // };
42
44
 
45
+ // const [downloadingIndex, setDownloadingIndex] = useState<number | null>(null)
46
+ const [downloadProgress, setDownloadProgress] = useState<number>(0)
47
+ const [downloadController, setDownloadController] = useState<AbortController | null>(null)
48
+
43
49
  const getStatusIcon = () => {
44
50
  if (!fromMe) return null;
45
51
 
@@ -55,35 +61,138 @@ const Message = ({ message }: MessageProps) => {
55
61
  case "sending":
56
62
  return <span className="message-status sending">🔄</span>;
57
63
  case "sent":
58
- return <span className="message-status sent">✓</span>;
64
+ return <span className="message-status sent">
65
+ <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>
66
+ </span>;
59
67
  case "delivered":
60
- return <span className="message-status delivered">✓✓</span>;
68
+ return <span className="message-status delivered">
69
+ <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>
70
+ </span>;
61
71
  case "read":
62
- return <span className="message-status read">✓✓</span>;
72
+ return <span className="message-status read">
73
+ <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>
74
+ </span>;
63
75
  default:
64
76
  return null;
65
77
  }
66
78
  };
67
79
 
80
+
81
+ const cancelDownload = () => {
82
+ if (downloadController) {
83
+ downloadController.abort()
84
+ setDownloadingIndex(null)
85
+ setDownloadProgress(0)
86
+ setDownloadController(null)
87
+ }
88
+ }
89
+
90
+ // const handleDownload = async (url: string, name: string, index: number) => {
91
+ // setDownloadingIndex(index);
92
+ // try {
93
+ // var xhr = new XMLHttpRequest()
94
+ // xhr.open('HEAD', url, false)
95
+ // xhr.send()
96
+ // const blob = await response.blob();
97
+ // const link = document.createElement("a");
98
+ // link.href = URL.createObjectURL(blob);
99
+ // link.download = name;
100
+ // link.click();
101
+ // URL.revokeObjectURL(link.href);
102
+ // } catch (error) {
103
+ // console.error("Download failed:", error);
104
+ // } finally {
105
+ // setDownloadingIndex(null);
106
+ // }
107
+ // };
108
+
109
+ // const renderMedia = () => {
110
+ // if (!message.media || message.media.length === 0) return null;
111
+
112
+
68
113
  const handleDownload = async (url: string, name: string, index: number) => {
69
114
  setDownloadingIndex(index);
115
+ setDownloadProgress(0);
116
+
117
+ const controller = new AbortController();
118
+ setDownloadController(controller);
119
+
70
120
  try {
71
- const response = await fetch(url);
72
- const blob = await response.blob();
73
- const link = document.createElement("a");
74
- link.href = URL.createObjectURL(blob);
75
- link.download = name;
76
- link.click();
77
- URL.revokeObjectURL(link.href);
121
+ // First try to download directly
122
+ try {
123
+ const response = await fetch(`${apiUrl}${Path.apiProxy}?url=${encodeURIComponent(url)}&name=${encodeURIComponent(name)}`,
124
+ );
125
+
126
+ if (!response.ok) throw new Error('Network response was not ok');
127
+
128
+ const contentLength = response.headers.get("content-length");
129
+ const total = contentLength ? Number.parseInt(contentLength, 10) : 0;
130
+
131
+ const reader = response.body?.getReader();
132
+ if (!reader) throw new Error("Failed to get response reader");
133
+
134
+ let receivedLength = 0;
135
+ const chunks: Uint8Array[] = [];
136
+
137
+ while (true) {
138
+ const { done, value } = await reader.read();
139
+ if (done) break;
140
+
141
+ chunks.push(value);
142
+ receivedLength += value.length;
143
+
144
+ if (total) {
145
+ const progress = Math.round((receivedLength / total) * 100);
146
+ setDownloadProgress(progress);
147
+ }
148
+ }
149
+
150
+ const chunksAll = new Uint8Array(receivedLength);
151
+ let position = 0;
152
+ for (const chunk of chunks) {
153
+ chunksAll.set(chunk, position);
154
+ position += chunk.length;
155
+ }
156
+
157
+ const blob = new Blob([chunksAll]);
158
+ const link = document.createElement("a");
159
+ link.href = URL.createObjectURL(blob);
160
+ link.download = name;
161
+ document.body.appendChild(link);
162
+ link.click();
163
+ document.body.removeChild(link);
164
+ URL.revokeObjectURL(link.href);
165
+ } catch (directDownloadError) {
166
+ console.log('Direct download failed, trying alternative method', directDownloadError);
167
+
168
+ // Fallback: Open in new tab if download fails
169
+ window.open(url, '_blank');
170
+ }
78
171
  } catch (error) {
79
- console.error("Download failed:", error);
172
+ if ((error as Error).name === "AbortError") {
173
+ console.log("Download was cancelled");
174
+ } else {
175
+ console.error("Download failed:", error);
176
+ // Final fallback - create a temporary link
177
+ const link = document.createElement('a');
178
+ link.href = url;
179
+ link.target = '_blank';
180
+ link.rel = 'noopener noreferrer';
181
+ document.body.appendChild(link);
182
+ link.click();
183
+ document.body.removeChild(link);
184
+ }
80
185
  } finally {
81
186
  setDownloadingIndex(null);
187
+ setDownloadProgress(0);
188
+ setDownloadController(null);
82
189
  }
83
190
  };
191
+
84
192
 
193
+
85
194
  const renderMedia = () => {
86
- if (!message.media || message.media.length === 0) return null;
195
+ if (!message.media || message.media.length === 0) return null
87
196
 
88
197
  return (
89
198
  <div
@@ -98,7 +207,6 @@ const Message = ({ message }: MessageProps) => {
98
207
  media.uploadProgress !== undefined &&
99
208
  media.uploadProgress < 100 && (
100
209
  <>
101
- {console.log("media.uploadProgress", media.uploadProgress)};
102
210
  <div className="circular-progress-container">
103
211
  <div className="media-preview-background">
104
212
  <img
@@ -173,20 +281,26 @@ const Message = ({ message }: MessageProps) => {
173
281
  </div>
174
282
  )}
175
283
  <button
176
- className="download-btn"
177
- onClick={(e) => {
178
- e.stopPropagation();
179
- handleDownload(media.url, media.name, index);
180
- }}
181
- title="Download"
182
- disabled={downloadingIndex === index}
183
- >
184
- {downloadingIndex === index ? (
185
- <span className="loader" />
186
- ) : (
187
- "⬇️"
188
- )}
189
- </button>
284
+ className="download-btn"
285
+ onClick={(e) => {
286
+ e.stopPropagation()
287
+ if (downloadingIndex === index) {
288
+ cancelDownload()
289
+ } else {
290
+ handleDownload(media.url, media.name, index)
291
+ }
292
+ }}
293
+ title={downloadingIndex === index ? "Cancel" : "Download"}
294
+ >
295
+ {downloadingIndex === index ? (
296
+ <div className="download-progress-container">
297
+ <div className="download-progress" style={{ width: `${downloadProgress}%` }} />
298
+ <span className="cancel-download">✕</span>
299
+ </div>
300
+ ) : (
301
+ "⬇️"
302
+ )}
303
+ </button>
190
304
  </>
191
305
  )}
192
306
  </div>
@@ -220,4 +334,4 @@ const Message = ({ message }: MessageProps) => {
220
334
  );
221
335
  };
222
336
 
223
- export default Message;
337
+ export default Message;