ajaxter-chat 3.0.17 → 3.1.0
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/dist/components/BlockList/index.d.ts +1 -0
- package/dist/components/BlockList/index.d.ts.map +1 -0
- package/dist/components/BlockList/index.js +55 -28
- package/dist/components/BlockList/index.js.map +1 -0
- package/dist/components/CallScreen/index.d.ts +1 -0
- package/dist/components/CallScreen/index.d.ts.map +1 -0
- package/dist/components/CallScreen/index.js +107 -39
- package/dist/components/CallScreen/index.js.map +1 -0
- package/dist/components/ChatScreen/index.d.ts +1 -0
- package/dist/components/ChatScreen/index.d.ts.map +1 -0
- package/dist/components/ChatScreen/index.js +493 -294
- package/dist/components/ChatScreen/index.js.map +1 -0
- package/dist/components/ChatWidget.d.ts +1 -0
- package/dist/components/ChatWidget.d.ts.map +1 -0
- package/dist/components/ChatWidget.js +350 -255
- package/dist/components/ChatWidget.js.map +1 -0
- package/dist/components/EmojiPicker/index.d.ts +1 -0
- package/dist/components/EmojiPicker/index.d.ts.map +1 -0
- package/dist/components/EmojiPicker/index.js +19 -7
- package/dist/components/EmojiPicker/index.js.map +1 -0
- package/dist/components/ErrorBoundary/index.d.ts +20 -0
- package/dist/components/ErrorBoundary/index.d.ts.map +1 -0
- package/dist/components/ErrorBoundary/index.js +76 -0
- package/dist/components/ErrorBoundary/index.js.map +1 -0
- package/dist/components/HomeScreen/index.d.ts +1 -0
- package/dist/components/HomeScreen/index.d.ts.map +1 -0
- package/dist/components/HomeScreen/index.js +236 -158
- package/dist/components/HomeScreen/index.js.map +1 -0
- package/dist/components/MaintenanceView/index.d.ts +1 -0
- package/dist/components/MaintenanceView/index.d.ts.map +1 -0
- package/dist/components/MaintenanceView/index.js +28 -12
- package/dist/components/MaintenanceView/index.js.map +1 -0
- package/dist/components/MiniCallBar/index.d.ts +1 -0
- package/dist/components/MiniCallBar/index.d.ts.map +1 -0
- package/dist/components/MiniCallBar/index.js +85 -37
- package/dist/components/MiniCallBar/index.js.map +1 -0
- package/dist/components/PermissionsGateScreen/index.d.ts +1 -0
- package/dist/components/PermissionsGateScreen/index.d.ts.map +1 -0
- package/dist/components/PermissionsGateScreen/index.js +82 -28
- package/dist/components/PermissionsGateScreen/index.js.map +1 -0
- package/dist/components/RecentChatsScreen/index.d.ts +1 -0
- package/dist/components/RecentChatsScreen/index.d.ts.map +1 -0
- package/dist/components/RecentChatsScreen/index.js +79 -19
- package/dist/components/RecentChatsScreen/index.js.map +1 -0
- package/dist/components/SlideNavMenu.d.ts +1 -0
- package/dist/components/SlideNavMenu.d.ts.map +1 -0
- package/dist/components/SlideNavMenu.js +82 -63
- package/dist/components/SlideNavMenu.js.map +1 -0
- package/dist/components/Tabs/BottomTabs.d.ts +1 -0
- package/dist/components/Tabs/BottomTabs.d.ts.map +1 -0
- package/dist/components/Tabs/BottomTabs.js +34 -19
- package/dist/components/Tabs/BottomTabs.js.map +1 -0
- package/dist/components/TicketDetailScreen/index.d.ts +1 -0
- package/dist/components/TicketDetailScreen/index.d.ts.map +1 -0
- package/dist/components/TicketDetailScreen/index.js +66 -27
- package/dist/components/TicketDetailScreen/index.js.map +1 -0
- package/dist/components/TicketFormScreen/index.d.ts +1 -0
- package/dist/components/TicketFormScreen/index.d.ts.map +1 -0
- package/dist/components/TicketFormScreen/index.js +99 -49
- package/dist/components/TicketFormScreen/index.js.map +1 -0
- package/dist/components/TicketScreen/index.d.ts +1 -0
- package/dist/components/TicketScreen/index.d.ts.map +1 -0
- package/dist/components/TicketScreen/index.js +95 -26
- package/dist/components/TicketScreen/index.js.map +1 -0
- package/dist/components/UserListScreen/index.d.ts +1 -0
- package/dist/components/UserListScreen/index.d.ts.map +1 -0
- package/dist/components/UserListScreen/index.js +127 -53
- package/dist/components/UserListScreen/index.js.map +1 -0
- package/dist/components/ViewerBlockedScreen/index.d.ts +1 -0
- package/dist/components/ViewerBlockedScreen/index.d.ts.map +1 -0
- package/dist/components/ViewerBlockedScreen/index.js +113 -61
- package/dist/components/ViewerBlockedScreen/index.js.map +1 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +7 -2
- package/dist/config/index.js.map +1 -0
- package/dist/hooks/useChat.d.ts +9 -1
- package/dist/hooks/useChat.d.ts.map +1 -0
- package/dist/hooks/useChat.js +60 -18
- package/dist/hooks/useChat.js.map +1 -0
- package/dist/hooks/useRemoteConfig.d.ts +1 -0
- package/dist/hooks/useRemoteConfig.d.ts.map +1 -0
- package/dist/hooks/useRemoteConfig.js +12 -8
- package/dist/hooks/useRemoteConfig.js.map +1 -0
- package/dist/hooks/useSocket.d.ts +40 -0
- package/dist/hooks/useSocket.d.ts.map +1 -0
- package/dist/hooks/useSocket.js +190 -0
- package/dist/hooks/useSocket.js.map +1 -0
- package/dist/hooks/useWebRTC.d.ts +10 -2
- package/dist/hooks/useWebRTC.d.ts.map +1 -0
- package/dist/hooks/useWebRTC.js +101 -69
- package/dist/hooks/useWebRTC.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -21
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -1
- package/dist/types/index.js.map +1 -0
- package/dist/utils/chat.d.ts +1 -0
- package/dist/utils/chat.d.ts.map +1 -0
- package/dist/utils/chat.js +17 -7
- package/dist/utils/chat.js.map +1 -0
- package/dist/utils/fileName.d.ts +1 -0
- package/dist/utils/fileName.d.ts.map +1 -0
- package/dist/utils/fileName.js +5 -1
- package/dist/utils/fileName.js.map +1 -0
- package/dist/utils/messageSound.d.ts +1 -0
- package/dist/utils/messageSound.d.ts.map +1 -0
- package/dist/utils/messageSound.js +9 -3
- package/dist/utils/messageSound.js.map +1 -0
- package/dist/utils/presenceStatus.d.ts +1 -0
- package/dist/utils/presenceStatus.d.ts.map +1 -0
- package/dist/utils/presenceStatus.js +11 -4
- package/dist/utils/presenceStatus.js.map +1 -0
- package/dist/utils/privacyConsent.d.ts +1 -0
- package/dist/utils/privacyConsent.d.ts.map +1 -0
- package/dist/utils/privacyConsent.js +9 -3
- package/dist/utils/privacyConsent.js.map +1 -0
- package/dist/utils/reenableRequest.d.ts +1 -0
- package/dist/utils/reenableRequest.d.ts.map +1 -0
- package/dist/utils/reenableRequest.js +5 -1
- package/dist/utils/reenableRequest.js.map +1 -0
- package/dist/utils/theme.d.ts +1 -0
- package/dist/utils/theme.d.ts.map +1 -0
- package/dist/utils/theme.js +10 -4
- package/dist/utils/theme.js.map +1 -0
- package/dist/utils/widgetPermissions.d.ts +1 -0
- package/dist/utils/widgetPermissions.d.ts.map +1 -0
- package/dist/utils/widgetPermissions.js +13 -5
- package/dist/utils/widgetPermissions.js.map +1 -0
- package/dist/utils/widgetSession.d.ts +1 -0
- package/dist/utils/widgetSession.d.ts.map +1 -0
- package/dist/utils/widgetSession.js +9 -3
- package/dist/utils/widgetSession.js.map +1 -0
- package/package.json +3 -3
- package/src/components/ChatWidget.tsx +291 -269
- package/src/components/ErrorBoundary/index.tsx +62 -0
- package/src/hooks/useChat.ts +59 -12
- package/src/hooks/useSocket.ts +228 -0
- package/src/hooks/useWebRTC.ts +99 -64
- package/src/index.ts +7 -2
|
@@ -1,61 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
'use client';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ChatWidget = void 0;
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const config_1 = require("../config");
|
|
40
|
+
const theme_1 = require("../utils/theme");
|
|
41
|
+
const useRemoteConfig_1 = require("../hooks/useRemoteConfig");
|
|
42
|
+
const useChat_1 = require("../hooks/useChat");
|
|
43
|
+
const useWebRTC_1 = require("../hooks/useWebRTC");
|
|
44
|
+
const useSocket_1 = require("../hooks/useSocket");
|
|
45
|
+
const widgetSession_1 = require("../utils/widgetSession");
|
|
46
|
+
const messageSound_1 = require("../utils/messageSound");
|
|
47
|
+
const ErrorBoundary_1 = require("./ErrorBoundary");
|
|
48
|
+
const HomeScreen_1 = require("./HomeScreen");
|
|
49
|
+
const UserListScreen_1 = require("./UserListScreen");
|
|
50
|
+
const ChatScreen_1 = require("./ChatScreen");
|
|
51
|
+
const RecentChatsScreen_1 = require("./RecentChatsScreen");
|
|
52
|
+
const TicketScreen_1 = require("./TicketScreen");
|
|
53
|
+
const TicketDetailScreen_1 = require("./TicketDetailScreen");
|
|
54
|
+
const TicketFormScreen_1 = require("./TicketFormScreen");
|
|
55
|
+
const BlockList_1 = require("./BlockList");
|
|
56
|
+
const CallScreen_1 = require("./CallScreen");
|
|
57
|
+
const MiniCallBar_1 = require("./MiniCallBar");
|
|
58
|
+
const MaintenanceView_1 = require("./MaintenanceView");
|
|
59
|
+
const BottomTabs_1 = require("./Tabs/BottomTabs");
|
|
60
|
+
const ViewerBlockedScreen_1 = require("./ViewerBlockedScreen");
|
|
61
|
+
const PermissionsGateScreen_1 = require("./PermissionsGateScreen");
|
|
62
|
+
const widgetPermissions_1 = require("../utils/widgetPermissions");
|
|
63
|
+
/** WebSocket server URL — override via env NEXT_PUBLIC_SOCKET_URL / REACT_APP_SOCKET_URL */
|
|
64
|
+
function getSocketUrl() {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
67
|
+
return ((_b = (_a = process.env['NEXT_PUBLIC_SOCKET_URL']) !== null && _a !== void 0 ? _a : process.env['REACT_APP_SOCKET_URL']) !== null && _b !== void 0 ? _b : 'http://localhost:3005');
|
|
68
|
+
}
|
|
69
|
+
return 'http://localhost:3005';
|
|
70
|
+
}
|
|
71
|
+
const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
28
73
|
/* SSR guard */
|
|
29
|
-
const [mounted, setMounted] = useState(false);
|
|
30
|
-
useEffect(() => { setMounted(true); }, []);
|
|
31
|
-
|
|
32
|
-
const {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
39
|
-
const [closing, setClosing] = useState(false);
|
|
40
|
-
|
|
41
|
-
const [
|
|
42
|
-
|
|
43
|
-
const [
|
|
44
|
-
const [
|
|
45
|
-
const [
|
|
46
|
-
const [
|
|
47
|
-
const [
|
|
48
|
-
const [
|
|
49
|
-
|
|
50
|
-
const [
|
|
51
|
-
|
|
52
|
-
const [
|
|
53
|
-
/* App state */
|
|
54
|
-
const [tickets, setTickets] = useState((_a = data === null || data === void 0 ? void 0 : data.sampleTickets) !== null && _a !== void 0 ? _a : []);
|
|
55
|
-
const [recentChats, setRecentChats] = useState([]);
|
|
56
|
-
const [blockedUids, setBlockedUids] = useState((_b = data === null || data === void 0 ? void 0 : data.blockedUsers) !== null && _b !== void 0 ? _b : []);
|
|
74
|
+
const [mounted, setMounted] = (0, react_1.useState)(false);
|
|
75
|
+
(0, react_1.useEffect)(() => { setMounted(true); }, []);
|
|
76
|
+
const { apiKey, widgetId } = (0, config_1.loadLocalConfig)();
|
|
77
|
+
const { data, loading: cfgLoading, error: cfgError } = (0, useRemoteConfig_1.useRemoteConfig)(apiKey, widgetId);
|
|
78
|
+
const theme = (0, theme_1.mergeTheme)((data === null || data === void 0 ? void 0 : data.widget) ? {
|
|
79
|
+
primaryColor: data.widget.primaryColor,
|
|
80
|
+
buttonLabel: data.widget.buttonLabel,
|
|
81
|
+
buttonPosition: data.widget.buttonPosition,
|
|
82
|
+
} : undefined, localTheme);
|
|
83
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
84
|
+
const [closing, setClosing] = (0, react_1.useState)(false);
|
|
85
|
+
const [callMinimized, setCallMinimized] = (0, react_1.useState)(false);
|
|
86
|
+
const [activeTab, setActiveTab] = (0, react_1.useState)('home');
|
|
87
|
+
const [screen, setScreen] = (0, react_1.useState)('home');
|
|
88
|
+
const [userListCtx, setUserListCtx] = (0, react_1.useState)('support');
|
|
89
|
+
const [chatReturnCtx, setChatReturnCtx] = (0, react_1.useState)('conversation');
|
|
90
|
+
const [viewingTicketId, setViewingTicketId] = (0, react_1.useState)(null);
|
|
91
|
+
const [messageSoundEnabled, setMessageSoundEnabledState] = (0, react_1.useState)(true);
|
|
92
|
+
const [listEntranceAnimation, setListEntranceAnimation] = (0, react_1.useState)(false);
|
|
93
|
+
const [permissionsOk, setPermissionsOk] = (0, react_1.useState)(false);
|
|
94
|
+
const [socketStatus, setSocketStatus] = (0, react_1.useState)('disconnected');
|
|
95
|
+
const [tickets, setTickets] = (0, react_1.useState)((_a = data === null || data === void 0 ? void 0 : data.sampleTickets) !== null && _a !== void 0 ? _a : []);
|
|
96
|
+
const [recentChats, setRecentChats] = (0, react_1.useState)([]);
|
|
97
|
+
const [blockedUids, setBlockedUids] = (0, react_1.useState)((_b = data === null || data === void 0 ? void 0 : data.blockedUsers) !== null && _b !== void 0 ? _b : []);
|
|
57
98
|
/* Sync remote data into local state once loaded */
|
|
58
|
-
useEffect(() => {
|
|
99
|
+
(0, react_1.useEffect)(() => {
|
|
59
100
|
var _a, _b, _c, _d;
|
|
60
101
|
if (data) {
|
|
61
102
|
setTickets(data.sampleTickets);
|
|
@@ -81,58 +122,109 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
81
122
|
setRecentChats(recents);
|
|
82
123
|
}
|
|
83
124
|
}, [data, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
|
|
84
|
-
/*
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
/* ── Socket setup ───────────────────────────────────────────────────── */
|
|
126
|
+
const viewerUidForSocket = ((_e = (_c = viewer === null || viewer === void 0 ? void 0 : viewer.uid) !== null && _c !== void 0 ? _c : (_d = data === null || data === void 0 ? void 0 : data.widget) === null || _d === void 0 ? void 0 : _d.viewerUid) !== null && _e !== void 0 ? _e : '').trim();
|
|
127
|
+
const { joinRoom, leaveRoom, emitMessage, emitPauseToggle, emitReport, emitBlock, emitUnblock, emitTransfer, emitCallOffer, emitCallAnswer, emitIceCandidate, emitCallEnd, emitAddParticipant, status: _socketStatus, } = (0, useSocket_1.useSocket)({
|
|
128
|
+
widgetId,
|
|
129
|
+
viewerUid: viewerUidForSocket,
|
|
130
|
+
serverUrl: getSocketUrl(),
|
|
131
|
+
onMessage: (msg) => {
|
|
132
|
+
receiveMessage(msg);
|
|
133
|
+
if (messageSoundEnabled && msg.senderId !== 'me')
|
|
134
|
+
(0, messageSound_1.playMessageSound)();
|
|
135
|
+
// Update recent chats last message
|
|
136
|
+
setRecentChats(prev => prev.map(r => r.user.uid === msg.senderId || r.user.uid === msg.receiverId
|
|
137
|
+
? Object.assign(Object.assign({}, r), { lastMessage: msg.text, lastTime: msg.timestamp, unread: r.unread + 1 }) : r));
|
|
138
|
+
},
|
|
139
|
+
onMessageAck: ({ messageId, status }) => {
|
|
140
|
+
updateMessageStatus(messageId, status);
|
|
141
|
+
},
|
|
142
|
+
onChatPaused: (_roomId, paused) => {
|
|
143
|
+
// Sync pause state from remote
|
|
144
|
+
setRecentChats(prev => prev.map(r => activeUser && r.user.uid === activeUser.uid ? Object.assign(Object.assign({}, r), { isPaused: paused }) : r));
|
|
145
|
+
},
|
|
146
|
+
onCallOffer: async (offer, fromUid, callId) => {
|
|
147
|
+
var _a, _b;
|
|
148
|
+
// Find peer user
|
|
149
|
+
const allUsers = [...((_a = data === null || data === void 0 ? void 0 : data.developers) !== null && _a !== void 0 ? _a : []), ...((_b = data === null || data === void 0 ? void 0 : data.users) !== null && _b !== void 0 ? _b : [])];
|
|
150
|
+
const peer = allUsers.find(u => u.uid === fromUid);
|
|
151
|
+
if (peer) {
|
|
152
|
+
await acceptCall(offer, peer, callId);
|
|
153
|
+
setScreen('call');
|
|
154
|
+
setIsOpen(true);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
onCallAnswer: async (answer, _fromUid, _callId) => {
|
|
158
|
+
await addIceCandidate({ sdpMid: '0', sdpMLineIndex: 0, candidate: '' });
|
|
159
|
+
// WebRTC: set remote description
|
|
160
|
+
void answer;
|
|
161
|
+
},
|
|
162
|
+
onIceCandidate: async (candidate) => {
|
|
163
|
+
await addIceCandidate(candidate);
|
|
164
|
+
},
|
|
165
|
+
onCallEnd: () => { _endCall(); },
|
|
166
|
+
onError: (code, message) => {
|
|
167
|
+
console.error('[ChatWidget] Socket error:', code, message);
|
|
168
|
+
},
|
|
169
|
+
onUserStatus: (uid, status) => {
|
|
170
|
+
setRecentChats(prev => prev.map(r => r.user.uid === uid ? Object.assign(Object.assign({}, r), { user: Object.assign(Object.assign({}, r.user), { status }) }) : r));
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
(0, react_1.useEffect)(() => { setSocketStatus(_socketStatus); }, [_socketStatus]);
|
|
174
|
+
/* ── Chat hook ──────────────────────────────────────────────────────── */
|
|
175
|
+
const { messages, activeUser, isPaused, isReported, selectUser, sendMessage, receiveMessage, updateMessageStatus, togglePause: _togglePause, reportChat: _reportChat, clearChat, setMessages, } = (0, useChat_1.useChat)([], {
|
|
176
|
+
onEmitMessage: (msg) => { emitMessage(msg); },
|
|
177
|
+
onEmitPause: (roomId, targetUid, paused) => { emitPauseToggle(roomId, targetUid, paused); },
|
|
178
|
+
onEmitReport: (roomId) => { emitReport(roomId); },
|
|
179
|
+
});
|
|
180
|
+
/* ── WebRTC hook ────────────────────────────────────────────────────── */
|
|
181
|
+
const { session: callSession, localVideoRef, remoteVideoRef, startCall: _startCall, acceptCall, addIceCandidate, endCall: _endCall, toggleMute, toggleCamera, } = (0, useWebRTC_1.useWebRTC)({
|
|
182
|
+
onOfferReady: (offer, toUid, callId) => { emitCallOffer(offer, toUid, callId); },
|
|
183
|
+
onAnswerReady: (answer, toUid, callId) => { emitCallAnswer(answer, toUid, callId); },
|
|
184
|
+
onIceCandidateReady: (candidate, toUid) => { emitIceCandidate(candidate, toUid); },
|
|
185
|
+
onCallEnded: (callId, toUid) => { emitCallEnd(callId, toUid); },
|
|
186
|
+
});
|
|
88
187
|
const callInProgress = callSession.state === 'calling' || callSession.state === 'connected';
|
|
89
|
-
useEffect(() => {
|
|
188
|
+
(0, react_1.useEffect)(() => {
|
|
90
189
|
if (!callInProgress)
|
|
91
190
|
setCallMinimized(false);
|
|
92
191
|
}, [callInProgress]);
|
|
93
|
-
/* ── Drawer open/close
|
|
192
|
+
/* ── Drawer open/close ──────────────────────────────────────────────── */
|
|
94
193
|
const openDrawer = () => {
|
|
95
194
|
setClosing(false);
|
|
96
195
|
setIsOpen(true);
|
|
97
196
|
setCallMinimized(false);
|
|
98
197
|
};
|
|
99
|
-
const persistWidgetState = useCallback(() => {
|
|
198
|
+
const persistWidgetState = (0, react_1.useCallback)(() => {
|
|
100
199
|
var _a;
|
|
101
200
|
const w = data === null || data === void 0 ? void 0 : data.widget;
|
|
102
201
|
if (!w)
|
|
103
202
|
return;
|
|
104
|
-
saveSession(w.id, {
|
|
105
|
-
screen,
|
|
106
|
-
activeTab,
|
|
107
|
-
userListCtx,
|
|
203
|
+
(0, widgetSession_1.saveSession)(w.id, {
|
|
204
|
+
screen, activeTab, userListCtx,
|
|
108
205
|
activeUserUid: (_a = activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid) !== null && _a !== void 0 ? _a : null,
|
|
109
|
-
messages,
|
|
110
|
-
viewingTicketId,
|
|
111
|
-
chatReturnCtx,
|
|
206
|
+
messages, viewingTicketId, chatReturnCtx,
|
|
112
207
|
});
|
|
113
208
|
}, [data === null || data === void 0 ? void 0 : data.widget, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx]);
|
|
114
|
-
const closeDrawer = useCallback(() => {
|
|
209
|
+
const closeDrawer = (0, react_1.useCallback)(() => {
|
|
115
210
|
persistWidgetState();
|
|
116
211
|
setClosing(true);
|
|
117
|
-
setTimeout(() => {
|
|
118
|
-
setIsOpen(false);
|
|
119
|
-
setClosing(false);
|
|
120
|
-
}, 300);
|
|
212
|
+
setTimeout(() => { setIsOpen(false); setClosing(false); }, 300);
|
|
121
213
|
}, [persistWidgetState]);
|
|
122
|
-
useEffect(() => {
|
|
214
|
+
(0, react_1.useEffect)(() => {
|
|
123
215
|
var _a;
|
|
124
216
|
const id = (_a = data === null || data === void 0 ? void 0 : data.widget) === null || _a === void 0 ? void 0 : _a.id;
|
|
125
217
|
if (!id)
|
|
126
218
|
return;
|
|
127
|
-
setPermissionsOk(hasStoredPermissionsGrant(id));
|
|
128
|
-
}, [(
|
|
129
|
-
const restoredRef = useRef(false);
|
|
130
|
-
useEffect(() => {
|
|
219
|
+
setPermissionsOk((0, widgetPermissions_1.hasStoredPermissionsGrant)(id));
|
|
220
|
+
}, [(_f = data === null || data === void 0 ? void 0 : data.widget) === null || _f === void 0 ? void 0 : _f.id]);
|
|
221
|
+
const restoredRef = (0, react_1.useRef)(false);
|
|
222
|
+
(0, react_1.useEffect)(() => {
|
|
131
223
|
var _a, _b, _c, _d, _e, _f;
|
|
132
224
|
if (!(data === null || data === void 0 ? void 0 : data.widget) || restoredRef.current)
|
|
133
225
|
return;
|
|
134
226
|
const w = data.widget;
|
|
135
|
-
setMessageSoundEnabledState(getMessageSoundEnabled(w.id));
|
|
227
|
+
setMessageSoundEnabledState((0, messageSound_1.getMessageSoundEnabled)(w.id));
|
|
136
228
|
const uidForBlock = (_b = ((_a = viewer === null || viewer === void 0 ? void 0 : viewer.uid) !== null && _a !== void 0 ? _a : w.viewerUid)) === null || _b === void 0 ? void 0 : _b.trim();
|
|
137
229
|
let viewerIsBlocked = w.viewerBlocked === true;
|
|
138
230
|
if (!viewerIsBlocked && uidForBlock) {
|
|
@@ -147,7 +239,7 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
147
239
|
restoredRef.current = true;
|
|
148
240
|
return;
|
|
149
241
|
}
|
|
150
|
-
const p = loadSession(w.id);
|
|
242
|
+
const p = (0, widgetSession_1.loadSession)(w.id);
|
|
151
243
|
if (p) {
|
|
152
244
|
setScreen(p.screen);
|
|
153
245
|
setActiveTab(p.activeTab);
|
|
@@ -165,12 +257,15 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
165
257
|
? p.messages
|
|
166
258
|
: ((_f = data.sampleChats[u.uid]) !== null && _f !== void 0 ? _f : []);
|
|
167
259
|
selectUser(u, hist);
|
|
260
|
+
// Rejoin socket room
|
|
261
|
+
const roomId = [u.uid, viewerUidForSocket].sort().join('_');
|
|
262
|
+
joinRoom(roomId);
|
|
168
263
|
}
|
|
169
264
|
}
|
|
170
265
|
}
|
|
171
266
|
restoredRef.current = true;
|
|
172
|
-
}, [data, selectUser, clearChat, viewer === null || viewer === void 0 ? void 0 : viewer.projectId, viewer === null || viewer === void 0 ? void 0 : viewer.uid]);
|
|
173
|
-
useEffect(() => {
|
|
267
|
+
}, [data, selectUser, clearChat, viewer === null || viewer === void 0 ? void 0 : viewer.projectId, viewer === null || viewer === void 0 ? void 0 : viewer.uid, viewerUidForSocket, joinRoom]);
|
|
268
|
+
(0, react_1.useEffect)(() => {
|
|
174
269
|
var _a, _b;
|
|
175
270
|
if (!(data === null || data === void 0 ? void 0 : data.widget))
|
|
176
271
|
return;
|
|
@@ -188,36 +283,22 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
188
283
|
setActiveTab('home');
|
|
189
284
|
setViewingTicketId(null);
|
|
190
285
|
}, [data === null || data === void 0 ? void 0 : data.widget, data === null || data === void 0 ? void 0 : data.developers, data === null || data === void 0 ? void 0 : data.users, viewer === null || viewer === void 0 ? void 0 : viewer.uid, clearChat]);
|
|
191
|
-
useEffect(() => {
|
|
286
|
+
(0, react_1.useEffect)(() => {
|
|
192
287
|
if (!(data === null || data === void 0 ? void 0 : data.widget))
|
|
193
288
|
return;
|
|
194
289
|
persistWidgetState();
|
|
195
|
-
}, [(
|
|
196
|
-
const incomingSoundRef = useRef(0);
|
|
197
|
-
useEffect(() => {
|
|
198
|
-
|
|
199
|
-
}, [activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid]);
|
|
200
|
-
useEffect(() => {
|
|
201
|
-
if (!messageSoundEnabled || !activeUser || !(data === null || data === void 0 ? void 0 : data.widget))
|
|
202
|
-
return;
|
|
203
|
-
if (messages.length < incomingSoundRef.current) {
|
|
204
|
-
incomingSoundRef.current = messages.length;
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const added = messages.slice(incomingSoundRef.current);
|
|
208
|
-
incomingSoundRef.current = messages.length;
|
|
209
|
-
if (added.some(m => m.senderId !== 'me'))
|
|
210
|
-
playMessageSound();
|
|
211
|
-
}, [messages, messageSoundEnabled, activeUser, data === null || data === void 0 ? void 0 : data.widget]);
|
|
212
|
-
const toggleMessageSound = useCallback((enabled) => {
|
|
290
|
+
}, [(_g = data === null || data === void 0 ? void 0 : data.widget) === null || _g === void 0 ? void 0 : _g.id, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx, persistWidgetState]);
|
|
291
|
+
const incomingSoundRef = (0, react_1.useRef)(0);
|
|
292
|
+
(0, react_1.useEffect)(() => { incomingSoundRef.current = messages.length; }, [activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid]);
|
|
293
|
+
const toggleMessageSound = (0, react_1.useCallback)((enabled) => {
|
|
213
294
|
const w = data === null || data === void 0 ? void 0 : data.widget;
|
|
214
295
|
if (!w)
|
|
215
296
|
return;
|
|
216
|
-
setMessageSoundEnabled(w.id, enabled);
|
|
297
|
+
(0, messageSound_1.setMessageSoundEnabled)(w.id, enabled);
|
|
217
298
|
setMessageSoundEnabledState(enabled);
|
|
218
299
|
}, [data === null || data === void 0 ? void 0 : data.widget]);
|
|
219
|
-
/* ── Navigation
|
|
220
|
-
const handleCardClick = useCallback((ctx, options) => {
|
|
300
|
+
/* ── Navigation ─────────────────────────────────────────────────────── */
|
|
301
|
+
const handleCardClick = (0, react_1.useCallback)((ctx, options) => {
|
|
221
302
|
setListEntranceAnimation(!!(options === null || options === void 0 ? void 0 : options.fromMenu));
|
|
222
303
|
if (ctx === 'ticket') {
|
|
223
304
|
setActiveTab('tickets');
|
|
@@ -228,7 +309,7 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
228
309
|
setScreen('user-list');
|
|
229
310
|
}
|
|
230
311
|
}, []);
|
|
231
|
-
const handleNavFromMenu = useCallback((ctx) => {
|
|
312
|
+
const handleNavFromMenu = (0, react_1.useCallback)((ctx) => {
|
|
232
313
|
setListEntranceAnimation(false);
|
|
233
314
|
clearChat();
|
|
234
315
|
if (ctx === 'ticket') {
|
|
@@ -240,62 +321,71 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
240
321
|
setScreen('user-list');
|
|
241
322
|
}
|
|
242
323
|
}, [clearChat]);
|
|
243
|
-
const listCtxForUser = useCallback((user, viewerIsDev) => {
|
|
324
|
+
const listCtxForUser = (0, react_1.useCallback)((user, viewerIsDev) => {
|
|
244
325
|
if (viewerIsDev)
|
|
245
326
|
return user.type === 'user' ? 'support' : 'conversation';
|
|
246
327
|
return user.type === 'developer' ? 'support' : 'conversation';
|
|
247
328
|
}, []);
|
|
248
|
-
const handleSelectUser = useCallback((user, returnCtxOverride) => {
|
|
329
|
+
const handleSelectUser = (0, react_1.useCallback)((user, returnCtxOverride) => {
|
|
249
330
|
var _a;
|
|
250
331
|
setListEntranceAnimation(false);
|
|
251
332
|
setChatReturnCtx(returnCtxOverride !== null && returnCtxOverride !== void 0 ? returnCtxOverride : userListCtx);
|
|
252
333
|
const history = (_a = data === null || data === void 0 ? void 0 : data.sampleChats[user.uid]) !== null && _a !== void 0 ? _a : [];
|
|
253
334
|
selectUser(user, history);
|
|
254
335
|
setScreen('chat');
|
|
336
|
+
// Join socket room for real-time messages
|
|
337
|
+
const roomId = [user.uid, viewerUidForSocket].sort().join('_');
|
|
338
|
+
joinRoom(roomId);
|
|
255
339
|
setRecentChats(prev => {
|
|
256
340
|
const exists = prev.find(r => r.user.uid === user.uid);
|
|
257
341
|
if (exists)
|
|
258
|
-
return prev;
|
|
342
|
+
return prev.map(r => r.user.uid === user.uid ? Object.assign(Object.assign({}, r), { unread: 0 }) : r);
|
|
259
343
|
return [{ id: `rc_${user.uid}`, user, lastMessage: '', lastTime: new Date().toISOString(), unread: 0, isPaused: false }, ...prev];
|
|
260
344
|
});
|
|
261
|
-
}, [data, selectUser, userListCtx]);
|
|
262
|
-
const handleBackFromChat = useCallback(() => {
|
|
345
|
+
}, [data, selectUser, userListCtx, viewerUidForSocket, joinRoom]);
|
|
346
|
+
const handleBackFromChat = (0, react_1.useCallback)(() => {
|
|
263
347
|
setListEntranceAnimation(false);
|
|
348
|
+
if (activeUser) {
|
|
349
|
+
const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
|
|
350
|
+
leaveRoom(roomId);
|
|
351
|
+
}
|
|
264
352
|
clearChat();
|
|
265
353
|
setUserListCtx(chatReturnCtx);
|
|
266
354
|
setScreen('user-list');
|
|
267
|
-
}, [clearChat, chatReturnCtx]);
|
|
268
|
-
const handleOpenTicket = useCallback((id) => {
|
|
355
|
+
}, [clearChat, chatReturnCtx, activeUser, viewerUidForSocket, leaveRoom]);
|
|
356
|
+
const handleOpenTicket = (0, react_1.useCallback)((id) => {
|
|
269
357
|
setListEntranceAnimation(false);
|
|
270
358
|
setViewingTicketId(id);
|
|
271
359
|
setScreen('ticket-detail');
|
|
272
360
|
setActiveTab('tickets');
|
|
273
361
|
}, []);
|
|
274
|
-
const handleTabChange = useCallback((tab) => {
|
|
362
|
+
const handleTabChange = (0, react_1.useCallback)((tab) => {
|
|
275
363
|
setListEntranceAnimation(false);
|
|
276
364
|
setActiveTab(tab);
|
|
277
365
|
setScreen(tab === 'home' ? 'home' : tab === 'chats' ? 'recent-chats' : 'tickets');
|
|
278
366
|
}, []);
|
|
279
|
-
useEffect(() => {
|
|
367
|
+
(0, react_1.useEffect)(() => {
|
|
280
368
|
if (!listEntranceAnimation)
|
|
281
369
|
return;
|
|
282
370
|
const t = window.setTimeout(() => setListEntranceAnimation(false), 520);
|
|
283
371
|
return () => window.clearTimeout(t);
|
|
284
372
|
}, [listEntranceAnimation]);
|
|
285
|
-
/* ── Block/Unblock
|
|
286
|
-
const handleBlock = useCallback(() => {
|
|
373
|
+
/* ── Block/Unblock ──────────────────────────────────────────────────── */
|
|
374
|
+
const handleBlock = (0, react_1.useCallback)(() => {
|
|
287
375
|
if (!activeUser)
|
|
288
376
|
return;
|
|
289
377
|
setBlockedUids(prev => [...prev, activeUser.uid]);
|
|
378
|
+
emitBlock(activeUser.uid);
|
|
290
379
|
clearChat();
|
|
291
380
|
setScreen('block-list');
|
|
292
381
|
setActiveTab('home');
|
|
293
|
-
}, [activeUser, clearChat]);
|
|
294
|
-
const handleUnblock = useCallback((uid) => {
|
|
382
|
+
}, [activeUser, clearChat, emitBlock]);
|
|
383
|
+
const handleUnblock = (0, react_1.useCallback)((uid) => {
|
|
295
384
|
setBlockedUids(prev => prev.filter(id => id !== uid));
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
385
|
+
emitUnblock(uid);
|
|
386
|
+
}, [emitUnblock]);
|
|
387
|
+
/* ── Tickets ────────────────────────────────────────────────────────── */
|
|
388
|
+
const handleRaiseTicket = (0, react_1.useCallback)((title, desc, priority) => {
|
|
299
389
|
const t = {
|
|
300
390
|
id: `TKT-${String(Date.now()).slice(-4)}`,
|
|
301
391
|
title, description: desc, status: 'open', priority,
|
|
@@ -308,32 +398,31 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
308
398
|
setScreen('ticket-detail');
|
|
309
399
|
setActiveTab('tickets');
|
|
310
400
|
}, []);
|
|
311
|
-
/* ── Pause sync back into recent chats
|
|
312
|
-
const handleTogglePause = useCallback(() => {
|
|
313
|
-
|
|
401
|
+
/* ── Pause sync back into recent chats ─────────────────────────────── */
|
|
402
|
+
const handleTogglePause = (0, react_1.useCallback)(() => {
|
|
403
|
+
_togglePause();
|
|
314
404
|
if (activeUser) {
|
|
315
405
|
setRecentChats(prev => prev.map(r => r.user.uid === activeUser.uid ? Object.assign(Object.assign({}, r), { isPaused: !isPaused }) : r));
|
|
316
406
|
}
|
|
317
|
-
}, [
|
|
318
|
-
/* ── Call
|
|
319
|
-
const handleStartCall = useCallback((withVideo) => {
|
|
407
|
+
}, [_togglePause, activeUser, isPaused]);
|
|
408
|
+
/* ── Call ───────────────────────────────────────────────────────────── */
|
|
409
|
+
const handleStartCall = (0, react_1.useCallback)((withVideo) => {
|
|
320
410
|
if (!activeUser)
|
|
321
411
|
return;
|
|
322
|
-
|
|
412
|
+
_startCall(activeUser, withVideo);
|
|
323
413
|
setScreen('call');
|
|
324
|
-
}, [activeUser,
|
|
325
|
-
const handleEndCall = useCallback(() => {
|
|
326
|
-
|
|
414
|
+
}, [activeUser, _startCall]);
|
|
415
|
+
const handleEndCall = (0, react_1.useCallback)(() => {
|
|
416
|
+
_endCall();
|
|
327
417
|
setCallMinimized(false);
|
|
328
418
|
setScreen('chat');
|
|
329
|
-
}, [
|
|
330
|
-
const minimizeCall = useCallback(() => {
|
|
419
|
+
}, [_endCall]);
|
|
420
|
+
const minimizeCall = (0, react_1.useCallback)(() => {
|
|
331
421
|
setCallMinimized(true);
|
|
332
422
|
closeDrawer();
|
|
333
423
|
}, [closeDrawer]);
|
|
334
|
-
/* ── Derived
|
|
335
|
-
const
|
|
336
|
-
const widgetConfig = useMemo(() => {
|
|
424
|
+
/* ── Derived config (must be declared before callbacks that use it) ── */
|
|
425
|
+
const widgetConfig = (0, react_1.useMemo)(() => {
|
|
337
426
|
var _a;
|
|
338
427
|
if (!(data === null || data === void 0 ? void 0 : data.widget))
|
|
339
428
|
return undefined;
|
|
@@ -347,9 +436,36 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
347
436
|
}
|
|
348
437
|
return w;
|
|
349
438
|
}, [data === null || data === void 0 ? void 0 : data.widget, viewer]);
|
|
439
|
+
/* ── Transfer (developer feature) ──────────────────────────────────── */
|
|
440
|
+
const handleTransferToDeveloper = (0, react_1.useCallback)((dev) => {
|
|
441
|
+
var _a;
|
|
442
|
+
if (!activeUser || !widgetConfig)
|
|
443
|
+
return;
|
|
444
|
+
const agent = ((_a = widgetConfig.viewerName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Agent';
|
|
445
|
+
const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
|
|
446
|
+
const transferNote = {
|
|
447
|
+
id: `tr_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
448
|
+
senderId: 'me',
|
|
449
|
+
receiverId: dev.uid,
|
|
450
|
+
text: `— ${agent} transferred this conversation from ${activeUser.name} to ${dev.name} —`,
|
|
451
|
+
timestamp: new Date().toISOString(),
|
|
452
|
+
type: 'text',
|
|
453
|
+
status: 'sent',
|
|
454
|
+
};
|
|
455
|
+
emitTransfer(roomId, dev.uid, transferNote.text);
|
|
456
|
+
selectUser(dev, [...messages, transferNote]);
|
|
457
|
+
}, [activeUser, messages, selectUser, widgetConfig, viewerUidForSocket, emitTransfer]);
|
|
458
|
+
/* ── Add participant (developer feature) ────────────────────────────── */
|
|
459
|
+
const handleAddParticipant = (0, react_1.useCallback)((uid) => {
|
|
460
|
+
if (!activeUser)
|
|
461
|
+
return;
|
|
462
|
+
const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
|
|
463
|
+
emitAddParticipant(roomId, uid);
|
|
464
|
+
}, [activeUser, viewerUidForSocket, emitAddParticipant]);
|
|
465
|
+
/* ── Derived ────────────────────────────────────────────────────────── */
|
|
466
|
+
const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
|
|
350
467
|
const primaryColor = theme.primaryColor;
|
|
351
|
-
|
|
352
|
-
const allUsers = useMemo(() => {
|
|
468
|
+
const allUsers = (0, react_1.useMemo)(() => {
|
|
353
469
|
var _a, _b;
|
|
354
470
|
if (!data)
|
|
355
471
|
return [];
|
|
@@ -357,10 +473,9 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
357
473
|
const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
|
|
358
474
|
if (!pid)
|
|
359
475
|
return [...devs, ...data.users];
|
|
360
|
-
|
|
361
|
-
return [...devs, ...usersInProject];
|
|
476
|
+
return [...devs, ...data.users.filter(u => u.project === pid)];
|
|
362
477
|
}, [data, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
|
|
363
|
-
const effectiveViewerBlocked = useMemo(() => {
|
|
478
|
+
const effectiveViewerBlocked = (0, react_1.useMemo)(() => {
|
|
364
479
|
var _a, _b;
|
|
365
480
|
if (!widgetConfig)
|
|
366
481
|
return false;
|
|
@@ -377,61 +492,28 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
377
492
|
const filteredUsers = screen === 'user-list'
|
|
378
493
|
? allUsers.filter(u => {
|
|
379
494
|
if (userListCtx === 'support') {
|
|
380
|
-
|
|
381
|
-
return u.type === 'user';
|
|
382
|
-
return u.type === 'developer';
|
|
495
|
+
return viewerIsDev ? u.type === 'user' : u.type === 'developer';
|
|
383
496
|
}
|
|
384
|
-
if (viewerIsDev)
|
|
497
|
+
if (viewerIsDev)
|
|
385
498
|
return u.type === 'developer' && u.uid !== viewerUid;
|
|
386
|
-
}
|
|
387
499
|
return u.type === 'user';
|
|
388
500
|
})
|
|
389
501
|
: [];
|
|
390
|
-
const otherDevelopers = useMemo(() => allUsers.filter(u => u.type === 'developer' && u.uid !== viewerUid), [allUsers, viewerUid]);
|
|
502
|
+
const otherDevelopers = (0, react_1.useMemo)(() => allUsers.filter(u => u.type === 'developer' && u.uid !== viewerUid), [allUsers, viewerUid]);
|
|
391
503
|
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]);
|
|
393
|
-
const handleTransferToDeveloper = useCallback((dev) => {
|
|
394
|
-
var _a;
|
|
395
|
-
if (!activeUser || !widgetConfig)
|
|
396
|
-
return;
|
|
397
|
-
const agent = ((_a = widgetConfig.viewerName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Agent';
|
|
398
|
-
const transferNote = {
|
|
399
|
-
id: `tr_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
400
|
-
senderId: 'me',
|
|
401
|
-
receiverId: dev.uid,
|
|
402
|
-
text: `— ${agent} transferred this conversation from ${activeUser.name} to ${dev.name} —`,
|
|
403
|
-
timestamp: new Date().toISOString(),
|
|
404
|
-
type: 'text',
|
|
405
|
-
status: 'sent',
|
|
406
|
-
};
|
|
407
|
-
selectUser(dev, [...messages, transferNote]);
|
|
408
|
-
}, [activeUser, messages, selectUser, widgetConfig]);
|
|
409
|
-
/* Position */
|
|
504
|
+
const totalUnread = (0, react_1.useMemo)(() => recentChats.reduce((sum, c) => { var _a; return sum + Math.max(0, (_a = c.unread) !== null && _a !== void 0 ? _a : 0); }, 0), [recentChats]);
|
|
410
505
|
const posStyle = theme.buttonPosition === 'bottom-left'
|
|
411
506
|
? { left: 24, right: 'auto' }
|
|
412
507
|
: { right: 24, left: 'auto' };
|
|
413
|
-
/* No radius on top-left / bottom-left; left-docked panel keeps inner TR/BR curve */
|
|
414
508
|
const drawerPosStyle = theme.buttonPosition === 'bottom-left'
|
|
415
|
-
? {
|
|
416
|
-
|
|
417
|
-
borderTopLeftRadius: 0,
|
|
418
|
-
borderBottomLeftRadius: 0,
|
|
419
|
-
borderTopRightRadius: 16,
|
|
420
|
-
borderBottomRightRadius: 16,
|
|
421
|
-
}
|
|
422
|
-
: {
|
|
423
|
-
right: 0,
|
|
424
|
-
borderTopLeftRadius: 0,
|
|
425
|
-
borderBottomLeftRadius: 0,
|
|
426
|
-
};
|
|
427
|
-
/* ── Don't render until mounted (SSR safe) ──────────────────────────── */
|
|
509
|
+
? { left: 0, borderTopLeftRadius: 0, borderBottomLeftRadius: 0, borderTopRightRadius: 16, borderBottomRightRadius: 16 }
|
|
510
|
+
: { right: 0, borderTopLeftRadius: 0, borderBottomLeftRadius: 0 };
|
|
428
511
|
if (!mounted)
|
|
429
512
|
return null;
|
|
430
|
-
return (
|
|
513
|
+
return (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { primaryColor: primaryColor },
|
|
514
|
+
react_1.default.createElement("style", null, `
|
|
431
515
|
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
|
|
432
|
-
|
|
433
516
|
.cw-root * { box-sizing: border-box; font-family: 'DM Sans', 'Segoe UI', sans-serif; }
|
|
434
|
-
|
|
435
517
|
@keyframes cw-slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
436
518
|
@keyframes cw-slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
|
|
437
519
|
@keyframes cw-slideInLeft { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
@@ -440,82 +522,95 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
440
522
|
@keyframes cw-slideIn { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: translateX(0); } }
|
|
441
523
|
@keyframes cw-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
442
524
|
@keyframes cw-btnPop { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
443
|
-
|
|
525
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
444
526
|
.cw-scroll::-webkit-scrollbar { width: 4px; }
|
|
445
527
|
.cw-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
446
528
|
.cw-scroll::-webkit-scrollbar-thumb { background: #e0e0e0; border-radius: 4px; }
|
|
447
|
-
|
|
448
529
|
.cw-drawer-enter { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideInLeft' : 'cw-slideInRight'} 0.32s cubic-bezier(0.22,1,0.36,1) both; }
|
|
449
530
|
.cw-drawer-exit { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideOutLeft' : 'cw-slideOutRight'} 0.28s cubic-bezier(0.55,0,1,0.45) both; }
|
|
450
|
-
|
|
451
|
-
.cw-drawer-panel {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
531
|
+
.cw-drawer-panel { width: 30%; max-width: 100vw; min-width: 0; }
|
|
532
|
+
@media (max-width: 1024px) { .cw-drawer-panel { width: 100%; } }
|
|
533
|
+
`),
|
|
534
|
+
socketStatus !== 'connected' && socketStatus !== 'disconnected' && (react_1.default.createElement("div", { style: Object.assign(Object.assign({ position: 'fixed', bottom: 80 }, posStyle), { zIndex: 9996, background: socketStatus === 'connecting' ? '#f59e0b' : '#ef4444', color: '#fff', fontSize: 11, fontWeight: 700, padding: '3px 10px', borderRadius: 20, animation: 'cw-fadeUp 0.3s ease' }) }, socketStatus === 'connecting' ? '● Connecting…' : '● Offline')),
|
|
535
|
+
!isOpen && callMinimized && callInProgress && callSession.peer && (react_1.default.createElement(MiniCallBar_1.MiniCallBar, { session: callSession, primaryColor: primaryColor, buttonPosition: theme.buttonPosition, onExpand: openDrawer, onEnd: handleEndCall })),
|
|
536
|
+
!isOpen && (react_1.default.createElement("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 => {
|
|
537
|
+
e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)';
|
|
538
|
+
e.currentTarget.style.boxShadow = `0 14px 36px ${theme.buttonColor}66`;
|
|
539
|
+
}, onMouseLeave: e => {
|
|
540
|
+
e.currentTarget.style.transform = 'scale(1)';
|
|
541
|
+
e.currentTarget.style.boxShadow = `0 8px 28px ${theme.buttonColor}55`;
|
|
542
|
+
} },
|
|
543
|
+
react_1.default.createElement("span", { style: { position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } },
|
|
544
|
+
react_1.default.createElement("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none" },
|
|
545
|
+
react_1.default.createElement("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" })),
|
|
546
|
+
totalUnread > 0 && (react_1.default.createElement("span", { style: {
|
|
547
|
+
position: 'absolute', top: -8, right: -10,
|
|
548
|
+
minWidth: 20, height: 20, padding: '0 5px', borderRadius: 999,
|
|
549
|
+
background: '#ef4444', color: '#fff', fontSize: 11, fontWeight: 800,
|
|
550
|
+
lineHeight: '20px', textAlign: 'center', border: '2px solid #fff', boxSizing: 'border-box',
|
|
551
|
+
} }, totalUnread > 99 ? '99+' : totalUnread))),
|
|
552
|
+
react_1.default.createElement("span", null, theme.buttonLabel))),
|
|
553
|
+
isOpen && (react_1.default.createElement("div", { "aria-hidden": true, style: {
|
|
554
|
+
position: 'fixed', inset: 0, zIndex: 9997,
|
|
555
|
+
backgroundColor: 'rgba(0,0,0,0.35)',
|
|
556
|
+
opacity: closing ? 0 : 1,
|
|
557
|
+
transition: 'opacity 0.3s',
|
|
558
|
+
} })),
|
|
559
|
+
isOpen && (react_1.default.createElement("div", { className: `cw-root cw-drawer-panel ${closing ? 'cw-drawer-exit' : 'cw-drawer-enter'}`, style: Object.assign(Object.assign({ position: 'fixed', top: 0, bottom: 0 }, drawerPosStyle), { zIndex: 9998, backgroundColor: '#fff', boxShadow: theme.buttonPosition === 'bottom-left'
|
|
560
|
+
? '4px 0 40px rgba(0,0,0,0.18)'
|
|
561
|
+
: '-4px 0 40px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }) },
|
|
562
|
+
cfgLoading && (react_1.default.createElement("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 } },
|
|
563
|
+
react_1.default.createElement("div", { style: { width: 40, height: 40, borderRadius: '50%', border: `3px solid ${primaryColor}30`, borderTopColor: primaryColor, animation: 'spin 0.8s linear infinite' } }),
|
|
564
|
+
react_1.default.createElement("p", { style: { fontSize: 14, color: '#7b8fa1' } }, "Loading chat\u2026"))),
|
|
565
|
+
cfgError && !cfgLoading && (react_1.default.createElement("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 12, padding: 32, textAlign: 'center' } },
|
|
566
|
+
react_1.default.createElement("div", { style: { fontSize: 40 } }, "\u26A0\uFE0F"),
|
|
567
|
+
react_1.default.createElement("p", { style: { fontWeight: 700, color: '#1a2332' } }, "Could not load chat configuration"),
|
|
568
|
+
react_1.default.createElement("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6 } }, cfgError),
|
|
569
|
+
react_1.default.createElement("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 } }, "Close"))),
|
|
570
|
+
!cfgLoading && !cfgError && widgetConfig && (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { primaryColor: primaryColor },
|
|
571
|
+
screen !== 'chat' && screen !== 'call' && !effectiveViewerBlocked && (react_1.default.createElement("div", { style: {
|
|
572
|
+
position: 'absolute', top: 12,
|
|
573
|
+
right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
|
|
574
|
+
left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
|
|
575
|
+
zIndex: 20, display: 'flex', gap: 6,
|
|
576
|
+
} },
|
|
577
|
+
react_1.default.createElement(CornerBtn, { onClick: closeDrawer, title: "Close" },
|
|
578
|
+
react_1.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none" },
|
|
579
|
+
react_1.default.createElement("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }))))),
|
|
580
|
+
widgetConfig.status === 'MAINTENANCE' && react_1.default.createElement(MaintenanceView_1.MaintenanceView, { primaryColor: primaryColor }),
|
|
581
|
+
widgetConfig.status === 'DISABLE' && (react_1.default.createElement("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 } },
|
|
582
|
+
react_1.default.createElement("div", { style: { fontSize: 40 } }, "\uD83D\uDD12"),
|
|
583
|
+
react_1.default.createElement("p", { style: { fontWeight: 700, color: '#1a2332' } }, "Chat is disabled"),
|
|
584
|
+
react_1.default.createElement("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 } }, "Close"))),
|
|
585
|
+
widgetConfig.status === 'ACTIVE' && effectiveViewerBlocked && (react_1.default.createElement(ViewerBlockedScreen_1.ViewerBlockedScreen, { config: widgetConfig, apiKey: apiKey, onClose: closeDrawer })),
|
|
586
|
+
widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (react_1.default.createElement(PermissionsGateScreen_1.PermissionsGateScreen, { primaryColor: primaryColor, widgetId: widgetConfig.id, onGranted: () => setPermissionsOk(true) })),
|
|
587
|
+
widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (react_1.default.createElement("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' } },
|
|
588
|
+
screen === 'home' && (react_1.default.createElement(HomeScreen_1.HomeScreen, { config: widgetConfig, apiKey: apiKey, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })),
|
|
589
|
+
screen === 'user-list' && (react_1.default.createElement(UserListScreen_1.UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_h = widgetConfig.viewerType) !== null && _h !== void 0 ? _h : 'user', onBack: () => { setListEntranceAnimation(false); setScreen('home'); }, onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && widgetConfig.viewerType !== 'developer', animateEntrance: listEntranceAnimation })),
|
|
590
|
+
screen === 'chat' && activeUser && (react_1.default.createElement(ChatScreen_1.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 })),
|
|
591
|
+
screen === 'call' && callSession.peer && (react_1.default.createElement(CallScreen_1.CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor, onMinimize: minimizeCall })),
|
|
592
|
+
screen === 'recent-chats' && (react_1.default.createElement(RecentChatsScreen_1.RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)), animateEntrance: listEntranceAnimation })),
|
|
593
|
+
screen === 'tickets' && (react_1.default.createElement(TicketScreen_1.TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => { setListEntranceAnimation(false); setScreen('ticket-new'); }, onSelectTicket: id => {
|
|
594
|
+
setListEntranceAnimation(false);
|
|
595
|
+
setViewingTicketId(id);
|
|
596
|
+
setScreen('ticket-detail');
|
|
597
|
+
}, animateEntrance: listEntranceAnimation })),
|
|
598
|
+
screen === 'ticket-new' && (react_1.default.createElement(TicketFormScreen_1.TicketFormScreen, { config: widgetConfig, onSubmit: handleRaiseTicket, onCancel: () => setScreen('tickets') })),
|
|
599
|
+
screen === 'ticket-detail' && viewingTicketId && (() => {
|
|
600
|
+
const t = tickets.find(x => x.id === viewingTicketId);
|
|
601
|
+
return t ? (react_1.default.createElement(TicketDetailScreen_1.TicketDetailScreen, { ticket: t, config: widgetConfig, onBack: () => { setViewingTicketId(null); setScreen('tickets'); } })) : null;
|
|
602
|
+
})(),
|
|
603
|
+
screen === 'block-list' && (react_1.default.createElement(BlockList_1.BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } })))),
|
|
604
|
+
widgetConfig.status === 'ACTIVE' &&
|
|
605
|
+
!effectiveViewerBlocked && permissionsOk &&
|
|
606
|
+
screen !== 'chat' && screen !== 'call' &&
|
|
607
|
+
screen !== 'user-list' && screen !== 'block-list' &&
|
|
608
|
+
screen !== 'ticket-detail' && screen !== 'ticket-new' && (react_1.default.createElement(BottomTabs_1.BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: primaryColor }))))))));
|
|
514
609
|
};
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const CornerBtn = ({ onClick, title, children }) => (
|
|
518
|
-
width: 26, height: 26, borderRadius: '50%',
|
|
519
|
-
background: 'rgba(0,0,0,0.25)', border: 'none',
|
|
610
|
+
exports.ChatWidget = ChatWidget;
|
|
611
|
+
exports.default = exports.ChatWidget;
|
|
612
|
+
const CornerBtn = ({ onClick, title, children }) => (react_1.default.createElement("button", { onClick: onClick, title: title, style: {
|
|
613
|
+
width: 26, height: 26, borderRadius: '50%', background: 'rgba(0,0,0,0.25)', border: 'none',
|
|
520
614
|
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
|
|
521
|
-
}, children
|
|
615
|
+
} }, children));
|
|
616
|
+
//# sourceMappingURL=ChatWidget.js.map
|