ajaxter-chat 3.0.9 → 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.
@@ -4,24 +4,28 @@ import { avatarColor, initials, formatTime, formatDate, generateTranscript, down
4
4
  import { shortAttachmentLabel } from '../../utils/fileName';
5
5
  import { shouldShowPrivacyNotice, dismissPrivacyNotice } from '../../utils/privacyConsent';
6
6
  import { EmojiPicker } from '../EmojiPicker';
7
- import { SlideNavMenu } from '../SlideNavMenu';
8
7
  export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, messageSoundEnabled = true, onToggleMessageSound, }) => {
9
- var _a;
10
8
  const [text, setText] = useState('');
11
9
  const [showEmoji, setShowEmoji] = useState(false);
12
10
  const [showMenu, setShowMenu] = useState(false);
13
- const [slideMenuOpen, setSlideMenuOpen] = useState(false);
14
11
  const [transferOpen, setTransferOpen] = useState(false);
15
12
  const [isRecording, setIsRecording] = useState(false);
16
13
  const [recordSec, setRecordSec] = useState(0);
17
14
  const [showConfirm, setShowConfirm] = useState(null);
18
15
  const [showPrivacy, setShowPrivacy] = useState(false);
16
+ const [pendingAttach, setPendingAttach] = useState(null);
17
+ const [waveBars, setWaveBars] = useState(() => Array(24).fill(0.08));
19
18
  const endRef = useRef(null);
20
19
  const inputRef = useRef(null);
21
20
  const fileRef = useRef(null);
22
21
  const recordTimer = useRef(null);
23
22
  const mediaRecorder = useRef(null);
24
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);
25
29
  useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
26
30
  const privacyEnabled = config.showPrivacyNotice !== false;
27
31
  useEffect(() => {
@@ -41,63 +45,189 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
41
45
  dismissPrivacyNotice(config.id);
42
46
  setShowPrivacy(false);
43
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
+ }, []);
44
55
  const handleSend = useCallback(() => {
45
- var _a;
46
- 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())
47
74
  return;
48
75
  onSend(text.trim());
49
76
  setText('');
50
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
51
- }, [text, isPaused, isBlocked, onSend]);
77
+ (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus();
78
+ }, [text, isPaused, isBlocked, onSend, pendingAttach]);
52
79
  const handleKey = (e) => {
53
80
  if (e.key === 'Enter' && !e.shiftKey) {
54
81
  e.preventDefault();
55
82
  handleSend();
56
83
  }
57
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
+ }, []);
58
98
  const startRecording = async () => {
59
99
  if (isPaused || isBlocked)
60
100
  return;
61
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
+ }
62
136
  recordChunks.current = [];
63
137
  const mr = new MediaRecorder(stream);
64
138
  mediaRecorder.current = mr;
65
139
  mr.ondataavailable = e => { if (e.data.size)
66
140
  recordChunks.current.push(e.data); };
67
141
  mr.onstop = () => {
142
+ stopWaveLoop();
68
143
  stream.getTracks().forEach(t => t.stop());
69
144
  const chunks = recordChunks.current;
145
+ if (discardRecordingRef.current) {
146
+ discardRecordingRef.current = false;
147
+ setRecordSec(0);
148
+ recordSecRef.current = 0;
149
+ return;
150
+ }
70
151
  if (!chunks.length) {
71
152
  setRecordSec(0);
153
+ recordSecRef.current = 0;
72
154
  return;
73
155
  }
74
156
  const blob = new Blob(chunks, { type: chunks[0] instanceof Blob ? chunks[0].type : 'audio/webm' });
75
157
  const voiceUrl = URL.createObjectURL(blob);
76
- const dur = Math.max(1, recordSec);
158
+ const dur = Math.max(1, recordSecRef.current);
77
159
  onSend('Voice message', 'voice', { voiceDuration: dur, voiceUrl });
78
160
  setRecordSec(0);
161
+ recordSecRef.current = 0;
79
162
  };
