ajaxter-chat 2.0.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +119 -128
  2. package/dist/components/BlockList/index.d.ts +10 -0
  3. package/dist/components/BlockList/index.js +33 -0
  4. package/dist/components/CallScreen/index.d.ts +13 -0
  5. package/dist/components/CallScreen/index.js +48 -0
  6. package/dist/components/ChatScreen/index.d.ts +10 -3
  7. package/dist/components/ChatScreen/index.js +142 -57
  8. package/dist/components/ChatWidget.js +192 -98
  9. package/dist/components/EmojiPicker/index.d.ts +8 -0
  10. package/dist/components/EmojiPicker/index.js +18 -0
  11. package/dist/components/HomeScreen/index.d.ts +2 -3
  12. package/dist/components/HomeScreen/index.js +25 -41
  13. package/dist/components/MaintenanceView/index.d.ts +0 -1
  14. package/dist/components/MaintenanceView/index.js +4 -6
  15. package/dist/components/RecentChatsScreen/index.d.ts +4 -3
  16. package/dist/components/RecentChatsScreen/index.js +7 -37
  17. package/dist/components/Tabs/BottomTabs.d.ts +1 -1
  18. package/dist/components/Tabs/BottomTabs.js +25 -20
  19. package/dist/components/TicketScreen/index.d.ts +3 -3
  20. package/dist/components/TicketScreen/index.js +39 -56
  21. package/dist/components/UserListScreen/index.d.ts +2 -4
  22. package/dist/components/UserListScreen/index.js +33 -62
  23. package/dist/config/index.d.ts +3 -3
  24. package/dist/config/index.js +18 -26
  25. package/dist/hooks/useChat.d.ts +8 -3
  26. package/dist/hooks/useChat.js +22 -18
  27. package/dist/hooks/useRemoteConfig.d.ts +6 -0
  28. package/dist/hooks/useRemoteConfig.js +22 -0
  29. package/dist/hooks/useWebRTC.d.ts +11 -0
  30. package/dist/hooks/useWebRTC.js +112 -0
  31. package/dist/index.d.ts +9 -5
  32. package/dist/index.js +8 -4
  33. package/dist/types/index.d.ts +62 -21
  34. package/dist/utils/chat.d.ts +13 -0
  35. package/dist/utils/chat.js +62 -0
  36. package/dist/utils/theme.d.ts +3 -1
  37. package/dist/utils/theme.js +14 -7
  38. package/package.json +4 -4
  39. package/public/chatData.json +162 -0
  40. package/src/components/BlockList/index.tsx +94 -0
  41. package/src/components/CallScreen/index.tsx +144 -0
  42. package/src/components/ChatScreen/index.tsx +403 -139
  43. package/src/components/ChatWidget.tsx +394 -250
  44. package/src/components/EmojiPicker/index.tsx +48 -0
  45. package/src/components/HomeScreen/index.tsx +58 -82
  46. package/src/components/MaintenanceView/index.tsx +6 -9
  47. package/src/components/RecentChatsScreen/index.tsx +51 -96
  48. package/src/components/Tabs/BottomTabs.tsx +45 -37
  49. package/src/components/TicketScreen/index.tsx +87 -133
  50. package/src/components/UserListScreen/index.tsx +75 -153
  51. package/src/config/index.ts +22 -28
  52. package/src/hooks/useChat.ts +31 -14
  53. package/src/hooks/useRemoteConfig.ts +20 -0
  54. package/src/hooks/useWebRTC.ts +130 -0
  55. package/src/index.ts +26 -15
  56. package/src/types/index.ts +85 -40
  57. package/src/utils/chat.ts +70 -0
  58. package/src/utils/theme.ts +18 -7
  59. package/dist/hooks/useUsers.d.ts +0 -7
  60. package/dist/hooks/useUsers.js +0 -26
  61. package/dist/services/userService.d.ts +0 -2
  62. package/dist/services/userService.js +0 -9
  63. package/dist/src/components/ChatScreen/index.d.ts +0 -12
  64. package/dist/src/components/ChatScreen/index.js +0 -83
  65. package/dist/src/components/ChatWidget.d.ts +0 -4
  66. package/dist/src/components/ChatWidget.js +0 -141
  67. package/dist/src/components/HomeScreen/index.d.ts +0 -9
  68. package/dist/src/components/HomeScreen/index.js +0 -71
  69. package/dist/src/components/MaintenanceView/index.d.ts +0 -7
  70. package/dist/src/components/MaintenanceView/index.js +0 -16
  71. package/dist/src/components/RecentChatsScreen/index.d.ts +0 -16
  72. package/dist/src/components/RecentChatsScreen/index.js +0 -38
  73. package/dist/src/components/Tabs/BottomTabs.d.ts +0 -10
  74. package/dist/src/components/Tabs/BottomTabs.js +0 -29
  75. package/dist/src/components/TicketScreen/index.d.ts +0 -9
  76. package/dist/src/components/TicketScreen/index.js +0 -71
  77. package/dist/src/components/UserListScreen/index.d.ts +0 -13
  78. package/dist/src/components/UserListScreen/index.js +0 -64
  79. package/dist/src/config/index.d.ts +0 -3
  80. package/dist/src/config/index.js +0 -38
  81. package/dist/src/hooks/useChat.d.ts +0 -8
  82. package/dist/src/hooks/useChat.js +0 -26
  83. package/dist/src/hooks/useUsers.d.ts +0 -7
  84. package/dist/src/hooks/useUsers.js +0 -26
  85. package/dist/src/index.d.ts +0 -14
  86. package/dist/src/index.js +0 -13
  87. package/dist/src/services/userService.d.ts +0 -2
  88. package/dist/src/services/userService.js +0 -9
  89. package/dist/src/types/index.d.ts +0 -59
  90. package/dist/src/types/index.js +0 -1
  91. package/dist/src/utils/theme.d.ts +0 -3
  92. package/dist/src/utils/theme.js +0 -13
  93. package/src/hooks/useUsers.ts +0 -27
  94. package/src/services/userService.ts +0 -9
