ajaxter-chat 3.1.0 → 3.2.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.
@@ -60,26 +60,104 @@ const BottomTabs_1 = require("./Tabs/BottomTabs");
60
60
  const ViewerBlockedScreen_1 = require("./ViewerBlockedScreen");
61
61
  const PermissionsGateScreen_1 = require("./PermissionsGateScreen");
62
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');
63
+ function getPanelGeometry(displayMode, buttonPosition, ajaxterMaximized) {
64
+ var _a, _b, _c, _d;
65
+ const w = (_b = (_a = ajaxterMaximized === null || ajaxterMaximized === void 0 ? void 0 : ajaxterMaximized.desktop) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 550;
66
+ const h = (_d = (_c = ajaxterMaximized === null || ajaxterMaximized === void 0 ? void 0 : ajaxterMaximized.desktop) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 600;
67
+ if (displayMode === 'popup') {
68
+ // Centered modal / popup
69
+ return {
70
+ panel: {
71
+ position: 'fixed',
72
+ top: '50%',
73
+ left: '50%',
74
+ transform: 'translate(-50%, -50%)',
75
+ width: `min(${w}px, 96vw)`,
76
+ height: `min(${h}px, 94vh)`,
77
+ maxWidth: '100vw',
78
+ maxHeight: '100vh',
79
+ borderRadius: 16,
80
+ boxShadow: '0 24px 64px rgba(0,0,0,0.28)',
81
+ zIndex: 9998,
82
+ backgroundColor: '#fff',
83
+ display: 'flex',
84
+ flexDirection: 'column',
85
+ overflow: 'hidden',
86
+ },
87
+ enterClass: 'cw-popup-enter',
88
+ exitClass: 'cw-popup-exit',
89
+ backdrop: {
90
+ position: 'fixed', inset: 0, zIndex: 9997,
91
+ backgroundColor: 'rgba(0,0,0,0.45)',
92
+ backdropFilter: 'blur(2px)',
93
+ },
94
+ };
95
+ }
96
+ if (displayMode === 'inline') {
97
+ // Embedded — no fixed positioning, caller wraps it
98
+ return {
99
+ panel: {
100
+ position: 'relative',
101
+ width: '100%',
102
+ height: '100%',
103
+ display: 'flex',
104
+ flexDirection: 'column',
105
+ overflow: 'hidden',
106
+ backgroundColor: '#fff',
107
+ },
108
+ enterClass: '',
109
+ exitClass: '',
110
+ };
68
111
  }
69
- return 'http://localhost:3005';
112
+ // Default: slider (side drawer)
113
+ const isLeft = buttonPosition === 'bottom-left';
114
+ return {
115
+ panel: {
116
+ position: 'fixed',
117
+ top: 0, bottom: 0,
118
+ [isLeft ? 'left' : 'right']: 0,
119
+ width: 'min(380px, 100vw)',
120
+ maxWidth: '100vw',
121
+ borderTopLeftRadius: isLeft ? 0 : 16,
122
+ borderBottomLeftRadius: isLeft ? 0 : 16,
123
+ borderTopRightRadius: isLeft ? 16 : 0,
124
+ borderBottomRightRadius: isLeft ? 16 : 0,
125
+ boxShadow: isLeft ? '4px 0 40px rgba(0,0,0,0.18)' : '-4px 0 40px rgba(0,0,0,0.18)',
126
+ zIndex: 9998,
127
+ backgroundColor: '#fff',
128
+ display: 'flex',
129
+ flexDirection: 'column',
130
+ overflow: 'hidden',
131
+ },
132
+ enterClass: isLeft ? 'cw-slideInLeft' : 'cw-slideInRight',
133
+ exitClass: isLeft ? 'cw-slideOutLeft' : 'cw-slideOutRight',
134
+ backdrop: {
135
+ position: 'fixed', inset: 0, zIndex: 9997,
136
+ backgroundColor: 'rgba(0,0,0,0.35)',
137
+ },
138
+ };
70
139
  }
140
+ // ─── ChatWidget ───────────────────────────────────────────────────────────────
71
141
  const ChatWidget = ({ theme: localTheme, viewer }) => {
72
- var _a, _b, _c, _d, _e, _f, _g, _h;
73
- /* SSR guard */
142
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
74
143
  const [mounted, setMounted] = (0, react_1.useState)(false);
75
144
  (0, react_1.useEffect)(() => { setMounted(true); }, []);
76
- const { apiKey, widgetId } = (0, config_1.loadLocalConfig)();
145
+ const { apiKey, widgetId, socketUrl } = (0, config_1.loadLocalConfig)();
77
146
  const { data, loading: cfgLoading, error: cfgError } = (0, useRemoteConfig_1.useRemoteConfig)(apiKey, widgetId);
78
147
  const theme = (0, theme_1.mergeTheme)((data === null || data === void 0 ? void 0 : data.widget) ? {
79
148
  primaryColor: data.widget.primaryColor,
80
149
  buttonLabel: data.widget.buttonLabel,
81
150
  buttonPosition: data.widget.buttonPosition,
82
151
  } : undefined, localTheme);
152
+ // Pull real colours from Ajaxter settings if available
153
+ const headerBg = (_d = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.widget.ajaxterSettings) === null || _a === void 0 ? void 0 : _a.theme) === null || _b === void 0 ? void 0 : _b.header) === null || _c === void 0 ? void 0 : _c.background) !== null && _d !== void 0 ? _d : theme.primaryColor;
154
+ const agentBg = (_h = (_g = (_f = (_e = data === null || data === void 0 ? void 0 : data.widget.ajaxterSettings) === null || _e === void 0 ? void 0 : _e.theme) === null || _f === void 0 ? void 0 : _f.agent) === null || _g === void 0 ? void 0 : _g.messageBackground) !== null && _h !== void 0 ? _h : '#54aadd';
155
+ const visitorBg = (_m = (_l = (_k = (_j = data === null || data === void 0 ? void 0 : data.widget.ajaxterSettings) === null || _j === void 0 ? void 0 : _j.theme) === null || _k === void 0 ? void 0 : _k.visitor) === null || _l === void 0 ? void 0 : _l.messageBackground) !== null && _m !== void 0 ? _m : '#e5e5e5';
156
+ const displayMode = (_o = data === null || data === void 0 ? void 0 : data.widget.displayMode) !== null && _o !== void 0 ? _o : 'slider';
157
+ const ajaxterSettings = data === null || data === void 0 ? void 0 : data.widget.ajaxterSettings;
158
+ // Minimized button style from Ajaxter settings
159
+ const minimizedDesktop = (_p = ajaxterSettings === null || ajaxterSettings === void 0 ? void 0 : ajaxterSettings.minimized) === null || _p === void 0 ? void 0 : _p.desktop;
160
+ const btnType = (_q = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.type) !== null && _q !== void 0 ? _q : 'slide';
83
161
  const [isOpen, setIsOpen] = (0, react_1.useState)(false);