80
163
  mr.start(200);
81
164
  setIsRecording(true);
82
- 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);
83
172
  };
84
- const stopRecording = () => {
173
+ const cancelRecording = () => {
85
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
+ }
86
182
  (_a = mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.stop();
87
- 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) {
88
191
  clearInterval(recordTimer.current);
192
+ recordTimer.current = null;
193
+ }
194
+ (_a = mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.stop();
89
195
  setIsRecording(false);
90
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
+ };
91
221
  const handleFileChange = (e) => {
92
222
  var _a;
93
223
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
94
224
  if (!file || isPaused || isBlocked)
95
225
  return;
96
- const attachmentUrl = URL.createObjectURL(file);
97
- onSend(file.name, 'attachment', {
98
- attachmentName: file.name,
99
- attachmentSize: `${(file.size / 1024).toFixed(1)} KB`,
100
- attachmentUrl,
226
+ const url = URL.createObjectURL(file);
227
+ setPendingAttach(prev => {
228
+ if (prev)
229
+ URL.revokeObjectURL(prev.url);
230
+ return { file, url };
101
231
  });
102
232
  e.target.value = '';
103
233
  };
@@ -123,10 +253,10 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
123
253
  const headerRole = viewerIsDev
124
254
  ? (activeUser.type === 'user' ? 'Customer' : 'Developer')
125
255
  : 'Support';
126
- 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: {
127
257
  background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
128
258
  padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
129
- }, 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 }), onToggleMessageSound && (_jsxs("label", { 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: {
130
260
  display: 'flex',
131
261
  alignItems: 'center',
132
262
  gap: 6,
@@ -151,7 +281,7 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
151
281
  borderRadius: '50%',
152
282
  background: '#fff',
153
283
  transition: 'left 0.15s ease',
154
- } }) })] })), 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: {
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: {
155
285
  position: 'relative',
156
286
  marginBottom: 10,
157
287
  padding: '12px 36px 12px 12px',
@@ -174,12 +304,90 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
174
304
  justifyContent: 'center',
175
305
  padding: 0,
176
306
  lineHeight: 1,
177
- }, 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: {
178
349
  border: `1.5px solid ${isPaused || isBlocked ? '#e5e7eb' : '#bfdbfe'}`,
179
350
  borderRadius: 16,
180
351
  padding: '10px 12px 8px',
181
352
  background: isPaused || isBlocked ? '#f9fafb' : '#fff',
182
- }, 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: {
183
391
  width: '100%',
184
392
  resize: 'none',
185
393
  border: 'none',
@@ -192,19 +400,19 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
192
400
  overflowY: 'auto',
193
401
  fontFamily: 'inherit',
194
402
  marginBottom: 8,
195
- } }), _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: {
196
404
  width: 36,
197
405
  height: 36,
198
406
  borderRadius: '50%',
199
- backgroundColor: text.trim() && !isPaused && !isBlocked ? config.primaryColor : '#e2e8f0',
407
+ backgroundColor: (text.trim() || pendingAttach) && !isPaused && !isBlocked ? config.primaryColor : '#e2e8f0',
200
408
  border: 'none',
201
- cursor: text.trim() && !isPaused && !isBlocked ? 'pointer' : 'default',
409
+ cursor: (text.trim() || pendingAttach) && !isPaused && !isBlocked ? 'pointer' : 'default',
202
410
  display: 'flex',
203
411
  alignItems: 'center',
204
412
  justifyContent: 'center',
205
413
  flexShrink: 0,
206
414
  transition: 'background 0.15s',
207
- }, 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: {
208
416
  position: 'absolute',
209
417
  inset: 0,
210
418
  background: 'rgba(0,0,0,0.45)',
@@ -312,21 +520,24 @@ const VoiceRow = ({ msg, isMe, primaryColor }) => {
312
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 })] })] }));
313
521
  };