@@ -1,83 +1,168 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useRef, useEffect } from 'react';
3
- import { mergeTheme } from '../../utils/theme';
4
- export const ChatScreen = ({ activeUser, messages, onSend, onBack, onClose, theme, }) => {
5
- const t = mergeTheme(theme);
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from '../../utils/chat';
4
+ import { EmojiPicker } from '../EmojiPicker';
5
+ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, }) => {
6
6
  const [text, setText] = useState('');
7
+ const [showEmoji, setShowEmoji] = useState(false);
8
+ const [showMenu, setShowMenu] = useState(false);
9
+ const [isRecording, setIsRecording] = useState(false);
10
+ const [recordSec, setRecordSec] = useState(0);
11
+ const [showConfirm, setShowConfirm] = useState(null);
7
12
  const endRef = useRef(null);
8
13
  const inputRef = useRef(null);
14
+ const fileRef = useRef(null);
15
+ const recordTimer = useRef(null);
16
+ const mediaRecorder = useRef(null);
9
17
  useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
10
- const handleSend = () => {
18
+ const handleSend = useCallback(() => {
11
19
  var _a;
12
- if (!text.trim())
20
+ if (!text.trim() || isPaused || isBlocked)
13
21
  return;
14
- onSend(text);
22
+ onSend(text.trim());
15
23
  setText('');
16
24
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
17
- };
25
+ }, [text, isPaused, isBlocked, onSend]);
18
26
  const handleKey = (e) => {
19
27
  if (e.key === 'Enter' && !e.shiftKey) {
20
28
  e.preventDefault();
21
29
  handleSend();
22
30
  }
23
31
  };
24
- const initials = activeUser.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
25
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideInRight 0.25s ease' }, children: [_jsxs("div", { style: {
26
- backgroundColor: t.primaryColor,
27
- padding: '14px 18px',
28
- display: 'flex',
29
- alignItems: 'center',
30
- gap: '10px',
31
- flexShrink: 0,
32
- }, children: [_jsx("button", { onClick: onBack, style: iconBtnStyle, 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.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsx("div", { style: {
33
- width: 36, height: 36, borderRadius: '50%',
34
- backgroundColor: 'rgba(255,255,255,0.25)',
35
- display: 'flex', alignItems: 'center', justifyContent: 'center',
36
- fontWeight: 700, fontSize: '13px', color: '#fff', flexShrink: 0,
37
- }, children: initials }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: '14px', color: '#fff', fontFamily: t.fontFamily }, children: activeUser.name }), _jsxs("div", { style: { fontSize: '11px', color: 'rgba(255,255,255,0.8)', display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("span", { style: { width: 6, height: 6, borderRadius: '50%', backgroundColor: '#a8f0c6', display: 'inline-block' } }), "Online"] })] }), _jsx("button", { onClick: onClose, style: iconBtnStyle, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) })] }), _jsxs("div", { style: {
38
- flex: 1, overflowY: 'auto', padding: '18px 16px',
39
- display: 'flex', flexDirection: 'column', gap: '10px',
40
- backgroundColor: '#f7f8fc',
41
- }, children: [messages.length === 0 && (_jsxs("div", { style: {
42
- margin: 'auto', textAlign: 'center',
43
- fontSize: '13px', color: '#b0bec5',
44
- fontFamily: t.fontFamily,
45
- }, children: [_jsx("div", { style: { fontSize: '28px', marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hi to ", activeUser.name, "!"] })), messages.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: t.primaryColor, font: t.fontFamily }, msg.id))), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: {
46
- borderTop: '1px solid #eef0f5',
47
- padding: '10px 14px',
48
- backgroundColor: '#fff',
49
- display: 'flex',
50
- alignItems: 'flex-end',
51
- gap: '10px',
52
- flexShrink: 0,
53
- }, children: [_jsx("textarea", { ref: inputRef, value: text, onChange: e => setText(e.target.value), onKeyDown: handleKey, placeholder: "Type and press [enter]..", rows: 1, style: {
54
- flex: 1, resize: 'none', border: 'none', outline: 'none',
55
- fontFamily: t.fontFamily, fontSize: '14px', lineHeight: '1.5',
56
- maxHeight: '80px', overflowY: 'auto', color: '#1a2332',
57
- padding: '6px 0', backgroundColor: 'transparent',
58
- } }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }, children: [_jsx(IconBtn, { onClick: () => { }, title: "Reaction", children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M14 9h.01M10 9h.01M12 2a10 10 0 100 20A10 10 0 0012 2zm0 14s-4-1.5-4-4", stroke: "#9aa3af", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M8 15s1.5 2 4 2 4-2 4-2", stroke: "#9aa3af", strokeWidth: "1.8", strokeLinecap: "round" })] }) }), _jsx(IconBtn, { onClick: () => { }, title: "Attach", 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: "#9aa3af", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), _jsx(IconBtn, { onClick: () => { }, 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: "#9aa3af", strokeWidth: "1.8" }), _jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2", stroke: "#9aa3af", strokeWidth: "1.8", strokeLinecap: "round" }), _jsx("line", { x1: "9", y1: "9", x2: "9.01", y2: "9", stroke: "#9aa3af", strokeWidth: "2.5", strokeLinecap: "round" }), _jsx("line", { x1: "15", y1: "9", x2: "15.01", y2: "9", stroke: "#9aa3af", strokeWidth: "2.5", strokeLinecap: "round" })] }) }), text.trim() && (_jsx("button", { onClick: handleSend, style: {
59
- width: 36, height: 36, borderRadius: '50%',
60
- backgroundColor: t.primaryColor, border: 'none',
61
- cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
32
+ // Voice recording
33
+ const startRecording = async () => {
34
+ if (isPaused || isBlocked)
35
+ return;
36
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
37
+ const mr = new MediaRecorder(stream);
38
+ mediaRecorder.current = mr;
39
+ const chunks = [];
40
+ mr.ondataavailable = e => chunks.push(e.data);
41
+ mr.onstop = () => {
42
+ 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 });
45
+ setRecordSec(0);
46
+ };
47
+ mr.start();
48
+ setIsRecording(true);
49
+ recordTimer.current = setInterval(() => setRecordSec(s => s + 1), 1000);
50
+ };
51
+ const stopRecording = () => {
52
+ var _a;
53
+ (_a = mediaRecorder.current) === null || _a === void 0 ? void 0 : _a.stop();
54
+ if (recordTimer.current)
55
+ clearInterval(recordTimer.current);
56
+ setIsRecording(false);
57
+ };
58
+ // Attachment
59
+ const handleFileChange = (e) => {
60
+ var _a;
61
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
62
+ if (!file || isPaused || isBlocked)
63
+ return;
64
+ onSend(`[Attachment: ${file.name}]`, 'attachment', {
65
+ attachmentName: file.name,
66
+ attachmentSize: `${(file.size / 1024).toFixed(1)} KB`,
67
+ });
68
+ e.target.value = '';
69
+ };
70
+ // Download transcript
71
+ const handleTranscript = () => {
72
+ const content = generateTranscript(messages, activeUser);
73
+ downloadText(content, `chat-${activeUser.name.replace(/\s+/g, '_')}-${Date.now()}.txt`);
74
+ setShowMenu(false);
75
+ };
76
+ // Confirm actions
77
+ const handleConfirm = (action) => {
78
+ setShowConfirm(null);
79
+ setShowMenu(false);
80
+ if (action === 'report')
81
+ onReport();
82
+ if (action === 'block')
83
+ onBlock();
84
+ if (action === 'pause')
85
+ onTogglePause();
86
+ };
87
+ const peerAvatar = avatarColor(activeUser.name);
88
+ const peerInit = initials(activeUser.name);
89
+ // Group messages by date
90
+ 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: {
92
+ 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',
105
+ 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,
62
109
  transition: 'transform 0.15s',
63
- }, 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" }) }) }))] })] })] }));
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: {
117
+ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.45)',
118
+ display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 300,
119
+ 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: {
121
+ flex: 1, padding: '9px', borderRadius: 10, border: 'none',
122
+ background: showConfirm === 'block' ? '#ef4444' : config.primaryColor,
123
+ color: '#fff', cursor: 'pointer', fontSize: 13, fontWeight: 700,
124
+ }, children: "Confirm" })] })] }) }))] }));
64
125
  };
