ajaxter-chat 3.0.4 → 3.0.6

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.
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { ChatMessage, ChatUser, WidgetConfig } from '../../types';
2
+ import { ChatMessage, ChatUser, WidgetConfig, UserListContext } from '../../types';
3
3
  interface ChatScreenProps {
4
4
  activeUser: ChatUser;
5
5
  messages: ChatMessage[];
@@ -14,6 +14,11 @@ interface ChatScreenProps {
14
14
  onReport: () => void;
15
15
  onBlock: () => void;
16
16
  onStartCall: (withVideo: boolean) => void;
17
+ /** Navigate to support list, colleague list, or tickets (from slide menu) */
18
+ onNavAction: (ctx: UserListContext | 'ticket') => void;
19
+ /** Other devs (excl. viewer) — for transfer when staff chats with a customer */
20
+ otherDevelopers?: ChatUser[];
21
+ onTransferToDeveloper?: (dev: ChatUser) => void;
17
22
  }
18
23
  export declare const ChatScreen: React.FC<ChatScreenProps>;
19
24
  export {};
@@ -2,10 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from '../../utils/chat';
4
4
  import { EmojiPicker } from '../EmojiPicker';
5
- export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, }) => {
5
+ import { SlideNavMenu } from '../SlideNavMenu';
6
+ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, }) => {
7
+ var _a;
6
8
  const [text, setText] = useState('');
7
9
  const [showEmoji, setShowEmoji] = useState(false);
8
10
  const [showMenu, setShowMenu] = useState(false);
11
+ const [slideMenuOpen, setSlideMenuOpen] = useState(false);
12
+ const [transferOpen, setTransferOpen] = useState(false);
9
13
  const [isRecording, setIsRecording] = useState(false);
10
14
  const [recordSec, setRecordSec] = useState(0);
11
15
  const [showConfirm, setShowConfirm] = useState(null);
@@ -14,6 +18,7 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
14
18
  const fileRef = useRef(null);
15
19
  const recordTimer = useRef(null);
16
20
  const mediaRecorder = useRef(null);
21
+ const recordChunks = useRef([]);
17
22
  useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
18
23
  const handleSend = useCallback(() => {
19
24
  var _a;
@@ -29,22 +34,29 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
29
34
  handleSend();
30
35
  }
31
36
  };
32
- // Voice recording
33
37
  const startRecording = async () => {
34
38
  if (isPaused || isBlocked)
35
39
  return;
36
40
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
41
+ recordChunks.current = [];
37
42
  const mr = new MediaRecorder(stream);
38
43
  mediaRecorder.current = mr;
39
- const chunks = [];
40
- mr.ondataavailable = e => chunks.push(e.data);
44
+ mr.ondataavailable = e => { if (e.data.size)
45
+ recordChunks.current.push(e.data); };
41
46
  mr.onstop = () => {
42
47
  stream.getTracks().forEach(t => t.stop());
43
- // In production: upload blob, get URL, then send as voice message
44
- onSend('[Voice Message]', 'voice', { voiceDuration: recordSec });
48
+ const chunks = recordChunks.current;
49
+ if (!chunks.length) {
50
+ setRecordSec(0);
51
+ return;
52
+ }
53
+ const blob = new Blob(chunks, { type: chunks[0] instanceof Blob ? chunks[0].type : 'audio/webm' });
54
+ const voiceUrl = URL.createObjectURL(blob);
55
+ const dur = Math.max(1, recordSec);
56
+ onSend('Voice message', 'voice', { voiceDuration: dur, voiceUrl });
45
57
  setRecordSec(0);
46
58
  };
47
- mr.start();
59
+ mr.start(200);
48
60
  setIsRecording(true);
49
61
  recordTimer.current = setInterval(() => setRecordSec(s => s + 1), 1000);
50
62
  };
@@ -55,25 +67,24 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
55
67
  clearInterval(recordTimer.current);
56
68
  setIsRecording(false);
57
69
  };
