ajaxter-chat 1.0.3 → 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 (75) hide show
  1. package/README.md +124 -241
  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 +19 -0
  7. package/dist/components/ChatScreen/index.js +168 -0
  8. package/dist/components/ChatWidget.d.ts +0 -24
  9. package/dist/components/ChatWidget.js +228 -43
  10. package/dist/components/EmojiPicker/index.d.ts +8 -0
  11. package/dist/components/EmojiPicker/index.js +18 -0
  12. package/dist/components/HomeScreen/index.d.ts +8 -0
  13. package/dist/components/HomeScreen/index.js +55 -0
  14. package/dist/components/MaintenanceView/index.d.ts +0 -1
  15. package/dist/components/MaintenanceView/index.js +13 -52
  16. package/dist/components/RecentChatsScreen/index.d.ts +17 -0
  17. package/dist/components/RecentChatsScreen/index.js +8 -0
  18. package/dist/components/Tabs/BottomTabs.d.ts +10 -0
  19. package/dist/components/Tabs/BottomTabs.js +34 -0
  20. package/dist/components/TicketScreen/index.d.ts +9 -0
  21. package/dist/components/TicketScreen/index.js +54 -0
  22. package/dist/components/UserListScreen/index.d.ts +11 -0
  23. package/dist/components/UserListScreen/index.js +35 -0
  24. package/dist/config/index.d.ts +3 -16
  25. package/dist/config/index.js +20 -103
  26. package/dist/hooks/useChat.d.ts +10 -9
  27. package/dist/hooks/useChat.js +22 -40
  28. package/dist/hooks/useRemoteConfig.d.ts +6 -0
  29. package/dist/hooks/useRemoteConfig.js +22 -0
  30. package/dist/hooks/useWebRTC.d.ts +11 -0
  31. package/dist/hooks/useWebRTC.js +112 -0
  32. package/dist/index.d.ts +16 -11
  33. package/dist/index.js +15 -16
  34. package/dist/types/index.d.ts +66 -38
  35. package/dist/utils/chat.d.ts +13 -0
  36. package/dist/utils/chat.js +62 -0
  37. package/dist/utils/theme.d.ts +3 -2
  38. package/dist/utils/theme.js +13 -21
  39. package/package.json +10 -20
  40. package/public/chatData.json +162 -0
  41. package/src/components/BlockList/index.tsx +94 -0
  42. package/src/components/CallScreen/index.tsx +144 -0
  43. package/src/components/ChatScreen/index.tsx +469 -0
  44. package/src/components/ChatWidget.tsx +471 -0
  45. package/src/components/EmojiPicker/index.tsx +48 -0
  46. package/src/components/HomeScreen/index.tsx +106 -0
  47. package/src/components/MaintenanceView/index.tsx +38 -0
  48. package/src/components/RecentChatsScreen/index.tsx +63 -0
  49. package/src/components/Tabs/BottomTabs.tsx +90 -0
  50. package/src/components/TicketScreen/index.tsx +124 -0
  51. package/src/components/UserListScreen/index.tsx +103 -0
  52. package/src/config/index.ts +40 -0
  53. package/src/hooks/useChat.ts +48 -0
  54. package/src/hooks/useRemoteConfig.ts +20 -0
  55. package/src/hooks/useWebRTC.ts +130 -0
  56. package/src/index.ts +29 -0
  57. package/src/types/index.ts +127 -0
  58. package/src/utils/chat.ts +70 -0
  59. package/src/utils/theme.ts +27 -0
  60. package/dist/components/BottomNav/index.d.ts +0 -10
  61. package/dist/components/BottomNav/index.js +0 -32
  62. package/dist/components/ChatBox/index.d.ts +0 -15
  63. package/dist/components/ChatBox/index.js +0 -228
  64. package/dist/components/ChatButton/index.d.ts +0 -9
  65. package/dist/components/ChatButton/index.js +0 -17
  66. package/dist/components/ChatWindow/index.d.ts +0 -10
  67. package/dist/components/ChatWindow/index.js +0 -286
  68. package/dist/components/HomeView/index.d.ts +0 -12
  69. package/dist/components/HomeView/index.js +0 -51
  70. package/dist/components/UserList/index.d.ts +0 -13
  71. package/dist/components/UserList/index.js +0 -136
  72. package/dist/hooks/useUsers.d.ts +0 -14
  73. package/dist/hooks/useUsers.js +0 -32
  74. package/dist/services/userService.d.ts +0 -7
  75. package/dist/services/userService.js +0 -18
