ajaxter-chat 3.0.15 → 3.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/dist/components/CallScreen/index.d.ts +2 -0
- package/dist/components/CallScreen/index.js +12 -2
- package/dist/components/ChatScreen/index.js +20 -20
- package/dist/components/ChatWidget.js +35 -4
- package/dist/components/HomeScreen/index.d.ts +2 -0
- package/dist/components/HomeScreen/index.js +75 -19
- package/dist/components/MiniCallBar/index.d.ts +14 -0
- package/dist/components/MiniCallBar/index.js +68 -0
- package/dist/components/ViewerBlockedScreen/index.d.ts +1 -0
- package/dist/components/ViewerBlockedScreen/index.js +29 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/utils/presenceStatus.d.ts +13 -0
- package/dist/utils/presenceStatus.js +45 -0
- package/package.json +1 -1
- package/src/components/CallScreen/index.tsx +23 -1
- package/src/components/ChatScreen/index.tsx +2 -1
- package/src/components/ChatWidget.tsx +71 -9
- package/src/components/HomeScreen/index.tsx +94 -5
- package/src/components/MiniCallBar/index.tsx +150 -0
- package/src/components/ViewerBlockedScreen/index.tsx +48 -5
- package/src/index.ts +3 -0
- package/src/types/index.ts +13 -0
- package/src/utils/presenceStatus.ts +56 -0
package/README.md
CHANGED
|
Binary file
|
|
@@ -8,6 +8,8 @@ interface CallScreenProps {
|
|
|
8
8
|
onToggleMute: () => void;
|
|
9
9
|
onToggleCamera: () => void;
|
|
10
10
|
primaryColor: string;
|
|
11
|
+
/** Collapse the drawer while keeping the call active (mic/cam stay on). */
|
|
12
|
+
onMinimize?: () => void;
|
|
11
13
|
}
|
|
12
14
|
export declare const CallScreen: React.FC<CallScreenProps>;
|
|
13
15
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { avatarColor, initials } from '../../utils/chat';
|
|
4
|
-
export const CallScreen = ({ session, localVideoRef, remoteVideoRef, onEnd, onToggleMute, onToggleCamera, primaryColor, }) => {
|
|
4
|
+
export const CallScreen = ({ session, localVideoRef, remoteVideoRef, onEnd, onToggleMute, onToggleCamera, primaryColor, onMinimize, }) => {
|
|
5
5
|
const [duration, setDuration] = useState(0);
|
|
6
6
|
const peer = session.peer;
|
|
7
7
|
useEffect(() => {
|
|
@@ -25,7 +25,17 @@ export const CallScreen = ({ session, localVideoRef, remoteVideoRef, onEnd, onTo
|
|
|
25
25
|
objectFit: 'cover', border: '2px solid rgba(255,255,255,0.3)',
|
|
26
26
|
display: session.isCameraOn ? 'block' : 'none',
|
|
27
27
|
zIndex: 10,
|
|
28
|
-
} }), _jsxs("div", { style: { position: 'relative', zIndex: 5, display: 'flex', flexDirection: 'column', height: '100%', background: 'rgba(0,0,0,0.35)' }, children: [
|
|
28
|
+
} }), _jsxs("div", { style: { position: 'relative', zIndex: 5, display: 'flex', flexDirection: 'column', height: '100%', background: 'rgba(0,0,0,0.35)' }, children: [_jsxs("div", { style: { padding: '16px 18px', display: 'flex', alignItems: 'center', gap: 10 }, children: [_jsx("div", { style: { flex: 1 }, children: _jsxs("div", { style: { fontWeight: 700, fontSize: 15, color: '#fff' }, children: [session.state === 'calling' && 'Calling...', session.state === 'connected' && 'Connected', session.state === 'ended' && 'Call Ended'] }) }), (session.state === 'calling' || session.state === 'connected') && onMinimize && (_jsx("button", { type: "button", onClick: onMinimize, title: "Minimize \u2014 keep call while you use the page", style: {
|
|
29
|
+
padding: '8px 12px',
|
|
30
|
+
borderRadius: 10,
|
|
31
|
+
border: '1px solid rgba(255,255,255,0.35)',
|
|
32
|
+
background: 'rgba(0,0,0,0.25)',
|
|
33
|
+
color: '#fff',
|
|
34
|
+
fontSize: 13,
|
|
35
|
+
fontWeight: 600,
|
|
36
|
+
cursor: 'pointer',
|
|
37
|
+
flexShrink: 0,
|
|
38
|
+
}, children: "Minimize" }))] }), _jsx("div", { style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16 }, children: peer && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
29
39
|
width: 90, height: 90, borderRadius: '50%',
|
|
30
40
|
backgroundColor: avatarColor(peer.name),
|
|
31
41
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
@@ -256,32 +256,32 @@ export const ChatScreen = ({ activeUser, messages, config, isPaused, isReported,
|
|
|
256
256
|
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease', position: 'relative', overflow: 'hidden' }, children: [_jsxs("div", { style: {
|
|
257
257
|
background: `linear-gradient(135deg,${config.primaryColor},${config.primaryColor}cc)`,
|
|
258
258
|
padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
|
|
259
|
-
}, children: [_jsx("button", { type: "button", onClick: onBack, style: hdrBtn, "aria-label": "Back", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M19 12H5M5 12L12 19M5 12L12 5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "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 }), onToggleMessageSound && (
|
|
259
|
+
}, children: [_jsx("button", { type: "button", onClick: onBack, style: hdrBtn, "aria-label": "Back", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M19 12H5M5 12L12 19M5 12L12 5", stroke: "#fff", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "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 }), onToggleMessageSound && (_jsx("label", { style: {
|
|
260
260
|
display: 'flex',
|
|
261
261
|
alignItems: 'center',
|
|
262
262
|
gap: 6,
|
|
263
263
|
cursor: 'pointer',
|
|
264
264
|
flexShrink: 0,
|
|
265
265
|
marginLeft: 4,
|
|
266
|
-
}, children:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
266
|
+
}, children: _jsx("button", { type: "button", role: "switch", "aria-checked": messageSoundEnabled, onClick: () => onToggleMessageSound(!messageSoundEnabled), "aria-label": "Toggle message sound", title: "Toggle message sound", style: {
|
|
267
|
+
width: 36,
|
|
268
|
+
height: 20,
|
|
269
|
+
borderRadius: 10,
|
|
270
|
+
border: 'none',
|
|
271
|
+
background: messageSoundEnabled ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.2)',
|
|
272
|
+
position: 'relative',
|
|
273
|
+
cursor: 'pointer',
|
|
274
|
+
padding: 0,
|
|
275
|
+
}, children: _jsx("span", { style: {
|
|
276
|
+
position: 'absolute',
|
|
277
|
+
top: 2,
|
|
278
|
+
left: messageSoundEnabled ? 18 : 2,
|
|
279
|
+
width: 16,
|
|
280
|
+
height: 16,
|
|
281
|
+
borderRadius: '50%',
|
|
282
|
+
background: '#fff',
|
|
283
|
+
transition: 'left 0.15s ease',
|
|
284
|
+
} }) }) })), 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: [navEntriesForChat(config.chatType, viewerIsDev).map(item => (_jsx(MenuItem, { icon: item.icon, label: item.label, onClick: () => { setShowMenu(false); onNavAction(item.key); } }, item.key))), _jsx("div", { style: { borderTop: '1px solid #f0f2f5', margin: '4px 0' } }), 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: {
|
|
285
285
|
position: 'relative',
|
|
286
286
|
marginBottom: 10,
|
|
287
287
|
padding: '12px 36px 12px 12px',
|
|
@@ -17,6 +17,7 @@ import { TicketDetailScreen } from './TicketDetailScreen';
|
|
|
17
17
|
import { TicketFormScreen } from './TicketFormScreen';
|
|
18
18
|
import { BlockListScreen } from './BlockList';
|
|
19
19
|
import { CallScreen } from './CallScreen';
|
|
20
|
+
import { MiniCallBar } from './MiniCallBar';
|
|
20
21
|
import { MaintenanceView } from './MaintenanceView';
|
|
21
22
|
import { BottomTabs } from './Tabs/BottomTabs';
|
|
22
23
|
import { ViewerBlockedScreen } from './ViewerBlockedScreen';
|
|
@@ -36,6 +37,8 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
36
37
|
/* Drawer open state */
|
|
37
38
|
const [isOpen, setIsOpen] = useState(false);
|
|
38
39
|
const [closing, setClosing] = useState(false); // for slide-out animation
|
|
40
|
+
/** True when user hid the drawer during ringing/connected call; WebRTC session stays active. */
|
|
41
|
+
const [callMinimized, setCallMinimized] = useState(false);
|
|
39
42
|
/* Navigation */
|
|
40
43
|
const [activeTab, setActiveTab] = useState('home');
|
|
41
44
|
const [screen, setScreen] = useState('home');
|
|
@@ -82,10 +85,16 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
82
85
|
const { messages, activeUser, isPaused, isReported, selectUser, sendMessage, togglePause, reportChat, clearChat, setMessages, } = useChat();
|
|
83
86
|
/* WebRTC hook */
|
|
84
87
|
const { session: callSession, localVideoRef, remoteVideoRef, startCall, endCall, toggleMute, toggleCamera } = useWebRTC();
|
|
88
|
+
const callInProgress = callSession.state === 'calling' || callSession.state === 'connected';
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!callInProgress)
|
|
91
|
+
setCallMinimized(false);
|
|
92
|
+
}, [callInProgress]);
|
|
85
93
|
/* ── Drawer open/close with slide animation ───────────────────────────── */
|
|
86
94
|
const openDrawer = () => {
|
|
87
95
|
setClosing(false);
|
|
88
96
|
setIsOpen(true);
|
|
97
|
+
setCallMinimized(false);
|
|
89
98
|
};
|
|
90
99
|
const persistWidgetState = useCallback(() => {
|
|
91
100
|
var _a;
|
|
@@ -315,8 +324,13 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
315
324
|
}, [activeUser, startCall]);
|
|
316
325
|
const handleEndCall = useCallback(() => {
|
|
317
326
|
endCall();
|
|
327
|
+
setCallMinimized(false);
|
|
318
328
|
setScreen('chat');
|
|
319
329
|
}, [endCall]);
|
|
330
|
+
const minimizeCall = useCallback(() => {
|
|
331
|
+
setCallMinimized(true);
|
|
332
|
+
closeDrawer();
|
|
333
|
+
}, [closeDrawer]);
|
|
320
334
|
/* ── Derived ─────────────────────────────────────────────────────────── */
|
|
321
335
|
const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
|
|
322
336
|
const widgetConfig = useMemo(() => {
|
|
@@ -375,6 +389,7 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
375
389
|
: [];
|
|
376
390
|
const otherDevelopers = useMemo(() => allUsers.filter(u => u.type === 'developer' && u.uid !== viewerUid), [allUsers, viewerUid]);
|
|
377
391
|
const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
|
|
392
|
+
const totalUnread = useMemo(() => recentChats.reduce((sum, c) => { var _a; return sum + Math.max(0, (_a = c.unread) !== null && _a !== void 0 ? _a : 0); }, 0), [recentChats]);
|
|
378
393
|
const handleTransferToDeveloper = useCallback((dev) => {
|
|
379
394
|
var _a;
|
|
380
395
|
if (!activeUser || !widgetConfig)
|
|
@@ -441,13 +456,29 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
441
456
|
@media (max-width: 1024px) {
|
|
442
457
|
.cw-drawer-panel { width: 100%; }
|
|
443
458
|
}
|
|
444
|
-
` }), !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 => {
|
|
459
|
+
` }), !isOpen && callMinimized && callInProgress && callSession.peer && (_jsx(MiniCallBar, { session: callSession, primaryColor: primaryColor, buttonPosition: theme.buttonPosition, onExpand: openDrawer, onEnd: handleEndCall })), !isOpen && (_jsxs("button", { className: "cw-root", type: "button", onClick: openDrawer, "aria-label": totalUnread > 0 ? `${theme.buttonLabel}, ${totalUnread} unread` : theme.buttonLabel, title: totalUnread > 0 ? `${totalUnread} unread message${totalUnread === 1 ? '' : 's'}` : 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 => {
|
|
445
460
|
e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)';
|
|
446
461
|
e.currentTarget.style.boxShadow = `0 14px 36px ${theme.buttonColor}66`;
|
|
447
462
|
}, onMouseLeave: e => {
|
|
448
463
|
e.currentTarget.style.transform = 'scale(1)';
|
|
449
464
|
e.currentTarget.style.boxShadow = `0 8px 28px ${theme.buttonColor}55`;
|
|
450
|
-
}, 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" }) }),
|
|
465
|
+
}, children: [_jsxs("span", { style: { position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }, 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" }) }), totalUnread > 0 && (_jsx("span", { style: {
|
|
466
|
+
position: 'absolute',
|
|
467
|
+
top: -8,
|
|
468
|
+
right: -10,
|
|
469
|
+
minWidth: 20,
|
|
470
|
+
height: 20,
|
|
471
|
+
padding: '0 5px',
|
|
472
|
+
borderRadius: 999,
|
|
473
|
+
background: '#ef4444',
|
|
474
|
+
color: '#fff',
|
|
475
|
+
fontSize: 11,
|
|
476
|
+
fontWeight: 800,
|
|
477
|
+
lineHeight: '20px',
|
|
478
|
+
textAlign: 'center',
|
|
479
|
+
border: '2px solid #fff',
|
|
480
|
+
boxSizing: 'border-box',
|
|
481
|
+
}, children: totalUnread > 99 ? '99+' : totalUnread }))] }), _jsx("span", { children: theme.buttonLabel })] })), isOpen && (_jsx("div", { "aria-hidden": true, style: {
|
|
451
482
|
position: 'fixed', inset: 0, zIndex: 9997,
|
|
452
483
|
backgroundColor: 'rgba(0,0,0,0.35)',
|
|
453
484
|
opacity: closing ? 0 : 1,
|
|
@@ -459,12 +490,12 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
459
490
|
border: `3px solid ${primaryColor}30`,
|
|
460
491
|
borderTopColor: primaryColor,
|
|
461
492
|
animation: 'spin 0.8s linear infinite',
|
|
462
|
-
} }), _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' && (_jsx("div", { style: {
|
|
493
|
+
} }), _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' && !effectiveViewerBlocked && (_jsx("div", { style: {
|
|
463
494
|
position: 'absolute', top: 12,
|
|
464
495
|
right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
|
|
465
496
|
left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
|
|
466
497
|
zIndex: 20, display: 'flex', gap: 6,
|
|
467
|
-
}, children: _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' && effectiveViewerBlocked && (_jsx(ViewerBlockedScreen, { config: widgetConfig, apiKey: apiKey })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (_jsx(PermissionsGateScreen, { primaryColor: primaryColor, widgetId: widgetConfig.id, onGranted: () => setPermissionsOk(true) })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => { setListEntranceAnimation(false); setScreen('home'); }, onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && widgetConfig.viewerType !== 'developer', animateEntrance: listEntranceAnimation })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), 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: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)), animateEntrance: listEntranceAnimation })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => { setListEntranceAnimation(false); setScreen('ticket-new'); }, onSelectTicket: id => {
|
|
498
|
+
}, children: _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' && effectiveViewerBlocked && (_jsx(ViewerBlockedScreen, { config: widgetConfig, apiKey: apiKey, onClose: closeDrawer })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (_jsx(PermissionsGateScreen, { primaryColor: primaryColor, widgetId: widgetConfig.id, onGranted: () => setPermissionsOk(true) })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, apiKey: apiKey, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => { setListEntranceAnimation(false); setScreen('home'); }, onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && widgetConfig.viewerType !== 'developer', animateEntrance: listEntranceAnimation })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor, onMinimize: minimizeCall })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)), animateEntrance: listEntranceAnimation })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => { setListEntranceAnimation(false); setScreen('ticket-new'); }, onSelectTicket: id => {
|
|
468
499
|
setListEntranceAnimation(false);
|
|
469
500
|
setViewingTicketId(id);
|
|
470
501
|
setScreen('ticket-detail');
|
|
@@ -6,6 +6,8 @@ export interface HomeNavigateOptions {
|
|
|
6
6
|
}
|
|
7
7
|
interface HomeScreenProps {
|
|
8
8
|
config: WidgetConfig;
|
|
9
|
+
/** Same as env / chatData — required to POST presence in production */
|
|
10
|
+
apiKey: string;
|
|
9
11
|
onNavigate: (ctx: UserListContext | 'ticket', options?: HomeNavigateOptions) => void;
|
|
10
12
|
/** Open a specific pending ticket (full detail) */
|
|
11
13
|
onOpenTicket: (ticketId: string) => void;
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo } from 'react';
|
|
2
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
3
3
|
import { SlideNavMenu } from '../SlideNavMenu';
|
|
4
4
|
import { truncateWords } from '../../utils/chat';
|
|
5
|
-
|
|
5
|
+
import { resolveInitialPresence, savePresenceStatus, syncPresenceToServer, } from '../../utils/presenceStatus';
|
|
6
|
+
const STATUS_OPTIONS = [
|
|
7
|
+
{ value: 'ACTIVE', label: 'Active' },
|
|
8
|
+
{ value: 'AWAY', label: 'Away' },
|
|
9
|
+
{ value: 'DND', label: 'DND' },
|
|
10
|
+
];
|
|
11
|
+
export const HomeScreen = ({ config, apiKey, onNavigate, onOpenTicket, tickets }) => {
|
|
6
12
|
var _a, _b, _c, _d;
|
|
7
13
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
14
|
+
const [presence, setPresence] = useState(() => resolveInitialPresence(config.id, config.presenceStatus));
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setPresence(resolveInitialPresence(config.id, config.presenceStatus));
|
|
17
|
+
}, [config.id, config.presenceStatus]);
|
|
18
|
+
const setPresenceAndSave = (s) => {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
setPresence(s);
|
|
21
|
+
savePresenceStatus(config.id, s);
|
|
22
|
+
const url = (_a = config.presenceUpdateUrl) === null || _a === void 0 ? void 0 : _a.trim();
|
|
23
|
+
if (!url)
|
|
24
|
+
return;
|
|
25
|
+
void syncPresenceToServer(url, {
|
|
26
|
+
widgetId: config.id,
|
|
27
|
+
apiKey,
|
|
28
|
+
viewerUid: ((_b = config.viewerUid) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
|
|
29
|
+
status: s,
|
|
30
|
+
}).catch(err => {
|
|
31
|
+
console.error('[ajaxter-chat] presence sync failed', err);
|
|
32
|
+
});
|
|
33
|
+
};
|
|
8
34
|
const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
|
|
9
35
|
const showChat = config.chatType === 'CHAT' || config.chatType === 'BOTH';
|
|
10
36
|
const viewerIsDev = config.viewerType === 'developer';
|
|
@@ -25,28 +51,58 @@ export const HomeScreen = ({ config, onNavigate, onOpenTicket, tickets }) => {
|
|
|
25
51
|
};
|
|
26
52
|
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: ctx => {
|
|
27
53
|
onNavigate(ctx, { fromMenu: true });
|
|
28
|
-
} }),
|
|
54
|
+
} }), _jsxs("div", { style: {
|
|
29
55
|
flexShrink: 0,
|
|
30
|
-
padding: '14px
|
|
56
|
+
padding: '12px 14px 12px',
|
|
31
57
|
display: 'flex',
|
|
32
58
|
alignItems: 'center',
|
|
33
|
-
gap:
|
|
59
|
+
gap: 10,
|
|
34
60
|
background: '#fff',
|
|
35
61
|
borderBottom: '1px solid #eef0f5',
|
|
36
|
-
}, children: _jsxs("button", { type: "button", "aria-label": "Open menu", onClick: () => setMenuOpen(true), style: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
}, children: [_jsxs("button", { type: "button", "aria-label": "Open menu", onClick: () => setMenuOpen(true), style: {
|
|
63
|
+
width: 40,
|
|
64
|
+
height: 40,
|
|
65
|
+
borderRadius: 10,
|
|
66
|
+
border: 'none',
|
|
67
|
+
background: '#f1f5f9',
|
|
68
|
+
cursor: 'pointer',
|
|
69
|
+
display: 'flex',
|
|
70
|
+
flexDirection: 'column',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
justifyContent: 'center',
|
|
73
|
+
gap: 5,
|
|
74
|
+
flexShrink: 0,
|
|
75
|
+
}, children: [_jsx("span", { style: { width: 18, height: 2, background: '#334155', borderRadius: 1 } }), _jsx("span", { style: { width: 18, height: 2, background: '#334155', borderRadius: 1 } }), _jsx("span", { style: { width: 18, height: 2, background: '#334155', borderRadius: 1 } })] }), _jsx("div", { style: { flex: 1, minWidth: 0 } }), _jsx("div", { style: {
|
|
76
|
+
display: 'flex',
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
gap: 6,
|
|
79
|
+
flexShrink: 0,
|
|
80
|
+
flexWrap: 'wrap',
|
|
81
|
+
justifyContent: 'flex-end',
|
|
82
|
+
}, children: _jsx("div", { role: "group", "aria-label": "Your status", style: {
|
|
83
|
+
display: 'flex',
|
|
84
|
+
borderRadius: 10,
|
|
85
|
+
padding: 3,
|
|
86
|
+
background: '#f1f5f9',
|
|
87
|
+
gap: 2,
|
|
88
|
+
}, children: STATUS_OPTIONS.map(({ value, label }) => {
|
|
89
|
+
const isOn = presence === value;
|
|
90
|
+
return (_jsx("button", { type: "button", onClick: () => setPresenceAndSave(value), style: {
|
|
91
|
+
border: 'none',
|
|
92
|
+
borderRadius: 8,
|
|
93
|
+
padding: '7px 10px',
|
|
94
|
+
fontSize: 11,
|
|
95
|
+
fontWeight: 700,
|
|
96
|
+
letterSpacing: '0.04em',
|
|
97
|
+
cursor: 'pointer',
|
|
98
|
+
fontFamily: 'inherit',
|
|
99
|
+
textTransform: 'uppercase',
|
|
100
|
+
background: isOn ? config.primaryColor : 'transparent',
|
|
101
|
+
color: isOn ? '#fff' : '#64748b',
|
|
102
|
+
boxShadow: isOn ? `0 2px 8px ${config.primaryColor}55` : 'none',
|
|
103
|
+
transition: 'background 0.15s, color 0.15s',
|
|
104
|
+
}, children: label }, value));
|
|
105
|
+
}) }) })] }), _jsxs("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', padding: '20px 18px 28px' }, children: [_jsx("h1", { style: {
|
|
50
106
|
margin: '0 0 8px',
|
|
51
107
|
fontSize: 24,
|
|
52
108
|
fontWeight: 800,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CallSession } from '../../types';
|
|
3
|
+
export interface MiniCallBarProps {
|
|
4
|
+
session: CallSession;
|
|
5
|
+
primaryColor: string;
|
|
6
|
+
buttonPosition: 'bottom-left' | 'bottom-right';
|
|
7
|
+
onExpand: () => void;
|
|
8
|
+
onEnd: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Shown when the user minimizes the widget during an active call (ringing or connected).
|
|
12
|
+
* Sits above the main launcher button so the user can work on the page and return to the call.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MiniCallBar: React.FC<MiniCallBarProps>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { avatarColor, initials } from '../../utils/chat';
|
|
5
|
+
/**
|
|
6
|
+
* Shown when the user minimizes the widget during an active call (ringing or connected).
|
|
7
|
+
* Sits above the main launcher button so the user can work on the page and return to the call.
|
|
8
|
+
*/
|
|
9
|
+
export const MiniCallBar = ({ session, primaryColor, buttonPosition, onExpand, onEnd, }) => {
|
|
10
|
+
var _a;
|
|
11
|
+
const peer = session.peer;
|
|
12
|
+
const [duration, setDuration] = useState(0);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (session.state !== 'connected' || !session.startedAt)
|
|
15
|
+
return;
|
|
16
|
+
const t = setInterval(() => {
|
|
17
|
+
setDuration(Math.floor((Date.now() - session.startedAt.getTime()) / 1000));
|
|
18
|
+
}, 1000);
|
|
19
|
+
return () => clearInterval(t);
|
|
20
|
+
}, [session.state, session.startedAt]);
|
|
21
|
+
const mins = String(Math.floor(duration / 60)).padStart(2, '0');
|
|
22
|
+
const secs = String(duration % 60).padStart(2, '0');
|
|
23
|
+
const pos = buttonPosition === 'bottom-left'
|
|
24
|
+
? { left: 24, right: 'auto' }
|
|
25
|
+
: { right: 24, left: 'auto' };
|
|
26
|
+
return (_jsxs("div", { role: "toolbar", "aria-label": "Call in progress", style: Object.assign(Object.assign({ position: 'fixed', bottom: 88, zIndex: 10000 }, pos), { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', maxWidth: 'min(360px, calc(100vw - 48px))', borderRadius: 14, background: `linear-gradient(135deg, ${primaryColor}ee, #0f172a)`, color: '#fff', boxShadow: '0 10px 32px rgba(0,0,0,0.28)', animation: 'cw-miniBarIn 0.28s cubic-bezier(0.22,1,0.36,1)', cursor: 'default' }), children: [_jsx("style", { children: `
|
|
27
|
+
@keyframes cw-miniBarIn {
|
|
28
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
29
|
+
to { opacity: 1; transform: translateY(0); }
|
|
30
|
+
}
|
|
31
|
+
` }), _jsxs("button", { type: "button", onClick: onExpand, title: "Open call", style: {
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
gap: 10,
|
|
35
|
+
flex: 1,
|
|
36
|
+
minWidth: 0,
|
|
37
|
+
padding: 0,
|
|
38
|
+
border: 'none',
|
|
39
|
+
background: 'transparent',
|
|
40
|
+
color: 'inherit',
|
|
41
|
+
cursor: 'pointer',
|
|
42
|
+
textAlign: 'left',
|
|
43
|
+
}, children: [peer && (_jsx("div", { style: {
|
|
44
|
+
width: 40,
|
|
45
|
+
height: 40,
|
|
46
|
+
borderRadius: '50%',
|
|
47
|
+
backgroundColor: avatarColor(peer.name),
|
|
48
|
+
display: 'flex',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
fontSize: 14,
|
|
52
|
+
fontWeight: 700,
|
|
53
|
+
flexShrink: 0,
|
|
54
|
+
animation: session.state === 'calling' ? 'cw-pulse 1.5s ease infinite' : 'none',
|
|
55
|
+
}, children: initials(peer.name) })), _jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 14, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: (_a = peer === null || peer === void 0 ? void 0 : peer.name) !== null && _a !== void 0 ? _a : 'Call' }), _jsxs("div", { style: { fontSize: 12, opacity: 0.9, marginTop: 2 }, children: [session.state === 'calling' && 'Calling…', session.state === 'connected' && `${mins}:${secs}`] })] })] }), _jsx("button", { type: "button", onClick: onEnd, title: "End call", style: {
|
|
56
|
+
width: 40,
|
|
57
|
+
height: 40,
|
|
58
|
+
borderRadius: '50%',
|
|
59
|
+
border: 'none',
|
|
60
|
+
background: '#ef4444',
|
|
61
|
+
cursor: 'pointer',
|
|
62
|
+
display: 'flex',
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
flexShrink: 0,
|
|
66
|
+
boxShadow: '0 2px 10px rgba(239,68,68,0.45)',
|
|
67
|
+
}, children: _jsx("svg", { width: "18", height: "18", 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", transform: "rotate(135 12 12)" }) }) })] }));
|
|
68
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { submitReenableRequest } from '../../utils/reenableRequest';
|
|
5
5
|
const DEFAULT_MESSAGE = 'You have been marked as Blocked user due to spam';
|
|
6
|
-
export const ViewerBlockedScreen = ({ config, apiKey }) => {
|
|
6
|
+
export const ViewerBlockedScreen = ({ config, apiKey, onClose }) => {
|
|
7
7
|
var _a, _b;
|
|
8
8
|
const [text, setText] = useState('');
|
|
9
9
|
const [status, setStatus] = useState('idle');
|
|
@@ -56,7 +56,18 @@ export const ViewerBlockedScreen = ({ config, apiKey }) => {
|
|
|
56
56
|
fontWeight: 600,
|
|
57
57
|
color: '#1e293b',
|
|
58
58
|
lineHeight: 1.55,
|
|
59
|
-
}, children: body }), status === 'sent' ? (_jsx("p", { style: { margin: 0, fontSize: 14, color: '#16a34a', fontWeight: 600 }, children: "Your request was sent. We will review it shortly." })
|
|
59
|
+
}, children: body }), status === 'sent' ? (_jsxs(_Fragment, { children: [_jsx("p", { style: { margin: '0 0 16px', fontSize: 14, color: '#16a34a', fontWeight: 600 }, children: "Your request was sent. We will review it shortly." }), _jsx("button", { type: "button", onClick: onClose, style: {
|
|
60
|
+
width: '100%',
|
|
61
|
+
padding: '12px 16px',
|
|
62
|
+
borderRadius: 12,
|
|
63
|
+
border: '2px solid #ef4444',
|
|
64
|
+
background: '#fff',
|
|
65
|
+
color: '#ef4444',
|
|
66
|
+
fontWeight: 700,
|
|
67
|
+
fontSize: 15,
|
|
68
|
+
cursor: 'pointer',
|
|
69
|
+
fontFamily: 'inherit',
|
|
70
|
+
}, children: "Close" })] })) : (_jsxs(_Fragment, { children: [_jsx("label", { htmlFor: "cw-reenable-msg", style: { display: 'block', textAlign: 'left', fontSize: 13, fontWeight: 600, color: '#475569', marginBottom: 8 }, children: "Request access restoration" }), _jsx("textarea", { id: "cw-reenable-msg", value: text, onChange: e => { setText(e.target.value); setError(null); setStatus('idle'); }, placeholder: "Explain briefly why your access should be restored\u2026", rows: 4, maxLength: 500, minLength: 50, disabled: status === 'sending', style: {
|
|
60
71
|
width: '100%',
|
|
61
72
|
boxSizing: 'border-box',
|
|
62
73
|
padding: '12px 14px',
|
|
@@ -65,8 +76,9 @@ export const ViewerBlockedScreen = ({ config, apiKey }) => {
|
|
|
65
76
|
fontSize: 14,
|
|
66
77
|
fontFamily: 'inherit',
|
|
67
78
|
color: '#1e293b',
|
|
68
|
-
resize: '
|
|
79
|
+
resize: 'none',
|
|
69
80
|
minHeight: 100,
|
|
81
|
+
maxHeight: 250,
|
|
70
82
|
marginBottom: 14,
|
|
71
83
|
outline: 'none',
|
|
72
84
|
} }), _jsx("button", { type: "button", onClick: handleSubmit, disabled: status === 'sending' || !text.trim(), style: {
|
|
@@ -79,5 +91,17 @@ export const ViewerBlockedScreen = ({ config, apiKey }) => {
|
|
|
79
91
|
fontWeight: 700,
|
|
80
92
|
fontSize: 15,
|
|
81
93
|
cursor: text.trim() && status !== 'sending' ? 'pointer' : 'default',
|
|
82
|
-
}, children: status === 'sending' ? 'Sending…' : 'Submit request' }),
|
|
94
|
+
}, children: status === 'sending' ? 'Sending…' : 'Submit request' }), _jsx("button", { type: "button", onClick: onClose, style: {
|
|
95
|
+
width: '100%',
|
|
96
|
+
marginTop: 12,
|
|
97
|
+
padding: '12px 16px',
|
|
98
|
+
borderRadius: 12,
|
|
99
|
+
border: '2px solid #ef4444',
|
|
100
|
+
background: '#fff',
|
|
101
|
+
color: '#ef4444',
|
|
102
|
+
fontWeight: 700,
|
|
103
|
+
fontSize: 15,
|
|
104
|
+
cursor: 'pointer',
|
|
105
|
+
fontFamily: 'inherit',
|
|
106
|
+
}, children: "Close" }), error && (_jsx("p", { style: { margin: '12px 0 0', fontSize: 13, color: '#dc2626', lineHeight: 1.45 }, children: error })), !url && (_jsxs("p", { style: { margin: '14px 0 0', fontSize: 12, color: '#94a3b8', lineHeight: 1.5 }, children: ["Your administrator must set ", _jsx("code", { style: { fontSize: 11 }, children: "reenableRequestUrl" }), " in widget config for online requests."] }))] }))] }) }));
|
|
83
107
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -18,5 +18,7 @@ export { submitReenableRequest } from './utils/reenableRequest';
|
|
|
18
18
|
export type { ReenableRequestPayload } from './utils/reenableRequest';
|
|
19
19
|
export { loadLocalConfig, fetchRemoteChatData } from './config';
|
|
20
20
|
export { mergeTheme, darken } from './utils/theme';
|
|
21
|
+
export { loadPresenceStatus, savePresenceStatus, resolveInitialPresence, syncPresenceToServer } from './utils/presenceStatus';
|
|
22
|
+
export type { PresenceSyncPayload } from './utils/presenceStatus';
|
|
21
23
|
export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText, truncateWords } from './utils/chat';
|
|
22
|
-
export type { ChatWidgetProps, ChatWidgetTheme, ChatWidgetViewer, WidgetConfig, RemoteChatData, ChatUser, ChatMessage, Ticket, RecentChat, CallSession, CallState, ChatStatus, ChatType, UserType, OnlineStatus, Screen, BottomTab, UserListContext, MessageType, LocalEnvConfig, } from './types';
|
|
24
|
+
export type { ChatWidgetProps, ChatWidgetTheme, ChatWidgetViewer, WidgetConfig, RemoteChatData, ChatUser, ChatMessage, Ticket, RecentChat, CallSession, CallState, ChatStatus, ChatType, UserType, OnlineStatus, Screen, BottomTab, UserListContext, MessageType, LocalEnvConfig, PresenceStatus, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -17,4 +17,5 @@ export { shouldShowPrivacyNotice, dismissPrivacyNotice, getPrivacyDismissedAt }
|
|
|
17
17
|
export { submitReenableRequest } from './utils/reenableRequest';
|
|
18
18
|
export { loadLocalConfig, fetchRemoteChatData } from './config';
|
|
19
19
|
export { mergeTheme, darken } from './utils/theme';
|
|
20
|
+
export { loadPresenceStatus, savePresenceStatus, resolveInitialPresence, syncPresenceToServer } from './utils/presenceStatus';
|
|
20
21
|
export { avatarColor, initials, formatTime, formatDate, generateTranscript, downloadText, truncateWords } from './utils/chat';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -58,6 +58,16 @@ export interface WidgetConfig {
|
|
|
58
58
|
* @example https://api.example.com/widgets/reenable-request
|
|
59
59
|
*/
|
|
60
60
|
reenableRequestUrl?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Current presence from your API/DB (include in chatData or a session payload).
|
|
63
|
+
* When set, it initializes the status control and overrides session-only cache.
|
|
64
|
+
*/
|
|
65
|
+
presenceStatus?: PresenceStatus;
|
|
66
|
+
/**
|
|
67
|
+
* Production: `POST` JSON `{ widgetId, apiKey, viewerUid?, status }` to save presence in your database.
|
|
68
|
+
* The client still mirrors to sessionStorage as a local fallback.
|
|
69
|
+
*/
|
|
70
|
+
presenceUpdateUrl?: string;
|
|
61
71
|
}
|
|
62
72
|
export interface RemoteChatData {
|
|
63
73
|
widget: WidgetConfig;
|
|
@@ -75,6 +85,8 @@ export type BottomTab = 'home' | 'chats' | 'tickets';
|
|
|
75
85
|
export type Screen = 'home' | 'user-list' | 'chat' | 'recent-chats' | 'tickets' | 'ticket-new' | 'ticket-detail' | 'block-list' | 'call';
|
|
76
86
|
export type UserListContext = 'support' | 'conversation';
|
|
77
87
|
export type MessageType = 'text' | 'voice' | 'attachment' | 'emoji';
|
|
88
|
+
/** Home status selector; persist via `presenceUpdateUrl` in production */
|
|
89
|
+
export type PresenceStatus = 'ACTIVE' | 'AWAY' | 'DND';
|
|
78
90
|
export interface ChatUser {
|
|
79
91
|
uid: string;
|
|
80
92
|
name: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PresenceStatus } from '../types';
|
|
2
|
+
export declare function loadPresenceStatus(widgetId: string): PresenceStatus;
|
|
3
|
+
export declare function savePresenceStatus(widgetId: string, status: PresenceStatus): void;
|
|
4
|
+
/** Prefer server value from DB when the host includes it in config */
|
|
5
|
+
export declare function resolveInitialPresence(widgetId: string, serverStatus: PresenceStatus | undefined): PresenceStatus;
|
|
6
|
+
export interface PresenceSyncPayload {
|
|
7
|
+
widgetId: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
viewerUid?: string;
|
|
10
|
+
status: PresenceStatus;
|
|
11
|
+
}
|
|
12
|
+
/** Call your backend to persist presence (production DB). */
|
|
13
|
+
export declare function syncPresenceToServer(url: string, payload: PresenceSyncPayload): Promise<void>;
|