84
162
  const [closing, setClosing] = (0, react_1.useState)(false);
85
163
  const [callMinimized, setCallMinimized] = (0, react_1.useState)(false);
@@ -92,60 +170,56 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
92
170
  const [listEntranceAnimation, setListEntranceAnimation] = (0, react_1.useState)(false);
93
171
  const [permissionsOk, setPermissionsOk] = (0, react_1.useState)(false);
94
172
  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 : []);
173
+ const [typingUsers, setTypingUsers] = (0, react_1.useState)(new Set());
174
+ const [tickets, setTickets] = (0, react_1.useState)([]);
96
175
  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 : []);
98
- /* Sync remote data into local state once loaded */
176
+ const [blockedUids, setBlockedUids] = (0, react_1.useState)([]);
177
+ // Sync remote data once loaded
99
178
  (0, react_1.useEffect)(() => {
100
179
  var _a, _b, _c, _d;
101
- if (data) {
102
- setTickets(data.sampleTickets);
103
- setBlockedUids(data.blockedUsers);
104
- const pid = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _a === void 0 ? void 0 : _a.trim();
105
- const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
106
- const usr = pid ? ((_c = data.users) !== null && _c !== void 0 ? _c : []).filter(u => u.project === pid) : ((_d = data.users) !== null && _d !== void 0 ? _d : []);
107
- const all = [...devs, ...usr];
108
- const recents = Object.entries(data.sampleChats).map(([uid, msgs]) => {
109
- const user = all.find(u => u.uid === uid);
110
- if (!user || msgs.length === 0)
111
- return null;
112
- const last = msgs[msgs.length - 1];
113
- return {
114
- id: `rc_${uid}`,
115
- user,
116
- lastMessage: last.text,
117
- lastTime: last.timestamp,
118
- unread: Math.floor(Math.random() * 3),
119
- isPaused: false,
120
- };
121
- }).filter(Boolean);
122
- setRecentChats(recents);
123
- }
180
+ if (!data)
181
+ return;
182
+ setTickets(data.sampleTickets);
183
+ setBlockedUids(data.blockedUsers);
184
+ const pid = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _a === void 0 ? void 0 : _a.trim();
185
+ const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
186
+ const usr = pid ? ((_c = data.users) !== null && _c !== void 0 ? _c : []).filter(u => u.project === pid) : ((_d = data.users) !== null && _d !== void 0 ? _d : []);
187
+ const all = [...devs, ...usr];
188
+ const recents = Object.entries(data.sampleChats).map(([uid, msgs]) => {
189
+ const user = all.find(u => u.uid === uid);
190
+ if (!user || msgs.length === 0)
191
+ return null;
192
+ const last = msgs[msgs.length - 1];
193
+ return { id: `rc_${uid}`, user, lastMessage: last.text, lastTime: last.timestamp, unread: 0, isPaused: false };
194
+ }).filter(Boolean);
195
+ setRecentChats(recents);
124
196
  }, [data, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
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)({
197
+ // ── Socket ────────────────────────────────────────────────────────────────
198
+ const viewerUidForSocket = ((_t = (_r = viewer === null || viewer === void 0 ? void 0 : viewer.uid) !== null && _r !== void 0 ? _r : (_s = data === null || data === void 0 ? void 0 : data.widget) === null || _s === void 0 ? void 0 : _s.viewerUid) !== null && _t !== void 0 ? _t : '').trim();
199
+ const { joinRoom, leaveRoom, emitMessage, emitPauseToggle, emitReport, emitBlock, emitUnblock, emitTransfer, emitCallOffer, emitCallAnswer, emitIceCandidate, emitCallEnd, emitAddParticipant, emitPresence, emitTicketCreate, emitTicketUpdate, status: _socketStatus, } = (0, useSocket_1.useSocket)({
128
200
  widgetId,
129
201
  viewerUid: viewerUidForSocket,
130
- serverUrl: getSocketUrl(),
202
+ serverUrl: socketUrl,
131
203
  onMessage: (msg) => {
132
204
  receiveMessage(msg);
133
205
  if (messageSoundEnabled && msg.senderId !== 'me')
134
206
  (0, messageSound_1.playMessageSound)();
135
- // Update recent chats last message
136
207
  setRecentChats(prev => prev.map(r => r.user.uid === msg.senderId || r.user.uid === msg.receiverId
137
208
  ? Object.assign(Object.assign({}, r), { lastMessage: msg.text, lastTime: msg.timestamp, unread: r.unread + 1 }) : r));
138
209
  },
139
- onMessageAck: ({ messageId, status }) => {
140
- updateMessageStatus(messageId, status);
141
- },
210
+ onMessageAck: ({ messageId, status }) => { updateMessageStatus(messageId, status); },
142
211
  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));
212
+ if (activeUser)
213
+ setRecentChats(prev => prev.map(r => r.user.uid === activeUser.uid ? Object.assign(Object.assign({}, r), { isPaused: paused }) : r));
214
+ },
215
+ onTypingStart: (roomId, userId) => {
216
+ setTypingUsers(prev => { const s = new Set(prev); s.add(userId); return s; });
217
+ },
218
+ onTypingStop: (_roomId, userId) => {
219
+ setTypingUsers(prev => { const s = new Set(prev); s.delete(userId); return s; });
145
220
  },
146
221
  onCallOffer: async (offer, fromUid, callId) => {
147
222
  var _a, _b;
148
- // Find peer user
149
223
  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
224
  const peer = allUsers.find(u => u.uid === fromUid);
151
225
  if (peer) {
@@ -154,142 +228,91 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
154
228
  setIsOpen(true);
155
229
  }
156
230
  },
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
231
  onCallEnd: () => { _endCall(); },
166
- onError: (code, message) => {
167
- console.error('[ChatWidget] Socket error:', code, message);
168
- },
169
232
  onUserStatus: (uid, status) => {
170
233
  setRecentChats(prev => prev.map(r => r.user.uid === uid ? Object.assign(Object.assign({}, r), { user: Object.assign(Object.assign({}, r.user), { status }) }) : r));
171
234
  },
235
+ onTicketCreated: (t) => { if (t)
236
+ setTickets(prev => [t, ...prev]); },
237
+ onTicketUpdated: (t) => {
238
+ if (t)
239
+ setTickets(prev => prev.map(x => x.id === t.id ? t : x));
240
+ },
241
+ onError: (code, msg) => console.error('[ChatWidget] socket error:', code, msg),
172
242
  });