58
- // Attachment
59
70
  const handleFileChange = (e) => {
60
71
  var _a;
61
72
  const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
62
73
  if (!file || isPaused || isBlocked)
63
74
  return;
64
- onSend(`[Attachment: ${file.name}]`, 'attachment', {
75
+ const attachmentUrl = URL.createObjectURL(file);
76
+ onSend(file.name, 'attachment', {
65
77
  attachmentName: file.name,
66
78
  attachmentSize: `${(file.size / 1024).toFixed(1)} KB`,
79
+ attachmentUrl,
67
80
  });
68
81
  e.target.value = '';
69
82
  };
70
- // Download transcript
71
83
  const handleTranscript = () => {
72
84
  const content = generateTranscript(messages, activeUser);
73
85
  downloadText(content, `chat-${activeUser.name.replace(/\s+/g, '_')}-${Date.now()}.txt`);
74
86
  setShowMenu(false);
75
87
  };
76
- // Confirm actions
77
88
  const handleConfirm = (action) => {
78
89
  setShowConfirm(null);
79
90
  setShowMenu(false);
@@ -86,50 +97,168 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
86
97
  };
87
98
  const peerAvatar = avatarColor(activeUser.name);
88
99
  const peerInit = initials(activeUser.name);
89
- // Group messages by date
90
100
  const grouped = groupByDate(messages);
91
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative' }, children: [_jsxs("div", { style: {
101
+ const viewerIsDev = config.viewerType === 'developer';
102
+ const headerRole = viewerIsDev
103
+ ? (activeUser.type === 'user' ? 'Customer' : 'Developer')
104
+ : 'Support';
105
+ 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: {
92
106
  background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
93
- padding: '12px 14px', display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0,
94
- }, children: [_jsx("button", { onClick: onBack, style: hdrBtn, 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: 14, fontWeight: 700, color: '#fff', opacity: 0.9 }, children: "Support" }), config.allowWebCall && (_jsx("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", { style: hdrBtn, title: "Fullscreen", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2", strokeLinecap: "round" }) }) })] }), 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", { 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" })), _jsx("div", { style: { padding: '14px 14px 0', flexShrink: 0 }, children: _jsxs("div", { style: {
95
- background: `linear-gradient(135deg, ${config.primaryColor}, ${config.primaryColor}cc)`,
96
- borderRadius: 12, padding: '14px 16px',
97
- display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
98
- }, children: [_jsx("div", { style: { width: 32, height: 32, borderRadius: '50%', background: 'rgba(255,255,255,0.25)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }, children: _jsx("span", { style: { fontSize: 16 }, children: "\u2753" }) }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff' }, children: "Enter your details" }), _jsx("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.85)' }, children: "Click here to provide your information" })] })] }) }), _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, peer: activeUser, 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 14px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [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: '6px 12px', background: '#fee2e2', borderRadius: 8 }, 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", { onClick: stopRecording, style: { marginLeft: 'auto', background: '#ef4444', color: '#fff', border: 'none', borderRadius: 6, padding: '3px 10px', fontSize: 12, cursor: 'pointer' }, children: "Stop" })] })), _jsxs("div", { style: { display: 'flex', alignItems: 'flex-end', gap: 8 }, children: [_jsx("textarea", { ref: inputRef, value: text, onChange: e => setText(e.target.value), onKeyDown: handleKey, placeholder: isPaused || isBlocked ? 'Chat is unavailable' : 'Message...', disabled: isPaused || isBlocked || isRecording, rows: 1, style: {
99
- flex: 1, resize: 'none', border: '1.5px solid #e5e7eb',
100
- borderRadius: 22, padding: '9px 14px',
101
- fontSize: 14, outline: 'none', lineHeight: 1.5,
102
- maxHeight: 80, overflowY: 'auto', color: '#1a2332',
103
- background: isPaused || isBlocked ? '#f9fafb' : '#fff',
104
- transition: 'border-color 0.2s',
107
+ padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
108
+ }, 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: [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: {
109
+ border: `1.5px solid ${isPaused || isBlocked ? '#e5e7eb' : '#bfdbfe'}`,
110
+ borderRadius: 16,
111
+ padding: '10px 12px 8px',
112
+ background: isPaused || isBlocked ? '#f9fafb' : '#fff',
113
+ }, 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: {
114
+ width: '100%',
115
+ resize: 'none',
116
+ border: 'none',
117
+ outline: 'none',
118
+ fontSize: 14,
119
+ lineHeight: 1.45,
120
+ color: '#1a2332',
121
+ background: 'transparent',
122
+ maxHeight: 88,
123
+ overflowY: 'auto',
105
124
  fontFamily: 'inherit',
106
- }, onFocus: e => (e.target.style.borderColor = config.primaryColor), onBlur: e => (e.target.style.borderColor = '#e5e7eb') }), config.allowEmoji && (_jsx(ActionBtn, { onClick: () => setShowEmoji(v => !v), title: "Emoji", children: _jsxs("svg", { width: "19", height: "19", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#9ca3af", strokeWidth: "1.8" }), _jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2", stroke: "#9ca3af", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("circle", { cx: "9", cy: "9", r: "1", fill: "#9ca3af" }), _jsx("circle", { cx: "15", cy: "9", r: "1", fill: "#9ca3af" })] }) })), 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: "19", height: "19", 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: "#9ca3af", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })), config.allowVoiceMessage && !isRecording && (_jsx(ActionBtn, { onClick: startRecording, title: "Voice message", children: _jsxs("svg", { width: "19", height: "19", 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: "#9ca3af", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("path", { d: "M19 10v2a7 7 0 01-14 0v-2M12 19v4M8 23h8", stroke: "#9ca3af", strokeWidth: "1.8", strokeLinecap: "round" })] }) })), text.trim() && (_jsx("button", { onClick: handleSend, style: {
107
- width: 36, height: 36, borderRadius: '50%', backgroundColor: config.primaryColor,
108
- border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
109
- transition: 'transform 0.15s',
110
- }, 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: "#fff", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] })] }), _jsxs("div", { style: { position: 'absolute', top: 12, right: 52, zIndex: 50 }, children: [_jsx("button", { onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.18)' }), title: "More options", 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: {
111
- position: 'absolute', top: '100%', right: 0, marginTop: 4,
112
- background: '#fff', borderRadius: 12,
113
- boxShadow: '0 8px 30px rgba(0,0,0,0.16)',
114
- padding: '6px', minWidth: 200, zIndex: 200,
115
- animation: 'cw-fadeUp 0.18s ease',
116
- }, children: [config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), _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 })] }))] }), showConfirm && (_jsx("div", { style: {
125
+ marginBottom: 8,
126
+ } }), _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: {
127
+ width: 36,
128
+ height: 36,
129
+ borderRadius: '50%',
130
+ backgroundColor: text.trim() && !isPaused && !isBlocked ? config.primaryColor : '#e2e8f0',
131
+ border: 'none',
132
+ cursor: text.trim() && !isPaused && !isBlocked ? 'pointer' : 'default',
133
+ display: 'flex',
134
+ alignItems: 'center',
135
+ justifyContent: 'center',
136
+ flexShrink: 0,
137
+ transition: 'background 0.15s',
138
+ }, 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: {
139
+ position: 'absolute',
140
+ inset: 0,
141
+ background: 'rgba(0,0,0,0.45)',
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ zIndex: 280,
146
+ padding: 16,
147
+ }, children: _jsxs("div", { style: {
148
+ background: '#fff',
149
+ borderRadius: 16,
150
+ padding: '18px 16px',
151
+ width: '100%',
152
+ maxWidth: 320,
153
+ maxHeight: '70%',
154
+ overflow: 'hidden',
155
+ display: 'flex',
156
+ flexDirection: 'column',
157
+ boxShadow: '0 16px 48px rgba(0,0,0,0.22)',
158
+ }, children: [_jsx("div", { style: { fontWeight: 800, fontSize: 16, color: '#1a2332', marginBottom: 6 }, children: "Transfer chat to" }), _jsx("p", { style: { fontSize: 12, color: '#7b8fa1', margin: '0 0 12px', lineHeight: 1.5 }, children: "Assign this conversation to another developer. History is kept and a handoff note is added." }), _jsx("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', margin: '0 -4px' }, children: otherDevelopers.map(dev => (_jsxs("button", { type: "button", onClick: () => {
159
+ onTransferToDeveloper(dev);
160
+ setTransferOpen(false);
161
+ }, style: {
162
+ width: '100%',
163
+ textAlign: 'left',
164
+ padding: '12px 12px',
165
+ marginBottom: 6,
166
+ border: '1px solid #eef0f5',
167
+ borderRadius: 12,
168
+ background: '#f8fafc',
169
+ cursor: 'pointer',
170
+ fontSize: 14,
171
+ fontWeight: 600,
172
+ color: '#1e293b',
173
+ }, children: [dev.name, _jsx("span", { style: { display: 'block', fontSize: 11, fontWeight: 500, color: '#64748b', marginTop: 2 }, children: dev.designation })] }, dev.uid))) }), _jsx("button", { type: "button", onClick: () => setTransferOpen(false), style: {
174
+ marginTop: 12,
175
+ padding: '10px',
176
+ borderRadius: 10,
177
+ border: '1.5px solid #e5e7eb',
178
+ background: '#fff',
179
+ fontWeight: 600,
180
+ fontSize: 13,
181
+ color: '#475569',
182
+ cursor: 'pointer',
183
+ }, children: "Cancel" })] }) })), showConfirm && (_jsx("div", { style: {
117
184
  position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.45)',
118
185
  display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 300,
119
186
  borderRadius: 'inherit',
120
- }, children: _jsxs("div", { style: { background: '#fff', borderRadius: 16, padding: '24px 20px', width: 280, boxShadow: '0 16px 48px rgba(0,0,0,0.22)', animation: 'cw-fadeUp 0.2s ease' }, children: [_jsxs("div", { style: { fontWeight: 800, fontSize: 16, color: '#1a2332', marginBottom: 8 }, children: [showConfirm === 'pause' && (isPaused ? 'Resume Chat?' : 'Pause Chat?'), showConfirm === 'report' && 'Report this chat?', showConfirm === 'block' && 'Block this user?'] }), _jsxs("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6, margin: '0 0 18px' }, children: [showConfirm === 'pause' && (isPaused ? 'The user will be able to send messages again.' : 'The user will not be able to send new messages.'), showConfirm === 'report' && 'This chat will be flagged for review by the admin team.', showConfirm === 'block' && 'This user will be blocked and added to your block list. You can unblock them later.'] }), _jsxs("div", { style: { display: 'flex', gap: 10 }, children: [_jsx("button", { onClick: () => setShowConfirm(null), style: { flex: 1, padding: '9px', borderRadius: 10, border: '1.5px solid #e5e7eb', background: '#fff', cursor: 'pointer', fontSize: 13, fontWeight: 600, color: '#374151' }, children: "Cancel" }), _jsx("button", { onClick: () => handleConfirm(showConfirm), style: {
187
+ }, children: _jsxs("div", { style: { background: '#fff', borderRadius: 16, padding: '24px 20px', width: 280, boxShadow: '0 16px 48px rgba(0,0,0,0.22)', animation: 'cw-fadeUp 0.2s ease' }, children: [_jsxs("div", { style: { fontWeight: 800, fontSize: 16, color: '#1a2332', marginBottom: 8 }, children: [showConfirm === 'pause' && (isPaused ? 'Resume Chat?' : 'Pause Chat?'), showConfirm === 'report' && 'Report this chat?', showConfirm === 'block' && 'Block this user?'] }), _jsxs("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6, margin: '0 0 18px' }, children: [showConfirm === 'pause' && (isPaused ? 'The user will be able to send messages again.' : 'The user will not be able to send new messages.'), showConfirm === 'report' && 'This chat will be flagged for review by the admin team.', showConfirm === 'block' && 'This user will be blocked and added to your block list. You can unblock them later.'] }), _jsxs("div", { style: { display: 'flex', gap: 10 }, children: [_jsx("button", { type: "button", onClick: () => setShowConfirm(null), style: { flex: 1, padding: '9px', borderRadius: 10, border: '1.5px solid #e5e7eb', background: '#fff', cursor: 'pointer', fontSize: 13, fontWeight: 600, color: '#374151' }, children: "Cancel" }), _jsx("button", { type: "button", onClick: () => handleConfirm(showConfirm), style: {
121
188
  flex: 1, padding: '9px', borderRadius: 10, border: 'none',
122
189
  background: showConfirm === 'block' ? '#ef4444' : config.primaryColor,
123
190
  color: '#fff', cursor: 'pointer', fontSize: 13, fontWeight: 700,
124
191
  }, children: "Confirm" })] })] }) }))] }));