@@ -0,0 +1,168 @@
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
+ 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);
12
+ const endRef = useRef(null);
13
+ const inputRef = useRef(null);
14
+ const fileRef = useRef(null);
15
+ const recordTimer = useRef(null);
16
+ const mediaRecorder = useRef(null);
17
+ useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
18
+ const handleSend = useCallback(() => {
19
+ var _a;
20
+ if (!text.trim() || isPaused || isBlocked)
21
+ return;
22
+ onSend(text.trim());
23
+ setText('');
24
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
25
+ }, [text, isPaused, isBlocked, onSend]);
26
+ const handleKey = (e) => {
27
+ if (e.key === 'Enter' && !e.shiftKey) {
28
+ e.preventDefault();
29
+ handleSend();
30
+ }
31
+ };
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,
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: {
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" })] })] }) }))] }));
125
+ };
126
+ // ── Sub-components ─────────────────────────────────────────────────────────────
127
+ const Bubble = ({ msg, peer, primaryColor }) => {
128
+ var _a;
129
+ 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 });
131
+ 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',
133
+ borderRadius: isMe ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
134
+ backgroundColor: isMe ? primaryColor : '#fff',
135
+ color: isMe ? '#fff' : '#1a2332',
136
+ fontSize: 14, lineHeight: 1.5,
137
+ boxShadow: '0 1px 4px rgba(0,0,0,0.07)',
138
+ wordBreak: 'break-word',
139
+ }, children: content }), _jsx("span", { style: { fontSize: 11, color: '#b0bec5', padding: '0 4px' }, children: formatTime(msg.timestamp) })] }));
140
+ };
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 = {
154
+ background: 'rgba(255,255,255,0.2)', border: 'none', borderRadius: '50%',
155
+ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
156
+ cursor: 'pointer', flexShrink: 0,
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
+ }
@@ -1,28 +1,4 @@
1
1
  import React from 'react';
2
2
  import { ChatWidgetProps } from '../types';
3
- /**
4
- * ChatWidget
5
- *
6
- * Drop-in chat widget for React.js and Next.js apps.
7
- * All behavior is configured via environment variables.
8
- * All UI styling is configured via the `theme` prop.
9
- *
10
- * @example
11
- * // Basic usage
12
- * <ChatWidget />
13
- *
14
- * @example
15
- * // With custom theme
16
- * <ChatWidget
17
- * theme={{
18
- * primaryColor: '#FF6B6B',
19
- * buttonColor: '#FF6B6B',
20
- * buttonLabel: 'Need Help?',
21
- * buttonPosition: 'bottom-left',
22
- * fontFamily: "'Inter', sans-serif",
23
- * borderRadius: '12px',
24
- * }}
25
- * />
26
- */
27
3
  export declare const ChatWidget: React.FC<ChatWidgetProps>;
28
4
  export default ChatWidget;
@@ -1,50 +1,235 @@
1
- 'use client'; // Next.js App Router directive (ignored in React/Pages Router)
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect } from 'react';
4
- import { loadChatConfig } from '../config';
5
- import { ChatButton } from './ChatButton';
6
- import { ChatWindow } from './ChatWindow';
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import { loadLocalConfig } from '../config';
7
5
  import { mergeTheme } from '../utils/theme';
