ajaxter-chat 3.0.5 → 3.0.7

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.
@@ -16,6 +16,9 @@ interface ChatScreenProps {
16
16
  onStartCall: (withVideo: boolean) => void;
17
17
  /** Navigate to support list, colleague list, or tickets (from slide menu) */
18
18
  onNavAction: (ctx: UserListContext | 'ticket') => void;
19
+ /** Other devs (excl. viewer) — for transfer when staff chats with a customer */
20
+ otherDevelopers?: ChatUser[];
21
+ onTransferToDeveloper?: (dev: ChatUser) => void;
19
22
  }
20
23
  export declare const ChatScreen: React.FC<ChatScreenProps>;
21
24
  export {};
@@ -1,16 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from '../../utils/chat';
4
+ import { shouldShowPrivacyNotice, dismissPrivacyNotice } from '../../utils/privacyConsent';
4
5
  import { EmojiPicker } from '../EmojiPicker';
5
6
  import { SlideNavMenu } from '../SlideNavMenu';
6
- export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, }) => {
7
+ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported, isBlocked, onSend, onBack, onClose, onTogglePause, onReport, onBlock, onStartCall, onNavAction, otherDevelopers = [], onTransferToDeveloper, }) => {
8
+ var _a;
7
9
  const [text, setText] = useState('');
8
10
  const [showEmoji, setShowEmoji] = useState(false);
9
11
  const [showMenu, setShowMenu] = useState(false);
10
12
  const [slideMenuOpen, setSlideMenuOpen] = useState(false);
13
+ const [transferOpen, setTransferOpen] = useState(false);
11
14
  const [isRecording, setIsRecording] = useState(false);
12
15
  const [recordSec, setRecordSec] = useState(0);
13
16
  const [showConfirm, setShowConfirm] = useState(null);
17
+ const [showPrivacy, setShowPrivacy] = useState(false);
14
18
  const endRef = useRef(null);
15
19
  const inputRef = useRef(null);
16
20
  const fileRef = useRef(null);
@@ -18,6 +22,24 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
18
22
  const mediaRecorder = useRef(null);
19
23
  const recordChunks = useRef([]);
20
24
  useEffect(() => { var _a; (_a = endRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
25
+ const privacyEnabled = config.showPrivacyNotice !== false;
26
+ useEffect(() => {
27
+ if (!privacyEnabled)
28
+ return;
29
+ setShowPrivacy(shouldShowPrivacyNotice(config.id));
30
+ }, [config.id, privacyEnabled]);
31
+ useEffect(() => {
32
+ if (!privacyEnabled)
33
+ return;
34
+ const id = window.setInterval(() => {
35
+ setShowPrivacy(shouldShowPrivacyNotice(config.id));
36
+ }, 60000);
37
+ return () => window.clearInterval(id);
38
+ }, [config.id, privacyEnabled]);
39
+ const dismissPrivacy = useCallback(() => {
40
+ dismissPrivacyNotice(config.id);
41
+ setShowPrivacy(false);
42
+ }, [config.id]);
21
43
  const handleSend = useCallback(() => {
22
44
  var _a;
23
45
  if (!text.trim() || isPaused || isBlocked)
@@ -96,10 +118,37 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
96
118
  const peerAvatar = avatarColor(activeUser.name);
97
119
  const peerInit = initials(activeUser.name);
98
120
  const grouped = groupByDate(messages);
99
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative', overflow: 'hidden' }, children: [_jsx(SlideNavMenu, { open: slideMenuOpen, onClose: () => setSlideMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, onSelect: onNavAction, onBackHome: onBack }), _jsxs("div", { style: {
121
+ const viewerIsDev = config.viewerType === 'developer';
122
+ const headerRole = viewerIsDev
123
+ ? (activeUser.type === 'user' ? 'Customer' : 'Developer')
124
+ : 'Support';
125
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative', overflow: 'hidden' }, children: [_jsx(SlideNavMenu, { open: slideMenuOpen, onClose: () => setSlideMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, viewerType: (_a = config.viewerType) !== null && _a !== void 0 ? _a : 'user', onSelect: onNavAction, onBackHome: onBack }), _jsxs("div", { style: {
100
126
  background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
101
127
  padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
102
- }, children: [_jsx("button", { type: "button", onClick: () => setSlideMenuOpen(true), style: hdrBtn, "aria-label": "Open menu", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [_jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" })] }) }), _jsxs("div", { style: { width: 36, height: 36, borderRadius: '50%', backgroundColor: peerAvatar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700, fontSize: 13, flexShrink: 0, position: 'relative' }, children: [peerInit, _jsx("span", { style: { position: 'absolute', bottom: 0, right: 0, width: 9, height: 9, borderRadius: '50%', border: '2px solid', borderColor: 'transparent', backgroundColor: activeUser.status === 'online' ? '#22c55e' : activeUser.status === 'away' ? '#f59e0b' : '#9ca3af' } })] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: activeUser.name }), _jsx("div", { style: { fontSize: 11, color: 'rgba(255,255,255,0.8)' }, children: activeUser.designation })] }), _jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#fff', opacity: 0.95, flexShrink: 0 }, children: "Support" }), config.allowWebCall && (_jsx("button", { type: "button", onClick: () => onStartCall(false), style: hdrBtn, title: "Voice Call", children: _jsx("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }) })), _jsx("button", { type: "button", onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.2)' }), title: "More options", "aria-expanded": showMenu, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "12", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "19", r: "1.5", fill: "#fff" })] }) })] }), showMenu && (_jsxs("div", { style: { position: 'absolute', top: 52, right: 12, zIndex: 120, background: '#fff', borderRadius: 12, boxShadow: '0 8px 30px rgba(0,0,0,0.16)', padding: '6px', minWidth: 200, animation: 'cw-fadeUp 0.18s ease' }, children: [config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), _jsx(MenuItem, { icon: isPaused ? '▶️' : '⏸', label: isPaused ? 'Resume Chat' : 'Pause Chat', onClick: () => { setShowMenu(false); setShowConfirm('pause'); } }), config.allowReport && !isReported && (_jsx(MenuItem, { icon: "\u26A0\uFE0F", label: "Report Chat", onClick: () => { setShowMenu(false); setShowConfirm('report'); } })), config.allowBlock && activeUser.type === 'user' && !isBlocked && (_jsx(MenuItem, { icon: "\uD83D\uDEAB", label: "Block User", onClick: () => { setShowMenu(false); setShowConfirm('block'); }, danger: true })), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), _jsx(MenuItem, { icon: "\u2715", label: "Close Chat", onClick: onClose })] })), isPaused && (_jsxs("div", { style: { background: '#fef3c7', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#92400e', textAlign: 'center', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }, children: ["\u23F8 Chat is paused \u2014 users cannot send messages", _jsx("button", { type: "button", onClick: onTogglePause, style: { background: '#92400e', color: '#fff', border: 'none', borderRadius: 6, padding: '2px 8px', fontSize: 11, cursor: 'pointer', marginLeft: 4 }, children: "Resume" })] })), isBlocked && (_jsx("div", { style: { background: '#fee2e2', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#991b1b', textAlign: 'center', flexShrink: 0 }, children: "\uD83D\uDEAB This user is blocked" })), isReported && (_jsx("div", { style: { background: '#fef3c7', padding: '6px 16px', fontSize: 11, color: '#92400e', textAlign: 'center', flexShrink: 0 }, children: "\u26A0\uFE0F This chat has been reported" })), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '14px', display: 'flex', flexDirection: 'column', gap: 10, background: '#f8f9fc' }, className: "cw-scroll", children: [grouped.map(({ date, msgs }) => (_jsxs(React.Fragment, { children: [_jsx(DateDivider, { label: date }), msgs.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: config.primaryColor }, msg.id)))] }, date))), messages.length === 0 && (_jsxs("div", { style: { margin: 'auto', textAlign: 'center', color: '#c4cad4', fontSize: 13 }, children: [_jsx("div", { style: { fontSize: 28, marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hello to ", activeUser.name, "!"] })), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #eef0f5', padding: '10px 12px 8px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [showEmoji && config.allowEmoji && (_jsx(EmojiPicker, { primaryColor: config.primaryColor, onSelect: e => setText(t => t + e), onClose: () => setShowEmoji(false) })), isRecording && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, padding: '8px 12px', background: '#fee2e2', borderRadius: 10 }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: '50%', background: '#ef4444', display: 'inline-block', animation: 'cw-pulse 1s infinite' } }), _jsxs("span", { style: { fontSize: 13, color: '#991b1b', fontWeight: 600 }, children: ["Recording ", recordSec, "s"] }), _jsx("button", { type: "button", onClick: stopRecording, style: { marginLeft: 'auto', background: '#ef4444', color: '#fff', border: 'none', borderRadius: 6, padding: '4px 12px', fontSize: 12, cursor: 'pointer', fontWeight: 600 }, children: "Stop & send" })] })), _jsxs("div", { style: {
128
+ }, children: [_jsx("button", { type: "button", onClick: () => setSlideMenuOpen(true), style: hdrBtn, "aria-label": "Open menu", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [_jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }), _jsx("line", { x1: "3", y1: "18", x2: "21", y2: "18", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" })] }) }), _jsxs("div", { style: { width: 36, height: 36, borderRadius: '50%', backgroundColor: peerAvatar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 700, fontSize: 13, flexShrink: 0, position: 'relative' }, children: [peerInit, _jsx("span", { style: { position: 'absolute', bottom: 0, right: 0, width: 9, height: 9, borderRadius: '50%', border: '2px solid', borderColor: 'transparent', backgroundColor: activeUser.status === 'online' ? '#22c55e' : activeUser.status === 'away' ? '#f59e0b' : '#9ca3af' } })] }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: activeUser.name }), _jsx("div", { style: { fontSize: 11, color: 'rgba(255,255,255,0.8)' }, children: activeUser.designation })] }), _jsx("span", { style: { fontSize: 13, fontWeight: 700, color: '#fff', opacity: 0.95, flexShrink: 0 }, children: headerRole }), config.allowWebCall && (_jsx("button", { type: "button", onClick: () => onStartCall(false), style: hdrBtn, title: "Voice Call", children: _jsx("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }) })), _jsx("button", { type: "button", onClick: () => setShowMenu(v => !v), style: Object.assign(Object.assign({}, hdrBtn), { background: 'rgba(255,255,255,0.2)' }), title: "More options", "aria-expanded": showMenu, children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "5", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "12", r: "1.5", fill: "#fff" }), _jsx("circle", { cx: "12", cy: "19", r: "1.5", fill: "#fff" })] }) })] }), showMenu && (_jsxs("div", { style: { position: 'absolute', top: 52, right: 12, zIndex: 120, background: '#fff', borderRadius: 12, boxShadow: '0 8px 30px rgba(0,0,0,0.16)', padding: '6px', minWidth: 200, animation: 'cw-fadeUp 0.18s ease' }, children: [config.allowTranscriptDownload && (_jsx(MenuItem, { icon: "\uD83D\uDCE5", label: "Download Transcript", onClick: handleTranscript })), viewerIsDev && activeUser.type === 'user' && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx(MenuItem, { icon: "\uD83D\uDD00", label: "Transfer to developer", onClick: () => { setShowMenu(false); setTransferOpen(true); } })), _jsx(MenuItem, { icon: isPaused ? '▶️' : '⏸', label: isPaused ? 'Resume Chat' : 'Pause Chat', onClick: () => { setShowMenu(false); setShowConfirm('pause'); } }), config.allowReport && !isReported && (_jsx(MenuItem, { icon: "\u26A0\uFE0F", label: "Report Chat", onClick: () => { setShowMenu(false); setShowConfirm('report'); } })), config.allowBlock && activeUser.type === 'user' && !isBlocked && (_jsx(MenuItem, { icon: "\uD83D\uDEAB", label: "Block User", onClick: () => { setShowMenu(false); setShowConfirm('block'); }, danger: true })), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), _jsx(MenuItem, { icon: "\u2715", label: "Close Chat", onClick: onClose })] })), isPaused && (_jsxs("div", { style: { background: '#fef3c7', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#92400e', textAlign: 'center', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }, children: ["\u23F8 Chat is paused \u2014 users cannot send messages", _jsx("button", { type: "button", onClick: onTogglePause, style: { background: '#92400e', color: '#fff', border: 'none', borderRadius: 6, padding: '2px 8px', fontSize: 11, cursor: 'pointer', marginLeft: 4 }, children: "Resume" })] })), isBlocked && (_jsx("div", { style: { background: '#fee2e2', padding: '8px 16px', fontSize: 12, fontWeight: 600, color: '#991b1b', textAlign: 'center', flexShrink: 0 }, children: "\uD83D\uDEAB This user is blocked" })), isReported && (_jsx("div", { style: { background: '#fef3c7', padding: '6px 16px', fontSize: 11, color: '#92400e', textAlign: 'center', flexShrink: 0 }, children: "\u26A0\uFE0F This chat has been reported" })), _jsxs("div", { style: { flex: 1, overflowY: 'auto', padding: '14px', display: 'flex', flexDirection: 'column', gap: 10, background: '#f8f9fc' }, className: "cw-scroll", children: [grouped.map(({ date, msgs }) => (_jsxs(React.Fragment, { children: [_jsx(DateDivider, { label: date }), msgs.map(msg => (_jsx(Bubble, { msg: msg, primaryColor: config.primaryColor }, msg.id)))] }, date))), messages.length === 0 && (_jsxs("div", { style: { margin: 'auto', textAlign: 'center', color: '#c4cad4', fontSize: 13 }, children: [_jsx("div", { style: { fontSize: 28, marginBottom: 8 }, children: "\uD83D\uDCAC" }), "Say hello to ", activeUser.name, "!"] })), _jsx("div", { ref: endRef })] }), _jsxs("div", { style: { borderTop: '1px solid #eef0f5', padding: '10px 12px 8px', background: '#fff', flexShrink: 0, position: 'relative' }, children: [privacyEnabled && showPrivacy && (_jsxs("div", { style: {
129
+ position: 'relative',
130
+ marginBottom: 10,
131
+ padding: '12px 36px 12px 12px',
132
+ borderRadius: 12,
133
+ background: '#fff',
134
+ border: '1px solid #e8ecf1',
135
+ boxShadow: '0 1px 4px rgba(15,23,42,0.06)',
136
+ }, children: [_jsx("button", { type: "button", "aria-label": "Dismiss privacy notice", onClick: dismissPrivacy, style: {
137
+ position: 'absolute',
138
+ top: 8,
139
+ right: 8,
140
+ width: 26,
141
+ height: 26,
142
+ borderRadius: '50%',
143
+ border: 'none',
144
+ background: '#f1f5f9',
145
+ cursor: 'pointer',
146
+ display: 'flex',
147
+ alignItems: 'center',
148
+ justifyContent: 'center',
149
+ padding: 0,
150
+ lineHeight: 1,
151
+ }, children: _jsx("span", { style: { fontSize: 14, color: '#475569', fontWeight: 700 }, children: "\u00D7" }) }), _jsxs("p", { style: { margin: 0, fontSize: 12, color: '#64748b', lineHeight: 1.55 }, children: ["By chatting here, you agree we and authorized partners may process, monitor, and record this chat and your data in line with", ' ', config.privacyPolicyUrl ? (_jsx("a", { href: config.privacyPolicyUrl, target: "_blank", rel: "noopener noreferrer", style: { color: config.primaryColor, textDecoration: 'underline', fontWeight: 600 }, children: "Privacy Policy" })) : (_jsx("span", { style: { textDecoration: 'underline', fontWeight: 600 }, children: "Privacy Policy" })), "."] })] })), showEmoji && config.allowEmoji && (_jsx(EmojiPicker, { primaryColor: config.primaryColor, onSelect: e => setText(t => t + e), onClose: () => setShowEmoji(false) })), isRecording && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, padding: '8px 12px', background: '#fee2e2', borderRadius: 10 }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: '50%', background: '#ef4444', display: 'inline-block', animation: 'cw-pulse 1s infinite' } }), _jsxs("span", { style: { fontSize: 13, color: '#991b1b', fontWeight: 600 }, children: ["Recording ", recordSec, "s"] }), _jsx("button", { type: "button", onClick: stopRecording, style: { marginLeft: 'auto', background: '#ef4444', color: '#fff', border: 'none', borderRadius: 6, padding: '4px 12px', fontSize: 12, cursor: 'pointer', fontWeight: 600 }, children: "Stop & send" })] })), _jsxs("div", { style: {
103
152
  border: `1.5px solid ${isPaused || isBlocked ? '#e5e7eb' : '#bfdbfe'}`,
104
153
  borderRadius: 16,
105
154
  padding: '10px 12px 8px',
@@ -129,7 +178,52 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
129
178
  justifyContent: 'center',
130
179
  flexShrink: 0,
131
180
  transition: 'background 0.15s',
132
- }, title: "Send", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: text.trim() && !isPaused && !isBlocked ? '#fff' : '#94a3b8', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] }), (config.footerPoweredBy || config.branch) && (_jsxs("p", { style: { margin: '10px 0 0', textAlign: 'center', fontSize: 12, color: '#94a3b8' }, children: [config.footerPoweredBy, config.footerPoweredBy && config.branch ? ' · ' : '', config.branch && _jsx("span", { style: { fontWeight: 600, color: '#64748b' }, children: config.branch })] }))] }), showConfirm && (_jsx("div", { style: {
181
+ }, title: "Send", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z", stroke: text.trim() && !isPaused && !isBlocked ? '#fff' : '#94a3b8', strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] }), (config.footerPoweredBy || config.branch) && (_jsxs("p", { style: { margin: '10px 0 0', textAlign: 'center', fontSize: 12, color: '#94a3b8' }, children: [config.footerPoweredBy, config.footerPoweredBy && config.branch ? ' · ' : '', config.branch && _jsx("span", { style: { fontWeight: 600, color: '#64748b' }, children: config.branch })] }))] }), transferOpen && otherDevelopers.length > 0 && onTransferToDeveloper && (_jsx("div", { style: {
182
+ position: 'absolute',
183
+ inset: 0,
184
+ background: 'rgba(0,0,0,0.45)',
185
+ display: 'flex',
186
+ alignItems: 'center',
187
+ justifyContent: 'center',
188
+ zIndex: 280,
189
+ padding: 16,
190
+ }, children: _jsxs("div", { style: {
191
+ background: '#fff',
192
+ borderRadius: 16,
193
+ padding: '18px 16px',
194
+ width: '100%',
195
+ maxWidth: 320,
196
+ maxHeight: '70%',
197
+ overflow: 'hidden',
198
+ display: 'flex',
199
+ flexDirection: 'column',
200
+ boxShadow: '0 16px 48px rgba(0,0,0,0.22)',
201
+ }, children: [_jsx("div", { style: { fontWeight: 800, fontSize: 16, color: '#1a2332', marginBottom: 6 }, children: "Transfer chat to" }), _jsx("p", { style: { fontSize: 12, color: '#7b8fa1', margin: '0 0 12px', lineHeight: 1.5 }, children: "Assign this conversation to another developer. History is kept and a handoff note is added." }), _jsx("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', margin: '0 -4px' }, children: otherDevelopers.map(dev => (_jsxs("button", { type: "button", onClick: () => {
202
+ onTransferToDeveloper(dev);
203
+ setTransferOpen(false);
204
+ }, style: {
205
+ width: '100%',
206
+ textAlign: 'left',
207
+ padding: '12px 12px',
208
+ marginBottom: 6,
209
+ border: '1px solid #eef0f5',
210
+ borderRadius: 12,
211
+ background: '#f8fafc',
212
+ cursor: 'pointer',
213
+ fontSize: 14,
214
+ fontWeight: 600,
215
+ color: '#1e293b',
216
+ }, children: [dev.name, _jsx("span", { style: { display: 'block', fontSize: 11, fontWeight: 500, color: '#64748b', marginTop: 2 }, children: dev.designation })] }, dev.uid))) }), _jsx("button", { type: "button", onClick: () => setTransferOpen(false), style: {
217
+ marginTop: 12,
218
+ padding: '10px',
219
+ borderRadius: 10,
220
+ border: '1.5px solid #e5e7eb',
221
+ background: '#fff',
222
+ fontWeight: 600,
223
+ fontSize: 13,
224
+ color: '#475569',
225
+ cursor: 'pointer',
226
+ }, children: "Cancel" })] }) })), showConfirm && (_jsx("div", { style: {
133
227
  position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.45)',
134
228
  display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 300,
135
229
  borderRadius: 'inherit',
@@ -19,7 +19,7 @@ import { BottomTabs } from './Tabs/BottomTabs';
19
19
  const DRAWER_W_NORMAL = 380;
20
20
  const DRAWER_W_MAX = 480;
21
21
  export const ChatWidget = ({ theme: localTheme }) => {
22
- var _a, _b;
22
+ var _a, _b, _c, _d;
23
23
  /* SSR guard */
24
24
  const [mounted, setMounted] = useState(false);
25
25
  useEffect(() => { setMounted(true); }, []);
@@ -172,10 +172,39 @@ export const ChatWidget = ({ theme: localTheme }) => {
172
172
  const widgetConfig = data === null || data === void 0 ? void 0 : data.widget;
173
173
  const primaryColor = theme.primaryColor;
174
174
  const allUsers = data ? [...data.developers, ...data.users] : [];
175
+ const viewerIsDev = (widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerType) === 'developer';
176
+ const viewerUid = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerUid;
175
177
  const filteredUsers = screen === 'user-list'
176
- ? allUsers.filter(u => userListCtx === 'support' ? u.type === 'developer' : u.type === 'user')
178
+ ? allUsers.filter(u => {
179
+ if (userListCtx === 'support') {
180
+ if (viewerIsDev)
181
+ return u.type === 'user';
182
+ return u.type === 'developer';
183
+ }
184
+ if (viewerIsDev) {
185
+ return u.type === 'developer' && u.uid !== viewerUid;
186
+ }
187
+ return u.type === 'user';
188
+ })
177
189
  : [];
190
+ const otherDevelopers = (_c = data === null || data === void 0 ? void 0 : data.developers.filter(d => d.uid !== viewerUid)) !== null && _c !== void 0 ? _c : [];
178
191
  const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
192
+ const handleTransferToDeveloper = useCallback((dev) => {
193
+ var _a;
194
+ if (!activeUser || !widgetConfig)
195
+ return;
196
+ const agent = ((_a = widgetConfig.viewerName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Agent';
197
+ const transferNote = {
198
+ id: `tr_${Date.now()}_${Math.random().toString(36).slice(2)}`,
199
+ senderId: 'me',
200
+ receiverId: dev.uid,
201
+ text: `— ${agent} transferred this conversation from ${activeUser.name} to ${dev.name} —`,
202
+ timestamp: new Date().toISOString(),
203
+ type: 'text',
204
+ status: 'sent',
205
+ };
206
+ selectUser(dev, [...messages, transferNote]);
207
+ }, [activeUser, messages, selectUser, widgetConfig]);
179
208
  /* Position */
180
209
  const posStyle = theme.buttonPosition === 'bottom-left'
181
210
  ? { left: 24, right: 'auto' }
@@ -231,7 +260,7 @@ export const ChatWidget = ({ theme: localTheme }) => {
231
260
  zIndex: 20, display: 'flex', gap: 6,
232
261
  }, children: [_jsx(CornerBtn, { onClick: () => setIsMaximized(m => !m), title: isMaximized ? 'Minimize' : 'Maximize', children: isMaximized
233
262
  ? _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3v5H3M21 8h-5V3M3 16h5v5M16 21v-5h5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) })
234
- : _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }) }), _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) })] })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, onBack: () => setScreen('home'), onSelectUser: handleSelectUser })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: () => { clearChat(); setScreen('home'); setActiveTab('home'); }, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: handleSelectUser })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onRaiseTicket: handleRaiseTicket })), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
263
+ : _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3M21 8V5a2 2 0 00-2-2h-3M3 16v3a2 2 0 002 2h3M16 21h3a2 2 0 002-2v-3", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round" }) }) }), _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) })] })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_d = widgetConfig.viewerType) !== null && _d !== void 0 ? _d : 'user', onBack: () => setScreen('home'), onSelectUser: handleSelectUser })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: () => { clearChat(); setScreen('home'); setActiveTab('home'); }, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: handleSelectUser })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onRaiseTicket: handleRaiseTicket })), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
235
264
  screen !== 'chat' &&