125
192
  };
126
- // ── Sub-components ─────────────────────────────────────────────────────────────
127
- const Bubble = ({ msg, peer, primaryColor }) => {
193
+ const VoiceRow = ({ msg, isMe, primaryColor }) => {
128
194
  var _a;
195
+ const audioRef = useRef(null);
196
+ const [playing, setPlaying] = useState(false);
197
+ const [current, setCurrent] = useState(0);
198
+ const [dur, setDur] = useState((_a = msg.voiceDuration) !== null && _a !== void 0 ? _a : 0);
199
+ const url = msg.voiceUrl;
200
+ useEffect(() => {
201
+ const a = audioRef.current;
202
+ if (!a || !url)
203
+ return;
204
+ const onMeta = () => setDur(a.duration || msg.voiceDuration || 0);
205
+ const onTime = () => setCurrent(a.currentTime);
206
+ a.addEventListener('loadedmetadata', onMeta);
207
+ a.addEventListener('timeupdate', onTime);
208
+ return () => {
209
+ a.removeEventListener('loadedmetadata', onMeta);
210
+ a.removeEventListener('timeupdate', onTime);
211
+ };
212
+ }, [url, msg.voiceDuration]);
213
+ const toggle = () => {
214
+ const a = audioRef.current;
215
+ if (!a)
216
+ return;
217
+ if (playing) {
218
+ a.pause();
219
+ setPlaying(false);
220
+ }
221
+ else {
222
+ void a.play().then(() => setPlaying(true)).catch(() => { });
223
+ }
224
+ };
225
+ const pct = dur > 0 ? Math.min(100, (current / dur) * 100) : 0;
226
+ const timeLabel = fmtTime(Math.floor(current)) + ' / ' + fmtTime(Math.floor(dur || msg.voiceDuration || 0));
227
+ if (!url) {
228
+ return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { fontSize: 13 }, children: "\uD83C\uDFA4" }), _jsxs("span", { style: { fontSize: 13 }, children: ["Voice message", msg.voiceDuration ? ` · ${msg.voiceDuration}s` : ''] })] }));
229
+ }
230
+ return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, minWidth: 200 }, children: [url && (_jsx("audio", { ref: audioRef, src: url, preload: "metadata", onPlay: () => setPlaying(true), onPause: () => setPlaying(false), onEnded: () => { setPlaying(false); setCurrent(0); } })), _jsx("button", { type: "button", onClick: toggle, style: {
231
+ width: 36,
232
+ height: 36,
233
+ borderRadius: '50%',
234
+ border: 'none',
235
+ background: isMe ? 'rgba(255,255,255,0.95)' : '#fff',
236
+ color: isMe ? primaryColor : primaryColor,
237
+ cursor: 'pointer',
238
+ display: 'flex',
239
+ alignItems: 'center',
240
+ justifyContent: 'center',
241
+ flexShrink: 0,
242
+ boxShadow: '0 1px 4px rgba(0,0,0,0.12)',
243
+ }, "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 })] })] }));
244
+ };
245
+ const AttachmentRow = ({ msg, isMe, primaryColor }) => {
246
+ var _a;
247
+ const name = (_a = msg.attachmentName) !== null && _a !== void 0 ? _a : 'File';
248
+ const href = msg.attachmentUrl;
249
+ 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: {
250
+ fontSize: 12,
251
+ fontWeight: 700,
252
+ color: isMe ? '#fff' : primaryColor,
253
+ textDecoration: 'underline',
254
+ whiteSpace: 'nowrap',
255
+ }, children: "Download" }))] }));
256
+ };
257
+ const Bubble = ({ msg, primaryColor }) => {
129
258
  const isMe = msg.senderId === 'me';
130
- const content = msg.type === 'voice' ? (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsxs("svg", { width: "16", height: "16", 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: isMe ? '#fff' : '#374151', strokeWidth: "2", strokeLinecap: "round" }), _jsx("path", { d: "M19 10v2a7 7 0 01-14 0v-2M12 19v4", stroke: isMe ? '#fff' : '#374151', strokeWidth: "2", strokeLinecap: "round" })] }), _jsxs("span", { children: ["Voice Message ", msg.voiceDuration ? `· ${msg.voiceDuration}s` : ''] })] })) : msg.type === 'attachment' ? (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("svg", { width: "14", height: "14", 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: isMe ? '#fff' : '#374151', strokeWidth: "2", strokeLinecap: "round" }) }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600 }, children: (_a = msg.attachmentName) !== null && _a !== void 0 ? _a : 'File' }), msg.attachmentSize && _jsx("div", { style: { fontSize: 11, opacity: 0.75 }, children: msg.attachmentSize })] })] })) : _jsx("span", { children: msg.text });
259
+ 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 }));
131
260
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start', gap: 3 }, children: [_jsx("div", { style: {
132
- maxWidth: '76%', padding: '10px 13px',
261
+ maxWidth: '85%', padding: '10px 13px',
133
262
  borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
