ajaxter-chat 3.0.8 → 3.0.10

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.
Files changed (35) hide show
  1. package/dist/components/ChatScreen/index.d.ts +2 -0
  2. package/dist/components/ChatScreen/index.js +283 -34
  3. package/dist/components/ChatWidget.js +111 -15
  4. package/dist/components/HomeScreen/index.d.ts +2 -0
  5. package/dist/components/HomeScreen/index.js +2 -2
  6. package/dist/components/Tabs/BottomTabs.d.ts +0 -1
  7. package/dist/components/Tabs/BottomTabs.js +13 -20
  8. package/dist/components/TicketDetailScreen/index.d.ts +9 -0
  9. package/dist/components/TicketDetailScreen/index.js +46 -0
  10. package/dist/components/TicketFormScreen/index.d.ts +9 -0
  11. package/dist/components/TicketFormScreen/index.js +76 -0
  12. package/dist/components/TicketScreen/index.d.ts +2 -1
  13. package/dist/components/TicketScreen/index.js +8 -35
  14. package/dist/components/UserListScreen/index.d.ts +4 -0
  15. package/dist/components/UserListScreen/index.js +21 -3
  16. package/dist/types/index.d.ts +3 -1
  17. package/dist/utils/fileName.d.ts +2 -0
  18. package/dist/utils/fileName.js +7 -0
  19. package/dist/utils/messageSound.d.ts +4 -0
  20. package/dist/utils/messageSound.js +51 -0
  21. package/dist/utils/widgetSession.d.ts +13 -0
  22. package/dist/utils/widgetSession.js +24 -0
  23. package/package.json +1 -1
  24. package/src/components/ChatScreen/index.tsx +415 -58
  25. package/src/components/ChatWidget.tsx +140 -17
  26. package/src/components/HomeScreen/index.tsx +4 -2
  27. package/src/components/Tabs/BottomTabs.tsx +2 -22
  28. package/src/components/TicketDetailScreen/index.tsx +111 -0
  29. package/src/components/TicketFormScreen/index.tsx +151 -0
  30. package/src/components/TicketScreen/index.tsx +18 -58
  31. package/src/components/UserListScreen/index.tsx +51 -5
  32. package/src/types/index.ts +4 -0
  33. package/src/utils/fileName.ts +6 -0
  34. package/src/utils/messageSound.ts +47 -0
  35. package/src/utils/widgetSession.ts +34 -0
@@ -19,6 +19,8 @@ interface ChatScreenProps {
19
19
  /** Other devs (excl. viewer) — for transfer when staff chats with a customer */
20
20
  otherDevelopers?: ChatUser[];
21
21
  onTransferToDeveloper?: (dev: ChatUser) => void;
22
+ messageSoundEnabled?: boolean;
23
+ onToggleMessageSound?: (enabled: boolean) => void;
22
24
  }
23
25
  export declare const ChatScreen: React.FC<ChatScreenProps>;
24
26
  export {};
@@ -1,26 +1,31 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from '../../utils/chat';
4
+ import { shortAttachmentLabel } from '../../utils/fileName';
4
5
  import { shouldShowPrivacyNotice, dismissPrivacyNotice } from '../../utils/privacyConsent';
5
6
  import { EmojiPicker } from '../EmojiPicker';
6
- import { SlideNavMenu } from '../SlideNavMenu';
7
- export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, }) => {
8
- var _a;
7
+ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, messageSoundEnabled = true, onToggleMessageSound, }) => {
9
8
  const [text, setText] = useState('');
10
9
  const [showEmoji, setShowEmoji] = useState(false);
11
10
  const [showMenu, setShowMenu] = useState(false);
12
- const [slideMenuOpen, setSlideMenuOpen] = useState(false);
13
11
  const [transferOpen, setTransferOpen] = useState(false);
14
12
  const [isRecording, setIsRecording] = useState(false);
15
13
  const [recordSec, setRecordSec] = useState(0);
16
14
  const [showConfirm, setShowConfirm] = useState(null);
17
15
  const [showPrivacy, setShowPrivacy] = useState(false);
16
+ const [pendingAttach, setPendingAttach] = useState(null);
17
+ const [waveBars, setWaveBars] = useState(() => Array(24).fill(0.08));
18
18
  const endRef = useRef(null);
19
19
  const inputRef = useRef(null);
20
20
  const fileRef = useRef(null);
21
21
  const recordTimer = useRef(null);
22
22
  const mediaRecorder = useRef(null);
23
23
  const recordChunks = useRef([]);
24
+ const discardRecordingRef = useRef(false);
25
+ const waveStreamRef = useRef(null);
26
+ const audioCtxRef = useRef(null);
27
+ const analyserRef = useRef(null);
28
+ const waveRafRef = useRef(0);
24
29
  useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
25
30
  const privacyEnabled = config.showPrivacyNotice !== false;
26
31
  useEffect(() => {
@@ -40,63 +45,189 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
40
45
  dismissPrivacyNotice(config.id);
41
46
  setShowPrivacy(false);
42
47
  }, [config.id]);
48
+ const clearPendingAttach = useCallback((revoke) => {
49
+ setPendingAttach(prev => {
50
+ if (prev && revoke)
51
+ URL.revokeObjectURL(prev.url);
52
+ return null;
53
+ });
54
+ }, []);
43
55
  const handleSend = useCallback(() => {
44
- var _a;
45
- if (!text.trim() || isPaused || isBlocked)
56
+ var _a, _b;
57
+ if (isPaused || isBlocked)
58
+ return;
59
+ if (pendingAttach) {
60
+ const { file, url } = pendingAttach;
61
+ const body = text.trim();
62
+ onSend(body || ' ', 'attachment', {
63
+ attachmentName: file.name,
64
+ attachmentSize: `${(file.size / 1024).toFixed(1)} KB`,
65
+ attachmentUrl: url,
66
+ attachmentMime: file.type,
67
+ });
68
+ setPendingAttach(null);
69
+ setText('');
70
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
71
+ return;
72
+ }
73
+ if (!text.trim())
46
74
  return;
47
75
  onSend(text.trim());
48
76
  setText('');
49
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
50
- }, [text, isPaused, isBlocked, onSend]);
77
+ (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus();
78
+ }, [text, isPaused, isBlocked, onSend, pendingAttach]);
51
79
  const handleKey = (e) => {
52
80
  if (e.key === 'Enter' && !e.shiftKey) {
53
81
  e.preventDefault();
54
82
  handleSend();
55
83
  }
56
84
  };