314
522
  const AttachmentRow = ({ msg, isMe, primaryColor }) => {
315
- var _a;
523
+ var _a, _b;
316
524
  const name = (_a = msg.attachmentName) !== null && _a !== void 0 ? _a : 'File';
317
525
  const href = msg.attachmentUrl;
318
526
  const label = shortAttachmentLabel(name, 10);
319
- 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: [href ? (_jsxs("a", { href: href, download: name, title: name, style: {
320
- fontWeight: 700,
321
- fontSize: 14,
322
- wordBreak: 'break-word',
323
- color: isMe ? '#fff' : primaryColor,
324
- textDecoration: 'underline',
325
- }, 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 })] })] }));
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 })] })] })] }));
326
536
  };
327
537
  const Bubble = ({ msg, primaryColor }) => {
328
538
  const isMe = msg.senderId === 'me';
329
- 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 }));
330
541
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start', gap: 3 }, children: [_jsx("div", { style: {
331
542
  maxWidth: '85%', padding: '10px 13px',
332
543
  borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
@@ -354,6 +565,17 @@ const hdrBtn = {
354
565
  width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
355
566
  cursor: 'pointer', flexShrink: 0,
356
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
+ }
357
579
  function groupByDate(messages) {
358
580
  const map = new Map();
359
581
  messages.forEach(m => {
@@ -364,7 +364,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
364
364
  right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
365
365
  left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
366
366
  zIndex: 20, display: 'flex', gap: 6,
367
- }, children: _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) }) })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => setScreen('home'), onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)) })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => setScreen('ticket-new'), onSelectTicket: id => { setViewingTicketId(id); setScreen('ticket-detail'); } })), screen === 'ticket-new' && (_jsx(TicketFormScreen, { config: widgetConfig, onSubmit: handleRaiseTicket, onCancel: () => setScreen('tickets') })), screen === 'ticket-detail' && viewingTicketId && ((() => {
367
+ }, children: _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) }) })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => setScreen('home'), onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && widgetConfig.viewerType !== 'developer' })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)) })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => setScreen('ticket-new'), onSelectTicket: id => { setViewingTicketId(id); setScreen('ticket-detail'); } })), screen === 'ticket-new' && (_jsx(TicketFormScreen, { config: widgetConfig, onSubmit: handleRaiseTicket, onCancel: () => setScreen('tickets') })), screen === 'ticket-detail' && viewingTicketId && ((() => {
368
368
  const t = tickets.find(x => x.id === viewingTicketId);
369
369
  return t ? (_jsx(TicketDetailScreen, { ticket: t, config: widgetConfig, onBack: () => { setViewingTicketId(null); setScreen('tickets'); } })) : null;
370
370
  })()), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
@@ -28,7 +28,7 @@ export const TicketFormScreen = ({ config, onSubmit, onCancel }) => {
28
28
  return;
29
29
  onSubmit(title.trim(), desc.trim(), priority);
30
30
  };
31
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#fff' }, children: [_jsxs("div", { style: {
31
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#fff', minHeight: 0 }, children: [_jsxs("div", { style: {
32
32
  background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
33
33
  padding: '14px 18px',
34
34
  display: 'flex',