134
263
  backgroundColor: isMe ? primaryColor : '#fff',
135
264
  color: isMe ? '#fff' : '#1a2332',
@@ -138,24 +267,23 @@ const Bubble = ({ msg, peer, primaryColor }) => {
138
267
  wordBreak: 'break-word',
139
268
  }, children: content }), _jsx("span", { style: { fontSize: 11, color: '#b0bec5', padding: '0 4px' }, children: formatTime(msg.timestamp) })] }));
140
269
  };
141
- const DateDivider = ({ label }) => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, margin: '4px 0' }, children: [_jsx("div", { style: { flex: 1, height: 1, background: '#e5e7eb' } }), _jsx("span", { style: { fontSize: 11, fontWeight: 600, color: '#9ca3af', whiteSpace: 'nowrap' }, children: label }), _jsx("div", { style: { flex: 1, height: 1, background: '#e5e7eb' } })] }));
142
- const MenuItem = ({ icon, label, onClick, danger }) => (_jsxs("button", { onClick: onClick, style: {
270
+ const DateDivider = ({ label }) => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, margin: '4px 0' }, children: [_jsx("div", { style: { flex: 1, height: 1, background: '#e5e7eb' } }), _jsx("span", { style: { fontSize: 11, fontWeight: 600, color: '#64748b', whiteSpace: 'nowrap' }, children: label }), _jsx("div", { style: { flex: 1, height: 1, background: '#e5e7eb' } })] }));
271
+ const MenuItem = ({ icon, label, onClick, danger }) => (_jsxs("button", { type: "button", onClick: onClick, style: {
143
272
  display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '9px 12px',
144
273
  background: 'none', border: 'none', borderRadius: 8, cursor: 'pointer', textAlign: 'left',
145
274
  fontSize: 13, fontWeight: 600, color: danger ? '#ef4444' : '#374151',
146
275
  transition: 'background 0.12s',
147
276
  }, onMouseEnter: e => e.currentTarget.style.background = danger ? '#fee2e2' : '#f3f4f6', onMouseLeave: e => e.currentTarget.style.background = 'none', children: [_jsx("span", { children: icon }), " ", label] }));
