ajaxter-chat 3.0.14 → 3.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/dist/components/ChatWidget.js +51 -21
- package/dist/types/index.d.ts +5 -0
- package/package.json +1 -1
- package/public/chatData.json +4 -2
- package/src/components/ChatWidget.tsx +39 -13
- package/src/types/index.ts +5 -0
package/README.md
CHANGED
|
Binary file
|
|
@@ -23,7 +23,7 @@ import { ViewerBlockedScreen } from './ViewerBlockedScreen';
|
|
|
23
23
|
import { PermissionsGateScreen } from './PermissionsGateScreen';
|
|
24
24
|
import { hasStoredPermissionsGrant } from '../utils/widgetPermissions';
|
|
25
25
|
export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
26
|
-
var _a, _b, _c, _d, _e
|
|
26
|
+
var _a, _b, _c, _d, _e;
|
|
27
27
|
/* SSR guard */
|
|
28
28
|
const [mounted, setMounted] = useState(false);
|
|
29
29
|
useEffect(() => { setMounted(true); }, []);
|
|
@@ -53,13 +53,14 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
53
53
|
const [blockedUids, setBlockedUids] = useState((_b = data === null || data === void 0 ? void 0 : data.blockedUsers) !== null && _b !== void 0 ? _b : []);
|
|
54
54
|
/* Sync remote data into local state once loaded */
|
|
55
55
|
useEffect(() => {
|
|
56
|
-
var _a, _b, _c;
|
|
56
|
+
var _a, _b, _c, _d;
|
|
57
57
|
if (data) {
|
|
58
58
|
setTickets(data.sampleTickets);
|
|
59
59
|
setBlockedUids(data.blockedUsers);
|
|
60
60
|
const pid = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _a === void 0 ? void 0 : _a.trim();
|
|
61
|
-
const
|
|
62
|
-
const
|
|
61
|
+
const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
|
|
62
|
+
const usr = pid ? ((_c = data.users) !== null && _c !== void 0 ? _c : []).filter(u => u.project === pid) : ((_d = data.users) !== null && _d !== void 0 ? _d : []);
|
|
63
|
+
const all = [...devs, ...usr];
|
|
63
64
|
const recents = Object.entries(data.sampleChats).map(([uid, msgs]) => {
|
|
64
65
|
const user = all.find(u => u.uid === uid);
|
|
65
66
|
if (!user || msgs.length === 0)
|
|
@@ -118,12 +119,18 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
118
119
|
}, [(_c = data === null || data === void 0 ? void 0 : data.widget) === null || _c === void 0 ? void 0 : _c.id]);
|
|
119
120
|
const restoredRef = useRef(false);
|
|
120
121
|
useEffect(() => {
|
|
121
|
-
var _a, _b, _c, _d;
|
|
122
|
+
var _a, _b, _c, _d, _e, _f;
|
|
122
123
|
if (!(data === null || data === void 0 ? void 0 : data.widget) || restoredRef.current)
|
|
123
124
|
return;
|
|
124
125
|
const w = data.widget;
|
|
125
126
|
setMessageSoundEnabledState(getMessageSoundEnabled(w.id));
|
|
126
|
-
|
|
127
|
+
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();
|
|
128
|
+
let viewerIsBlocked = w.viewerBlocked === true;
|
|
129
|
+
if (!viewerIsBlocked && uidForBlock) {
|
|
130
|
+
const rec = [...data.developers, ...data.users].find(x => x.uid === uidForBlock);
|
|
131
|
+
viewerIsBlocked = (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
|
|
132
|
+
}
|
|
133
|
+
if (viewerIsBlocked) {
|
|
127
134
|
clearChat();
|
|
128
135
|
setScreen('home');
|
|
129
136
|
setActiveTab('home');
|
|
@@ -136,10 +143,10 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
136
143
|
setScreen(p.screen);
|
|
137
144
|
setActiveTab(p.activeTab);
|
|
138
145
|
setUserListCtx(p.userListCtx);
|
|
139
|
-
setViewingTicketId((
|
|
140
|
-
setChatReturnCtx((
|
|
146
|
+
setViewingTicketId((_c = p.viewingTicketId) !== null && _c !== void 0 ? _c : null);
|
|
147
|
+
setChatReturnCtx((_d = p.chatReturnCtx) !== null && _d !== void 0 ? _d : 'conversation');
|
|
141
148
|
if (p.activeUserUid) {
|
|
142
|
-
const pid = (
|
|
149
|
+
const pid = (_e = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _e === void 0 ? void 0 : _e.trim();
|
|
143
150
|
const pool = pid
|
|
144
151
|
? [...data.developers, ...data.users].filter(u => u.project === pid)
|
|
145
152
|
: [...data.developers, ...data.users];
|
|
@@ -147,27 +154,36 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
147
154
|
if (u) {
|
|
148
155
|
const hist = Array.isArray(p.messages) && p.messages.length
|
|
149
156
|
? p.messages
|
|
150
|
-
: ((
|
|
157
|
+
: ((_f = data.sampleChats[u.uid]) !== null && _f !== void 0 ? _f : []);
|
|
151
158
|
selectUser(u, hist);
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
restoredRef.current = true;
|
|
156
|
-
}, [data, selectUser, clearChat, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
|
|
163
|
+
}, [data, selectUser, clearChat, viewer === null || viewer === void 0 ? void 0 : viewer.projectId, viewer === null || viewer === void 0 ? void 0 : viewer.uid]);
|
|
157
164
|
useEffect(() => {
|
|
158
|
-
var _a;
|
|
159
|
-
if (!(
|
|
165
|
+
var _a, _b;
|
|
166
|
+
if (!(data === null || data === void 0 ? void 0 : data.widget))
|
|
167
|
+
return;
|
|
168
|
+
const w = data.widget;
|
|
169
|
+
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();
|
|
170
|
+
let blocked = w.viewerBlocked === true;
|
|
171
|
+
if (!blocked && uid) {
|
|
172
|
+
const rec = [...data.developers, ...data.users].find(x => x.uid === uid);
|
|
173
|
+
blocked = (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
|
|
174
|
+
}
|
|
175
|
+
if (!blocked)
|
|
160
176
|
return;
|
|
161
177
|
clearChat();
|
|
162
178
|
setScreen('home');
|
|
163
179
|
setActiveTab('home');
|
|
164
180
|
setViewingTicketId(null);
|
|
165
|
-
}, [
|
|
181
|
+
}, [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]);
|
|
166
182
|
useEffect(() => {
|
|
167
183
|
if (!(data === null || data === void 0 ? void 0 : data.widget))
|
|
168
184
|
return;
|
|
169
185
|
persistWidgetState();
|
|
170
|
-
}, [(
|
|
186
|
+
}, [(_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]);
|
|
171
187
|
const incomingSoundRef = useRef(0);
|
|
172
188
|
useEffect(() => {
|
|
173
189
|
incomingSoundRef.current = messages.length;
|
|
@@ -318,16 +334,30 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
318
334
|
return w;
|
|
319
335
|
}, [data === null || data === void 0 ? void 0 : data.widget, viewer]);
|
|
320
336
|
const primaryColor = theme.primaryColor;
|
|
337
|
+
/** All developers are listed; only end-`user` rows are filtered by `viewer.projectId`. */
|
|
321
338
|
const allUsers = useMemo(() => {
|
|
322
|
-
var _a;
|
|
339
|
+
var _a, _b;
|
|
323
340
|
if (!data)
|
|
324
341
|
return [];
|
|
325
342
|
const pid = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.projectId) === null || _a === void 0 ? void 0 : _a.trim();
|
|
326
|
-
const
|
|
343
|
+
const devs = (_b = data.developers) !== null && _b !== void 0 ? _b : [];
|
|
327
344
|
if (!pid)
|
|
328
|
-
return
|
|
329
|
-
|
|
345
|
+
return [...devs, ...data.users];
|
|
346
|
+
const usersInProject = data.users.filter(u => u.project === pid);
|
|
347
|
+
return [...devs, ...usersInProject];
|
|
330
348
|
}, [data, viewer === null || viewer === void 0 ? void 0 : viewer.projectId]);
|
|
349
|
+
const effectiveViewerBlocked = useMemo(() => {
|
|
350
|
+
var _a, _b;
|
|
351
|
+
if (!widgetConfig)
|
|
352
|
+
return false;
|
|
353
|
+
if (widgetConfig.viewerBlocked === true)
|
|
354
|
+
return true;
|
|
355
|
+
const uid = (_b = ((_a = viewer === null || viewer === void 0 ? void 0 : viewer.uid) !== null && _a !== void 0 ? _a : widgetConfig.viewerUid)) === null || _b === void 0 ? void 0 : _b.trim();
|
|
356
|
+
if (!uid || !data)
|
|
357
|
+
return false;
|
|
358
|
+
const rec = [...data.developers, ...data.users].find(x => x.uid === uid);
|
|
359
|
+
return (rec === null || rec === void 0 ? void 0 : rec.viewerBlocked) === true;
|
|
360
|
+
}, [widgetConfig, viewer === null || viewer === void 0 ? void 0 : viewer.uid, data]);
|
|
331
361
|
const viewerIsDev = (widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerType) === 'developer';
|
|
332
362
|
const viewerUid = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.viewerUid;
|
|
333
363
|
const filteredUsers = screen === 'user-list'
|
|
@@ -434,7 +464,7 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
434
464
|
right: theme.buttonPosition === 'bottom-left' ? 'auto' : 12,
|
|
435
465
|
left: theme.buttonPosition === 'bottom-left' ? 12 : 'auto',
|
|
436
466
|
zIndex: 20, display: 'flex', gap: 6,
|
|
437
|
-
}, 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' &&
|
|
467
|
+
}, children: _jsx(CornerBtn, { onClick: closeDrawer, title: "Close", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "#fff", strokeWidth: "2.5", strokeLinecap: "round" }) }) }) })), widgetConfig.status === 'MAINTENANCE' && (_jsx(MaintenanceView, { primaryColor: primaryColor })), widgetConfig.status === 'DISABLE' && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', padding: 32, textAlign: 'center', gap: 12 }, children: [_jsx("div", { style: { fontSize: 40 }, children: "\uD83D\uDD12" }), _jsx("p", { style: { fontWeight: 700, color: '#1a2332' }, children: "Chat is disabled" }), _jsx("button", { onClick: closeDrawer, style: { padding: '9px 20px', borderRadius: 10, border: 'none', background: primaryColor, color: '#fff', cursor: 'pointer', fontWeight: 700 }, children: "Close" })] })), widgetConfig.status === 'ACTIVE' && effectiveViewerBlocked && (_jsx(ViewerBlockedScreen, { config: widgetConfig, apiKey: apiKey })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (_jsx(PermissionsGateScreen, { primaryColor: primaryColor, widgetId: widgetConfig.id, onGranted: () => setPermissionsOk(true) })), widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (_jsxs("div", { className: "cw-scroll", style: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [screen === 'home' && (_jsx(HomeScreen, { config: widgetConfig, onNavigate: handleCardClick, onOpenTicket: handleOpenTicket, tickets: tickets })), screen === 'user-list' && (_jsx(UserListScreen, { context: userListCtx, users: filteredUsers, primaryColor: primaryColor, viewerType: (_e = widgetConfig.viewerType) !== null && _e !== void 0 ? _e : 'user', onBack: () => { setListEntranceAnimation(false); setScreen('home'); }, onSelectUser: handleSelectUser, onBlockList: userListCtx === 'conversation' ? () => setScreen('block-list') : undefined, useHomeHeader: userListCtx === 'support' && widgetConfig.viewerType !== 'developer', animateEntrance: listEntranceAnimation })), screen === 'chat' && activeUser && (_jsx(ChatScreen, { activeUser: activeUser, messages: messages, config: widgetConfig, isPaused: isPaused, isReported: isReported, isBlocked: isBlocked, onSend: sendMessage, onBack: handleBackFromChat, onClose: closeDrawer, onTogglePause: handleTogglePause, onReport: reportChat, onBlock: handleBlock, onStartCall: handleStartCall, onNavAction: handleNavFromMenu, otherDevelopers: otherDevelopers, onTransferToDeveloper: handleTransferToDeveloper, messageSoundEnabled: messageSoundEnabled, onToggleMessageSound: toggleMessageSound })), screen === 'call' && callSession.peer && (_jsx(CallScreen, { session: callSession, localVideoRef: localVideoRef, remoteVideoRef: remoteVideoRef, onEnd: handleEndCall, onToggleMute: toggleMute, onToggleCamera: toggleCamera, primaryColor: primaryColor })), screen === 'recent-chats' && (_jsx(RecentChatsScreen, { chats: recentChats, config: widgetConfig, onSelectChat: u => handleSelectUser(u, listCtxForUser(u, viewerIsDev)), animateEntrance: listEntranceAnimation })), screen === 'tickets' && (_jsx(TicketScreen, { tickets: tickets, config: widgetConfig, onNewTicket: () => { setListEntranceAnimation(false); setScreen('ticket-new'); }, onSelectTicket: id => {
|
|
438
468
|
setListEntranceAnimation(false);
|
|
439
469
|
setViewingTicketId(id);
|
|
440
470
|
setScreen('ticket-detail');
|
|
@@ -442,7 +472,7 @@ export const ChatWidget = ({ theme: localTheme, viewer }) => {
|
|
|
442
472
|
const t = tickets.find(x => x.id === viewingTicketId);
|
|
443
473
|
return t ? (_jsx(TicketDetailScreen, { ticket: t, config: widgetConfig, onBack: () => { setViewingTicketId(null); setScreen('tickets'); } })) : null;
|
|
444
474
|
})()), screen === 'block-list' && (_jsx(BlockListScreen, { blockedUsers: blockedUsers, config: widgetConfig, onUnblock: handleUnblock, onBack: () => { setScreen('home'); setActiveTab('home'); } }))] })), widgetConfig.status === 'ACTIVE' &&
|
|
445
|
-
!
|
|
475
|
+
!effectiveViewerBlocked &&
|
|
446
476
|
permissionsOk &&
|
|
447
477
|
screen !== 'chat' &&
|
|
448
478
|
screen !== 'call' &&
|
package/dist/types/index.d.ts
CHANGED
|
@@ -85,6 +85,11 @@ export interface ChatUser {
|
|
|
85
85
|
avatar: string | null;
|
|
86
86
|
status: OnlineStatus;
|
|
87
87
|
designation: string;
|
|
88
|
+
/**
|
|
89
|
+
* When `true` for the row matching the current viewer (`viewerUid` / `viewer.uid`),
|
|
90
|
+
* the widget shows the spam/blocked screen (same as `widget.viewerBlocked`).
|
|
91
|
+
*/
|
|
92
|
+
viewerBlocked?: boolean;
|
|
88
93
|
}
|
|
89
94
|
export interface ChatMessage {
|
|
90
95
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajaxter-chat",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.15",
|
|
4
4
|
"description": "Drawer-based chat widget with support chat, tickets, WebRTC calling, voice messages, block list, and transcript download.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/public/chatData.json
CHANGED
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
"type": "user",
|
|
63
63
|
"avatar": null,
|
|
64
64
|
"status": "online",
|
|
65
|
-
"designation": "Product Manager"
|
|
65
|
+
"designation": "Product Manager",
|
|
66
|
+
"viewerBlocked": false
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
69
|
"uid": "usr_002",
|
|
@@ -73,7 +74,8 @@
|
|
|
73
74
|
"type": "user",
|
|
74
75
|
"avatar": null,
|
|
75
76
|
"status": "away",
|
|
76
|
-
"designation": "Business Analyst"
|
|
77
|
+
"designation": "Business Analyst",
|
|
78
|
+
"viewerBlocked": true
|
|
77
79
|
},
|
|
78
80
|
{
|
|
79
81
|
"uid": "usr_003",
|
|
@@ -69,8 +69,9 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
69
69
|
setTickets(data.sampleTickets);
|
|
70
70
|
setBlockedUids(data.blockedUsers);
|
|
71
71
|
const pid = viewer?.projectId?.trim();
|
|
72
|
-
const
|
|
73
|
-
const
|
|
72
|
+
const devs = data.developers ?? [];
|
|
73
|
+
const usr = pid ? (data.users ?? []).filter(u => u.project === pid) : (data.users ?? []);
|
|
74
|
+
const all = [...devs, ...usr];
|
|
74
75
|
const recents: RecentChat[] = Object.entries(data.sampleChats).map(([uid, msgs]) => {
|
|
75
76
|
const user = all.find(u => u.uid === uid);
|
|
76
77
|
if (!user || msgs.length === 0) return null;
|
|
@@ -137,7 +138,13 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
137
138
|
if (!data?.widget || restoredRef.current) return;
|
|
138
139
|
const w = data.widget;
|
|
139
140
|
setMessageSoundEnabledState(getMessageSoundEnabled(w.id));
|
|
140
|
-
|
|
141
|
+
const uidForBlock = (viewer?.uid ?? w.viewerUid)?.trim();
|
|
142
|
+
let viewerIsBlocked = w.viewerBlocked === true;
|
|
143
|
+
if (!viewerIsBlocked && uidForBlock) {
|
|
144
|
+
const rec = [...data.developers, ...data.users].find(x => x.uid === uidForBlock);
|
|
145
|
+
viewerIsBlocked = rec?.viewerBlocked === true;
|
|
146
|
+
}
|
|
147
|
+
if (viewerIsBlocked) {
|
|
141
148
|
clearChat();
|
|
142
149
|
setScreen('home');
|
|
143
150
|
setActiveTab('home');
|
|
@@ -167,15 +174,23 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
176
|
restoredRef.current = true;
|
|
170
|
-
}, [data, selectUser, clearChat, viewer?.projectId]);
|
|
177
|
+
}, [data, selectUser, clearChat, viewer?.projectId, viewer?.uid]);
|
|
171
178
|
|
|
172
179
|
useEffect(() => {
|
|
173
|
-
if (!data?.widget
|
|
180
|
+
if (!data?.widget) return;
|
|
181
|
+
const w = data.widget;
|
|
182
|
+
const uid = (viewer?.uid ?? w.viewerUid)?.trim();
|
|
183
|
+
let blocked = w.viewerBlocked === true;
|
|
184
|
+
if (!blocked && uid) {
|
|
185
|
+
const rec = [...data.developers, ...data.users].find(x => x.uid === uid);
|
|
186
|
+
blocked = rec?.viewerBlocked === true;
|
|
187
|
+
}
|
|
188
|
+
if (!blocked) return;
|
|
174
189
|
clearChat();
|
|
175
190
|
setScreen('home');
|
|
176
191
|
setActiveTab('home');
|
|
177
192
|
setViewingTicketId(null);
|
|
178
|
-
}, [data?.widget?.
|
|
193
|
+
}, [data?.widget, data?.developers, data?.users, viewer?.uid, clearChat]);
|
|
179
194
|
|
|
180
195
|
useEffect(() => {
|
|
181
196
|
if (!data?.widget) return;
|
|
@@ -338,14 +353,25 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
338
353
|
|
|
339
354
|
const primaryColor = theme.primaryColor;
|
|
340
355
|
|
|
356
|
+
/** All developers are listed; only end-`user` rows are filtered by `viewer.projectId`. */
|
|
341
357
|
const allUsers = useMemo(() => {
|
|
342
358
|
if (!data) return [];
|
|
343
359
|
const pid = viewer?.projectId?.trim();
|
|
344
|
-
const
|
|
345
|
-
if (!pid) return
|
|
346
|
-
|
|
360
|
+
const devs = data.developers ?? [];
|
|
361
|
+
if (!pid) return [...devs, ...data.users];
|
|
362
|
+
const usersInProject = data.users.filter(u => u.project === pid);
|
|
363
|
+
return [...devs, ...usersInProject];
|
|
347
364
|
}, [data, viewer?.projectId]);
|
|
348
365
|
|
|
366
|
+
const effectiveViewerBlocked = useMemo(() => {
|
|
367
|
+
if (!widgetConfig) return false;
|
|
368
|
+
if (widgetConfig.viewerBlocked === true) return true;
|
|
369
|
+
const uid = (viewer?.uid ?? widgetConfig.viewerUid)?.trim();
|
|
370
|
+
if (!uid || !data) return false;
|
|
371
|
+
const rec = [...data.developers, ...data.users].find(x => x.uid === uid);
|
|
372
|
+
return rec?.viewerBlocked === true;
|
|
373
|
+
}, [widgetConfig, viewer?.uid, data]);
|
|
374
|
+
|
|
349
375
|
const viewerIsDev = widgetConfig?.viewerType === 'developer';
|
|
350
376
|
const viewerUid = widgetConfig?.viewerUid;
|
|
351
377
|
|
|
@@ -567,12 +593,12 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
567
593
|
)}
|
|
568
594
|
|
|
569
595
|
{/* ── ACTIVE: viewer spam-blocked (no chat/tickets UI) ── */}
|
|
570
|
-
{widgetConfig.status === 'ACTIVE' &&
|
|
596
|
+
{widgetConfig.status === 'ACTIVE' && effectiveViewerBlocked && (
|
|
571
597
|
<ViewerBlockedScreen config={widgetConfig} apiKey={apiKey} />
|
|
572
598
|
)}
|
|
573
599
|
|
|
574
600
|
{/* ── ACTIVE: microphone, location, screen share required ── */}
|
|
575
|
-
{widgetConfig.status === 'ACTIVE' && !
|
|
601
|
+
{widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && !permissionsOk && (
|
|
576
602
|
<PermissionsGateScreen
|
|
577
603
|
primaryColor={primaryColor}
|
|
578
604
|
widgetId={widgetConfig.id}
|
|
@@ -581,7 +607,7 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
581
607
|
)}
|
|
582
608
|
|
|
583
609
|
{/* ── ACTIVE ── */}
|
|
584
|
-
{widgetConfig.status === 'ACTIVE' && !
|
|
610
|
+
{widgetConfig.status === 'ACTIVE' && !effectiveViewerBlocked && permissionsOk && (
|
|
585
611
|
<div className="cw-scroll" style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
|
586
612
|
|
|
587
613
|
{screen === 'home' && (
|
|
@@ -699,7 +725,7 @@ export const ChatWidget: React.FC<ChatWidgetProps> = ({ theme: localTheme, viewe
|
|
|
699
725
|
|
|
700
726
|
{/* ── Bottom Tabs (hidden during chat/call/user-list/block-list) ── */}
|
|
701
727
|
{widgetConfig.status === 'ACTIVE' &&
|
|
702
|
-
!
|
|
728
|
+
!effectiveViewerBlocked &&
|
|
703
729
|
permissionsOk &&
|
|
704
730
|
screen !== 'chat' &&
|
|
705
731
|
screen !== 'call' &&
|
package/src/types/index.ts
CHANGED
|
@@ -100,6 +100,11 @@ export interface ChatUser {
|
|
|
100
100
|
avatar: string | null;
|
|
101
101
|
status: OnlineStatus;
|
|
102
102
|
designation: string;
|
|
103
|
+
/**
|
|
104
|
+
* When `true` for the row matching the current viewer (`viewerUid` / `viewer.uid`),
|
|
105
|
+
* the widget shows the spam/blocked screen (same as `widget.viewerBlocked`).
|
|
106
|
+
*/
|
|
107
|
+
viewerBlocked?: boolean;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
// ─── Message ────────────────────────────────────────────────────────────────
|