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.
- package/README.md +119 -128
- package/dist/components/BlockList/index.d.ts +10 -0
- package/dist/components/BlockList/index.js +33 -0
- package/dist/components/CallScreen/index.d.ts +13 -0
- package/dist/components/CallScreen/index.js +48 -0
- package/dist/components/ChatScreen/index.d.ts +10 -3
- package/dist/components/ChatScreen/index.js +142 -57
- package/dist/components/ChatWidget.js +192 -98
- package/dist/components/EmojiPicker/index.d.ts +8 -0
- package/dist/components/EmojiPicker/index.js +18 -0
- package/dist/components/HomeScreen/index.d.ts +2 -3
- package/dist/components/HomeScreen/index.js +25 -41
- package/dist/components/MaintenanceView/index.d.ts +0 -1
- package/dist/components/MaintenanceView/index.js +4 -6
- package/dist/components/RecentChatsScreen/index.d.ts +4 -3
- package/dist/components/RecentChatsScreen/index.js +7 -37
- package/dist/components/Tabs/BottomTabs.d.ts +1 -1
- package/dist/components/Tabs/BottomTabs.js +25 -20
- package/dist/components/TicketScreen/index.d.ts +3 -3
- package/dist/components/TicketScreen/index.js +39 -56
- package/dist/components/UserListScreen/index.d.ts +2 -4
- package/dist/components/UserListScreen/index.js +33 -62
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +18 -26
- package/dist/hooks/useChat.d.ts +8 -3
- package/dist/hooks/useChat.js +22 -18
- package/dist/hooks/useRemoteConfig.d.ts +6 -0
- package/dist/hooks/useRemoteConfig.js +22 -0
- package/dist/hooks/useWebRTC.d.ts +11 -0
- package/dist/hooks/useWebRTC.js +112 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +8 -4
- package/dist/types/index.d.ts +62 -21
- package/dist/utils/chat.d.ts +13 -0
- package/dist/utils/chat.js +62 -0
- package/dist/utils/theme.d.ts +3 -1
- package/dist/utils/theme.js +14 -7
- package/package.json +4 -4
- package/public/chatData.json +162 -0
- package/src/components/BlockList/index.tsx +94 -0
- package/src/components/CallScreen/index.tsx +144 -0
- package/src/components/ChatScreen/index.tsx +403 -139
- package/src/components/ChatWidget.tsx +394 -250
- package/src/components/EmojiPicker/index.tsx +48 -0
- package/src/components/HomeScreen/index.tsx +58 -82
- package/src/components/MaintenanceView/index.tsx +6 -9
- package/src/components/RecentChatsScreen/index.tsx +51 -96
- package/src/components/Tabs/BottomTabs.tsx +45 -37
- package/src/components/TicketScreen/index.tsx +87 -133
- package/src/components/UserListScreen/index.tsx +75 -153
- package/src/config/index.ts +22 -28
- package/src/hooks/useChat.ts +31 -14
- package/src/hooks/useRemoteConfig.ts +20 -0
- package/src/hooks/useWebRTC.ts +130 -0
- package/src/index.ts +26 -15
- package/src/types/index.ts +85 -40
- package/src/utils/chat.ts +70 -0
- package/src/utils/theme.ts +18 -7
- package/dist/hooks/useUsers.d.ts +0 -7
- package/dist/hooks/useUsers.js +0 -26
- package/dist/services/userService.d.ts +0 -2
- package/dist/services/userService.js +0 -9
- package/dist/src/components/ChatScreen/index.d.ts +0 -12
- package/dist/src/components/ChatScreen/index.js +0 -83
- package/dist/src/components/ChatWidget.d.ts +0 -4
- package/dist/src/components/ChatWidget.js +0 -141
- package/dist/src/components/HomeScreen/index.d.ts +0 -9
- package/dist/src/components/HomeScreen/index.js +0 -71
- package/dist/src/components/MaintenanceView/index.d.ts +0 -7
- package/dist/src/components/MaintenanceView/index.js +0 -16
- package/dist/src/components/RecentChatsScreen/index.d.ts +0 -16
- package/dist/src/components/RecentChatsScreen/index.js +0 -38
- package/dist/src/components/Tabs/BottomTabs.d.ts +0 -10
- package/dist/src/components/Tabs/BottomTabs.js +0 -29
- package/dist/src/components/TicketScreen/index.d.ts +0 -9
- package/dist/src/components/TicketScreen/index.js +0 -71
- package/dist/src/components/UserListScreen/index.d.ts +0 -13
- package/dist/src/components/UserListScreen/index.js +0 -64
- package/dist/src/config/index.d.ts +0 -3
- package/dist/src/config/index.js +0 -38
- package/dist/src/hooks/useChat.d.ts +0 -8
- package/dist/src/hooks/useChat.js +0 -26
- package/dist/src/hooks/useUsers.d.ts +0 -7
- package/dist/src/hooks/useUsers.js +0 -26
- package/dist/src/index.d.ts +0 -14
- package/dist/src/index.js +0 -13
- package/dist/src/services/userService.d.ts +0 -2
- package/dist/src/services/userService.js +0 -9
- package/dist/src/types/index.d.ts +0 -59
- package/dist/src/types/index.js +0 -1
- package/dist/src/utils/theme.d.ts +0 -3
- package/dist/src/utils/theme.js +0 -13
- package/src/hooks/useUsers.ts +0 -27
- 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 {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
126
|
+
// ── Sub-components ─────────────────────────────────────────────────────────────
|
|
127
|
+
const Bubble = ({ msg, peer, primaryColor }) => {
|
|
128
|
+
var _a;
|
|
66
129
|
const isMe = msg.senderId === 'me';
|
|
67
|
-
const
|
|
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: '
|
|
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:
|
|
136
|
+
fontSize: 14, lineHeight: 1.5,
|
|
74
137
|
boxShadow: '0 1px 4px rgba(0,0,0,0.07)',
|
|
75
|
-
|
|
76
|
-
}, children:
|
|
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
|
|
79
|
-
const
|
|
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:
|
|
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
|
+
}
|