65
- const Bubble = ({ msg, primaryColor, font }) => {
126
+ // ── Sub-components ─────────────────────────────────────────────────────────────
127
+ const Bubble = ({ msg, peer, primaryColor }) => {
128
+ var _a;
66
129
  const isMe = msg.senderId === 'me';
67
- const time = new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
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 });
68
131
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start', gap: 3 }, children: [_jsx("div", { style: {
69
- maxWidth: '75%', padding: '10px 14px',
132
+ maxWidth: '76%', padding: '10px 13px',
70
133
  borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
71
134
  backgroundColor: isMe ? primaryColor : '#fff',
72
135
  color: isMe ? '#fff' : '#1a2332',
73
- fontSize: '14px', lineHeight: '1.5',
136
+ fontSize: 14, lineHeight: 1.5,
74
137
  boxShadow: '0 1px 4px rgba(0,0,0,0.07)',
75
- fontFamily: font, wordBreak: 'break-word',
76
- }, children: msg.text }), _jsx("span", { style: { fontSize: '11px', color: '#b0bec5', padding: '0 4px' }, children: time })] }));
138
+ wordBreak: 'break-word',
139
+ }, children: content }), _jsx("span", { style: { fontSize: 11, color: '#b0bec5', padding: '0 4px' }, children: formatTime(msg.timestamp) })] }));
77
140
  };
