ajaxter-chat 3.0.17 → 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.
Files changed (146) hide show
  1. package/dist/components/BlockList/index.d.ts +1 -0
  2. package/dist/components/BlockList/index.d.ts.map +1 -0
  3. package/dist/components/BlockList/index.js +55 -28
  4. package/dist/components/BlockList/index.js.map +1 -0
  5. package/dist/components/CallScreen/index.d.ts +1 -0
  6. package/dist/components/CallScreen/index.d.ts.map +1 -0
  7. package/dist/components/CallScreen/index.js +107 -39
  8. package/dist/components/CallScreen/index.js.map +1 -0
  9. package/dist/components/ChatScreen/index.d.ts +1 -0
  10. package/dist/components/ChatScreen/index.d.ts.map +1 -0
  11. package/dist/components/ChatScreen/index.js +493 -294
  12. package/dist/components/ChatScreen/index.js.map +1 -0
  13. package/dist/components/ChatWidget.d.ts +1 -0
  14. package/dist/components/ChatWidget.d.ts.map +1 -0
  15. package/dist/components/ChatWidget.js +499 -367
  16. package/dist/components/ChatWidget.js.map +1 -0
  17. package/dist/components/EmojiPicker/index.d.ts +1 -0
  18. package/dist/components/EmojiPicker/index.d.ts.map +1 -0
  19. package/dist/components/EmojiPicker/index.js +19 -7
  20. package/dist/components/EmojiPicker/index.js.map +1 -0
  21. package/dist/components/ErrorBoundary/index.d.ts +20 -0
  22. package/dist/components/ErrorBoundary/index.d.ts.map +1 -0
  23. package/dist/components/ErrorBoundary/index.js +76 -0
  24. package/dist/components/ErrorBoundary/index.js.map +1 -0
  25. package/dist/components/HomeScreen/index.d.ts +1 -0
  26. package/dist/components/HomeScreen/index.d.ts.map +1 -0
  27. package/dist/components/HomeScreen/index.js +236 -158
  28. package/dist/components/HomeScreen/index.js.map +1 -0
  29. package/dist/components/MaintenanceView/index.d.ts +1 -0
  30. package/dist/components/MaintenanceView/index.d.ts.map +1 -0
  31. package/dist/components/MaintenanceView/index.js +28 -12
  32. package/dist/components/MaintenanceView/index.js.map +1 -0
  33. package/dist/components/MiniCallBar/index.d.ts +1 -0
  34. package/dist/components/MiniCallBar/index.d.ts.map +1 -0
  35. package/dist/components/MiniCallBar/index.js +85 -37
  36. package/dist/components/MiniCallBar/index.js.map +1 -0
  37. package/dist/components/PermissionsGateScreen/index.d.ts +1 -0
  38. package/dist/components/PermissionsGateScreen/index.d.ts.map +1 -0
  39. package/dist/components/PermissionsGateScreen/index.js +82 -28
  40. package/dist/components/PermissionsGateScreen/index.js.map +1 -0
  41. package/dist/components/RecentChatsScreen/index.d.ts +1 -0
  42. package/dist/components/RecentChatsScreen/index.d.ts.map +1 -0
  43. package/dist/components/RecentChatsScreen/index.js +79 -19
  44. package/dist/components/RecentChatsScreen/index.js.map +1 -0
  45. package/dist/components/SlideNavMenu.d.ts +1 -0
  46. package/dist/components/SlideNavMenu.d.ts.map +1 -0
  47. package/dist/components/SlideNavMenu.js +82 -63
  48. package/dist/components/SlideNavMenu.js.map +1 -0
  49. package/dist/components/Tabs/BottomTabs.d.ts +1 -0
  50. package/dist/components/Tabs/BottomTabs.d.ts.map +1 -0
  51. package/dist/components/Tabs/BottomTabs.js +34 -19
  52. package/dist/components/Tabs/BottomTabs.js.map +1 -0
  53. package/dist/components/TicketDetailScreen/index.d.ts +1 -0
  54. package/dist/components/TicketDetailScreen/index.d.ts.map +1 -0
  55. package/dist/components/TicketDetailScreen/index.js +66 -27
  56. package/dist/components/TicketDetailScreen/index.js.map +1 -0
  57. package/dist/components/TicketFormScreen/index.d.ts +1 -0
  58. package/dist/components/TicketFormScreen/index.d.ts.map +1 -0
  59. package/dist/components/TicketFormScreen/index.js +99 -49
  60. package/dist/components/TicketFormScreen/index.js.map +1 -0
  61. package/dist/components/TicketScreen/index.d.ts +1 -0
  62. package/dist/components/TicketScreen/index.d.ts.map +1 -0
  63. package/dist/components/TicketScreen/index.js +95 -26
  64. package/dist/components/TicketScreen/index.js.map +1 -0
  65. package/dist/components/UserListScreen/index.d.ts +1 -0
  66. package/dist/components/UserListScreen/index.d.ts.map +1 -0
  67. package/dist/components/UserListScreen/index.js +127 -53
  68. package/dist/components/UserListScreen/index.js.map +1 -0
  69. package/dist/components/ViewerBlockedScreen/index.d.ts +1 -0
  70. package/dist/components/ViewerBlockedScreen/index.d.ts.map +1 -0
  71. package/dist/components/ViewerBlockedScreen/index.js +113 -61
  72. package/dist/components/ViewerBlockedScreen/index.js.map +1 -0
  73. package/dist/config/index.d.ts +7 -3
  74. package/dist/config/index.d.ts.map +1 -0
  75. package/dist/config/index.js +73 -22
  76. package/dist/config/index.js.map +1 -0
  77. package/dist/hooks/useChat.d.ts +9 -1
  78. package/dist/hooks/useChat.d.ts.map +1 -0
  79. package/dist/hooks/useChat.js +60 -18
  80. package/dist/hooks/useChat.js.map +1 -0
  81. package/dist/hooks/useRemoteConfig.d.ts +1 -0
  82. package/dist/hooks/useRemoteConfig.d.ts.map +1 -0
  83. package/dist/hooks/useRemoteConfig.js +22 -15
  84. package/dist/hooks/useRemoteConfig.js.map +1 -0
  85. package/dist/hooks/useSocket.d.ts +59 -0
  86. package/dist/hooks/useSocket.d.ts.map +1 -0
  87. package/dist/hooks/useSocket.js +203 -0
  88. package/dist/hooks/useSocket.js.map +1 -0
  89. package/dist/hooks/useWebRTC.d.ts +10 -2
  90. package/dist/hooks/useWebRTC.d.ts.map +1 -0
  91. package/dist/hooks/useWebRTC.js +101 -69
  92. package/dist/hooks/useWebRTC.js.map +1 -0
  93. package/dist/index.d.ts +7 -1
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +67 -21
  96. package/dist/index.js.map +1 -0
  97. package/dist/types/index.d.ts +129 -48
  98. package/dist/types/index.d.ts.map +1 -0
  99. package/dist/types/index.js +4 -1
  100. package/dist/types/index.js.map +1 -0
  101. package/dist/utils/chat.d.ts +1 -0
  102. package/dist/utils/chat.d.ts.map +1 -0
  103. package/dist/utils/chat.js +17 -7
  104. package/dist/utils/chat.js.map +1 -0
  105. package/dist/utils/fileName.d.ts +1 -0
  106. package/dist/utils/fileName.d.ts.map +1 -0
  107. package/dist/utils/fileName.js +5 -1
  108. package/dist/utils/fileName.js.map +1 -0
  109. package/dist/utils/messageSound.d.ts +1 -0
  110. package/dist/utils/messageSound.d.ts.map +1 -0
  111. package/dist/utils/messageSound.js +9 -3
  112. package/dist/utils/messageSound.js.map +1 -0
  113. package/dist/utils/presenceStatus.d.ts +1 -0
  114. package/dist/utils/presenceStatus.d.ts.map +1 -0
  115. package/dist/utils/presenceStatus.js +11 -4
  116. package/dist/utils/presenceStatus.js.map +1 -0
  117. package/dist/utils/privacyConsent.d.ts +1 -0
  118. package/dist/utils/privacyConsent.d.ts.map +1 -0
  119. package/dist/utils/privacyConsent.js +9 -3
  120. package/dist/utils/privacyConsent.js.map +1 -0
  121. package/dist/utils/reenableRequest.d.ts +1 -0
  122. package/dist/utils/reenableRequest.d.ts.map +1 -0
  123. package/dist/utils/reenableRequest.js +5 -1
  124. package/dist/utils/reenableRequest.js.map +1 -0
  125. package/dist/utils/theme.d.ts +1 -0
  126. package/dist/utils/theme.d.ts.map +1 -0
  127. package/dist/utils/theme.js +10 -4
  128. package/dist/utils/theme.js.map +1 -0
  129. package/dist/utils/widgetPermissions.d.ts +1 -0
  130. package/dist/utils/widgetPermissions.d.ts.map +1 -0
  131. package/dist/utils/widgetPermissions.js +13 -5
  132. package/dist/utils/widgetPermissions.js.map +1 -0
  133. package/dist/utils/widgetSession.d.ts +1 -0
  134. package/dist/utils/widgetSession.d.ts.map +1 -0
  135. package/dist/utils/widgetSession.js +9 -3
  136. package/dist/utils/widgetSession.js.map +1 -0
  137. package/package.json +8 -4
  138. package/src/components/ChatWidget.tsx +643 -622
  139. package/src/components/ErrorBoundary/index.tsx +62 -0
  140. package/src/config/index.ts +87 -26
  141. package/src/hooks/useChat.ts +59 -12
  142. package/src/hooks/useRemoteConfig.ts +8 -3
  143. package/src/hooks/useSocket.ts +249 -0
  144. package/src/hooks/useWebRTC.ts +99 -64
  145. package/src/index.ts +14 -3
  146. package/src/types/index.ts +177 -143