@@ -45,7 +45,7 @@ export const TicketFormScreen = ({ config, onSubmit, onCancel }) => {
45
45
  alignItems: 'center',
46
46
  justifyContent: 'center',
47
47
  cursor: 'pointer',
48
- }, children: _jsx("svg", { width: "18", height: "18", 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", { children: [_jsx("h2", { style: { margin: 0, fontSize: 18, fontWeight: 800, color: '#fff' }, children: "New ticket" }), _jsx("p", { style: { margin: '4px 0 0', fontSize: 12, color: 'rgba(255,255,255,0.85)' }, children: "Describe your issue below" })] })] }), _jsxs("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', padding: '20px 18px' }, children: [_jsx("input", { placeholder: "Title *", value: title, onChange: e => setTitle(e.target.value), style: inputStyle(config.primaryColor), onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), _jsx("textarea", { placeholder: "Describe the issue\u2026", value: desc, onChange: e => setDesc(e.target.value), rows: 5, style: Object.assign(Object.assign({}, inputStyle(config.primaryColor)), { resize: 'none', marginTop: 12 }), onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), _jsx("div", { style: { display: 'flex', gap: 8, marginTop: 12, marginBottom: 20 }, children: ['low', 'medium', 'high'].map(p => (_jsx("button", { type: "button", onClick: () => setPriority(p), style: {
48
+ }, children: _jsx("svg", { width: "18", height: "18", 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", { children: [_jsx("h2", { style: { margin: 0, fontSize: 18, fontWeight: 800, color: '#fff' }, children: "New ticket" }), _jsx("p", { style: { margin: '4px 0 0', fontSize: 12, color: 'rgba(255,255,255,0.85)' }, children: "Describe your issue below" })] })] }), _jsxs("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', padding: '20px 18px', minHeight: 0 }, children: [_jsx("input", { placeholder: "Title *", value: title, onChange: e => setTitle(e.target.value), style: inputStyle(config.primaryColor), onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), _jsx("textarea", { placeholder: "Describe the issue\u2026", value: desc, onChange: e => setDesc(e.target.value), rows: 5, style: Object.assign(Object.assign({}, inputStyle(config.primaryColor)), { resize: 'none', marginTop: 12 }), onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), _jsx("div", { style: { display: 'flex', gap: 8, marginTop: 12, paddingBottom: 8 }, children: ['low', 'medium', 'high'].map(p => (_jsx("button", { type: "button", onClick: () => setPriority(p), style: {
49
49
  flex: 1,
50
50
  padding: '8px',
51
51
  border: `1.5px solid ${priority === p ? pm[p].color : '#e5e7eb'}`,
@@ -56,15 +56,21 @@ export const TicketFormScreen = ({ config, onSubmit, onCancel }) => {
56
56
  fontSize: 12,
57
57
  cursor: 'pointer',
58
58
  textTransform: 'capitalize',
59
- }, children: pm[p].label }, p))) }), _jsx("button", { type: "button", onClick: handleSubmit, disabled: !title.trim(), style: {
60
- width: '100%',
61
- padding: '12px',
62
- borderRadius: 10,
63
- border: 'none',
64
- background: title.trim() ? config.primaryColor : '#e5e7eb',
65
- color: title.trim() ? '#fff' : '#9ca3af',
66
- fontWeight: 700,
67
- fontSize: 15,
68
- cursor: title.trim() ? 'pointer' : 'not-allowed',
69
- }, children: "Submit ticket" })] })] }));
59
+ }, children: pm[p].label }, p))) })] }), _jsx("div", { style: {
60
+ flexShrink: 0,
61
+ padding: '12px 18px 18px',
62
+ borderTop: '1px solid #eef0f5',
63
+ background: '#fff',
64
+ boxShadow: '0 -4px 20px rgba(15,23,42,0.06)',
65
+ }, children: _jsx("button", { type: "button", onClick: handleSubmit, disabled: !title.trim(), style: {
66
+ width: '100%',
67
+ padding: '14px',
68
+ borderRadius: 10,
69
+ border: 'none',
70
+ background: title.trim() ? config.primaryColor : '#e5e7eb',
71
+ color: title.trim() ? '#fff' : '#9ca3af',
72
+ fontWeight: 700,
73
+ fontSize: 15,
74
+ cursor: title.trim() ? 'pointer' : 'not-allowed',
75
+ }, children: "Submit ticket" }) })] }));
70
76
  };
@@ -10,6 +10,8 @@ interface UserListScreenProps {
10
10
  onSelectUser: (user: ChatUser) => void;
11
11
  /** Shown on “New Conversation” list — opens block list */
12
12
  onBlockList?: () => void;
13
+ /** “Need Support” (user → agents): show home icon instead of back arrow */
14
+ useHomeHeader?: boolean;
13
15
  }