78
- const IconBtn = ({ onClick, title, children }) => (_jsx("button", { onClick: onClick, title: title, style: { background: 'none', border: 'none', cursor: 'pointer', padding: '4px', display: 'flex', alignItems: 'center', borderRadius: '6px' }, onMouseEnter: e => e.currentTarget.style.background = '#f3f4f6', onMouseLeave: e => e.currentTarget.style.background = 'none', children: children }));
79
- const iconBtnStyle = {
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: {
143
+ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '9px 12px',
144
+ background: 'none', border: 'none', borderRadius: 8, cursor: 'pointer', textAlign: 'left',
145
+ fontSize: 13, fontWeight: 600, color: danger ? '#ef4444' : '#374151',
146
+ transition: 'background 0.12s',
147
+ }, 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',
150
+ borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
151
+ transition: 'background 0.13s',
152
+ }, onMouseEnter: e => e.currentTarget.style.background = '#f3f4f6', onMouseLeave: e => e.currentTarget.style.background = 'none', children: children }));
153
+ const hdrBtn = {
80
154
  background: 'rgba(255,255,255,0.2)', border: 'none', borderRadius: '50%',
81
- width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center',
155
+ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
82
156
  cursor: 'pointer', flexShrink: 0,
83
157
  };
158
+ // Group messages by date
159
+ function groupByDate(messages) {
160
+ const map = new Map();
161
+ messages.forEach(m => {
162
+ const d = formatDate(m.timestamp);
163
+ if (!map.has(d))
164
+ map.set(d, []);
165
+ map.get(d).push(m);
166
+ });
167
+ return Array.from(map.entries()).map(([date, msgs]) => ({ date, msgs }));
168
+ }