173
243
  (0, react_1.useEffect)(() => { setSocketStatus(_socketStatus); }, [_socketStatus]);
174
- /* ── Chat hook ──────────────────────────────────────────────────────── */
244
+ // ── Chat hook ─────────────────────────────────────────────────────────────
175
245
  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); },
246
+ onEmitMessage: (msg) => emitMessage(msg),
247
+ onEmitPause: (roomId, targetUid, paused) => emitPauseToggle(roomId, targetUid, paused),
248
+ onEmitReport: (roomId) => emitReport(roomId),
179
249
  });
180
- /* ── WebRTC hook ────────────────────────────────────────────────────── */
250
+ // ── WebRTC hook ───────────────────────────────────────────────────────────
181
251
  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); },
252
+ onOfferReady: (offer, toUid, callId) => emitCallOffer(offer, toUid, callId),
253
+ onAnswerReady: (answer, toUid, callId) => emitCallAnswer(answer, toUid, callId),
254
+ onIceCandidateReady: (candidate, toUid) => emitIceCandidate(candidate, toUid),
255
+ onCallEnded: (callId, toUid) => emitCallEnd(callId, toUid),
186
256
  });
187
257
  const callInProgress = callSession.state === 'calling' || callSession.state === 'connected';
188
- (0, react_1.useEffect)(() => {
189
- if (!callInProgress)
190
- setCallMinimized(false);
191
- }, [callInProgress]);
192
- /* ── Drawer open/close ──────────────────────────────────────────────── */
193
- const openDrawer = () => {
194
- setClosing(false);
195
- setIsOpen(true);
196
- setCallMinimized(false);
197
- };
258
+ (0, react_1.useEffect)(() => { if (!callInProgress)
259
+ setCallMinimized(false); }, [callInProgress]);
260
+ // ── Drawer open / close ───────────────────────────────────────────────────
261
+ const openDrawer = () => { setClosing(false); setIsOpen(true); setCallMinimized(false); };
198
262
  const persistWidgetState = (0, react_1.useCallback)(() => {
199
263
  var _a;
200
264
  const w = data === null || data === void 0 ? void 0 : data.widget;
201
265
  if (!w)
202
266
  return;
203
- (0, widgetSession_1.saveSession)(w.id, {
204
- screen, activeTab, userListCtx,
205
- activeUserUid: (_a = activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid) !== null && _a !== void 0 ? _a : null,
206
- messages, viewingTicketId, chatReturnCtx,
207
- });
267
+ (0, widgetSession_1.saveSession)(w.id, { screen, activeTab, userListCtx, activeUserUid: (_a = activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid) !== null && _a !== void 0 ? _a : null, messages, viewingTicketId, chatReturnCtx });
208
268
  }, [data === null || data === void 0 ? void 0 : data.widget, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx]);
209
269
  const closeDrawer = (0, react_1.useCallback)(() => {
210
270
  persistWidgetState();
271
+ if (displayMode === 'inline')
272
+ return; // inline is always open
211
273
  setClosing(true);
212
274
  setTimeout(() => { setIsOpen(false); setClosing(false); }, 300);
213
- }, [persistWidgetState]);
275
+ }, [persistWidgetState, displayMode]);
214
276
  (0, react_1.useEffect)(() => {
215
277
  var _a;
216
278
  const id = (_a = data === null || data === void 0 ? void 0 : data.widget) === null || _a === void 0 ? void 0 : _a.id;
217
279
  if (!id)
218
280
  return;
219
281
  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]);
282
+ }, [(_u = data === null || data === void 0 ? void 0 : data.widget) === null || _u === void 0 ? void 0 : _u.id]);
283
+ // Session restore
221
284
  const restoredRef = (0, react_1.useRef)(false);