85
+ const recordSecRef = useRef(0);
86
+ const stopWaveLoop = useCallback(() => {
87
+ var _a;
88
+ if (waveRafRef.current) {
89
+ cancelAnimationFrame(waveRafRef.current);
90
+ waveRafRef.current = 0;
91
+ }
92
+ analyserRef.current = null;
93
+ void ((_a = audioCtxRef.current) === null || _a === void 0 ? void 0 : _a.close());
94
+ audioCtxRef.current = null;
95
+ waveStreamRef.current = null;
96
+ setWaveBars(Array(24).fill(0.08));
97
+ }, []);
57
98
  const startRecording = async () => {
58
99
  if (isPaused || isBlocked)
59
100
  return;
60
101
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
102
+ waveStreamRef.current = stream;
103
+ discardRecordingRef.current = false;
104
+ setRecordSec(0);
105
+ recordSecRef.current = 0;
106
+ try {
107
+ const audioCtx = new AudioContext();
108
+ await audioCtx.resume();
109
+ audioCtxRef.current = audioCtx;
110
+ const source = audioCtx.createMediaStreamSource(stream);
111
+ const analyser = audioCtx.createAnalyser();
112
+ analyser.fftSize = 128;
113
+ analyser.smoothingTimeConstant = 0.65;
114
+ source.connect(analyser);
115
+ analyserRef.current = analyser;
116
+ const data = new Uint8Array(analyser.frequencyBinCount);
117
+ const tick = () => {
118
+ const a = analyserRef.current;
119
+ if (!a)
120
+ return;
121
+ a.getByteFrequencyData(data);
122
+ const bars = [];
123
+ const step = Math.max(1, Math.floor(data.length / 24));
124
+ for (let i = 0; i < 24; i++) {
125
+ const v = data[Math.min(i * step, data.length - 1)] / 255;
126
+ bars.push(Math.max(0.08, v));
127
+ }
128
+ setWaveBars(bars);
129
+ waveRafRef.current = requestAnimationFrame(tick);
130
+ };
131
+ waveRafRef.current = requestAnimationFrame(tick);
132
+ }
133
+ catch (_a) {
134
+ /* optional waveform */
135
+ }
61
136
  recordChunks.current = [];
62
137
  const mr = new MediaRecorder(stream);
63
138
  mediaRecorder.current = mr;
64
139
  mr.ondataavailable = e => { if (e.data.size)
65
140
  recordChunks.current.push(e.data); };
66
141
  mr.onstop = () => {
142
+ stopWaveLoop();
67
143
  stream.getTracks().forEach(t => t.stop());
68
144
  const chunks = recordChunks.current;
145
+ if (discardRecordingRef.current) {
146
+ discardRecordingRef.current = false;
147
+ setRecordSec(0);
148
+ recordSecRef.current = 0;
149
+ return;
150
+ }
69
151
  if (!chunks.length) {
70
152
  setRecordSec(0);
153
+ recordSecRef.current = 0;
71
154
  return;
72
155
  }
73
156
  const blob = new Blob(chunks, { type: chunks[0] instanceof Blob ? chunks[0].type : 'audio/webm' });
74
157
  const voiceUrl = URL.createObjectURL(blob);
75
- const dur = Math.max(1, recordSec);
158
+ const dur = Math.max(1, recordSecRef.current);
76
159
  onSend('Voice message', 'voice', { voiceDuration: dur, voiceUrl });
77
160
  setRecordSec(0);
161
+ recordSecRef.current = 0;
78
162
  };
79
163
  mr.start(200);
80
164
  setIsRecording(true);
81
- recordTimer.current = setInterval(() => setRecordSec(s => s + 1), 1000);
165
+ recordTimer.current = setInterval(() => {
166
+ setRecordSec(s => {
167
+ const n = s + 1;
168
+ recordSecRef.current = n;
169
+ return n;
170
+ });
171
+ }, 1000);
82
172
  };