236
265
  screen !== 'call' &&
237
266
  screen !== 'user-list' &&
@@ -1,11 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useState, useMemo } from 'react';
3
3
  import { SlideNavMenu } from '../SlideNavMenu';
4
+ import { truncateWords } from '../../utils/chat';
4
5
  export const HomeScreen = ({ config, onNavigate, tickets }) => {
6
+ var _a, _b, _c, _d;
5
7
  const [menuOpen, setMenuOpen] = useState(false);
6
8
  const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
7
9
  const showChat = config.chatType === 'CHAT' || config.chatType === 'BOTH';
8
- const continueItems = tickets.slice(0, 2);
10
+ const viewerIsDev = config.viewerType === 'developer';
11
+ const pendingTickets = useMemo(() => tickets
12
+ .filter(t => t.status === 'open' || t.status === 'in-progress')
13
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
14
+ .slice(0, 5), [tickets]);
15
+ const brand = ((_a = config.brandName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Ajaxter';
16
+ const promotionLead = ((_b = config.promotionLead) === null || _b === void 0 ? void 0 : _b.trim()) ||
17
+ 'Need specialized help? Our teams are ready to assist you with any questions.';
18
+ const tourUrl = (_c = config.websiteTourUrl) === null || _c === void 0 ? void 0 : _c.trim();
9
19
  const handleCallUs = () => {
10
20
  var _a;
11
21
  const raw = (_a = config.supportPhone) === null || _a === void 0 ? void 0 : _a.trim();
@@ -13,7 +23,7 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
13
23
  return;
14
24
  window.location.href = `tel:${raw.replace(/\s/g, '')}`;
15
25
  };
16
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', position: 'relative', overflow: 'hidden', background: '#fafbfc' }, children: [_jsx(SlideNavMenu, { open: menuOpen, onClose: () => setMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, onSelect: onNavigate }), _jsx("div", { style: {
26
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', position: 'relative', overflow: 'hidden', background: '#fafbfc' }, children: [_jsx(SlideNavMenu, { open: menuOpen, onClose: () => setMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, viewerType: (_d = config.viewerType) !== null && _d !== void 0 ? _d : 'user', onSelect: onNavigate }), _jsx("div", { style: {
17
27
  flexShrink: 0,
18
28
  padding: '14px 16px 10px',
19
29
  display: 'flex',
@@ -41,7 +51,7 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
41
51
  color: '#0f172a',
42
52
  letterSpacing: '-0.03em',
43
53
  lineHeight: 1.2,
44
- }, children: config.welcomeTitle }), _jsx("p", { style: { margin: '0 0 28px', fontSize: 14, color: '#64748b', lineHeight: 1.55 }, children: config.welcomeSubtitle }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: "Continue Conversations" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 28 }, children: continueItems.length > 0 ? (continueItems.map(t => (_jsx("button", { type: "button", onClick: () => onNavigate('ticket'), style: {
54
+ }, children: config.welcomeTitle }), _jsx("p", { style: { margin: '0 0 28px', fontSize: 14, color: '#64748b', lineHeight: 1.55 }, children: config.welcomeSubtitle }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: "Continue Conversations" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 28 }, children: pendingTickets.length > 0 ? (pendingTickets.map(t => (_jsxs("button", { type: "button", onClick: () => onNavigate('ticket'), style: {
45
55
  width: '100%',
46
56
  textAlign: 'left',
47
57
  padding: '14px 16px',
@@ -49,11 +59,9 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
49
59
  border: 'none',
50
60
  background: '#e0f2fe',
51
61
  color: '#0369a1',
52
- fontSize: 14,
53
- fontWeight: 600,
54
62
  cursor: 'pointer',
55
63
  boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
56
- }, children: t.title }, t.id)))) : (_jsxs(_Fragment, { children: [_jsx("div", { style: {
64
+ }, children: [_jsx("div", { style: { fontSize: 14, fontWeight: 700, color: '#0c4a6e', marginBottom: 6 }, children: t.title }), _jsx("div", { style: { fontSize: 12, fontWeight: 500, color: '#64748b', lineHeight: 1.45 }, children: truncateWords(t.description, 50) })] }, t.id)))) : (_jsxs(_Fragment, { children: [_jsx("div", { style: {
57
65
  padding: '14px 16px',
58
66
  borderRadius: 14,
59
67
  background: '#e0f2fe',
@@ -67,7 +75,7 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
67
75
  color: '#64748b',
68
76
  fontSize: 14,
69
77
  fontWeight: 500,
70
- }, children: "Start via Raise ticket below" })] })) }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: "Talk to our experts" }), showSupport && (_jsxs("button", { type: "button", onClick: () => onNavigate('support'), style: {
78
+ }, children: "Start via Raise ticket below" })] })) }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: viewerIsDev ? 'Support tools' : 'Talk to our experts' }), showSupport && (_jsxs("button", { type: "button", onClick: () => onNavigate('support'), style: {
71
79
  width: '100%',
72
80
  display: 'flex',
73
81
  alignItems: 'center',
@@ -83,7 +91,7 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
83
91
  fontWeight: 700,
84
92
  cursor: 'pointer',
85
93
  boxShadow: '0 2px 8px rgba(91,33,182,0.12)',
86
- }, children: [_jsx("span", { style: { fontSize: 18 }, children: "\uD83D\uDC64" }), "Support"] })), showChat && showSupport && (_jsx("button", { type: "button", onClick: () => onNavigate('conversation'), style: {
94
+ }, children: [_jsx("span", { style: { fontSize: 18 }, children: "\uD83D\uDC64" }), viewerIsDev ? 'Provide Support' : 'Support'] })), showChat && showSupport && (_jsx("button", { type: "button", onClick: () => onNavigate('conversation'), style: {
87
95
  width: '100%',
88
96
  padding: '12px 16px',
89
97
  marginBottom: 14,
@@ -94,7 +102,7 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
94
102
  fontSize: 14,
95
103
  fontWeight: 600,
96
104
  cursor: 'pointer',
97
- }, children: "New Conversation" })), showChat && !showSupport && (_jsxs("button", { type: "button", onClick: () => onNavigate('conversation'), style: {
105
+ }, children: viewerIsDev ? 'Chat with a developer' : 'New Conversation' })), showChat && !showSupport && (_jsxs("button", { type: "button", onClick: () => onNavigate('conversation'), style: {
98
106
  width: '100%',
99
107
  display: 'flex',
100
108
  alignItems: 'center',
@@ -115,18 +123,31 @@ export const HomeScreen = ({ config, onNavigate, tickets }) => {
115
123
  background: 'linear-gradient(145deg, #fce7f3 0%, #e9d5ff 45%, #ddd6fe 100%)',
116
124
  position: 'relative',
117
125
  overflow: 'hidden',
118
- }, children: [_jsx("div", { style: { position: 'absolute', top: -20, right: -20, width: 100, height: 100, borderRadius: '50%', background: 'rgba(255,255,255,0.35)' } }), _jsx("p", { style: { margin: '0 0 16px', fontSize: 15, fontWeight: 700, color: '#4c1d95', lineHeight: 1.45, position: 'relative' }, children: "Need specialized help? Our teams are ready to assist you with any questions." }), _jsxs("button", { type: "button", onClick: handleCallUs, disabled: !config.supportPhone, style: {
119
- display: 'inline-flex',
120
- alignItems: 'center',
121
- gap: 8,
122
- padding: '10px 18px',
123
- borderRadius: 12,
124
- border: 'none',
125
- background: config.supportPhone ? config.primaryColor : '#94a3b8',
126
- color: '#fff',
127
- fontSize: 14,
128
- fontWeight: 700,
129
- cursor: config.supportPhone ? 'pointer' : 'not-allowed',
130
- position: 'relative',
131
- }, children: [_jsx("svg", { width: "16", height: "16", 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" }) }), "Call Us"] })] })] })] }));
126
+ }, children: [_jsx("div", { style: { position: 'absolute', top: -20, right: -20, width: 100, height: 100, borderRadius: '50%', background: 'rgba(255,255,255,0.35)' } }), _jsx("p", { style: { margin: '0 0 10px', fontSize: 15, fontWeight: 700, color: '#4c1d95', lineHeight: 1.45, position: 'relative' }, children: promotionLead }), _jsxs("p", { style: { margin: '0 0 16px', fontSize: 13, fontWeight: 500, color: '#5b21b6', lineHeight: 1.5, position: 'relative' }, children: [_jsx("strong", { style: { fontWeight: 800 }, children: brand }), " \u2014 embedded chat for your workspace.", ' ', _jsx("span", { style: { whiteSpace: 'nowrap' }, children: "Free for users." }), " 24\u00D77 availability. Dedicated workspace experience."] }), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 10, position: 'relative' }, children: [tourUrl && (_jsx("a", { href: tourUrl, target: "_blank", rel: "noopener noreferrer", style: {
127
+ display: 'inline-flex',
128
+ alignItems: 'center',
129
+ justifyContent: 'center',
130
+ gap: 6,
131
+ padding: '10px 16px',
132
+ borderRadius: 12,
133
+ border: 'none',
134
+ background: '#fff',
135
+ color: '#5b21b6',
136
+ fontSize: 13,
137
+ fontWeight: 700,
138
+ textDecoration: 'none',
139
+ boxShadow: '0 2px 8px rgba(91,33,182,0.15)',
140
+ }, children: "Take a Website Tour" })), _jsxs("button", { type: "button", onClick: handleCallUs, disabled: !config.supportPhone, style: {
141
+ display: 'inline-flex',
142
+ alignItems: 'center',
143
+ gap: 8,
144
+ padding: '10px 18px',
145
+ borderRadius: 12,
146
+ border: 'none',
147
+ background: config.supportPhone ? config.primaryColor : '#94a3b8',
148
+ color: '#fff',
149
+ fontSize: 14,
150
+ fontWeight: 700,
151
+ cursor: config.supportPhone ? 'pointer' : 'not-allowed',
152
+ }, children: [_jsx("svg", { width: "16", height: "16", 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" }) }), "Call Us"] })] })] })] })] }));
132
153
  };
@@ -5,6 +5,8 @@ export interface SlideNavMenuProps {
5
5
  onClose: () => void;
6
6
  primaryColor: string;
7
7
  chatType: ChatType;
8
+ /** When `developer`, relabels the first two entries for staff */
9
+ viewerType?: 'user' | 'developer';
8
10
  onSelect: (ctx: UserListContext | 'ticket') => void;
9
11
  /** When set, shows “Back to home” at the bottom (e.g. chat screen) */
10
12
  onBackHome?: () => void;
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- export const SlideNavMenu = ({ open, onClose, primaryColor, chatType, onSelect, onBackHome, }) => {
2
+ export const SlideNavMenu = ({ open, onClose, primaryColor, chatType, viewerType = 'user', onSelect, onBackHome, }) => {
3
3
  const showSupport = chatType === 'SUPPORT' || chatType === 'BOTH';
4
4
  const showChat = chatType === 'CHAT' || chatType === 'BOTH';
5
+ const isStaff = viewerType === 'developer';
5
6
  const items = [
6
- showSupport ? { key: 'support', icon: '🛠', title: 'Need Support' } : null,
7
- showChat ? { key: 'conversation', icon: '💬', title: 'New Conversation' } : null,
7
+ showSupport ? { key: 'support', icon: '🛠', title: isStaff ? 'Provide Support' : 'Need Support' } : null,
8
+ showChat ? { key: 'conversation', icon: '💬', title: isStaff ? 'Chat with developer' : 'New Conversation' } : null,
8
9
  { key: 'ticket', icon: '🎫', title: 'Raise ticket' },
9
10
  ];
10
11
  if (!open)
@@ -4,6 +4,8 @@ interface UserListScreenProps {
4
4
  context: UserListContext;
5
5
  users: ChatUser[];
6
6
  primaryColor: string;
7
+ /** `developer` = staff using the widget (lists customers vs teammates) */
8
+ viewerType?: 'user' | 'developer';
7
9
  onBack: () => void;
8
10
  onSelectUser: (user: ChatUser) => void;
9
11
  }
@@ -1,8 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { avatarColor, initials } from '../../utils/chat';
3
- export const UserListScreen = ({ context, users, primaryColor, onBack, onSelectUser, }) => {
4
- const title = context === 'support' ? 'Need Support' : 'New Conversation';
5
- const subtitle = context === 'support' ? 'Choose a support agent' : 'Choose a colleague';
3
+ export const UserListScreen = ({ context, users, primaryColor, viewerType = 'user', onBack, onSelectUser, }) => {
4
+ const isStaff = viewerType === 'developer';
5
+ const title = context === 'support'
6
+ ? (isStaff ? 'Provide Support' : 'Need Support')
7
+ : (isStaff ? 'Developers' : 'New Conversation');
8
+ const subtitle = context === 'support'
9
+ ? (isStaff ? 'All chat users — choose who to help' : 'Choose a support agent')
10
+ : (isStaff ? 'Chat with another developer or coordinate handoff' : 'Choose a colleague');
6
11
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }, children: [_jsxs("div", { style: { background: `linear-gradient(135deg,${primaryColor},${primaryColor}cc)`, padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0 }, children: [_jsx(BackBtn, { onClick: onBack }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: 16, color: '#fff' }, children: title }), _jsx("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.8)' }, children: subtitle })] })] }), _jsx("div", { style: { flex: 1, overflowY: 'auto' }, children: users.length === 0 ? (_jsx(Empty, {})) : users.map((u, i) => (_jsxs("button", { onClick: () => onSelectUser(u), style: {
7
12
  width: '100%', padding: '13px 18px', display: 'flex',
8
13
  alignItems: 'center', gap: 13, background: 'transparent',
package/dist/index.d.ts CHANGED
@@ -13,7 +13,8 @@ export { SlideNavMenu } from './components/SlideNavMenu';
13
13
  export { useChat } from './hooks/useChat';
14
14
  export { useWebRTC } from './hooks/useWebRTC';
15
15
  export { useRemoteConfig } from './hooks/useRemoteConfig';
16
+ export { shouldShowPrivacyNotice, dismissPrivacyNotice, getPrivacyDismissedAt } from './utils/privacyConsent';
16
17
  export { loadLocalConfig, fetchRemoteChatData } from './config';
17
18
  export { mergeTheme, darken } from './utils/theme';
18
- export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from './utils/chat';
19
+ export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText, truncateWords } from './utils/chat';
19
20
  export type { ChatWidgetProps, ChatWidgetTheme, WidgetConfig, RemoteChatData, ChatUser, ChatMessage, Ticket, RecentChat, CallSession, CallState, ChatStatus, ChatType, UserType, OnlineStatus, Screen, BottomTab, UserListContext, MessageType, LocalEnvConfig, } from './types';
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export { SlideNavMenu } from './components/SlideNavMenu';
13
13
  export { useChat } from './hooks/useChat';
14
14
  export { useWebRTC } from './hooks/useWebRTC';
15
15
  export { useRemoteConfig } from './hooks/useRemoteConfig';
16
+ export { shouldShowPrivacyNotice, dismissPrivacyNotice, getPrivacyDismissedAt } from './utils/privacyConsent';
16
17
  export { loadLocalConfig, fetchRemoteChatData } from './config';
17
18
  export { mergeTheme, darken } from './utils/theme';
18
- export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText } from './utils/chat';
19
+ export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText, truncateWords } from './utils/chat';
@@ -14,6 +14,25 @@ export interface WidgetConfig {
14
14
  footerPoweredBy?: string;
15
15
  /** Shown on home “Call Us” (tel: link) */
16
16
  supportPhone?: string;
17
+ /**
18
+ * Who is using the widget. `developer` = support staff: “Need Support” becomes “Provide Support”
19
+ * and lists customers; “New Conversation” lists other developers (excl. viewerUid).
20
+ */
21
+ viewerType?: 'user' | 'developer';
22
+ /** Current user id when viewerType is developer — excluded from developer pick lists */
23
+ viewerUid?: string;
24
+ /** Display name for transfer notes (optional) */
25
+ viewerName?: string;
26
+ /** Privacy Policy URL (linked from chat consent banner) */
27
+ privacyPolicyUrl?: string;
28
+ /** Set false to hide the consent note above the composer */
29
+ showPrivacyNotice?: boolean;
30
+ /** Product brand (e.g. Ajaxter) */
31
+ brandName?: string;
32
+ /** Home promotion: “Take a Website Tour” link */
33
+ websiteTourUrl?: string;
34
+ /** Optional override for the lead line in the promotion card */
35
+ promotionLead?: string;
17
36
  allowVoiceMessage: boolean;
18
37
  allowAttachment: boolean;
19
38
  allowEmoji: boolean;
@@ -9,5 +9,7 @@ export declare function formatTime(ts: string | Date): string;
9
9
  export declare function formatDate(ts: string | Date): string;
10
10
  /** Generate a plain-text transcript from messages */
11
11
  export declare function generateTranscript(messages: ChatMessage[], peer: ChatUser, myName?: string): string;
12
+ /** Truncate to max words, append ellipsis (…) if shortened */
13
+ export declare function truncateWords(text: string, maxWords: number): string;
12
14
  /** Trigger a file download in the browser */
13
15
  export declare function downloadText(content: string, filename: string): void;
@@ -50,6 +50,15 @@ export function generateTranscript(messages, peer, myName = 'Me') {
50
50
  }).join('\n');
51
51
  return header + body;
52
52
  }
53
+ /** Truncate to max words, append ellipsis (…) if shortened */
54
+ export function truncateWords(text, maxWords) {
55
+ const w = text.trim().split(/\s+/).filter(Boolean);
56
+ if (w.length === 0)
57
+ return '';
58
+ if (w.length <= maxWords)
59
+ return w.join(' ');
60
+ return `${w.slice(0, maxWords).join(' ')}…`;
61
+ }
53
62
  /** Trigger a file download in the browser */
54
63
  export function downloadText(content, filename) {
55
64
  const blob = new Blob([content], { type: 'text/plain' });
@@ -0,0 +1,4 @@
1
+ export declare function getPrivacyDismissedAt(widgetId: string): number | null;
2
+ /** After dismiss, banner stays hidden until one hour has passed */
3
+ export declare function shouldShowPrivacyNotice(widgetId: string): boolean;
4
+ export declare function dismissPrivacyNotice(widgetId: string): void;
@@ -0,0 +1,33 @@
1
+ const HOUR_MS = 60 * 60 * 1000;
2
+ function key(widgetId) {
3
+ return `ajaxter_privacy_dismiss_${widgetId}`;
4
+ }
5
+ export function getPrivacyDismissedAt(widgetId) {
6
+ if (typeof window === 'undefined')
7
+ return null;
8
+ try {
9
+ const v = localStorage.getItem(key(widgetId));
10
+ if (v == null)
11
+ return null;
12
+ const n = parseInt(v, 10);
13
+ return Number.isFinite(n) ? n : null;
14
+ }
15
+ catch (_a) {
16
+ return null;
17
+ }
18
+ }
19
+ /** After dismiss, banner stays hidden until one hour has passed */
20
+ export function shouldShowPrivacyNotice(widgetId) {
21
+ const at = getPrivacyDismissedAt(widgetId);
22
+ if (at == null)
23
+ return true;
24
+ return Date.now() - at >= HOUR_MS;
25
+ }
26
+ export function dismissPrivacyNotice(widgetId) {
27
+ try {
28
+ localStorage.setItem(key(widgetId), String(Date.now()));
29
+ }
30
+ catch (_a) {
31
+ /* ignore quota / private mode */
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajaxter-chat",
3
- "version": "3.0.5",
3
+ "version": "3.0.7",
4
4
  "description": "Drawer-based chat widget with support chat, tickets, WebRTC calling, voice messages, block list, and transcript download.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",