148
- const ActionBtn = ({ onClick, title, children }) => (_jsx("button", { onClick: onClick, title: title, style: {
149
- background: 'none', border: 'none', cursor: 'pointer', padding: '7px',
277
+ const ActionBtn = ({ onClick, title, children }) => (_jsx("button", { type: "button", onClick: onClick, title: title, style: {
278
+ background: 'none', border: 'none', cursor: 'pointer', padding: '8px',
150
279
  borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
151
280
  transition: 'background 0.13s',
152
- }, onMouseEnter: e => e.currentTarget.style.background = '#f3f4f6', onMouseLeave: e => e.currentTarget.style.background = 'none', children: children }));
281
+ }, onMouseEnter: e => e.currentTarget.style.background = '#f1f5f9', onMouseLeave: e => e.currentTarget.style.background = 'none', children: children }));
153
282
  const hdrBtn = {
154
283
  background: 'rgba(255,255,255,0.2)', border: 'none', borderRadius: '50%',
155
284
  width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
156
285
  cursor: 'pointer', flexShrink: 0,
157
286
  };
158
- // Group messages by date
159
287
  function groupByDate(messages) {
160
288
  const map = new Map();
161
289
  messages.forEach(m => {
@@ -166,3 +294,8 @@ function groupByDate(messages) {
166
294
  });
167
295
  return Array.from(map.entries()).map(([date, msgs]) => ({ date, msgs }));
168
296
  }
297
+ function fmtTime(sec) {
298
+ const m = Math.floor(sec / 60);
299
+ const s = Math.max(0, sec % 60);
300
+ return `${m}:${String(s).padStart(2, '0')}`;
301
+ }
@@ -19,7 +19,7 @@ import { BottomTabs } from './Tabs/BottomTabs';
19
19
  const DRAWER_W_NORMAL = 380;
20
20
  const DRAWER_W_MAX = 480;
21
21
  export const ChatWidget = ({ theme: localTheme }) => {
22
- var _a, _b;
22
+ var _a, _b, _c, _d;
23
23
  /* SSR guard */
24
24
  const [mounted, setMounted] = useState(false);
25
25
  useEffect(() => { setMounted(true); }, []);
@@ -96,6 +96,17 @@ export const ChatWidget = ({ theme: localTheme }) => {
96
96
  setScreen('user-list');
97
97
  }
98
98
  }, []);