@@ -1,223 +1,327 @@
1
+ "use strict";
1
2
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
4
- import { loadLocalConfig } from '../config';
5
- import { mergeTheme } from '../utils/theme';
6
- import { useRemoteConfig } from '../hooks/useRemoteConfig';
7
- import { useChat } from '../hooks/useChat';
8
- import { useWebRTC } from '../hooks/useWebRTC';
9
- import { saveSession, loadSession } from '../utils/widgetSession';
10
- import { playMessageSound, getMessageSoundEnabled, setMessageSoundEnabled } from '../utils/messageSound';
11
- import { HomeScreen } from './HomeScreen';
12
- import { UserListScreen } from './UserListScreen';
13
- import { ChatScreen } from './ChatScreen';
14
- import { RecentChatsScreen } from './RecentChatsScreen';
15
- import { TicketScreen } from './TicketScreen';
16
- import { TicketDetailScreen } from './TicketDetailScreen';
17
- import { TicketFormScreen } from './TicketFormScreen';
18
- import { BlockListScreen } from './BlockList';
19
- import { CallScreen } from './CallScreen';
20
- import { MiniCallBar } from './MiniCallBar';
21
- import { MaintenanceView } from './MaintenanceView';
22
- import { BottomTabs } from './Tabs/BottomTabs';
23
- import { ViewerBlockedScreen } from './ViewerBlockedScreen';
24
- import { PermissionsGateScreen } from './PermissionsGateScreen';
25
- import { hasStoredPermissionsGrant } from '../utils/widgetPermissions';
26
- export const ChatWidget = ({ theme: localTheme, viewer }) => {
27
- var _a, _b, _c, _d, _e;
28
- /* SSR guard */
29
- const [mounted, setMounted] = useState(false);
30
- useEffect(() => { setMounted(true); }, []);
31
- /* Env config */
32
- const { apiKey, widgetId } = loadLocalConfig();
33
- /* Remote config */
34
- const { data, loading: cfgLoading, error: cfgError } = useRemoteConfig(apiKey, widgetId);
35
- /* Merged theme — remote config overrides defaults, local prop overrides both */
36
- const theme = mergeTheme((data === null || data === void 0 ? void 0 : data.widget) ? { primaryColor: data.widget.primaryColor, buttonLabel: data.widget.buttonLabel, buttonPosition: data.widget.buttonPosition } : undefined, localTheme);
37
- /* Drawer open state */
38
- const [isOpen, setIsOpen] = useState(false);
39
- const [closing, setClosing] = useState(false); // for slide-out animation
40
- /** True when user hid the drawer during ringing/connected call; WebRTC session stays active. */
41
- const [callMinimized, setCallMinimized] = useState(false);
42
- /* Navigation */
43
- const [activeTab, setActiveTab] = useState('home');
44
- const [screen, setScreen] = useState('home');
45
- const [userListCtx, setUserListCtx] = useState('support');
46
- const [chatReturnCtx, setChatReturnCtx] = useState('conversation');
47
- const [viewingTicketId, setViewingTicketId] = useState(null);
48
- const [messageSoundEnabled, setMessageSoundEnabledState] = useState(true);
49
- /** Stagger list animation only when opening from home burger menu */
50
- const [listEntranceAnimation, setListEntranceAnimation] = useState(false);
51
- /** Microphone, geolocation, and screen capture granted for this tab */
52
- const [permissionsOk, setPermissionsOk] = useState(false);
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 : []);
57
- /* Sync remote data into local state once loaded */
58
- useEffect(() => {
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
+ 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
+ };
111
+ }
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
+ };
139
+ }
140
+ // ─── ChatWidget ───────────────────────────────────────────────────────────────
141
+ const ChatWidget = ({ theme: localTheme, viewer }) => {
142
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
143
+ const [mounted, setMounted] = (0, react_1.useState)(false);
144
+ (0, react_1.useEffect)(() => { setMounted(true); }, []);
145
+ const { apiKey, widgetId, socketUrl } = (0, config_1.loadLocalConfig)();
146
+ const { data, loading: cfgLoading, error: cfgError } = (0, useRemoteConfig_1.useRemoteConfig)(apiKey, widgetId);
147
+ const theme = (0, theme_1.mergeTheme)((data === null || data === void 0 ? void 0 : data.widget) ? {
148
+ primaryColor: data.widget.primaryColor,
149
+ buttonLabel: data.widget.buttonLabel,
150
+ buttonPosition: data.widget.buttonPosition,
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';
161
+ const [isOpen, setIsOpen] = (0, react_1.useState)(false);
162
+ const [closing, setClosing] = (0, react_1.useState)(false);
163
+ const [callMinimized, setCallMinimized] = (0, react_1.useState)(false);
164
+ const [activeTab, setActiveTab] = (0, react_1.useState)('home');
165
+ const [screen, setScreen] = (0, react_1.useState)('home');
166
+ const [userListCtx, setUserListCtx] = (0, react_1.useState)('support');
167
+ const [chatReturnCtx, setChatReturnCtx] = (0, react_1.useState)('conversation');
168
+ const [viewingTicketId, setViewingTicketId] = (0, react_1.useState)(null);
169
+ const [messageSoundEnabled, setMessageSoundEnabledState] = (0, react_1.useState)(true);
170
+ const [listEntranceAnimation, setListEntranceAnimation] = (0, react_1.useState)(false);
171
+ const [permissionsOk, setPermissionsOk] = (0, react_1.useState)(false);
172
+ const [socketStatus, setSocketStatus] = (0, react_1.useState)('disconnected');
173
+ const [typingUsers, setTypingUsers] = (0, react_1.useState)(new Set());
174
+ const [tickets, setTickets] = (0, react_1.useState)([]);
175
+ const [recentChats, setRecentChats] = (0, react_1.useState)([]);
176
+ const [blockedUids, setBlockedUids] = (0, react_1.useState)([]);
177
+ // Sync remote data once loaded
178
+ (0, react_1.useEffect)(() => {
59
179
  var _a, _b, _c, _d;
60
- if (data) {
61
- setTickets(data.sampleTickets);
62
- setBlockedUids(data.blockedUsers);
63
- const pid = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _a === void 0 ? void 0 : _a.trim();
64
- const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
65
- const usr = pid ? ((_c = data.users) !== null && _c !== void 0 ? _c : []).filter(u => u.project === pid) : ((_d = data.users) !== null && _d !== void 0 ? _d : []);
66
- const all = [...devs, ...usr];
67
- const recents = Object.entries(data.sampleChats).map(([uid, msgs]) => {
68
- const user = all.find(u => u.uid === uid);
69
- if (!user || msgs.length === 0)
70
- return null;
71
- const last = msgs[msgs.length - 1];
72
- return {
73
- id: `rc_${uid}`,
74
- user,
75
- lastMessage: last.text,
76
- lastTime: last.timestamp,
77
- unread: Math.floor(Math.random() * 3),
78
- isPaused: false,
79
- };
80
- }).filter(Boolean);
81
- setRecentChats(recents);
82
- }
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);
83
196
  }, [data, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
84
- /* Chat hook */
85
- const { messages, activeUser, isPaused, isReported, selectUser, sendMessage, togglePause, reportChat, clearChat, setMessages, } = useChat();
86
- /* WebRTC hook */
87
- const { session: callSession, localVideoRef, remoteVideoRef, startCall, endCall, toggleMute, toggleCamera } = useWebRTC();
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)({
200
+ widgetId,
201
+ viewerUid: viewerUidForSocket,
202
+ serverUrl: socketUrl,
203
+ onMessage: (msg) => {
204
+ receiveMessage(msg);
205
+ if (messageSoundEnabled && msg.senderId !== 'me')
206
+ (0, messageSound_1.playMessageSound)();
207
+ setRecentChats(prev => prev.map(r => r.user.uid === msg.senderId || r.user.uid === msg.receiverId
208
+ ? Object.assign(Object.assign({}, r), { lastMessage: msg.text, lastTime: msg.timestamp, unread: r.unread + 1 }) : r));
209
+ },
210
+ onMessageAck: ({ messageId, status }) => { updateMessageStatus(messageId, status); },
211
+ onChatPaused: (_roomId, paused) => {
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; });
220
+ },
221
+ onCallOffer: async (offer, fromUid, callId) => {
222
+ var _a, _b;
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 : [])];
224
+ const peer = allUsers.find(u => u.uid === fromUid);
225
+ if (peer) {
226
+ await acceptCall(offer, peer, callId);
227
+ setScreen('call');
228
+ setIsOpen(true);
229
+ }
230
+ },
231
+ onCallEnd: () => { _endCall(); },
232
+ onUserStatus: (uid, status) => {
233
+ setRecentChats(prev => prev.map(r => r.user.uid === uid ? Object.assign(Object.assign({}, r), { user: Object.assign(Object.assign({}, r.user), { status }) }) : r));
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),
242
+ });
243
+ (0, react_1.useEffect)(() => { setSocketStatus(_socketStatus); }, [_socketStatus]);
244
+ // ── Chat hook ─────────────────────────────────────────────────────────────
245
+ const { messages, activeUser, isPaused, isReported, selectUser, sendMessage, receiveMessage, updateMessageStatus, togglePause: _togglePause, reportChat: _reportChat, clearChat, setMessages, } = (0, useChat_1.useChat)([], {
246
+ onEmitMessage: (msg) => emitMessage(msg),
247
+ onEmitPause: (roomId, targetUid, paused) => emitPauseToggle(roomId, targetUid, paused),
248
+ onEmitReport: (roomId) => emitReport(roomId),
249
+ });
250
+ // ── WebRTC hook ───────────────────────────────────────────────────────────
251
+ const { session: callSession, localVideoRef, remoteVideoRef, startCall: _startCall, acceptCall, addIceCandidate, endCall: _endCall, toggleMute, toggleCamera, } = (0, useWebRTC_1.useWebRTC)({
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),
256
+ });
88
257
  const callInProgress = callSession.state === 'calling' || callSession.state === 'connected';