83
- const stopRecording = () => {
173
+ const cancelRecording = () => {
84
174
  var _a;
175
+ if (!isRecording)
176
+ return;
177
+ discardRecordingRef.current = true;
178
+ if (recordTimer.current) {
179
+ clearInterval(recordTimer.current);
180
+ recordTimer.current = null;
181
+ }
85
182
  (_a = mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.stop();
86
- if (recordTimer.current)
183
+ setIsRecording(false);
184
+ };
185
+ const stopRecordingSend = () => {
186
+ var _a;
187
+ if (!isRecording)
188
+ return;
189
+ discardRecordingRef.current = false;
190
+ if (recordTimer.current) {
87
191
  clearInterval(recordTimer.current);
192
+ recordTimer.current = null;
193
+ }
194
+ (_a = mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.stop();
88
195
  setIsRecording(false);
89
196
  };
197
+ const handlePaste = (e) => {
198
+ var _a;
199
+ if (isPaused || isBlocked || !config.allowAttachment)
200
+ return;
201
+ const items = (_a = e.clipboardData) === null || _a === void 0 ? void 0 : _a.items;
202
+ if (!(items === null || items === void 0 ? void 0 : items.length))
203
+ return;
204
+ for (let i = 0; i < items.length; i++) {
205
+ const item = items[i];
206
+ if (item.kind === 'file' && item.type.startsWith('image/')) {
207
+ const f = item.getAsFile();
208
+ if (f) {
209
+ e.preventDefault();
210
+ const url = URL.createObjectURL(f);
211
+ setPendingAttach(prev => {
212
+ if (prev)
213
+ URL.revokeObjectURL(prev.url);
214
+ return { file: f, url };
215
+ });
216
+ return;
217
+ }
218
+ }
219
+ }
220
+ };
90
221
  const handleFileChange = (e) => {
91
222
  var _a;
92
223
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
93
224
  if (!file || isPaused || isBlocked)
94
225
  return;
95
- const attachmentUrl = URL.createObjectURL(file);
96
- onSend(file.name, 'attachment', {
97
- attachmentName: file.name,
98
- attachmentSize: `${(file.size / 1024).toFixed(1)} KB`,
99
- attachmentUrl,
226
+ const url = URL.createObjectURL(file);
227
+ setPendingAttach(prev => {
228
+ if (prev)
229
+ URL.revokeObjectURL(prev.url);
230
+ return { file, url };
100
231
  });
101
232
  e.target.value = '';
102
233
  };
@@ -122,10 +253,35 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
122
253
  const headerRole = viewerIsDev
123
254
  ? (activeUser.type === 'user' ? 'Customer' : 'Developer')
124
255
  : 'Support';
125
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative', overflow: 'hidden' }, children: [_jsx(SlideNavMenu, { open: slideMenuOpen, onClose: () => setSlideMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, viewerType: (_a = config.viewerType) !== null && _a !== void 0 ? _a : 'user', onSelect: onNavAction, onBackHome: onBack }), _jsxs("div", { style: {
256
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative', overflow: 'hidden' }, children: [_jsxs("div", { style: {
126
257
  background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
127
258
  padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
128
- }, children: [_jsx("button", { type: "button", onClick: () => setSlideMenuOpen(true), style: hdrBtn, "aria-label": "Open menu", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [_jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" })] }) }), _jsxs("div", { style: { width: 36, height: 36, borderRadius: '50%', backgroundColor: peerAvatar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700, fontSize: 13, flexShrink: 0, position: 'relative' }, children: [peerInit, _jsx("span", { style: { position: 'absolute', bottom: 0, right: 0, width: 9, height: 9, borderRadius: '50%', border: '2px solid', borderColor: 'transparent', backgroundColor: activeUser.status === 'online' ? '#22c55e' : activeUser.status === 'away' ? '#f59e0b' : '#9ca3af' } })] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: activeUser.name }), _jsx("div", { style: { fontSize: 11, color: 'rgba(255,255,255,0.8)' }, children: activeUser.designation })] }), _jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#fff', opacity: 0.95, flexShrink: 0 }, children: headerRole }), config.allowWebCall && (_jsx("button", { type: "button", onClick: () => onStartCall(false), style: hdrBtn, title: "Voice Call", children: _jsx("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }) })), _jsx("button", { type: "button", onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.2)' }), title: "More options", "aria-expanded": showMenu, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "12", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "19", r: "1.5", fill: "#fff" })] }) })] }), showMenu && (_jsxs("div", { style: { position: 'absolute', top: 52, right: 12, zIndex: 120, background: '#fff', borderRadius: 12, boxShadow: '0 8px 30px rgba(0,0,0,0.16)', padding: '6px', minWidth: 200, animation: 'cw-fadeUp 0.18s ease' }, children: [config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), viewerIsDev && activeUser.type === 'user' && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx(MenuItem, { icon: "\uD83D\uDD00", label: "Transfer to developer", onClick: () => { setShowMenu(false); setTransferOpen(true); } })), _jsx(MenuItem, { icon: isPaused ? '▶️' : '⏸', label: isPaused ? 'Resume Chat' : 'Pause Chat', onClick: () => { setShowMenu(false); setShowConfirm('pause'); } }), config.allowReport && !isReported && (_jsx(MenuItem, { icon: "\u26A0\uFE0F", label: "Report Chat", onClick: () => { setShowMenu(false); setShowConfirm('report'); } })), config.allowBlock && activeUser.type === 'user' && !isBlocked && (_jsx(MenuItem, { icon: "\uD83D\uDEAB", label: "Block User", onClick: () => { setShowMenu(false); setShowConfirm('block'); }, danger: true })), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), _jsx(MenuItem, { icon: "\u2715", label: "Close Chat", onClick: onClose })] })), isPaused && (_jsxs("div", { style: { background: '#fef3c7', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#92400e', textAlign: 'center', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }, children: ["\u23F8 Chat is paused \u2014 users cannot send messages", _jsx("button", { type: "button", onClick: onTogglePause, style: { background: '#92400e', color: '#fff', border: 'none', borderRadius: 6, padding: '2px 8px', fontSize: 11, cursor: 'pointer', marginLeft: 4 }, children: "Resume" })] })), isBlocked && (_jsx("div", { style: { background: '#fee2e2', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#991b1b', textAlign: 'center', flexShrink: 0 }, children: "\uD83D\uDEAB This user is blocked" })), isReported && (_jsx("div", { style: { background: '#fef3c7', padding: '6px 16px', fontSize: 11, color: '#92400e', textAlign: 'center', flexShrink: 0 }, children: "\u26A0\uFE0F This chat has been reported" })), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '14px', display: 'flex', flexDirection: 'column', gap: 10, background: '#f8f9fc' }, className: "cw-scroll", children: [grouped.map(({ date, msgs }) => (_jsxs(React.Fragment, { children: [_jsx(DateDivider, { label: date }), msgs.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: config.primaryColor }, msg.id)))] }, date))), messages.length === 0 && (_jsxs("div", { style: { margin: 'auto', textAlign: 'center', color: '#c4cad4', fontSize: 13 }, children: [_jsx("div", { style: { fontSize: 28, marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hello to ", activeUser.name, "!"] })), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #eef0f5', padding: '10px 12px 8px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [privacyEnabled && showPrivacy && (_jsxs("div", { style: {
259
+ }, children: [_jsx("button", { type: "button", onClick: onBack, style: hdrBtn, "aria-label": "Back", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M19 12H5M5 12L12 19M5 12L12 5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs("div", { style: { width: 36, height: 36, borderRadius: '50%', backgroundColor: peerAvatar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700, fontSize: 13, flexShrink: 0, position: 'relative' }, children: [peerInit, _jsx("span", { style: { position: 'absolute', bottom: 0, right: 0, width: 9, height: 9, borderRadius: '50%', border: '2px solid', borderColor: 'transparent', backgroundColor: activeUser.status === 'online' ? '#22c55e' : activeUser.status === 'away' ? '#f59e0b' : '#9ca3af' } })] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: activeUser.name }), _jsx("div", { style: { fontSize: 11, color: 'rgba(255,255,255,0.8)' }, children: activeUser.designation })] }), _jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#fff', opacity: 0.95, flexShrink: 0 }, children: headerRole }), onToggleMessageSound && (_jsxs("label", { style: {
260
+ display: 'flex',
261
+ alignItems: 'center',
262
+ gap: 6,
263
+ cursor: 'pointer',
264
+ flexShrink: 0,
265
+ marginLeft: 4,
266
+ }, children: [_jsx("span", { style: { fontSize: 10, color: 'rgba(255,255,255,0.85)', fontWeight: 600 }, children: "Sound" }), _jsx("button", { type: "button", role: "switch", "aria-checked": messageSoundEnabled, onClick: () => onToggleMessageSound(!messageSoundEnabled), style: {
267
+ width: 36,
268
+ height: 20,
269
+ borderRadius: 10,
270
+ border: 'none',
271
+ background: messageSoundEnabled ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.2)',
272
+ position: 'relative',
273
+ cursor: 'pointer',
274
+ padding: 0,
275
+ }, children: _jsx("span", { style: {
276
+ position: 'absolute',
277
+ top: 2,
278
+ left: messageSoundEnabled ? 18 : 2,
279
+ width: 16,
280
+ height: 16,
281
+ borderRadius: '50%',
282
+ background: '#fff',
283
+ transition: 'left 0.15s ease',
284
+ } }) })] })), config.allowWebCall && (_jsx("button", { type: "button", onClick: () => onStartCall(false), style: hdrBtn, title: "Voice Call", children: _jsx("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }) })), _jsx("button", { type: "button", onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.2)' }), title: "More options", "aria-expanded": showMenu, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "12", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "19", r: "1.5", fill: "#fff" })] }) })] }), showMenu && (_jsxs("div", { style: { position: 'absolute', top: 52, right: 12, zIndex: 120, background: '#fff', borderRadius: 12, boxShadow: '0 8px 30px rgba(0,0,0,0.16)', padding: '6px', minWidth: 200, animation: 'cw-fadeUp 0.18s ease' }, children: [navEntriesForChat(config.chatType, viewerIsDev).map(item => (_jsx(MenuItem, { icon: item.icon, label: item.label, onClick: () => { setShowMenu(false); onNavAction(item.key); } }, item.key))), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), viewerIsDev && activeUser.type === 'user' && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx(MenuItem, { icon: "\uD83D\uDD00", label: "Transfer to developer", onClick: () => { setShowMenu(false); setTransferOpen(true); } })), _jsx(MenuItem, { icon: isPaused ? '▶️' : '⏸', label: isPaused ? 'Resume Chat' : 'Pause Chat', onClick: () => { setShowMenu(false); setShowConfirm('pause'); } }), config.allowReport && !isReported && (_jsx(MenuItem, { icon: "\u26A0\uFE0F", label: "Report Chat", onClick: () => { setShowMenu(false); setShowConfirm('report'); } })), config.allowBlock && activeUser.type === 'user' && !isBlocked && (_jsx(MenuItem, { icon: "\uD83D\uDEAB", label: "Block User", onClick: () => { setShowMenu(false); setShowConfirm('block'); }, danger: true })), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), _jsx(MenuItem, { icon: "\u2715", label: "Close Chat", onClick: onClose })] })), isPaused && (_jsxs("div", { style: { background: '#fef3c7', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#92400e', textAlign: 'center', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }, children: ["\u23F8 Chat is paused \u2014 users cannot send messages", _jsx("button", { type: "button", onClick: onTogglePause, style: { background: '#92400e', color: '#fff', border: 'none', borderRadius: 6, padding: '2px 8px', fontSize: 11, cursor: 'pointer', marginLeft: 4 }, children: "Resume" })] })), isBlocked && (_jsx("div", { style: { background: '#fee2e2', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#991b1b', textAlign: 'center', flexShrink: 0 }, children: "\uD83D\uDEAB This user is blocked" })), isReported && (_jsx("div", { style: { background: '#fef3c7', padding: '6px 16px', fontSize: 11, color: '#92400e', textAlign: 'center', flexShrink: 0 }, children: "\u26A0\uFE0F This chat has been reported" })), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '14px', display: 'flex', flexDirection: 'column', gap: 10, background: '#f8f9fc' }, className: "cw-scroll", children: [grouped.map(({ date, msgs }) => (_jsxs(React.Fragment, { children: [_jsx(DateDivider, { label: date }), msgs.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: config.primaryColor }, msg.id)))] }, date))), messages.length === 0 && (_jsxs("div", { style: { margin: 'auto', textAlign: 'center', color: '#c4cad4', fontSize: 13 }, children: [_jsx("div", { style: { fontSize: 28, marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hello to ", activeUser.name, "!"] })), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #eef0f5', padding: '10px 12px 8px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [privacyEnabled && showPrivacy && (_jsxs("div", { style: {
129
285
  position: 'relative',
130
286
  marginBottom: 10,
131
287
  padding: '12px 36px 12px 12px',
@@ -148,12 +304,90 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
148
304
  justifyContent: 'center',
149
305
  padding: 0,
150
306
  lineHeight: 1,
151
- }, children: _jsx("span", { style: { fontSize: 14, color: '#475569', fontWeight: 700 }, children: "\u00D7" }) }), _jsxs("p", { style: { margin: 0, fontSize: 12, color: '#64748b', lineHeight: 1.55 }, children: ["By chatting here, you agree we and authorized partners may process, monitor, and record this chat and your data in line with", ' ', config.privacyPolicyUrl ? (_jsx("a", { href: config.privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", style: { color: config.primaryColor, textDecoration: 'underline', fontWeight: 600 }, children: "Privacy Policy" })) : (_jsx("span", { style: { textDecoration: 'underline', fontWeight: 600 }, children: "Privacy Policy" })), "."] })] })), showEmoji && config.allowEmoji && (_jsx(EmojiPicker, { primaryColor: config.primaryColor, onSelect: e => setText(t => t + e), onClose: () => setShowEmoji(false) })), isRecording && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, padding: '8px 12px', background: '#fee2e2', borderRadius: 10 }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: '50%', background: '#ef4444', display: 'inline-block', animation: 'cw-pulse 1s infinite' } }), _jsxs("span", { style: { fontSize: 13, color: '#991b1b', fontWeight: 600 }, children: ["Recording ", recordSec, "s"] }), _jsx("button", { type: "button", onClick: stopRecording, style: { marginLeft: 'auto', background: '#ef4444', color: '#fff', border: 'none', borderRadius: 6, padding: '4px 12px', fontSize: 12, cursor: 'pointer', fontWeight: 600 }, children: "Stop & send" })] })), _jsxs("div", { style: {
307
+ }, children: _jsx("span", { style: { fontSize: 14, color: '#475569', fontWeight: 700 }, children: "\u00D7" }) }), _jsxs("p", { style: { margin: 0, fontSize: 12, color: '#64748b', lineHeight: 1.55 }, children: ["By chatting here, you agree we and authorized partners may process, monitor, and record this chat and your data in line with", ' ', config.privacyPolicyUrl ? (_jsx("a", { href: config.privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", style: { color: config.primaryColor, textDecoration: 'underline', fontWeight: 600 }, children: "Privacy Policy" })) : (_jsx("span", { style: { textDecoration: 'underline', fontWeight: 600 }, children: "Privacy Policy" })), "."] })] })), showEmoji && config.allowEmoji && (_jsx(EmojiPicker, { primaryColor: config.primaryColor, onSelect: e => setText(t => t + e), onClose: () => setShowEmoji(false) })), isRecording && (_jsxs("div", { style: {
308
+ marginBottom: 10,
309
+ padding: '12px 12px 14px',
310
+ background: '#fff',
311
+ borderRadius: 14,
312
+ border: '1px solid #e8ecf1',
313
+ boxShadow: '0 1px 4px rgba(15,23,42,0.06)',
314
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10, marginBottom: 10 }, children: [_jsx("button", { type: "button", onClick: cancelRecording, title: "Discard recording", "aria-label": "Discard recording", style: {
315
+ background: 'none',
316
+ border: 'none',
317
+ cursor: 'pointer',
318
+ padding: 6,
319
+ lineHeight: 0,
320
+ flexShrink: 0,
321
+ }, children: _jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6", stroke: "#ef4444", strokeWidth: "2", strokeLinecap: "round" }), _jsx("path", { d: "M10 11v6M14 11v6", stroke: "#ef4444", strokeWidth: "2", strokeLinecap: "round" })] }) }), _jsx("div", { style: { display: 'flex', alignItems: 'flex-end', gap: 3, height: 44, flex: 1, justifyContent: 'flex-end', minWidth: 0 }, children: waveBars.map((h, i) => (_jsx("span", { style: {
322
+ width: 3,
323
+ borderRadius: 2,
324
+ background: '#cbd5e1',
325
+ height: `${8 + h * 36}px`,
326
+ transition: 'height 0.05s ease-out',
327
+ } }, i))) })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }, children: [_jsx("div", { style: { flex: 1 } }), _jsx("div", { style: {
328
+ background: '#ef4444',
329
+ color: '#fff',
330
+ fontWeight: 700,
331
+ fontSize: 13,
332
+ padding: '6px 14px',
333
+ borderRadius: 999,
334
+ minWidth: 52,
335
+ textAlign: 'center',
336
+ }, children: fmtTime(recordSec) }), _jsx("button", { type: "button", onClick: stopRecordingSend, title: "Send voice message", "aria-label": "Send voice message", style: {
337
+ width: 44,
338
+ height: 44,
339
+ borderRadius: '50%',
340
+ border: 'none',
341
+ background: config.primaryColor,
342
+ cursor: 'pointer',
343
+ display: 'flex',
344
+ alignItems: 'center',
345
+ justifyContent: 'center',
346
+ flexShrink: 0,
347
+ boxShadow: `0 4px 14px ${config.primaryColor}55`,
348
+ }, children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] })), _jsxs("div", { style: {
152
349
  border: `1.5px solid ${isPaused || isBlocked ? '#e5e7eb' : '#bfdbfe'}`,
153
350
  borderRadius: 16,
154
351
  padding: '10px 12px 8px',
155
352
  background: isPaused || isBlocked ? '#f9fafb' : '#fff',
156
- }, children: [_jsx("textarea", { ref: inputRef, value: text, onChange: e => setText(e.target.value), onKeyDown: handleKey, placeholder: isPaused || isBlocked ? 'Chat is unavailable' : 'Compose your message…', disabled: isPaused || isBlocked || isRecording, rows: 2, style: {
353
+ }, children: [pendingAttach && (_jsxs("div", { style: {
354
+ display: 'flex',
355
+ alignItems: 'center',
356
+ gap: 10,
357
+ marginBottom: 10,
358
+ padding: '8px 10px',
359
+ borderRadius: 10,
360
+ background: '#f8fafc',
361
+ border: '1px solid #fecaca',
362
+ position: 'relative',
363
+ }, children: [_jsx("div", { style: {
364
+ width: 40,
365
+ height: 40,
366
+ borderRadius: 8,
367
+ background: config.primaryColor,
368
+ display: 'flex',
369
+ alignItems: 'center',
370
+ justifyContent: 'center',
371
+ flexShrink: 0,
372
+ }, children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 13, color: '#1a2332', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, title: pendingAttach.file.name, children: pendingAttach.file.name }), _jsx("div", { style: { fontSize: 11, color: '#94a3b8', fontWeight: 600, textTransform: 'uppercase' }, children: (pendingAttach.file.type.split('/')[1] || 'file').slice(0, 8) })] }), _jsx("button", { type: "button", onClick: () => clearPendingAttach(true), title: "Remove attachment", "aria-label": "Remove attachment", style: {
373
+ position: 'absolute',
374
+ top: 6,
375
+ right: 6,
376
+ width: 22,
377
+ height: 22,
378
+ borderRadius: '50%',
379
+ border: 'none',
380
+ background: '#ef4444',
381
+ color: '#fff',
382
+ cursor: 'pointer',
383
+ fontSize: 15,
384
+ fontWeight: 700,
385
+ lineHeight: 1,
386
+ display: 'flex',
387
+ alignItems: 'center',
388
+ justifyContent: 'center',
389
+ padding: 0,
390
+ }, children: "\u00D7" })] })), _jsx("textarea", { ref: inputRef, value: text, onChange: e => setText(e.target.value), onKeyDown: handleKey, onPaste: handlePaste, placeholder: isPaused || isBlocked ? 'Chat is unavailable' : 'Compose your message…', disabled: isPaused || isBlocked || isRecording, rows: 2, style: {
157
391
  width: '100%',
158
392
  resize: 'none',
159
393
  border: 'none',
@@ -166,19 +400,19 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
166
400
  overflowY: 'auto',
167
401
  fontFamily: 'inherit',
168
402
  marginBottom: 8,
169
- } }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [config.allowEmoji && (_jsx(ActionBtn, { onClick: () => setShowEmoji(v => !v), title: "Emoji", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#94a3b8", strokeWidth: "1.8" }), _jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("circle", { cx: "9", cy: "9", r: "1", fill: "#94a3b8" }), _jsx("circle", { cx: "15", cy: "9", r: "1", fill: "#94a3b8" })] }) })), config.allowAttachment && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileRef, type: "file", style: { display: 'none' }, onChange: handleFileChange }), _jsx(ActionBtn, { onClick: () => { var _a; return (_a = fileRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, title: "Attach file", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })), config.allowVoiceMessage && !isRecording && (_jsx(ActionBtn, { onClick: startRecording, title: "Voice message", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("path", { d: "M19 10v2a7 7 0 01-14 0v-2M12 19v4M8 23h8", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round" })] }) }))] }), _jsx("button", { type: "button", onClick: handleSend, disabled: !text.trim() || isPaused || isBlocked || isRecording, style: {
403
+ } }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 2 }, children: [config.allowEmoji && (_jsx(ActionBtn, { onClick: () => setShowEmoji(v => !v), title: "Emoji", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#94a3b8", strokeWidth: "1.8" }), _jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("circle", { cx: "9", cy: "9", r: "1", fill: "#94a3b8" }), _jsx("circle", { cx: "15", cy: "9", r: "1", fill: "#94a3b8" })] }) })), config.allowAttachment && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileRef, type: "file", style: { display: 'none' }, onChange: handleFileChange }), _jsx(ActionBtn, { onClick: () => { var _a; return (_a = fileRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, title: "Attach file", children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })), config.allowVoiceMessage && !isRecording && (_jsx(ActionBtn, { onClick: startRecording, title: "Voice message", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("path", { d: "M19 10v2a7 7 0 01-14 0v-2M12 19v4M8 23h8", stroke: "#94a3b8", strokeWidth: "1.8", strokeLinecap: "round" })] }) }))] }), _jsx("button", { type: "button", onClick: handleSend, disabled: (!text.trim() && !pendingAttach) || isPaused || isBlocked || isRecording, style: {
170
404
  width: 36,
171
405
  height: 36,
172
406
  borderRadius: '50%',
173
- backgroundColor: text.trim() && !isPaused && !isBlocked ? config.primaryColor : '#e2e8f0',
407
+ backgroundColor: (text.trim() || pendingAttach) && !isPaused && !isBlocked ? config.primaryColor : '#e2e8f0',
174
408
  border: 'none',
175
- cursor: text.trim() && !isPaused && !isBlocked ? 'pointer' : 'default',
409
+ cursor: (text.trim() || pendingAttach) && !isPaused && !isBlocked ? 'pointer' : 'default',
176
410
  display: 'flex',
177
411
  alignItems: 'center',
178
412
  justifyContent: 'center',
179
413
  flexShrink: 0,
180
414
  transition: 'background 0.15s',
181
- }, title: "Send", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: text.trim() && !isPaused && !isBlocked ? '#fff' : '#94a3b8', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] }), (config.footerPoweredBy || config.branch) && (_jsxs("p", { style: { margin: '10px 0 0', textAlign: 'center', fontSize: 12, color: '#94a3b8' }, children: [config.footerPoweredBy, config.footerPoweredBy && config.branch ? ' · ' : '', config.branch && _jsx("span", { style: { fontWeight: 600, color: '#64748b' }, children: config.branch })] }))] }), transferOpen && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx("div", { style: {
415
+ }, title: "Send", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: (text.trim() || pendingAttach) && !isPaused && !isBlocked ? '#fff' : '#94a3b8', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] }), (config.footerPoweredBy || config.branch) && (_jsxs("p", { style: { margin: '10px 0 0', textAlign: 'center', fontSize: 12, color: '#94a3b8' }, children: [config.footerPoweredBy, config.footerPoweredBy && config.branch ? ' · ' : '', config.branch && _jsx("span", { style: { fontWeight: 600, color: '#64748b' }, children: config.branch })] }))] }), transferOpen && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx("div", { style: {
182
416
  position: 'absolute',
183
417
  inset: 0,
184
418
  background: 'rgba(0,0,0,0.45)',
@@ -286,20 +520,24 @@ const VoiceRow = ({ msg, isMe, primaryColor }) => {
286
520
  }, "aria-label": playing ? 'Pause' : 'Play', children: playing ? (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", children: [_jsx("rect", { x: "6", y: "4", width: "4", height: "16" }), _jsx("rect", { x: "14", y: "4", width: "4", height: "16" })] })) : (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) })) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { height: 4, borderRadius: 2, background: isMe ? 'rgba(255,255,255,0.35)' : '#e2e8f0', overflow: 'hidden' }, children: _jsx("div", { style: { width: `${pct}%`, height: '100%', background: isMe ? '#fff' : primaryColor, borderRadius: 2, transition: 'width 0.1s linear' } }) }), _jsx("div", { style: { fontSize: 11, marginTop: 4, opacity: 0.9 }, children: timeLabel })] })] }));
287
521
  };
288
522
  const AttachmentRow = ({ msg, isMe, primaryColor }) => {
289
- var _a;
523
+ var _a, _b;
290
524
  const name = (_a = msg.attachmentName) !== null && _a !== void 0 ? _a : 'File';
291
525
  const href = msg.attachmentUrl;
292
- return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }, children: [_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: isMe ? '#fff' : '#334155', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, wordBreak: 'break-word' }, children: name }), msg.attachmentSize && _jsx("div", { style: { fontSize: 11, opacity: 0.8 }, children: msg.attachmentSize })] }), href && (_jsx("a", { href: href, download: name, style: {
293
- fontSize: 12,
294
- fontWeight: 700,
295
- color: isMe ? '#fff' : primaryColor,
296
- textDecoration: 'underline',
297
- whiteSpace: 'nowrap',
298
- }, children: "Download" }))] }));
526
+ const label = shortAttachmentLabel(name, 10);
527
+ const mime = (_b = msg.attachmentMime) !== null && _b !== void 0 ? _b : '';
528
+ const isImage = mime.startsWith('image/') || /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(name);
529
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'stretch', gap: 8, flexWrap: 'wrap' }, children: [isImage && href && (_jsx("a", { href: href, download: name, title: name, style: { alignSelf: 'flex-start', lineHeight: 0 }, children: _jsx("img", { src: href, alt: "", style: { maxWidth: 220, maxHeight: 200, borderRadius: 10, objectFit: 'cover', display: 'block' } }) })), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10 }, children: [!isImage && (_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66L9.41 17.41a2 2 0 01-2.83-2.83l8.49-8.48", stroke: isMe ? '#fff' : '#334155', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [href ? (_jsxs("a", { href: href, download: name, title: name, style: {
530
+ fontWeight: 700,
531
+ fontSize: 14,
532
+ wordBreak: 'break-word',
533
+ color: isMe ? '#fff' : primaryColor,
534
+ textDecoration: 'underline',
535
+ }, children: ["[", label, "]"] })) : (_jsxs("div", { style: { fontWeight: 700, fontSize: 14, wordBreak: 'break-word' }, title: name, children: ["[", label, "]"] })), msg.attachmentSize && _jsx("div", { style: { fontSize: 11, opacity: 0.8 }, children: msg.attachmentSize })] })] })] }));
299
536
  };