222
285
  (0, react_1.useEffect)(() => {
223
- var _a, _b, _c, _d, _e, _f;
286
+ var _a, _b, _c;
224
287
  if (!(data === null || data === void 0 ? void 0 : data.widget) || restoredRef.current)
225
288
  return;
226
289
  const w = data.widget;
227
290
  setMessageSoundEnabledState((0, messageSound_1.getMessageSoundEnabled)(w.id));
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();
229
- let viewerIsBlocked = w.viewerBlocked === true;
230
- if (!viewerIsBlocked && uidForBlock) {
231
- const rec = [...data.developers, ...data.users].find(x => x.uid === uidForBlock);
232
- viewerIsBlocked = (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
233
- }
234
- if (viewerIsBlocked) {
235
- clearChat();
236
- setScreen('home');
237
- setActiveTab('home');
238
- setViewingTicketId(null);
239
- restoredRef.current = true;
240
- return;
241
- }
242
291
  const p = (0, widgetSession_1.loadSession)(w.id);
243
292
  if (p) {
244
293
  setScreen(p.screen);
245
294
  setActiveTab(p.activeTab);
246
295
  setUserListCtx(p.userListCtx);
247
- setViewingTicketId((_c = p.viewingTicketId) !== null && _c !== void 0 ? _c : null);
248
- setChatReturnCtx((_d = p.chatReturnCtx) !== null && _d !== void 0 ? _d : 'conversation');
296
+ setViewingTicketId((_a = p.viewingTicketId) !== null && _a !== void 0 ? _a : null);
297
+ setChatReturnCtx((_b = p.chatReturnCtx) !== null && _b !== void 0 ? _b : 'conversation');
249
298
  if (p.activeUserUid) {
250
- const pid = (_e = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _e === void 0 ? void 0 : _e.trim();
251
- const pool = pid
252
- ? [...data.developers, ...data.users].filter(u => u.project === pid)
253
- : [...data.developers, ...data.users];
299
+ const pool = [...data.developers, ...data.users];
254
300
  const u = pool.find(x => x.uid === p.activeUserUid);
255
301
  if (u) {
256
- const hist = Array.isArray(p.messages) && p.messages.length
257
- ? p.messages
258
- : ((_f = data.sampleChats[u.uid]) !== null && _f !== void 0 ? _f : []);
302
+ const hist = Array.isArray(p.messages) && p.messages.length ? p.messages : ((_c = data.sampleChats[u.uid]) !== null && _c !== void 0 ? _c : []);
259
303
  selectUser(u, hist);
260
- // Rejoin socket room
261
304
  const roomId = [u.uid, viewerUidForSocket].sort().join('_');
262
305
  joinRoom(roomId);
263
306
  }
264
307
  }
265
308
  }
266
309
  restoredRef.current = true;
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)(() => {
269
- var _a, _b;
270
- if (!(data === null || data === void 0 ? void 0 : data.widget))
271
- return;
272
- const w = data.widget;
273
- const uid = (_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();
274
- let blocked = w.viewerBlocked === true;
275
- if (!blocked && uid) {
276
- const rec = [...data.developers, ...data.users].find(x => x.uid === uid);
277
- blocked = (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
278
- }
279
- if (!blocked)
280
- return;
281
- clearChat();
282
- setScreen('home');
283
- setActiveTab('home');
284
- setViewingTicketId(null);
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]);
310
+ }, [data, selectUser, viewer === null || viewer === void 0 ? void 0 : viewer.projectId, viewer === null || viewer === void 0 ? void 0 : viewer.uid, viewerUidForSocket, joinRoom]);
286
311
  (0, react_1.useEffect)(() => {
287
312
  if (!(data === null || data === void 0 ? void 0 : data.widget))
288
313
  return;
289
314
  persistWidgetState();
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]);
315
+ }, [(_v = data === null || data === void 0 ? void 0 : data.widget) === null || _v === void 0 ? void 0 : _v.id, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx, persistWidgetState]);
293
316
  const toggleMessageSound = (0, react_1.useCallback)((enabled) => {
294
317
  const w = data === null || data === void 0 ? void 0 : data.widget;
295
318
  if (!w)
@@ -297,7 +320,7 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
297
320
  (0, messageSound_1.setMessageSoundEnabled)(w.id, enabled);
298
321
  setMessageSoundEnabledState(enabled);
299
322
  }, [data === null || data === void 0 ? void 0 : data.widget]);
300
- /* ── Navigation ─────────────────────────────────────────────────────── */
323
+ // ── Navigation ────────────────────────────────────────────────────────────
301
324
  const handleCardClick = (0, react_1.useCallback)((ctx, options) => {
302
325
  setListEntranceAnimation(!!(options === null || options === void 0 ? void 0 : options.fromMenu));
303
326
  if (ctx === 'ticket') {
@@ -333,7 +356,6 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
333
356
  const history = (_a = data === null || data === void 0 ? void 0 : data.sampleChats[user.uid]) !== null && _a !== void 0 ? _a : [];
334
357
  selectUser(user, history);
335
358
  setScreen('chat');
336
- // Join socket room for real-time messages
337
359
  const roomId = [user.uid, viewerUidForSocket].sort().join('_');
338
360
  joinRoom(roomId);
339
361
  setRecentChats(prev => {
@@ -370,7 +392,7 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
370
392
  const t = window.setTimeout(() => setListEntranceAnimation(false), 520);
371
393
  return () => window.clearTimeout(t);
372
394
  }, [listEntranceAnimation]);
373
- /* ── Block/Unblock ──────────────────────────────────────────────────── */
395
+ // ── Block / Unblock ───────────────────────────────────────────────────────
374
396
  const handleBlock = (0, react_1.useCallback)(() => {
375
397
  if (!activeUser)
376
398
  return;
@@ -384,44 +406,34 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
384
406
  setBlockedUids(prev => prev.filter(id => id !== uid));
385
407
  emitUnblock(uid);
386
408
  }, [emitUnblock]);
387
- /* ── Tickets ────────────────────────────────────────────────────────── */
409
+ // ── Tickets ───────────────────────────────────────────────────────────────
388
410
  const handleRaiseTicket = (0, react_1.useCallback)((title, desc, priority) => {
389
411
  const t = {
390
- id: `TKT-${String(Date.now()).slice(-4)}`,
391
- title, description: desc, status: 'open', priority,
392
- createdAt: new Date().toISOString(),
393
- updatedAt: new Date().toISOString(),
394
- assignedTo: null,
412
+ id: `TKT-${String(Date.now()).slice(-4)}`, title, description: desc, status: 'open', priority,
413
+ createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), assignedTo: null,
395
414
  };
396
415
  setTickets(prev => [...prev, t]);
397
416
  setViewingTicketId(t.id);
398
417
  setScreen('ticket-detail');
399
418
  setActiveTab('tickets');
400
- }, []);
401
- /* ── Pause sync back into recent chats ─────────────────────────────── */
419
+ emitTicketCreate({ title, description: desc, priority, createdBy: viewerUidForSocket });
420
+ }, [emitTicketCreate, viewerUidForSocket]);
421
+ // ── Pause sync ────────────────────────────────────────────────────────────
402
422
  const handleTogglePause = (0, react_1.useCallback)(() => {
403
423
  _togglePause();
404
- if (activeUser) {
424
+ if (activeUser)
405
425
  setRecentChats(prev => prev.map(r => r.user.uid === activeUser.uid ? Object.assign(Object.assign({}, r), { isPaused: !isPaused }) : r));
406
- }
407
426
  }, [_togglePause, activeUser, isPaused]);
408
- /* ── Call ───────────────────────────────────────────────────────────── */
427
+ // ── Call ──────────────────────────────────────────────────────────────────
409
428
  const handleStartCall = (0, react_1.useCallback)((withVideo) => {
410
429
  if (!activeUser)
411
430
  return;
412
431
  _startCall(activeUser, withVideo);
413
432
  setScreen('call');
414
433
  }, [activeUser, _startCall]);
415
- const handleEndCall = (0, react_1.useCallback)(() => {
416
- _endCall();
417
- setCallMinimized(false);
418
- setScreen('chat');
419
- }, [_endCall]);
420
- const minimizeCall = (0, react_1.useCallback)(() => {
421
- setCallMinimized(true);
422
- closeDrawer();
423
- }, [closeDrawer]);
424
- /* ── Derived config (must be declared before callbacks that use it) ── */
434
+ const handleEndCall = (0, react_1.useCallback)(() => { _endCall(); setCallMinimized(false); setScreen('chat'); }, [_endCall]);
435
+ const minimizeCall = (0, react_1.useCallback)(() => { setCallMinimized(true); closeDrawer(); }, [closeDrawer]);
436
+ // ── Transfer ──────────────────────────────────────────────────────────────
425
437
  const widgetConfig = (0, react_1.useMemo)(() => {
426
438
  var _a;
427
439
  if (!(data === null || data === void 0 ? void 0 : data.widget))
@@ -436,7 +448,6 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
436
448
  }
437
449
  return w;
438
450
  }, [data === null || data === void 0 ? void 0 : data.widget, viewer]);