89
- useEffect(() => {
90
- if (!callInProgress)
91
- setCallMinimized(false);
92
- }, [callInProgress]);
93
- /* ── Drawer open/close with slide animation ───────────────────────────── */
94
- const openDrawer = () => {
95
- setClosing(false);
96
- setIsOpen(true);
97
- setCallMinimized(false);
98
- };
99
- const persistWidgetState = useCallback(() => {
258
+ (0, react_1.useEffect)(() => { if (!callInProgress)
259
+ setCallMinimized(false); }, [callInProgress]);
260
+ // ── Drawer open / close ───────────────────────────────────────────────────
261
+ const openDrawer = () => { setClosing(false); setIsOpen(true); setCallMinimized(false); };
262
+ const persistWidgetState = (0, react_1.useCallback)(() => {
100
263
  var _a;
101
264
  const w = data === null || data === void 0 ? void 0 : data.widget;
102
265
  if (!w)
103
266
  return;
104
- saveSession(w.id, {
105
- screen,
106
- activeTab,
107
- userListCtx,
108
- activeUserUid: (_a = activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid) !== null && _a !== void 0 ? _a : null,
109
- messages,
110
- viewingTicketId,
111
- chatReturnCtx,
112
- });
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 });
113
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]);
114
- const closeDrawer = useCallback(() => {
269
+ const closeDrawer = (0, react_1.useCallback)(() => {
115
270
  persistWidgetState();
271
+ if (displayMode === 'inline')
272
+ return; // inline is always open
116
273
  setClosing(true);
117
- setTimeout(() => {
118
- setIsOpen(false);
119
- setClosing(false);
120
- }, 300);
121
- }, [persistWidgetState]);
122
- useEffect(() => {
274
+ setTimeout(() => { setIsOpen(false); setClosing(false); }, 300);
275
+ }, [persistWidgetState, displayMode]);
276
+ (0, react_1.useEffect)(() => {
123
277
  var _a;
124
278
  const id = (_a = data === null || data === void 0 ? void 0 : data.widget) === null || _a === void 0 ? void 0 : _a.id;
125
279
  if (!id)
126
280
  return;
127
- setPermissionsOk(hasStoredPermissionsGrant(id));
128
- }, [(_c = data === null || data === void 0 ? void 0 : data.widget) === null || _c === void 0 ? void 0 : _c.id]);
129
- const restoredRef = useRef(false);
130
- useEffect(() => {
131
- var _a, _b, _c, _d, _e, _f;
281
+ setPermissionsOk((0, widgetPermissions_1.hasStoredPermissionsGrant)(id));
282
+ }, [(_u = data === null || data === void 0 ? void 0 : data.widget) === null || _u === void 0 ? void 0 : _u.id]);
283
+ // Session restore
284
+ const restoredRef = (0, react_1.useRef)(false);
285
+ (0, react_1.useEffect)(() => {
286
+ var _a, _b, _c;
132
287
  if (!(data === null || data === void 0 ? void 0 : data.widget) || restoredRef.current)
133
288
  return;
134
289
  const w = data.widget;
135
- setMessageSoundEnabledState(getMessageSoundEnabled(w.id));
136
- 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
- let viewerIsBlocked = w.viewerBlocked === true;
138
- if (!viewerIsBlocked && uidForBlock) {
139
- const rec = [...data.developers, ...data.users].find(x => x.uid === uidForBlock);
140
- viewerIsBlocked = (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
141
- }
142
- if (viewerIsBlocked) {
143
- clearChat();
144
- setScreen('home');
145
- setActiveTab('home');
146
- setViewingTicketId(null);
147
- restoredRef.current = true;
148
- return;
149
- }
150
- const p = loadSession(w.id);
290
+ setMessageSoundEnabledState((0, messageSound_1.getMessageSoundEnabled)(w.id));
291
+ const p = (0, widgetSession_1.loadSession)(w.id);
151
292
  if (p) {
152
293
  setScreen(p.screen);
153
294
  setActiveTab(p.activeTab);
154
295
  setUserListCtx(p.userListCtx);
155
- setViewingTicketId((_c = p.viewingTicketId) !== null && _c !== void 0 ? _c : null);
156
- 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');
157
298
  if (p.activeUserUid) {
158
- const pid = (_e = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _e === void 0 ? void 0 : _e.trim();
159
- const pool = pid
160
- ? [...data.developers, ...data.users].filter(u => u.project === pid)
161
- : [...data.developers, ...data.users];
299
+ const pool = [...data.developers, ...data.users];
162
300
  const u = pool.find(x => x.uid === p.activeUserUid);
163
301
  if (u) {
164
- const hist = Array.isArray(p.messages) && p.messages.length
165
- ? p.messages
166
- : ((_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 : []);
167
303
  selectUser(u, hist);
304
+ const roomId = [u.uid, viewerUidForSocket].sort().join('_');
305
+ joinRoom(roomId);
168
306
  }
169
307
  }
170
308
  }
171
309
  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(() => {
174
- var _a, _b;
175
- if (!(data === null || data === void 0 ? void 0 : data.widget))
176
- return;
177
- const w = data.widget;
178
- 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();
179
- let blocked = w.viewerBlocked === true;
180
- if (!blocked && uid) {
181
- const rec = [...data.developers, ...data.users].find(x => x.uid === uid);
182
- blocked = (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
183
- }
184
- if (!blocked)
185
- return;
186
- clearChat();
187
- setScreen('home');
188
- setActiveTab('home');
189
- setViewingTicketId(null);
190
- }, [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(() => {
310
+ }, [data, selectUser, viewer === null || viewer === void 0 ? void 0 : viewer.projectId, viewer === null || viewer === void 0 ? void 0 : viewer.uid, viewerUidForSocket, joinRoom]);
311
+ (0, react_1.useEffect)(() => {
192
312
  if (!(data === null || data === void 0 ? void 0 : data.widget))
193
313
  return;
194
314
  persistWidgetState();
195
- }, [(_d = data === null || data === void 0 ? void 0 : data.widget) === null || _d === void 0 ? void 0 : _d.id, screen, activeTab, userListCtx, activeUser === null || activeUser === void 0 ? void 0 : activeUser.uid, messages, viewingTicketId, chatReturnCtx, persistWidgetState]);
196
- const incomingSoundRef = useRef(0);
197
- useEffect(() => {
198
- incomingSoundRef.current = messages.length;
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) => {
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]);
316
+ const toggleMessageSound = (0, react_1.useCallback)((enabled) => {
213
317
  const w = data === null || data === void 0 ? void 0 : data.widget;
214
318
  if (!w)
215
319
  return;
216
- setMessageSoundEnabled(w.id, enabled);
320
+ (0, messageSound_1.setMessageSoundEnabled)(w.id, enabled);
217
321
  setMessageSoundEnabledState(enabled);
218
322
  }, [data === null || data === void 0 ? void 0 : data.widget]);
219
- /* ── Navigation ──────────────────────────────────────────────────────── */
220
- const handleCardClick = useCallback((ctx, options) => {
323
+ // ── Navigation ────────────────────────────────────────────────────────────
324
+ const handleCardClick = (0, react_1.useCallback)((ctx, options) => {
221
325
  setListEntranceAnimation(!!(options === null || options === void 0 ? void 0 : options.fromMenu));
222
326
  if (ctx === 'ticket') {
223
327
  setActiveTab('tickets');
@@ -228,7 +332,7 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
228
332
  setScreen('user-list');
229
333
  }
230
334
  }, []);
231
- const handleNavFromMenu = useCallback((ctx) => {
335
+ const handleNavFromMenu = (0, react_1.useCallback)((ctx) => {
232
336
  setListEntranceAnimation(false);
233
337
  clearChat();
234
338
  if (ctx === 'ticket') {
@@ -240,100 +344,97 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
240
344
  setScreen('user-list');
241
345
  }
242
346
  }, [clearChat]);
243
- const listCtxForUser = useCallback((user, viewerIsDev) => {
347
+ const listCtxForUser = (0, react_1.useCallback)((user, viewerIsDev) => {
244
348
  if (viewerIsDev)
245
349
  return user.type === 'user' ? 'support' : 'conversation';
246
350
  return user.type === 'developer' ? 'support' : 'conversation';
247
351
  }, []);
248
- const handleSelectUser = useCallback((user, returnCtxOverride) => {
352
+ const handleSelectUser = (0, react_1.useCallback)((user, returnCtxOverride) => {
249
353
  var _a;
250
354
  setListEntranceAnimation(false);
251
355
  setChatReturnCtx(returnCtxOverride !== null && returnCtxOverride !== void 0 ? returnCtxOverride : userListCtx);
252
356
  const history = (_a = data === null || data === void 0 ? void 0 : data.sampleChats[user.uid]) !== null && _a !== void 0 ? _a : [];
253
357
  selectUser(user, history);
254
358
  setScreen('chat');
359
+ const roomId = [user.uid, viewerUidForSocket].sort().join('_');
360
+ joinRoom(roomId);
255
361
  setRecentChats(prev => {
256
362
  const exists = prev.find(r => r.user.uid === user.uid);
257
363
  if (exists)
258
- return prev;
364
+ return prev.map(r => r.user.uid === user.uid ? Object.assign(Object.assign({}, r), { unread: 0 }) : r);
259
365
  return [{ id: `rc_${user.uid}`, user, lastMessage: '', lastTime: new Date().toISOString(), unread: 0, isPaused: false }, ...prev];
260
366
  });
261
- }, [data, selectUser, userListCtx]);
262
- const handleBackFromChat = useCallback(() => {
367
+ }, [data, selectUser, userListCtx, viewerUidForSocket, joinRoom]);
368
+ const handleBackFromChat = (0, react_1.useCallback)(() => {
263
369
  setListEntranceAnimation(false);
370
+ if (activeUser) {
371
+ const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
372
+ leaveRoom(roomId);
373
+ }
264
374
  clearChat();
265
375
  setUserListCtx(chatReturnCtx);
266
376
  setScreen('user-list');
267
- }, [clearChat, chatReturnCtx]);
268
- const handleOpenTicket = useCallback((id) => {
377
+ }, [clearChat, chatReturnCtx, activeUser, viewerUidForSocket, leaveRoom]);
378
+ const handleOpenTicket = (0, react_1.useCallback)((id) => {
269
379
  setListEntranceAnimation(false);
270
380
  setViewingTicketId(id);
271
381
  setScreen('ticket-detail');
272
382
  setActiveTab('tickets');
273
383
  }, []);
274
- const handleTabChange = useCallback((tab) => {
384
+ const handleTabChange = (0, react_1.useCallback)((tab) => {
275
385
  setListEntranceAnimation(false);
276
386
  setActiveTab(tab);
277
387
  setScreen(tab === 'home' ? 'home' : tab === 'chats' ? 'recent-chats' : 'tickets');
278
388
  }, []);
279
- useEffect(() => {
389
+ (0, react_1.useEffect)(() => {
280
390
  if (!listEntranceAnimation)
281
391
  return;
282
392
  const t = window.setTimeout(() => setListEntranceAnimation(false), 520);
283
393
  return () => window.clearTimeout(t);
284
394
  }, [listEntranceAnimation]);
285
- /* ── Block/Unblock ───────────────────────────────────────────────────── */
286
- const handleBlock = useCallback(() => {
395
+ // ── Block / Unblock ───────────────────────────────────────────────────────
396
+ const handleBlock = (0, react_1.useCallback)(() => {
287
397
  if (!activeUser)
288
398
  return;
289
399
  setBlockedUids(prev => [...prev, activeUser.uid]);
400
+ emitBlock(activeUser.uid);
290
401
  clearChat();
291
402
  setScreen('block-list');
292
403
  setActiveTab('home');
293
- }, [activeUser, clearChat]);
294
- const handleUnblock = useCallback((uid) => {
404
+ }, [activeUser, clearChat, emitBlock]);
405
+ const handleUnblock = (0, react_1.useCallback)((uid) => {
295
406
  setBlockedUids(prev => prev.filter(id => id !== uid));
296
- }, []);
297
- /* ── Tickets ─────────────────────────────────────────────────────────── */
298
- const handleRaiseTicket = useCallback((title, desc, priority) => {
407
+ emitUnblock(uid);
408
+ }, [emitUnblock]);
409
+ // ── Tickets ───────────────────────────────────────────────────────────────
410
+ const handleRaiseTicket = (0, react_1.useCallback)((title, desc, priority) => {
299
411
  const t = {
300
- id: `TKT-${String(Date.now()).slice(-4)}`,
301
- title, description: desc, status: 'open', priority,
302
- createdAt: new Date().toISOString(),
303
- updatedAt: new Date().toISOString(),
304
- 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,
305
414
  };
306
415
  setTickets(prev => [...prev, t]);
307
416
  setViewingTicketId(t.id);
308
417
  setScreen('ticket-detail');
309
418
  setActiveTab('tickets');
310
- }, []);
311
- /* ── Pause sync back into recent chats ──────────────────────────────── */
312
- const handleTogglePause = useCallback(() => {
313
- togglePause();
314
- if (activeUser) {
419
+ emitTicketCreate({ title, description: desc, priority, createdBy: viewerUidForSocket });
420
+ }, [emitTicketCreate, viewerUidForSocket]);
421
+ // ── Pause sync ────────────────────────────────────────────────────────────
422
+ const handleTogglePause = (0, react_1.useCallback)(() => {
423
+ _togglePause();
424
+ if (activeUser)
315
425
  setRecentChats(prev => prev.map(r => r.user.uid === activeUser.uid ? Object.assign(Object.assign({}, r), { isPaused: !isPaused }) : r));
316
- }
317
- }, [togglePause, activeUser, isPaused]);
318
- /* ── Call ────────────────────────────────────────────────────────────── */
319
- const handleStartCall = useCallback((withVideo) => {
426
+ }, [_togglePause, activeUser, isPaused]);
427
+ // ── Call ──────────────────────────────────────────────────────────────────
428
+ const handleStartCall = (0, react_1.useCallback)((withVideo) => {
320
429
  if (!activeUser)
321
430
  return;
322
- startCall(activeUser, withVideo);
431
+ _startCall(activeUser, withVideo);
323
432
  setScreen('call');
324
- }, [activeUser, startCall]);
325
- const handleEndCall = useCallback(() => {
326
- endCall();
327
- setCallMinimized(false);
328
- setScreen('chat');
329
- }, [endCall]);
330
- const minimizeCall = useCallback(() => {
331
- setCallMinimized(true);
332
- closeDrawer();
333
- }, [closeDrawer]);
334
- /* ── Derived ─────────────────────────────────────────────────────────── */
335
- const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
336
- const widgetConfig = useMemo(() => {
433
+ }, [activeUser, _startCall]);
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 ──────────────────────────────────────────────────────────────
437
+ const widgetConfig = (0, react_1.useMemo)(() => {
337
438
  var _a;
338
439
  if (!(data === null || data === void 0 ? void 0 : data.widget))
339
440
  return undefined;
@@ -347,9 +448,29 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
347
448
  }
348
449
  return w;
349
450
  }, [data === null || data === void 0 ? void 0 : data.widget, viewer]);
350
- const primaryColor = theme.primaryColor;
351
- /** All developers are listed; only end-`user` rows are filtered by `viewer.projectId`. */
352
- const allUsers = useMemo(() => {
451
+ const handleTransferToDeveloper = (0, react_1.useCallback)((dev) => {
452
+ var _a;
453
+ if (!activeUser || !widgetConfig)
454
+ return;
455
+ const agent = ((_a = widgetConfig.viewerName) === null || _a === void 0 ? void 0 : _a.trim()) || 'Agent';
456
+ const roomId = [activeUser.uid, viewerUidForSocket].sort().join('_');
457
+ const transferNote = {
458
+ id: `tr_${Date.now()}`, senderId: 'me', receiverId: dev.uid,
459
+ text: `— ${agent} transferred this conversation from ${activeUser.name} to ${dev.name} —`,
460
+ timestamp: new Date().toISOString(), type: 'text', status: 'sent',
461
+ };
462
+ emitTransfer(roomId, dev.uid, transferNote.text);
463
+ selectUser(dev, [...messages, transferNote]);
464
+ }, [activeUser, messages, selectUser, widgetConfig, viewerUidForSocket, emitTransfer]);
465
+ const handleAddParticipant = (0, react_1.useCallback)((uid) => {
466
+ if (!activeUser)
467
+ return;
468
+ emitAddParticipant([activeUser.uid, viewerUidForSocket].sort().join('_'), uid);
469
+ }, [activeUser, viewerUidForSocket, emitAddParticipant]);
470
+ // ── Derived ───────────────────────────────────────────────────────────────
471
+ const isBlocked = activeUser ? blockedUids.includes(activeUser.uid) : false;
472
+ const primaryColor = headerBg;
473
+ const allUsers = (0, react_1.useMemo)(() => {
353
474
  var _a, _b;
354
475
  if (!data)
355
476
  return [];
@@ -357,10 +478,9 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
357
478
  const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
358
479
  if (!pid)
359
480
  return [...devs, ...data.users];
360
- const usersInProject = data.users.filter(u => u.project === pid);
361
- return [...devs, ...usersInProject];
481
+ return [...devs, ...data.users.filter(u => u.project === pid)];
362
482
  }, [data, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
363
- const effectiveViewerBlocked = useMemo(() => {
483
+ const effectiveViewerBlocked = (0, react_1.useMemo)(() => {
364
484
  var _a, _b;
365
485
  if (!widgetConfig)
366
486
  return false;
@@ -376,146 +496,158 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
376
496
  const viewerUid = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerUid;
377
497
  const filteredUsers = screen === 'user-list'
378
498
  ? allUsers.filter(u => {
379
- if (userListCtx === 'support') {
380
- if (viewerIsDev)
381
- return u.type === 'user';
382
- return u.type === 'developer';
383
- }
384
- if (viewerIsDev) {
499
+ if (userListCtx === 'support')
500
+ return viewerIsDev ? u.type === 'user' : u.type === 'developer';
501
+ if (viewerIsDev)
385
502
  return u.type === 'developer' && u.uid !== viewerUid;
386
- }
387
503
  return u.type === 'user';
388
504
  })
389
505
  : [];
390
- const otherDevelopers = useMemo(() => allUsers.filter(u => u.type === 'developer' && u.uid !== viewerUid), [allUsers, viewerUid]);
506
+ const otherDevelopers = (0, react_1.useMemo)(() => allUsers.filter(u => u.type === 'developer' && u.uid !== viewerUid), [allUsers, viewerUid]);
391
507
  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 */
410
- const posStyle = theme.buttonPosition === 'bottom-left'
411
- ? { left: 24, right: 'auto' }
412
- : { right: 24, left: 'auto' };
413
- /* No radius on top-left / bottom-left; left-docked panel keeps inner TR/BR curve */
414
- const drawerPosStyle = theme.buttonPosition === 'bottom-left'
415
- ? {
416
- left: 0,
417
- borderTopLeftRadius: 0,
418
- borderBottomLeftRadius: 0,
419
- borderTopRightRadius: 16,
420
- borderBottomRightRadius: 16,
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)' });
421
517
  }
422
- : {
423
- right: 0,
424
- borderTopLeftRadius: 0,
425
- borderBottomLeftRadius: 0,
426
- };
427
- /* ── Don't render until mounted (SSR safe) ──────────────────────────── */
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]);
523
+ const posStyle = theme.buttonPosition === 'bottom-left'
524
+ ? { left: 24, right: 'auto' } : { right: 24, left: 'auto' };
428
525
  if (!mounted)
429
526
  return null;
430
- return (_jsxs(_Fragment, { children: [_jsx("style", { children: `
431
- @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap');
432
-
433
- .cw-root * { box-sizing: border-box; font-family: 'DM Sans', 'Segoe UI', sans-serif; }
434
-
435
- @keyframes cw-slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
436
- @keyframes cw-slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
437
- @keyframes cw-slideInLeft { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
438
- @keyframes cw-slideOutLeft { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-100%); opacity: 0; } }
439
- @keyframes cw-fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
440
- @keyframes cw-slideIn { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: translateX(0); } }
441
- @keyframes cw-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
442
- @keyframes cw-btnPop { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
443
-
444
- .cw-scroll::-webkit-scrollbar { width: 4px; }
445
- .cw-scroll::-webkit-scrollbar-track { background: transparent; }
446
- .cw-scroll::-webkit-scrollbar-thumb { background: #e0e0e0; border-radius: 4px; }
447
-
448
- .cw-drawer-enter { animation: ${theme.buttonPosition === 'bottom-left' ? 'cw-slideInLeft' : 'cw-slideInRight'} 0.32s cubic-bezier(0.22,1,0.36,1) both; }
449
- .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
- width: 30%;
453
- max-width: 100vw;
454
- min-width: 0;
455
- }
456
- @media (max-width: 1024px) {
457
- .cw-drawer-panel { width: 100%; }
458
- }
459
- ` }), !isOpen && callMinimized && callInProgress && callSession.peer && (_jsx(MiniCallBar, { session: callSession, primaryColor: primaryColor, buttonPosition: theme.buttonPosition, onExpand: openDrawer, onEnd: handleEndCall })), !isOpen && (_jsxs("button", { className: "cw-root", type: "button", onClick: openDrawer, "aria-label": totalUnread > 0 ? `${theme.buttonLabel}, ${totalUnread} unread` : theme.buttonLabel, title: totalUnread > 0 ? `${totalUnread} unread message${totalUnread === 1 ? '' : 's'}` : theme.buttonLabel, style: Object.assign(Object.assign({ position: 'fixed', bottom: 24, zIndex: 9999 }, posStyle), { display: 'flex', alignItems: 'center', gap: 10, padding: '13px 22px', backgroundColor: theme.buttonColor, color: theme.buttonTextColor, border: 'none', borderRadius: 50, cursor: 'pointer', fontSize: 15, fontWeight: 700, boxShadow: `0 8px 28px ${theme.buttonColor}55`, animation: 'cw-btnPop 0.4s cubic-bezier(0.34,1.56,0.64,1)', transition: 'transform 0.2s, box-shadow 0.2s' }), onMouseEnter: e => {
460
- e.currentTarget.style.transform = 'scale(1.06) translateY(-2px)';
461
- e.currentTarget.style.boxShadow = `0 14px 36px ${theme.buttonColor}66`;
462
- }, onMouseLeave: e => {
463
- e.currentTarget.style.transform = 'scale(1)';
464
- e.currentTarget.style.boxShadow = `0 8px 28px ${theme.buttonColor}55`;
465
- }, children: [_jsxs("span", { style: { position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }, children: [_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z", stroke: theme.buttonTextColor, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), totalUnread > 0 && (_jsx("span", { style: {
466
- position: 'absolute',
467
- top: -8,
468
- right: -10,
469
- minWidth: 20,
470
- height: 20,
471
- padding: '0 5px',
472
- borderRadius: 999,
473
- background: '#ef4444',
474
- color: '#fff',
475
- fontSize: 11,
476
- fontWeight: 800,
477
- lineHeight: '20px',
478
- textAlign: 'center',
479
- border: '2px solid #fff',
480
- boxSizing: 'border-box',
481
- }, children: totalUnread > 99 ? '99+' : totalUnread }))] }), _jsx("span", { children: theme.buttonLabel })] })), isOpen && (_jsx("div", { "aria-hidden": true, style: {
482
- position: 'fixed', inset: 0, zIndex: 9997,
483
- backgroundColor: 'rgba(0,0,0,0.35)',
484
- opacity: closing ? 0 : 1,
485
- transition: 'opacity 0.3s',
486
- } })), isOpen && (_jsxs("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'
487
- ? '4px 0 40px rgba(0,0,0,0.18)'
488
- : '-4px 0 40px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }), children: [cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 }, children: [_jsx("div", { style: {
489
- width: 40, height: 40, borderRadius: '50%',
490
- border: `3px solid ${primaryColor}30`,
491
- borderTopColor: primaryColor,
492
- animation: 'spin 0.8s linear infinite',
493
- } }), _jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }), _jsx("p", { style: { fontSize: 14, color: '#7b8fa1' }, children: "Loading chat\u2026" })] })), cfgError && !cfgLoading && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 12, padding: 32, textAlign: 'center' }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\u26A0\uFE0F" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Could not load chat configuration" }), _jsx("p", { style: { fontSize: 13, color: '#7b8fa1', lineHeight: 1.6 }, children: cfgError }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), !cfgLoading && !cfgError && widgetConfig && (_jsxs(_Fragment, { children: [screen !== 'chat' && screen !== 'call' && !effectiveViewerBlocked && (_jsx("div", { style: {
494
- position: 'absolute', top: 12,
495
- right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
496
- left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
497
- zIndex: 20, display: 'flex', gap: 6,
498
- }, children: _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) }) })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && effectiveViewerBlocked && (_jsx(ViewerBlockedScreen, { config: widgetConfig, apiKey: apiKey, onClose: closeDrawer })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (_jsx(PermissionsGateScreen, { primaryColor: primaryColor, widgetId: widgetConfig.id, onGranted: () => setPermissionsOk(true) })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, apiKey: apiKey, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => { setListEntranceAnimation(false); setScreen('home'); }, onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && widgetConfig.viewerType !== 'developer', animateEntrance: listEntranceAnimation })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor, onMinimize: minimizeCall })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)), animateEntrance: listEntranceAnimation })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => { setListEntranceAnimation(false); setScreen('ticket-new'); }, onSelectTicket: id => {
499
- setListEntranceAnimation(false);
500
- setViewingTicketId(id);
501
- setScreen('ticket-detail');
502
- }, animateEntrance: listEntranceAnimation })), screen === 'ticket-new' && (_jsx(TicketFormScreen, { config: widgetConfig, onSubmit: handleRaiseTicket, onCancel: () => setScreen('tickets') })), screen === 'ticket-detail' && viewingTicketId && ((() => {
503
- const t = tickets.find(x => x.id === viewingTicketId);
504
- return t ? (_jsx(TicketDetailScreen, { ticket: t, config: widgetConfig, onBack: () => { setViewingTicketId(null); setScreen('tickets'); } })) : null;
505
- })()), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
506
- !effectiveViewerBlocked &&
507
- permissionsOk &&
508
- screen !== 'chat' &&
509
- screen !== 'call' &&
510
- screen !== 'user-list' &&
511
- screen !== 'block-list' &&
512
- screen !== 'ticket-detail' &&
513
- screen !== 'ticket-new' && (_jsx(BottomTabs, { active: activeTab, onChange: handleTabChange, primaryColor: primaryColor }))] }))] }))] }));
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
+ }
556
+ return (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { primaryColor: primaryColor },
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')),
559
+ !isOpen && callMinimized && callInProgress && callSession.peer && (react_1.default.createElement(MiniCallBar_1.MiniCallBar, { session: callSession, primaryColor: primaryColor, buttonPosition: theme.buttonPosition, onExpand: openDrawer, onEnd: handleEndCall })),
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 },
570
+ cfgLoading && (react_1.default.createElement("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 16 } },
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' } }),
572
+ react_1.default.createElement("p", { style: { fontSize: 14, color: '#7b8fa1' } }, "Loading chat\u2026"))),
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' } },
574
+ react_1.default.createElement("div", { style: { fontSize: 40 } }, "\u26A0\uFE0F"),
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),
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"))),
578
+ !cfgLoading && !cfgError && widgetConfig && (react_1.default.createElement(ErrorBoundary_1.ErrorBoundary, { primaryColor: primaryColor },
579
+ react_1.default.createElement(WidgetContent, Object.assign({}, contentProps(), { widgetConfig: widgetConfig, primaryColor: primaryColor, agentBg: agentBg, visitorBg: visitorBg, panelStyle: {}, isClosable: true }))))))));
580
+ };
581
+ exports.ChatWidget = ChatWidget;
582
+ exports.default = exports.ChatWidget;
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 }))));
514
619
  };
515
- export default ChatWidget;
516
- /* ── Tiny corner button ────────────────────────────────────────────────────── */
517
- const CornerBtn = ({ onClick, title, children }) => (_jsx("button", { onClick: onClick, title: title, style: {
518
- width: 26, height: 26, borderRadius: '50%',
519
- background: 'rgba(0,0,0,0.25)', border: 'none',
520
- display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
521
- }, children: children }));
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
+ }
653
+ //# sourceMappingURL=ChatWidget.js.map