14
16
  export declare const UserListScreen: React.FC<UserListScreenProps>;
15
17
  export {};
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { avatarColor, initials } from '../../utils/chat';
3
- export const UserListScreen = ({ context, users, primaryColor, viewerType = 'user', onBack, onSelectUser, onBlockList, }) => {
3
+ export const UserListScreen = ({ context, users, primaryColor, viewerType = 'user', onBack, onSelectUser, onBlockList, useHomeHeader = false, }) => {
4
4
  const isStaff = viewerType === 'developer';
5
5
  const title = context === 'support'
6
6
  ? (isStaff ? 'Provide Support' : 'Need Support')
@@ -8,7 +8,7 @@ export const UserListScreen = ({ context, users, primaryColor, viewerType = 'use
8
8
  const subtitle = context === 'support'
9
9
  ? (isStaff ? 'All chat users — choose who to help' : 'Choose a support agent')
10
10
  : (isStaff ? 'Chat with another developer or coordinate handoff' : 'Choose a colleague');
11
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }, children: [_jsxs("div", { style: { background: `linear-gradient(135deg,${primaryColor},${primaryColor}cc)`, padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0, position: 'relative' }, children: [_jsx(BackBtn, { onClick: onBack }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 16, color: '#fff' }, children: title }), _jsx("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.8)' }, children: subtitle })] }), context === 'conversation' && onBlockList && (_jsxs("button", { type: "button", onClick: onBlockList, style: {
11
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }, children: [_jsxs("div", { style: { background: `linear-gradient(135deg,${primaryColor},${primaryColor}cc)`, padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0, position: 'relative' }, children: [useHomeHeader ? _jsx(HomeBtn, { onClick: onBack }) : _jsx(BackBtn, { onClick: onBack }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 16, color: '#fff' }, children: title }), _jsx("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.8)' }, children: subtitle })] }), context === 'conversation' && onBlockList && (_jsxs("button", { type: "button", onClick: onBlockList, style: {
12
12
  flexShrink: 0,
13
13
  background: 'rgba(255,255,255,0.2)',
14
14
  border: 'none',
@@ -45,9 +45,14 @@ export const UserListScreen = ({ context, users, primaryColor, viewerType = 'use
45
45
  border: `1px solid ${u.type === 'developer' ? primaryColor + '30' : '#16a34a30'}`,
46
46
  }, children: u.type === 'developer' ? 'Dev' : 'User' })] }, u.uid))) })] }));
47
47
  };
48
- const BackBtn = ({ onClick }) => (_jsx("button", { onClick: onClick, style: {
48
+ const BackBtn = ({ onClick }) => (_jsx("button", { type: "button", onClick: onClick, style: {
49
49
  background: 'rgba(255,255,255,0.22)', border: 'none', borderRadius: '50%',
50
50
  width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
51
51
  cursor: 'pointer', flexShrink: 0,
52
52
  }, 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" }) }) }));
53
+ const HomeBtn = ({ onClick }) => (_jsx("button", { type: "button", onClick: onClick, title: "Home", "aria-label": "Home", style: {
54
+ background: 'rgba(255,255,255,0.22)', border: 'none', borderRadius: '50%',
55
+ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
56
+ cursor: 'pointer', flexShrink: 0,
57
+ }, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M3 9.5L12 3l9 6.5V20a1 1 0 01-1 1H4a1 1 0 01-1-1V9.5z", stroke: "#fff", strokeWidth: "2.2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M9 21V12h6v9", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" })] }) }));
53
58
  const Empty = () => (_jsxs("div", { style: { padding: '50px 24px', textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 36, marginBottom: 10 }, children: "\uD83D\uDC65" }), _jsx("div", { style: { fontWeight: 700, color: '#1a2332', marginBottom: 6 }, children: "No users available" }), _jsx("div", { style: { fontSize: 13, color: '#7b8fa1' }, children: "Check back later" })] }));