300
537
  const Bubble = ({ msg, primaryColor }) => {
301
538
  const isMe = msg.senderId === 'me';
302
- const content = msg.type === 'voice' ? (_jsx(VoiceRow, { msg: msg, isMe: isMe, primaryColor: primaryColor })) : msg.type === 'attachment' ? (_jsx(AttachmentRow, { msg: msg, isMe: isMe, primaryColor: primaryColor })) : (_jsx("span", { children: msg.text }));
539
+ const caption = msg.text.trim();
540
+ const content = msg.type === 'voice' ? (_jsx(VoiceRow, { msg: msg, isMe: isMe, primaryColor: primaryColor })) : msg.type === 'attachment' ? (_jsxs(_Fragment, { children: [_jsx(AttachmentRow, { msg: msg, isMe: isMe, primaryColor: primaryColor }), caption && caption !== ' ' && (_jsx("div", { style: { marginTop: 6, fontSize: 14, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }, children: msg.text }))] })) : (_jsx("span", { children: msg.text }));
303
541
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start', gap: 3 }, children: [_jsx("div", { style: {
304
542
  maxWidth: '85%', padding: '10px 13px',
305
543
  borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
@@ -327,6 +565,17 @@ const hdrBtn = {
327
565
  width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
328
566
  cursor: 'pointer', flexShrink: 0,
329
567
  };
568
+ function navEntriesForChat(chatType, isStaff) {
569
+ const showSupport = chatType === 'SUPPORT' || chatType === 'BOTH';
570
+ const showChat = chatType === 'CHAT' || chatType === 'BOTH';
571
+ const items = [];
572
+ if (showSupport)
573
+ items.push({ key: 'support', icon: '🛠', label: isStaff ? 'Provide Support' : 'Need Support' });
574
+ if (showChat)
575
+ items.push({ key: 'conversation', icon: '💬', label: isStaff ? 'Chat with developer' : 'New Conversation' });
576
+ items.push({ key: 'ticket', icon: '🎫', label: 'Raise ticket' });
577
+ return items;
578
+ }
330
579
  function groupByDate(messages) {
331
580
  const map = new Map();
332
581
  messages.forEach(m => {