99
+ const handleNavFromMenu = useCallback((ctx) => {
100
+ clearChat();
101
+ if (ctx === 'ticket') {
102
+ setActiveTab('tickets');
103
+ setScreen('tickets');
104
+ }
105
+ else {
106
+ setUserListCtx(ctx);
107
+ setScreen('user-list');
108
+ }
109
+ }, [clearChat]);
99
110
  const handleSelectUser = useCallback((user) => {
100
111
  var _a;
101
112
  // Load history from sample chats if available
@@ -161,10 +172,39 @@ export const ChatWidget = ({ theme: localTheme }) => {
161
172
  const widgetConfig = data === null || data === void 0 ? void 0 : data.widget;
162
173
  const primaryColor = theme.primaryColor;
163
174
  const allUsers = data ? [...data.developers, ...data.users] : [];
175
+ const viewerIsDev = (widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerType) === 'developer';
176
+ const viewerUid = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerUid;
164
177
  const filteredUsers = screen === 'user-list'
165
- ? allUsers.filter(u => userListCtx === 'support' ? u.type === 'developer' : u.type === 'user')
178
+ ? allUsers.filter(u => {
179
+ if (userListCtx === 'support') {
180
+ if (viewerIsDev)
181
+ return u.type === 'user';
182
+ return u.type === 'developer';
183
+ }
184
+ if (viewerIsDev) {
185
+ return u.type === 'developer' && u.uid !== viewerUid;
186
+ }
187
+ return u.type === 'user';
188
+ })
166
189
  : [];
190
+ const otherDevelopers = (_c = data === null || data === void 0 ? void 0 : data.developers.filter(d => d.uid !== viewerUid)) !== null && _c !== void 0 ? _c : [];
167
191
  const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
192
+ const handleTransferToDeveloper = useCallback((dev) => {
193
+ var _a;
194
+ if (!activeUser || !widgetConfig)
195
+ return;
196
+ const agent = ((_a = widgetConfig.viewerName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Agent';
197
+ const transferNote = {
198
+ id: `tr_${Date.now()}_${Math.random().toString(36).slice(2)}`,
199
+ senderId: 'me',
200
+ receiverId: dev.uid,
201
+ text: `— ${agent} transferred this conversation from ${activeUser.name} to ${dev.name} —`,
202
+ timestamp: new Date().toISOString(),
203
+ type: 'text',
204
+ status: 'sent',
205
+ };
206
+ selectUser(dev, [...messages, transferNote]);
207
+ }, [activeUser, messages, selectUser, widgetConfig]);
168
208
  /* Position */
169
209
  const posStyle = theme.buttonPosition === 'bottom-left'
170
210
  ? { left: 24, right: 'auto' }
@@ -220,7 +260,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
220
260
  zIndex: 20, display: 'flex', gap: 6,
221
261
  }, children: [_jsx(CornerBtn, { onClick: () => setIsMaximized(m => !m), title: isMaximized ? 'Minimize' : 'Maximize', children: isMaximized
222
262
  ? _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3v5H3M21 8h-5V3M3 16h5v5M16 21v-5h5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) })
223
- : _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }) }), _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 })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, onBack: () => setScreen('home'), onSelectUser: handleSelectUser })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: () => { clearChat(); setScreen('home'); setActiveTab('home'); }, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall })), 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: handleSelectUser })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onRaiseTicket: handleRaiseTicket })), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
263
+ : _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }) }), _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, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_d = widgetConfig.viewerType) !== null && _d !== void 0 ? _d : 'user', onBack: () => setScreen('home'), onSelectUser: handleSelectUser })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: () => { clearChat(); setScreen('home'); setActiveTab('home'); }, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper })), 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: handleSelectUser })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onRaiseTicket: handleRaiseTicket })), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
224
264
  screen !== 'chat' &&
225
265
  screen !== 'call' &&
226
266
  screen !== 'user-list' &&
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- import { WidgetConfig, UserListContext } from '../../types';
2
+ import { WidgetConfig, UserListContext, Ticket } from '../../types';
3
3
  interface HomeScreenProps {
4
4
  config: WidgetConfig;
5
5
  onNavigate: (ctx: UserListContext | 'ticket') => void;
6
+ tickets: Ticket[];
6
7
  }
7
8
  export declare const HomeScreen: React.FC<HomeScreenProps>;
8
9
  export {};