@pubuduth-aplicy/chat-ui 2.1.54 → 2.1.56

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.54",
3
+ "version": "2.1.56",
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,10 +1,10 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { MessageStatus } from "../../types/type";
3
3
  import { useChatContext } from "../../providers/ChatProvider";
4
- // import { saveAs } from 'file-saver';
5
4
  import { useEffect, useState } from "react";
6
5
  import { FileType } from "../common/FilePreview";
7
6
 
7
+
8
8
  interface MessageProps {
9
9
  message: {
10
10
  _id?: string;
@@ -42,23 +42,23 @@ const Message = ({ message }: MessageProps) => {
42
42
 
43
43
  const getStatusIcon = () => {
44
44
  if (!fromMe) return null;
45
-
46
- if (message.isUploading || message.status === 'sending') {
45
+
46
+ if (message.isUploading || message.status === "sending") {
47
47
  return <span className="message-status uploading">🔄</span>;
48
48
  }
49
-
50
- if (message.status === 'failed') {
49
+
50
+ if (message.status === "failed") {
51
51
  return <span className="message-status failed">❌</span>;
52
52
  }
53
-
53
+
54
54
  switch (localStatus) {
55
- case 'sending':
55
+ case "sending":
56
56
  return <span className="message-status sending">🔄</span>;
57
- case 'sent':
57
+ case "sent":
58
58
  return <span className="message-status sent">✓</span>;
59
- case 'delivered':
59
+ case "delivered":
60
60
  return <span className="message-status delivered">✓✓</span>;
61
- case 'read':
61
+ case "read":
62
62
  return <span className="message-status read">✓✓</span>;
63
63
  default:
64
64
  return null;
@@ -70,7 +70,7 @@ const Message = ({ message }: MessageProps) => {
70
70
  try {
71
71
  const response = await fetch(url);
72
72
  const blob = await response.blob();
73
- const link = document.createElement('a');
73
+ const link = document.createElement("a");
74
74
  link.href = URL.createObjectURL(blob);
75
75
  link.download = name;
76
76
  link.click();
@@ -86,107 +86,132 @@ const Message = ({ message }: MessageProps) => {
86
86
  if (!message.media || message.media.length === 0) return null;
87
87
 
88
88
  return (
89
- <div className={`media-grid ${message.media.length > 1 ? 'multi-media' : 'single-media'}`}>
90
- {message.media.map((media, index) => (
91
- <div key={index} className="media-item">
92
-
93
- {/* Progress indicator */}
94
- {(media.uploadProgress !== undefined && media.uploadProgress < 100) && (
95
- <div className="circular-progress-container">
96
- <div className="media-preview-background">
97
- <img
98
- src={media.url}
99
- alt={media.name}
100
- className="blurred-preview"
101
- />
102
- </div>
103
- <div className="circular-progress">
104
- <svg className="circular-progress-svg" viewBox="0 0 36 36">
105
- <path
106
- className="circular-progress-track"
107
- d="M18 2.0845
89
+ <div
90
+ className={`media-grid ${
91
+ message.media.length > 1 ? "multi-media" : "single-media"
92
+ }`}
93
+ >
94
+ {message.media.map((media, index) => (
95
+ <div key={index} className="media-item">
96
+ {/* Progress indicator */}
97
+ {message.isUploading &&
98
+ media.uploadProgress !== undefined &&
99
+ media.uploadProgress < 100 && (
100
+ <>
101
+ {console.log("media.uploadProgress", media.uploadProgress)};
102
+ <div className="circular-progress-container">
103
+ <div className="media-preview-background">
104
+ <img
105
+ src={media.url}
106
+ alt={media.name}
107
+ className="blurred-preview"
108
+ />
109
+ </div>
110
+ <div className="circular-progress">
111
+ <svg
112
+ className="circular-progress-svg"
113
+ viewBox="0 0 36 36"
114
+ >
115
+ <path
116
+ className="circular-progress-track"
117
+ d="M18 2.0845
108
118
  a 15.9155 15.9155 0 0 1 0 31.831
109
119
  a 15.9155 15.9155 0 0 1 0 -31.831"
110
- />
111
- <path
112
- className="circular-progress-bar"
113
- strokeDasharray={`${media.uploadProgress}, 100`}
114
- d="M18 2.0845
120
+ />
121
+ <path
122
+ className="circular-progress-bar"
123
+ strokeDasharray={`${media.uploadProgress}, 100`}
124
+ d="M18 2.0845
115
125
  a 15.9155 15.9155 0 0 1 0 31.831
116
126
  a 15.9155 15.9155 0 0 1 0 -31.831"
117
- />
118
- </svg>
119
- <span className="circular-progress-text">
120
- {media.uploadProgress}%
121
- </span>
122
- </div>
123
- </div>
124
- )}
127
+ />
128
+ </svg>
129
+ <span className="circular-progress-text">
130
+ {media.uploadProgress}%
131
+ </span>
132
+ </div>
133
+ </div>
134
+ </>
135
+ )}
125
136
 
126
- {/* Error state */}
127
- {media.uploadError && (
128
- <div className="upload-error">
129
- <span>⚠️ Upload failed</span>
130
- </div>
131
- )}
137
+ {/* Error state */}
138
+ {media.uploadError && (
139
+ <div className="upload-error">
140
+ <span>⚠️ Upload failed</span>
141
+ </div>
142
+ )}
132
143
 
133
- {/* Actual media (shown when upload complete) */}
134
- {(!media.uploadProgress || media.uploadProgress >= 100) && !media.uploadError && (
135
- <>
136
- {media.type === 'image' ? (
137
- <img
138
- src={media.url}
139
- alt={media.name}
140
- className="media-content"
141
- onClick={() => window.open(media.url, '_blank')}
142
- />
143
- ) : media.type === 'video' ? (
144
- <video controls className="media-content">
145
- <source src={media.url} type={`video/${media.url.split('.').pop()}`} />
146
- </video>
147
- ) : (
148
- <div className="document-preview">
149
- <div className="file-icon">
150
- {media.type === 'document' && '📄'}
151
- </div>
152
- <div className="file-info">
153
- <span className="file-name">{media.name}</span>
154
- <span className="file-size">{(media.size / 1024).toFixed(1)} KB</span>
155
- </div>
156
-
157
- </div>
144
+ {/* Actual media (shown when upload complete) */}
145
+ {(!message.isUploading || media.uploadProgress === 100) &&
146
+ !media.uploadError && (
147
+ <>
148
+ {media.type === "image" ? (
149
+ <img
150
+ src={media.url}
151
+ alt={media.name}
152
+ className="media-content"
153
+ onClick={() => window.open(media.url, "_blank")}
154
+ />
155
+ ) : media.type === "video" ? (
156
+ <video controls className="media-content">
157
+ <source
158
+ src={media.url}
159
+ type={`video/${media.url.split(".").pop()}`}
160
+ />
161
+ </video>
162
+ ) : (
163
+ <div className="document-preview">
164
+ <div className="file-icon">
165
+ {media.type === "document" && "📄"}
166
+ </div>
167
+ <div className="file-info">
168
+ <span className="file-name">{media.name}</span>
169
+ <span className="file-size">
170
+ {(media.size / 1024).toFixed(1)} KB
171
+ </span>
172
+ </div>
173
+ </div>
174
+ )}
175
+ <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>
190
+ </>
158
191
  )}
159
- <button
160
- className="download-btn"
161
- onClick={(e) => {
162
- e.stopPropagation();
163
- handleDownload(media.url, media.name, index);
164
- }}
165
- title="Download"
166
- disabled={downloadingIndex === index}
167
- >
168
- {downloadingIndex === index ? <span className="loader" /> : '⬇️'}
169
- </button>
170
- </>
171
- )}
172
- </div>
173
- ))}
192
+ </div>
193
+ ))}
174
194
  </div>
175
195
  );
176
196
  };
177
197
 
178
198
  return (
179
- <div className="chat-container">
199
+ <div className="chat-container">
180
200
  <div className={`message-row ${alignItems}`}>
181
201
  <div className="bubble-container">
182
202
  {(message.message || (message.media && message.media.length > 0)) && (
183
203
  <div className="chat-bubble compact-bubble">
184
204
  {renderMedia()}
185
- {message.message && <div className="message-text">{message.message}</div>}
205
+ {message.message && (
206
+ <div className="message-text">{message.message}</div>
207
+ )}
186
208
  </div>
187
209
  )}
188
210
  <div className={`${timestamp}`}>
189
- {new Date(message.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
211
+ {new Date(message.createdAt).toLocaleTimeString([], {
212
+ hour: "2-digit",
213
+ minute: "2-digit",
214
+ })}
190
215
  <span className="status-icon">{getStatusIcon()}</span>
191
216
  </div>
192
217
  </div>
@@ -195,4 +220,4 @@ const Message = ({ message }: MessageProps) => {
195
220
  );
196
221
  };
197
222
 
198
- export default Message;
223
+ export default Message;
@@ -38,14 +38,15 @@ const MessageInput = () => {
38
38
  const fileInputRef = useRef<HTMLInputElement>(null);
39
39
  const attachmentsRef = useRef<Attachment[]>([]);
40
40
  const [tempMessageId, setTempMessageId] = useState<string | null>(null);
41
+ const [inputError, setInputError] = useState<string | null>(null);
41
42
 
42
43
  const generateTempId = () => `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
43
44
 
44
45
  // 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
- }
46
+ // const autoResizeTextarea = (element: HTMLTextAreaElement) => {
47
+ // element.style.height = "auto"
48
+ // element.style.height = Math.min(150, element.scrollHeight) + "px"
49
+ // }
49
50
 
50
51
  useEffect(() => {
51
52
  if (selectedConversation?._id) {
@@ -75,6 +76,42 @@ const MessageInput = () => {
75
76
  }, [message, socket, selectedConversation?._id, userId]);
76
77
 
77
78
 
79
+ const validateInput = (text: string) => {
80
+ // Check for email
81
+ const emailRegex = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
82
+ if (emailRegex.test(text)) {
83
+ return "Email addresses are not allowed.";
84
+ }
85
+
86
+ // Check for phone number (very basic)
87
+ const phoneRegex = /(\+?\d{1,4}[\s-]?)?(\(?\d{3}\)?[\s-]?)?\d{3}[\s-]?\d{4}/;
88
+ if (phoneRegex.test(text)) {
89
+ return "Phone numbers are not allowed.";
90
+ }
91
+
92
+ // // Check for bad words
93
+ // for (const word of badWords) {
94
+ // if (text.toLowerCase().includes(word)) {
95
+ // return "Inappropriate language is not allowed.";
96
+ // }
97
+ // }
98
+
99
+ return null; // No errors
100
+ };
101
+
102
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
103
+ const newValue = e.target.value;
104
+ const validationError = validateInput(newValue);
105
+
106
+ if (validationError) {
107
+ setInputError(validationError);
108
+ } else {
109
+ setInputError(null);
110
+ setMessage(newValue);
111
+ }
112
+ };
113
+
114
+
78
115
  useEffect(() => {
79
116
  if (!socket || !selectedConversation?._id) return;
80
117
 
@@ -476,10 +513,11 @@ const MessageInput = () => {
476
513
  className="chatMessageInput"
477
514
  placeholder="Send a message"
478
515
  value={message}
479
- onChange={(e) => {
480
- setMessage(e.target.value)
481
- autoResizeTextarea(e.target)
482
- }}
516
+ // onChange={(e) => {
517
+ // setMessage(e.target.value)
518
+ // autoResizeTextarea(e.target)
519
+ // }}
520
+ onChange={handleChange}
483
521
  rows={1}
484
522
  style={{ resize: "none" }}
485
523
  onKeyDown={(e) => {
@@ -489,7 +527,9 @@ const MessageInput = () => {
489
527
  }
490
528
  }}
491
529
  />
492
- <button type="submit" className="chatMessageInputSubmit" disabled={isSending}>
530
+
531
+ {inputError && <p style={{ color: 'red', fontSize: '12px' }}>{inputError}</p>}
532
+ <button type="submit" className="chatMessageInputSubmit" disabled={!!inputError || isSending}>
493
533
  <img width={10} height={10} src={paperplane} alt="send" />
494
534
  </button>
495
535
  </div>
@@ -1198,25 +1198,63 @@ background-color: #ccc;
1198
1198
 
1199
1199
 
1200
1200
 
1201
- /* Blurred background preview */
1201
+ .circular-progress-container {
1202
+ position: relative;
1203
+ width: 100%;
1204
+ height: 200px;
1205
+ display: flex;
1206
+ justify-content: center;
1207
+ align-items: center;
1208
+ }
1209
+
1202
1210
  .media-preview-background {
1203
1211
  position: absolute;
1204
- top: 0;
1205
- left: 0;
1206
1212
  width: 100%;
1207
1213
  height: 100%;
1208
- overflow: hidden;
1214
+ filter: blur(5px);
1215
+ opacity: 0.7;
1209
1216
  }
1210
1217
 
1218
+ .blurred-preview {
1219
+ width: 100%;
1220
+ height: 100%;
1221
+ object-fit: cover;
1222
+ }
1211
1223
 
1212
- /* Circular progress container (updated to be on top of blurred background) */
1213
- .circular-progress-container {
1224
+ .circular-progress {
1214
1225
  position: relative;
1215
- display: flex;
1216
- justify-content: center;
1217
- align-items: center;
1226
+ width: 80px;
1227
+ height: 80px;
1228
+ }
1229
+
1230
+ .circular-progress-svg {
1218
1231
  width: 100%;
1219
1232
  height: 100%;
1233
+ transform: rotate(-90deg);
1234
+ }
1235
+
1236
+ .circular-progress-track {
1237
+ fill: none;
1238
+ stroke: #eee;
1239
+ stroke-width: 4;
1240
+ }
1241
+
1242
+ .circular-progress-bar {
1243
+ fill: none;
1244
+ stroke: #4CAF50;
1245
+ stroke-width: 4;
1246
+ stroke-linecap: round;
1247
+ transition: stroke-dasharray 0.3s ease;
1248
+ }
1249
+
1250
+ .circular-progress-text {
1251
+ position: absolute;
1252
+ top: 50%;
1253
+ left: 50%;
1254
+ transform: translate(-50%, -50%);
1255
+ font-size: 16px;
1256
+ font-weight: bold;
1257
+ color: #333;
1220
1258
  }
1221
1259
 
1222
1260
  /* Error message (on top of blurred background) */