439
- /* ── Transfer (developer feature) ──────────────────────────────────── */
440
451
  const handleTransferToDeveloper = (0, react_1.useCallback)((dev) => {
441
452
  var _a;
442
453
  if (!activeUser || !widgetConfig)
@@ -444,27 +455,21 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
444
455
  const agent = ((_a = widgetConfig.viewerName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Agent';
445
456
  const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
446
457
  const transferNote = {
447
- id: `tr_${Date.now()}_${Math.random().toString(36).slice(2)}`,
448
- senderId: 'me',
449
- receiverId: dev.uid,
458
+ id: `tr_${Date.now()}`, senderId: 'me', receiverId: dev.uid,
450
459
  text: `— ${agent} transferred this conversation from ${activeUser.name} to ${dev.name} —`,
451
- timestamp: new Date().toISOString(),
452
- type: 'text',
453
- status: 'sent',
460
+ timestamp: new Date().toISOString(), type: 'text', status: 'sent',
454
461
  };
455
462
  emitTransfer(roomId, dev.uid, transferNote.text);
456
463
  selectUser(dev, [...messages, transferNote]);
457
464
  }, [activeUser, messages, selectUser, widgetConfig, viewerUidForSocket, emitTransfer]);
458
- /* ── Add participant (developer feature) ────────────────────────────── */
459
465
  const handleAddParticipant = (0, react_1.useCallback)((uid) => {
460
466
  if (!activeUser)
461
467
  return;
462
- const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
463
- emitAddParticipant(roomId, uid);
468
+ emitAddParticipant([activeUser.uid, viewerUidForSocket].sort().join('_'), uid);
464
469
  }, [activeUser, viewerUidForSocket, emitAddParticipant]);
465
- /* ── Derived ────────────────────────────────────────────────────────── */
470
+ // ── Derived ───────────────────────────────────────────────────────────────
466
471
  const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
467
- const primaryColor = theme.primaryColor;
472
+ const primaryColor = headerBg;
468
473
  const allUsers = (0, react_1.useMemo)(() => {
469
474
  var _a, _b;
470
475
  if (!data)
@@ -491,9 +496,8 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
491
496
  const viewerUid = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerUid;
492
497
  const filteredUsers = screen === 'user-list'
493
498
  ? allUsers.filter(u => {
494
- if (userListCtx === 'support') {
499
+ if (userListCtx === 'support')
495
500
  return viewerIsDev ? u.type === 'user' : u.type === 'developer';
496
- }
497
501
  if (viewerIsDev)
498
502
  return u.type === 'developer' && u.uid !== viewerUid;
499
503
  return u.type === 'user';
@@ -501,116 +505,149 @@ const ChatWidget = ({ theme: localTheme, viewer }) => {
501
505
  : [];
502
506
  const otherDevelopers = (0, react_1.useMemo)(() => allUsers.filter(u => u.type === 'developer' && u.uid !== viewerUid), [allUsers, viewerUid]);
503
507
  const blockedUsers = allUsers.filter(u => blockedUids.includes(u.uid));
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]);
508
+ const totalUnread = (0, react_1.useMemo)(() => recentChats.reduce((s, c) => { var _a; return s + Math.max(0, (_a = c.unread) !== null && _a !== void 0 ? _a : 0); }, 0), [recentChats]);
509
+ // ── Panel geometry from display mode ──────────────────────────────────────
510
+ const geo = (0, react_1.useMemo)(() => { var _a; return getPanelGeometry(displayMode, (_a = theme.buttonPosition) !== null && _a !== void 0 ? _a : 'bottom-right', ajaxterSettings === null || ajaxterSettings === void 0 ? void 0 : ajaxterSettings.maximized); }, [displayMode, theme.buttonPosition, ajaxterSettings === null || ajaxterSettings === void 0 ? void 0 : ajaxterSettings.maximized]);
511
+ // Button geometry from Ajaxter minimized config
512
+ const btnStyle = (0, react_1.useMemo)(() => {
513
+ var _a, _b, _c, _d, _e, _f;
514
+ const pos = theme.buttonPosition === 'bottom-left' ? { left: 24, right: 'auto' } : { right: 24, left: 'auto' };
515
+ if (btnType === 'round') {
516
+ return Object.assign(Object.assign({ position: 'fixed', bottom: 24, zIndex: 9999 }, pos), { width: 56, height: 56, borderRadius: '50%', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: theme.buttonColor, color: theme.buttonTextColor, border: 'none', cursor: 'pointer', boxShadow: `0 8px 28px ${theme.buttonColor}55`, animation: 'cw-btnPop 0.4s cubic-bezier(0.34,1.56,0.64,1)' });
517
+ }
518
+ // 'slide' type (pill)
519
+ const w = (_a = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.width) !== null && _a !== void 0 ? _a : 126;
520
+ const h = (_b = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.height) !== null && _b !== void 0 ? _b : 40;
521
+ return Object.assign(Object.assign({ position: 'fixed', bottom: 24, zIndex: 9999 }, pos), { width: w, height: h, borderRadius: `${(_c = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.borderRadiusTop) !== null && _c !== void 0 ? _c : 150}px ${(_d = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.borderRadiusBottom) !== null && _d !== void 0 ? _d : 140}px ${(_e = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.borderRadiusBottom) !== null && _e !== void 0 ? _e : 140}px ${(_f = minimizedDesktop === null || minimizedDesktop === void 0 ? void 0 : minimizedDesktop.borderRadiusTop) !== null && _f !== void 0 ? _f : 150}px`, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '0 16px', backgroundColor: theme.buttonColor, color: theme.buttonTextColor, border: 'none', cursor: 'pointer', fontSize: 13, fontWeight: 700, boxShadow: `0 8px 28px ${theme.buttonColor}55`, animation: 'cw-btnPop 0.4s cubic-bezier(0.34,1.56,0.64,1)' });
522
+ }, [btnType, theme, minimizedDesktop]);
505
523
  const posStyle = theme.buttonPosition === 'bottom-left'
506
- ? { left: 24, right: 'auto' }
507
- : { right: 24, left: 'auto' };
508
- const drawerPosStyle = theme.buttonPosition === 'bottom-left'
509
- ? { left: 0, borderTopLeftRadius: 0, borderBottomLeftRadius: 0, borderTopRightRadius: 16, borderBottomRightRadius: 16 }
510
- : { right: 0, borderTopLeftRadius: 0, borderBottomLeftRadius: 0 };
524
+ ? { left: 24, right: 'auto' } : { right: 24, left: 'auto' };
511
525
  if (!mounted)
512
526
  return null;
527
+ // Inline mode — always "open", no floating button
528
+ if (displayMode === 'inline') {
529
+ return (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { primaryColor: primaryColor },
530
+ react_1.default.createElement("style", null, GLOBAL_STYLES(agentBg, visitorBg, headerBg)),
531
+ widgetConfig && !cfgLoading && !cfgError && (react_1.default.createElement(WidgetContent, Object.assign({}, contentProps(), { widgetConfig: widgetConfig, primaryColor: primaryColor, agentBg: agentBg, visitorBg: visitorBg, panelStyle: geo.panel, isClosable: false })))));
532
+ }
533
+ // Helper to collect all props for WidgetContent
534
+ function contentProps() {
535
+ return {
536
+ screen, activeTab, userListCtx, viewingTicketId, listEntranceAnimation,
537
+ tickets, recentChats, blockedUsers, blockedUids, filteredUsers,
538
+ otherDevelopers, allUsers, messages, activeUser, isPaused, isReported,
539
+ isBlocked, callSession, localVideoRef, remoteVideoRef,
540
+ effectiveViewerBlocked, permissionsOk, messageSoundEnabled, typingUsers,
541
+ viewerIsDev, apiKey, totalUnread,
542
+ onClose: closeDrawer,
543
+ onTabChange: handleTabChange, onCardClick: handleCardClick,
544
+ onNavFromMenu: handleNavFromMenu, onSelectUser: handleSelectUser,
545
+ onBackFromChat: handleBackFromChat, onOpenTicket: handleOpenTicket,
546
+ onSend: sendMessage, onTogglePause: handleTogglePause, onReport: _reportChat,
547
+ onBlock: handleBlock, onUnblock: handleUnblock,
548
+ onStartCall: handleStartCall, onEndCall: handleEndCall, onMinimizeCall: minimizeCall,
549
+ onToggleMute: toggleMute, onToggleCamera: toggleCamera,
550
+ onRaiseTicket: handleRaiseTicket, onTransfer: handleTransferToDeveloper,
551
+ onAddParticipant: handleAddParticipant, onToggleMessageSound: toggleMessageSound,
552
+ setScreen, setActiveTab, setViewingTicketId, setListEntranceAnimation,
553
+ listCtxForUser,
554
+ };
555
+ }
513
556
  return (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { primaryColor: primaryColor },
514
- react_1.default.createElement("style", null, `
515
- @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
516
- .cw-root * { box-sizing: border-box; font-family: 'DM Sans', 'Segoe UI', sans-serif; }
517
- @keyframes cw-slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
518
- @keyframes cw-slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
519
- @keyframes cw-slideInLeft { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
520
- @keyframes cw-slideOutLeft { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-100%); opacity: 0; } }
521
- @keyframes cw-fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
522
- @keyframes cw-slideIn { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: translateX(0); } }
523
- @keyframes cw-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
524
- @keyframes cw-btnPop { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
525
- @keyframes spin { to { transform: rotate(360deg); } }
526
- .cw-scroll::-webkit-scrollbar { width: 4px; }
527
- .cw-scroll::-webkit-scrollbar-track { background: transparent; }
528
- .cw-scroll::-webkit-scrollbar-thumb { background: #e0e0e0; border-radius: 4px; }
529
- .cw-drawer-enter { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideInLeft' : 'cw-slideInRight'} 0.32s cubic-bezier(0.22,1,0.36,1) both; }
530
- .cw-drawer-exit { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideOutLeft' : 'cw-slideOutRight'} 0.28s cubic-bezier(0.55,0,1,0.45) both; }
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')),
557
+ react_1.default.createElement("style", null, GLOBAL_STYLES(agentBg, visitorBg, headerBg)),
558
+ socketStatus !== 'connected' && socketStatus !== 'disconnected' && isOpen && (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 }) }, socketStatus === 'connecting' ? '● Connecting…' : '● Offline')),
535
559
  !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' }) },
560
+ !isOpen && (react_1.default.createElement("button", { className: "cw-root", type: "button", onClick: openDrawer, "aria-label": totalUnread > 0 ? `${theme.buttonLabel}, ${totalUnread} unread` : theme.buttonLabel, style: btnStyle, onMouseEnter: e => { e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)'; }, onMouseLeave: e => { e.currentTarget.style.transform = 'scale(1)'; } },
561
+ react_1.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none" },
562
+ 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" })),
563
+ btnType !== 'round' && react_1.default.createElement("span", null, theme.buttonLabel),
564
+ totalUnread > 0 && (react_1.default.createElement("span", { style: { position: 'absolute', top: -6, right: -6,
565
+ minWidth: 18, height: 18, padding: '0 4px', borderRadius: 999,
566
+ background: '#ef4444', color: '#fff', fontSize: 10, fontWeight: 800,
567
+ lineHeight: '18px', textAlign: 'center', border: '2px solid #fff' } }, totalUnread > 99 ? '99+' : totalUnread)))),
568
+ isOpen && geo.backdrop && (react_1.default.createElement("div", { "aria-hidden": true, style: Object.assign(Object.assign({}, geo.backdrop), { opacity: closing ? 0 : 1, transition: 'opacity 0.3s' }) })),
569
+ isOpen && (react_1.default.createElement("div", { className: `cw-root ${closing ? geo.exitClass : geo.enterClass}`, style: geo.panel },
562
570
  cfgLoading && (react_1.default.createElement("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 } },
563
571
  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
572
  react_1.default.createElement("p", { style: { fontSize: 14, color: '#7b8fa1' } }, "Loading chat\u2026"))),
565
573
  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
574
  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),
575
+ react_1.default.createElement("p", { style: { fontWeight: 700, color: '#1a2332' } }, "Could not load chat"),
576
+ react_1.default.createElement("p", { style: { fontSize: 13, color: '#7b8fa1' } }, cfgError),
569
577
  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
578
  !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 }))))))));
579
+ react_1.default.createElement(WidgetContent, Object.assign({}, contentProps(), { widgetConfig: widgetConfig, primaryColor: primaryColor, agentBg: agentBg, visitorBg: visitorBg, panelStyle: {}, isClosable: true }))))))));
609
580
  };
610
581
  exports.ChatWidget = ChatWidget;
611
582
  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',
614
- display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
615
- } }, children));
583
+ // ─── WidgetContent extracted so inline mode can reuse ───────────────────────
584
+ const WidgetContent = (props) => {
585
+ var _a;
586
+ const { screen, activeTab, userListCtx, viewingTicketId, listEntranceAnimation, tickets, recentChats, blockedUsers, filteredUsers, otherDevelopers, messages, activeUser, isPaused, isReported, isBlocked, callSession, localVideoRef, remoteVideoRef, effectiveViewerBlocked, permissionsOk, messageSoundEnabled, viewerIsDev, apiKey, widgetConfig, primaryColor, agentBg, visitorBg, isClosable, onClose, onTabChange, onCardClick, onNavFromMenu, onSelectUser, onBackFromChat, onOpenTicket, onSend, onTogglePause, onReport, onBlock, onUnblock, onStartCall, onEndCall, onMinimizeCall, onToggleMute, onToggleCamera, onRaiseTicket, onTransfer, onToggleMessageSound, setScreen, setActiveTab, setViewingTicketId, setListEntranceAnimation, listCtxForUser, } = props;
587
+ const config = widgetConfig;
588
+ const callInProgress = callSession.state === 'calling' || callSession.state === 'connected';
589
+ return (react_1.default.createElement(react_1.default.Fragment, null,
590
+ isClosable && screen !== 'chat' && screen !== 'call' && !effectiveViewerBlocked && (react_1.default.createElement("div", { style: { position: 'absolute', top: 12, right: 12, zIndex: 20 } },
591
+ react_1.default.createElement("button", { onClick: onClose, title: "Close", style: {
592
+ width: 26, height: 26, borderRadius: '50%', background: 'rgba(0,0,0,0.22)', border: 'none',
593
+ display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
594
+ } },
595
+ react_1.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none" },
596
+ react_1.default.createElement("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }))))),
597
+ config.status === 'MAINTENANCE' && react_1.default.createElement(MaintenanceView_1.MaintenanceView, { primaryColor: primaryColor }),
598
+ config.status === 'DISABLE' && (react_1.default.createElement("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 } },
599
+ react_1.default.createElement("div", { style: { fontSize: 40 } }, "\uD83D\uDD12"),
600
+ react_1.default.createElement("p", { style: { fontWeight: 700, color: '#1a2332' } }, "Chat is disabled"),
601
+ isClosable && react_1.default.createElement("button", { onClick: onClose, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 } }, "Close"))),
602
+ config.status === 'ACTIVE' && effectiveViewerBlocked && (react_1.default.createElement(ViewerBlockedScreen_1.ViewerBlockedScreen, { config: config, apiKey: apiKey, onClose: onClose })),
603
+ config.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (react_1.default.createElement(PermissionsGateScreen_1.PermissionsGateScreen, { primaryColor: primaryColor, widgetId: config.id, onGranted: () => { } })),
604
+ config.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (react_1.default.createElement("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' } },
605
+ screen === 'home' && (react_1.default.createElement(HomeScreen_1.HomeScreen, { config: config, apiKey: apiKey, onNavigate: onCardClick, onOpenTicket: onOpenTicket, tickets: tickets })),
606
+ screen === 'user-list' && (react_1.default.createElement(UserListScreen_1.UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_a = config.viewerType) !== null && _a !== void 0 ? _a : 'user', onBack: () => { setListEntranceAnimation(false); setScreen('home'); }, onSelectUser: onSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && config.viewerType !== 'developer', animateEntrance: listEntranceAnimation })),
607
+ screen === 'chat' && activeUser && (react_1.default.createElement(ChatScreen_1.ChatScreen, { activeUser: activeUser, messages: messages, config: config, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: onSend, onBack: onBackFromChat, onClose: onClose, onTogglePause: onTogglePause, onReport: onReport, onBlock: onBlock, onStartCall: onStartCall, onNavAction: onNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: onTransfer, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: onToggleMessageSound })),
608
+ screen === 'call' && callSession.peer && (react_1.default.createElement(CallScreen_1.CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: onEndCall, onToggleMute: onToggleMute, onToggleCamera: onToggleCamera, primaryColor: primaryColor, onMinimize: onMinimizeCall })),
609
+ screen === 'recent-chats' && (react_1.default.createElement(RecentChatsScreen_1.RecentChatsScreen, { chats: recentChats, config: config, onSelectChat: u => onSelectUser(u, listCtxForUser(u, viewerIsDev)), animateEntrance: listEntranceAnimation })),
610
+ screen === 'tickets' && (react_1.default.createElement(TicketScreen_1.TicketScreen, { tickets: tickets, config: config, onNewTicket: () => { setListEntranceAnimation(false); setScreen('ticket-new'); }, onSelectTicket: id => { setListEntranceAnimation(false); setViewingTicketId(id); setScreen('ticket-detail'); }, animateEntrance: listEntranceAnimation })),
611
+ screen === 'ticket-new' && (react_1.default.createElement(TicketFormScreen_1.TicketFormScreen, { config: config, onSubmit: onRaiseTicket, onCancel: () => setScreen('tickets') })),
612
+ screen === 'ticket-detail' && viewingTicketId && (() => {
613
+ const t = tickets.find(x => x.id === viewingTicketId);
614
+ return t ? (react_1.default.createElement(TicketDetailScreen_1.TicketDetailScreen, { ticket: t, config: config, onBack: () => { setViewingTicketId(null); setScreen('tickets'); } })) : null;
615
+ })(),
616
+ screen === 'block-list' && (react_1.default.createElement(BlockList_1.BlockListScreen, { blockedUsers: blockedUsers, config: config, onUnblock: onUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } })))),
617
+ config.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk &&
618
+ !['chat', 'call', 'user-list', 'block-list', 'ticket-detail', 'ticket-new'].includes(screen) && (react_1.default.createElement(BottomTabs_1.BottomTabs, { active: activeTab, onChange: onTabChange, primaryColor: primaryColor }))));
619
+ };
620
+ // ─── Global CSS ───────────────────────────────────────────────────────────────
621
+ function GLOBAL_STYLES(agentBg, visitorBg, headerBg) {
622
+ return `
623
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
624
+ .cw-root * { box-sizing:border-box; font-family:'DM Sans','Segoe UI',sans-serif; }
625
+ @keyframes spin { to { transform: rotate(360deg); } }
626
+ @keyframes cw-btnPop { from { transform:scale(0.8);opacity:0; } to { transform:scale(1);opacity:1; } }
627
+ @keyframes cw-fadeUp { from { opacity:0;transform:translateY(10px); } to { opacity:1;transform:translateY(0); } }
628
+ @keyframes cw-slideIn { from { opacity:0;transform:translateX(18px); } to { opacity:1;transform:translateX(0); } }
629
+ @keyframes cw-pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
630
+ @keyframes cw-slideInRight { from{transform:translateX(100%);opacity:0} to{transform:translateX(0);opacity:1} }
631
+ @keyframes cw-slideOutRight { from{transform:translateX(0);opacity:1} to{transform:translateX(100%);opacity:0} }
632
+ @keyframes cw-slideInLeft { from{transform:translateX(-100%);opacity:0} to{transform:translateX(0);opacity:1} }
633
+ @keyframes cw-slideOutLeft { from{transform:translateX(0);opacity:1} to{transform:translateX(-100%);opacity:0} }
634
+ @keyframes cw-popupEnter { from{transform:translate(-50%,-50%) scale(0.9);opacity:0} to{transform:translate(-50%,-50%) scale(1);opacity:1} }
635
+ @keyframes cw-popupExit { from{transform:translate(-50%,-50%) scale(1);opacity:1} to{transform:translate(-50%,-50%) scale(0.9);opacity:0} }
636
+ .cw-slideInRight { animation:cw-slideInRight 0.32s cubic-bezier(0.22,1,0.36,1) both; }
637
+ .cw-slideOutRight { animation:cw-slideOutRight 0.28s cubic-bezier(0.55,0,1,0.45) both; }
638
+ .cw-slideInLeft { animation:cw-slideInLeft 0.32s cubic-bezier(0.22,1,0.36,1) both; }
639
+ .cw-slideOutLeft { animation:cw-slideOutLeft 0.28s cubic-bezier(0.55,0,1,0.45) both; }
640
+ .cw-popup-enter { animation:cw-popupEnter 0.28s cubic-bezier(0.22,1,0.36,1) both; }
641
+ .cw-popup-exit { animation:cw-popupExit 0.22s cubic-bezier(0.55,0,1,0.45) both; }
642
+ .cw-scroll::-webkit-scrollbar { width:4px; }
643
+ .cw-scroll::-webkit-scrollbar-track { background:transparent; }
644
+ .cw-scroll::-webkit-scrollbar-thumb { background:#e0e0e0; border-radius:4px; }
645
+ /* Ajaxter theme vars */
646
+ :root {
647
+ --cw-agent-bg: ${agentBg};
648
+ --cw-visitor-bg: ${visitorBg};
649
+ --cw-header-bg: ${headerBg};
650
+ }
651
+ `;
652
+ }
616
653
  //# sourceMappingURL=ChatWidget.js.map