8
- /**
9
- * ChatWidget
10
- *
11
- * Drop-in chat widget for React.js and Next.js apps.
12
- * All behavior is configured via environment variables.
13
- * All UI styling is configured via the `theme` prop.
14
- *
15
- * @example
16
- * // Basic usage
17
- * <ChatWidget />
18
- *
19
- * @example
20
- * // With custom theme
21
- * <ChatWidget
22
- * theme={{
23
- * primaryColor: '#FF6B6B',
24
- * buttonColor: '#FF6B6B',
25
- * buttonLabel: 'Need Help?',
26
- * buttonPosition: 'bottom-left',
27
- * fontFamily: "'Inter', sans-serif",
28
- * borderRadius: '12px',
29
- * }}
30
- * />
31
- */
32
- export const ChatWidget = ({ theme }) => {
6
+ import { useRemoteConfig } from '../hooks/useRemoteConfig';
7
+ import { useChat } from '../hooks/useChat';
8
+ import { useWebRTC } from '../hooks/useWebRTC';
9
+ import { HomeScreen } from './HomeScreen';
10
+ import { UserListScreen } from './UserListScreen';
11
+ import { ChatScreen } from './ChatScreen';
12
+ import { RecentChatsScreen } from './RecentChatsScreen';
13
+ import { TicketScreen } from './TicketScreen';
14
+ import { BlockListScreen } from './BlockList';
15
+ import { CallScreen } from './CallScreen';
16
+ import { MaintenanceView } from './MaintenanceView';
17
+ import { BottomTabs } from './Tabs/BottomTabs';
18
+ /* ─── Drawer width ─────────────────────────────────────────────────────────── */
19
+ const DRAWER_W_NORMAL = 380;
20
+ const DRAWER_W_MAX = 480;
21
+ export const ChatWidget = ({ theme: localTheme }) => {
22
+ var _a, _b;
23
+ /* SSR guard */
24
+ const [mounted, setMounted] = useState(false);
25
+ useEffect(() => { setMounted(true); }, []);
26
+ /* Env config */
27
+ const { apiKey, widgetId } = loadLocalConfig();
28
+ /* Remote config */
29
+ const { data, loading: cfgLoading, error: cfgError } = useRemoteConfig(apiKey, widgetId);
30
+ /* Merged theme remote config overrides defaults, local prop overrides both */
31
+ const theme = mergeTheme((data === null || data === void 0 ? void 0 : data.widget) ? { primaryColor: data.widget.primaryColor, buttonLabel: data.widget.buttonLabel, buttonPosition: data.widget.buttonPosition } : undefined, localTheme);
32
+ /* Drawer open state */
33
33
  const [isOpen, setIsOpen] = useState(false);
34
- const [isMounted, setIsMounted] = useState(false);
35
- // SSR Safety: Only render on the client
34
+ const [isMaximized, setIsMaximized] = useState(false);
35
+ const [closing, setClosing] = useState(false); // for slide-out animation
36
+ /* Navigation */
37
+ const [activeTab, setActiveTab] = useState('home');
38
+ const [screen, setScreen] = useState('home');
39
+ const [userListCtx, setUserListCtx] = useState('support');
40
+ /* App state */
41
+ const [tickets, setTickets] = useState((_a = data === null || data === void 0 ? void 0 : data.sampleTickets) !== null && _a !== void 0 ? _a : []);
42
+ const [recentChats, setRecentChats] = useState([]);
43
+ const [blockedUids, setBlockedUids] = useState((_b = data === null || data === void 0 ? void 0 : data.blockedUsers) !== null && _b !== void 0 ? _b : []);
44
+ /* Sync remote data into local state once loaded */
36
45
  useEffect(() => {
37
- setIsMounted(true);
46
+ var _a, _b;
47
+ if (data) {
48
+ setTickets(data.sampleTickets);
49
+ setBlockedUids(data.blockedUsers);
50
+ // Seed recent chats from sample chats
51
+ const all = [...((_a = data.developers) !== null && _a !== void 0 ? _a : []), ...((_b = data.users) !== null && _b !== void 0 ? _b : [])];
52
+ const recents = Object.entries(data.sampleChats).map(([uid, msgs]) => {
53
+ const user = all.find(u => u.uid === uid);
54
+ if (!user || msgs.length === 0)
55
+ return null;
56
+ const last = msgs[msgs.length - 1];
57
+ return {
58
+ id: `rc_${uid}`,
59
+ user,
60
+ lastMessage: last.text,
61
+ lastTime: last.timestamp,
62
+ unread: Math.floor(Math.random() * 3),
63
+ isPaused: false,
64
+ };
65
+ }).filter(Boolean);
66
+ setRecentChats(recents);
67
+ }
68
+ }, [data]);
69
+ /* Chat hook */
70
+ const { messages, activeUser, isPaused, isReported, selectUser, sendMessage, togglePause, reportChat, clearChat, setMessages, } = useChat();
71
+ /* WebRTC hook */
72
+ const { session: callSession, localVideoRef, remoteVideoRef, startCall, endCall, toggleMute, toggleCamera } = useWebRTC();
73
+ /* ── Drawer open/close with slide animation ───────────────────────────── */
74
+ const openDrawer = () => {
75
+ setClosing(false);
76
+ setIsOpen(true);
77
+ };
78
+ const closeDrawer = useCallback(() => {
79
+ setClosing(true);
80
+ setTimeout(() => {
81
+ setIsOpen(false);
82
+ setClosing(false);
83
+ setScreen('home');
84
+ setActiveTab('home');
85
+ clearChat();
86
+ }, 300);
87
+ }, [clearChat]);
88
+ /* ── Navigation ──────────────────────────────────────────────────────── */
89
+ const handleCardClick = useCallback((ctx) => {
90
+ if (ctx === 'ticket') {
91
+ setActiveTab('tickets');
92
+ setScreen('tickets');
93
+ }
94
+ else {
95
+ setUserListCtx(ctx);
96
+ setScreen('user-list');
97
+ }
38
98
  }, []);
39
- // Load config from env (safe on both client and server)
40
- const config = loadChatConfig();
41
- const t = mergeTheme(theme);
42
- // Don't render anything server-side
43
- if (!isMounted)
44
- return null;
45
- // DISABLE: render nothing at all
46
- if (config.status === 'DISABLE')
99
+ const handleSelectUser = useCallback((user) => {
100
+ var _a;
101
+ // Load history from sample chats if available
102
+ const history = (_a = data === null || data === void 0 ? void 0 : data.sampleChats[user.uid]) !== null && _a !== void 0 ? _a : [];
103
+ selectUser(user, history);
104
+ setScreen('chat');
105
+ // Update recent chats
106
+ setRecentChats(prev => {
107
+ const exists = prev.find(r => r.user.uid === user.uid);
108
+ if (exists)
109
+ return prev;
110
+ return [{ id: `rc_${user.uid}`, user, lastMessage: '', lastTime: new Date().toISOString(), unread: 0, isPaused: false }, ...prev];
111
+ });
112
+ }, [data, selectUser]);
113
+ const handleTabChange = useCallback((tab) => {
114
+ setActiveTab(tab);
115
+ setScreen(tab === 'home' ? 'home' : tab === 'chats' ? 'recent-chats' : 'tickets');
116
+ }, []);
117
+ /* ── Block/Unblock ───────────────────────────────────────────────────── */
118
+ const handleBlock = useCallback(() => {
119
+ if (!activeUser)
120
+ return;
121
+ setBlockedUids(prev => [...prev, activeUser.uid]);
122
+ clearChat();
123
+ setScreen('home');
124
+ setActiveTab('home');
125
+ }, [activeUser, clearChat]);
126
+ const handleUnblock = useCallback((uid) => {
127
+ setBlockedUids(prev => prev.filter(id => id !== uid));
128
+ }, []);
129
+ /* ── Tickets ─────────────────────────────────────────────────────────── */
130
+ const handleRaiseTicket = useCallback((title, desc, priority) => {
131
+ const t = {
132
+ id: `TKT-${String(Date.now()).slice(-4)}`,
133
+ title, description: desc, status: 'open', priority,
134
+ createdAt: new Date().toISOString(),
135
+ updatedAt: new Date().toISOString(),
136
+ assignedTo: null,
137
+ };
138
+ setTickets(prev => [t, ...prev]);
139
+ }, []);
140
+ /* ── Pause sync back into recent chats ──────────────────────────────── */
141
+ const handleTogglePause = useCallback(() => {
142
+ togglePause();
143
+ if (activeUser) {
144
+ setRecentChats(prev => prev.map(r => r.user.uid === activeUser.uid ? Object.assign(Object.assign({}, r), { isPaused: !isPaused }) : r));
145
+ }
146
+ }, [togglePause, activeUser, isPaused]);
147
+ /* ── Call ────────────────────────────────────────────────────────────── */
148
+ const handleStartCall = useCallback((withVideo) => {
149
+ if (!activeUser)
150
+ return;
151
+ startCall(activeUser, withVideo);
152
+ setScreen('call');
153
+ }, [activeUser, startCall]);
154
+ const handleEndCall = useCallback(() => {
155
+ endCall();
156
+ setScreen('chat');
157
+ }, [endCall]);
158
+ /* ── Derived ─────────────────────────────────────────────────────────── */
159
+ const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
160
+ const drawerW = isMaximized ? DRAWER_W_MAX : DRAWER_W_NORMAL;
161
+ const widgetConfig = data === null || data === void 0 ? void 0 : data.widget;
162
+ const primaryColor = theme.primaryColor;
163
+ const allUsers = data ? [...data.developers, ...data.users] : [];
164
+ const filteredUsers = screen === 'user-list'
165
+ ? allUsers.filter(u => userListCtx === 'support' ? u.type === 'developer' : u.type === 'user')
166
+ : [];
167
+ const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
168
+ /* Position */
169
+ const posStyle = theme.buttonPosition === 'bottom-left'
170
+ ? { left: 24, right: 'auto' }
171
+ : { right: 24, left: 'auto' };
172
+ const drawerPosStyle = theme.buttonPosition === 'bottom-left'
173
+ ? { left: 0, borderRadius: '0 16px 16px 0' }
174
+ : { right: 0, borderRadius: '16px 0 0 16px' };
175
+ /* ── Don't render until mounted (SSR safe) ──────────────────────────── */
176
+ if (!mounted)
47
177
  return null;
48
- return (_jsxs(_Fragment, { children: [isOpen && (_jsx(ChatWindow, { config: config, theme: theme, buttonPosition: t.buttonPosition, onClose: () => setIsOpen(false) })), _jsx(ChatButton, { isOpen: isOpen, onClick: () => setIsOpen((prev) => !prev), theme: theme })] }));
178
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
179
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
180
+
181
+ .cw-root * { box-sizing: border-box; font-family: 'DM Sans', 'Segoe UI', sans-serif; }
182
+
183
+ @keyframes cw-slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
184
+ @keyframes cw-slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
185
+ @keyframes cw-slideInLeft { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
186
+ @keyframes cw-slideOutLeft { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-100%); opacity: 0; } }
187
+ @keyframes cw-fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
188
+ @keyframes cw-slideIn { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: translateX(0); } }
189
+ @keyframes cw-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
190
+ @keyframes cw-btnPop { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
191
+
192
+ .cw-scroll::-webkit-scrollbar { width: 4px; }
193
+ .cw-scroll::-webkit-scrollbar-track { background: transparent; }
194
+ .cw-scroll::-webkit-scrollbar-thumb { background: #e0e0e0; border-radius: 4px; }
195
+
196
+ .cw-drawer-enter { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideInLeft' : 'cw-slideInRight'} 0.32s cubic-bezier(0.22,1,0.36,1) both; }
197
+ .cw-drawer-exit { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideOutLeft' : 'cw-slideOutRight'} 0.28s cubic-bezier(0.55,0,1,0.45) both; }
198
+ ` }), !isOpen && (_jsxs("button", { className: "cw-root", onClick: openDrawer, "aria-label": theme.buttonLabel, style: Object.assign(Object.assign({ position: 'fixed', bottom: 24, zIndex: 9999 }, posStyle), { display: 'flex', alignItems: 'center', gap: 10, padding: '13px 22px', backgroundColor: theme.buttonColor, color: theme.buttonTextColor, border: 'none', borderRadius: 50, cursor: 'pointer', fontSize: 15, fontWeight: 700, boxShadow: `0 8px 28px ${theme.buttonColor}55`, animation: 'cw-btnPop 0.4s cubic-bezier(0.34,1.56,0.64,1)', transition: 'transform 0.2s, box-shadow 0.2s' }), onMouseEnter: e => {
199
+ e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)';
200
+ e.currentTarget.style.boxShadow = `0 14px 36px ${theme.buttonColor}66`;
201
+ }, onMouseLeave: e => {
202
+ e.currentTarget.style.transform = 'scale(1)';
203
+ e.currentTarget.style.boxShadow = `0 8px 28px ${theme.buttonColor}55`;
204
+ }, children: [_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z", stroke: theme.buttonTextColor, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsx("span", { children: theme.buttonLabel })] })), isOpen && (_jsx("div", { onClick: closeDrawer, style: {
205
+ position: 'fixed', inset: 0, zIndex: 9997,
206
+ backgroundColor: 'rgba(0,0,0,0.35)',
207
+ opacity: closing ? 0 : 1,
208
+ transition: 'opacity 0.3s',
209
+ } })), isOpen && (_jsxs("div", { className: `cw-root ${closing ? 'cw-drawer-exit' : 'cw-drawer-enter'}`, style: Object.assign(Object.assign({ position: 'fixed', top: 0, bottom: 0 }, drawerPosStyle), { zIndex: 9998, width: drawerW, maxWidth: '100vw', backgroundColor: '#fff', boxShadow: theme.buttonPosition === 'bottom-left'
210
+ ? '4px 0 40px rgba(0,0,0,0.18)'
211
+ : '-4px 0 40px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column', overflow: 'hidden', transition: 'width 0.28s ease' }), children: [cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 }, children: [_jsx("div", { style: {
212
+ width: 40, height: 40, borderRadius: '50%',
213
+ border: `3px solid ${primaryColor}30`,
214
+ borderTopColor: primaryColor,
215
+ animation: 'spin 0.8s linear infinite',
216
+ } }), _jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }), _jsx("p", { style: { fontSize: 14, color: '#7b8fa1' }, children: "Loading chat\u2026" })] })), cfgError && !cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 12, padding: 32, textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\u26A0\uFE0F" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Could not load chat configuration" }), _jsx("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6 }, children: cfgError }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), !cfgLoading && !cfgError && widgetConfig && (_jsxs(_Fragment, { children: [screen !== 'chat' && screen !== 'call' && (_jsxs("div", { style: {
217
+ position: 'absolute', top: 12,
218
+ right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
219
+ left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
220
+ zIndex: 20, display: 'flex', gap: 6,
221
+ }, children: [_jsx(CornerBtn, { onClick: () => setIsMaximized(m => !m), title: isMaximized ? 'Minimize' : 'Maximize', children: isMaximized
222
+ ? _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' &&
224
+ screen !== 'chat' &&
225
+ screen !== 'call' &&
226
+ screen !== 'user-list' &&
227
+ screen !== 'block-list' && (_jsx(BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: primaryColor, onBlockList: () => setScreen('block-list') }))] }))] }))] }));
49
228
  };
50
229
  export default ChatWidget;
230
+ /* ── Tiny corner button ────────────────────────────────────────────────────── */
231
+ const CornerBtn = ({ onClick, title, children }) => (_jsx("button", { onClick: onClick, title: title, style: {
232
+ width: 26, height: 26, borderRadius: '50%',
233
+ background: 'rgba(0,0,0,0.25)', border: 'none',
234
+ display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
235
+ }, children: children }));
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface EmojiPickerProps {
3
+ onSelect: (emoji: string) => void;
4
+ onClose: () => void;
5
+ primaryColor: string;
6
+ }
7
+ export declare const EmojiPicker: React.FC<EmojiPickerProps>;
8
+ export {};
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const EMOJIS = [
3
+ '😀', '😂', '😊', '😍', '🤔', '😎', '😢', '😡',
4
+ '👍', '👎', '👏', '🙏', '🎉', '❤️', '🔥', '✅',
5
+ '🚀', '💡', '⚠️', '🎫',
6
+ ];
7
+ export const EmojiPicker = ({ onSelect, onClose, primaryColor }) => (_jsxs("div", { style: {
8
+ position: 'absolute', bottom: '100%', right: 0,
9
+ background: '#fff', borderRadius: 14,
10
+ boxShadow: '0 8px 32px rgba(0,0,0,0.18)',
11
+ padding: '12px', zIndex: 100,
12
+ animation: 'cw-fadeUp 0.18s ease',
13
+ marginBottom: 8,
14
+ }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, children: [_jsx("span", { style: { fontSize: 11, fontWeight: 700, color: '#7b8fa1', textTransform: 'uppercase', letterSpacing: '0.06em' }, children: "Emojis" }), _jsx("button", { onClick: onClose, style: { background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: '#7b8fa1', fontSize: 14 }, children: "\u2715" })] }), _jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 4, width: 200 }, children: EMOJIS.map(e => (_jsx("button", { onClick: () => { onSelect(e); onClose(); }, style: {
15
+ background: 'none', border: 'none', cursor: 'pointer',
16
+ fontSize: 22, padding: '6px', borderRadius: 8,
17
+ transition: 'background 0.12s',
18
+ }, onMouseEnter: el => el.currentTarget.style.background = `${primaryColor}15`, onMouseLeave: el => el.currentTarget.style.background = 'none', children: e }, e))) })] }));
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { WidgetConfig, UserListContext } from '../../types';
3
+ interface HomeScreenProps {
4
+ config: WidgetConfig;
5
+ onNavigate: (ctx: UserListContext | 'ticket') => void;
6
+ }
7
+ export declare const HomeScreen: React.FC<HomeScreenProps>;
8
+ export {};
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const HomeScreen = ({ config, onNavigate }) => {
3
+ const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
4
+ const showChat = config.chatType === 'CHAT' || config.chatType === 'BOTH';
5
+ const cards = [
6
+ showSupport && {
7
+ key: 'support',
8
+ icon: '🛠',
9
+ title: 'Need Support',
10
+ subtitle: 'We typically reply in a few minutes',
11
+ onClick: () => onNavigate('support'),
12
+ },
13
+ showChat && {
14
+ key: 'conversation',
15
+ icon: '💬',
16
+ title: 'New Conversation',
17
+ subtitle: 'With your colleague',
18
+ onClick: () => onNavigate('conversation'),
19
+ },
20
+ {
21
+ key: 'ticket',
22
+ icon: '🎫',
23
+ title: 'Raise Ticket',
24
+ subtitle: 'For major changes',
25
+ onClick: () => onNavigate('ticket'),
26
+ },
27
+ ].filter(Boolean);
28
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsxs("div", { style: {
29
+ background: `linear-gradient(145deg, ${config.primaryColor}, ${config.primaryColor}dd)`,
30
+ padding: '36px 24px 52px',
31
+ flexShrink: 0,
32
+ position: 'relative',
33
+ overflow: 'hidden',
34
+ }, children: [_jsx("div", { style: { position: 'absolute', top: -40, right: -40, width: 140, height: 140, borderRadius: '50%', background: 'rgba(255,255,255,0.07)' } }), _jsx("div", { style: { position: 'absolute', bottom: -20, left: -20, width: 90, height: 90, borderRadius: '50%', background: 'rgba(255,255,255,0.05)' } }), _jsx("h1", { style: { margin: '0 0 8px', fontSize: 26, fontWeight: 800, color: '#fff', letterSpacing: '-0.03em' }, children: config.welcomeTitle }), _jsx("p", { style: { margin: 0, fontSize: 14, color: 'rgba(255,255,255,0.85)', lineHeight: 1.6 }, children: config.welcomeSubtitle })] }), _jsx("div", { style: { flex: 1, overflowY: 'auto', padding: '0 16px 20px', marginTop: -28, display: 'flex', flexDirection: 'column', gap: 10 }, children: cards.map((card, i) => (_jsxs("button", { onClick: card.onClick, style: {
35
+ width: '100%', background: '#fff', border: 'none', borderRadius: 14,
36
+ padding: '18px 20px', display: 'flex', alignItems: 'center',
37
+ justifyContent: 'space-between', cursor: 'pointer', textAlign: 'left',
38
+ boxShadow: '0 2px 14px rgba(0,0,0,0.10)',
39
+ animation: `cw-fadeUp 0.35s ease both`,
40
+ animationDelay: `${i * 0.08}s`,
41
+ transition: 'transform 0.15s, box-shadow 0.15s',
42
+ }, onMouseEnter: e => {
43
+ e.currentTarget.style.transform = 'translateY(-2px)';
44
+ e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.14)';
45
+ }, onMouseLeave: e => {
46
+ e.currentTarget.style.transform = 'translateY(0)';
47
+ e.currentTarget.style.boxShadow = '0 2px 14px rgba(0,0,0,0.10)';
48
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 14 }, children: [_jsx("div", { style: {
49
+ width: 44, height: 44, borderRadius: 12,
50
+ backgroundColor: `${config.primaryColor}14`,
51
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
52
+ fontSize: 20, flexShrink: 0,
53
+ }, children: card.icon }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: 15, color: '#1a2332', marginBottom: 2 }, children: card.title }), _jsx("div", { style: { fontSize: 12, color: '#7b8fa1' }, children: card.subtitle })] })] }), _jsx(SendArrow, { color: config.primaryColor })] }, card.key))) })] }));
54
+ };
55
+ const SendArrow = ({ color }) => (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M5 12h14M12 5l7 7-7 7", stroke: color, strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  interface MaintenanceViewProps {
3
- fontFamily: string;
4
3
  primaryColor: string;
5
4
  }
6
5
  export declare const MaintenanceView: React.FC<MaintenanceViewProps>;