@realtimexsco/live-chat 1.4.15 → 1.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PERMISSIONS.md ADDED
@@ -0,0 +1,236 @@
1
+ # Admin permissions & custom UI
2
+
3
+ How `@realtimexsco/live-chat` applies workspace **effective settings**, and how to keep the same rules when you replace UI with your own components.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ After the chat session is ready, the package fetches:
10
+
11
+ ```http
12
+ GET /settings/effective
13
+ ```
14
+
15
+ Settings are stored in `ChatEffectiveSettingsProvider` and used to **gate** features in the default UI:
16
+
17
+ | Rule | Behavior |
18
+ |------|----------|
19
+ | Feature denied | Control stays **visible** |
20
+ | Interaction | **Disabled** |
21
+ | Hover | Tooltip with reason (e.g. *This feature is disabled by your administrator.*) |
22
+
23
+ Default components already honor these gates. **Custom slot components must honor them too** — the package cannot force-disable arbitrary host JSX.
24
+
25
+ ---
26
+
27
+ ## What the API controls
28
+
29
+ | API field | UI effect |
30
+ |-----------|-----------|
31
+ | `pushNotificationEnabled` | Settings → push toggle (visible, disabled + tooltip if off) |
32
+ | `audioCallEnabled` / `videoCallEnabled` | Header phone / video buttons |
33
+ | `calls.allowedByPlatform` | Checked first for calls |
34
+ | `calls.configured` | Calls not configured message |
35
+ | `calls.enabled` | Calls disabled by admin |
36
+ | `blockUsersEnabled` | Block user menu item |
37
+ | `attachments.enabled` | Paperclip / attach |
38
+ | `attachments.maxFileSizeMB` | Max upload size |
39
+ | `attachments.allowedMimeTypes` | Allowed MIME (`*` / `*/*` = all) |
40
+ | `attachments.maxAttachmentsPerMessage` | Max files per message |
41
+ | `groupCreationEnabled` | “New group” tab |
42
+ | `groupPolicy.defaults` / `locked` | Group permission drawer |
43
+ | `messageTimers.*` | Edit / delete time windows (DM vs group) |
44
+ | `maxAdminsPerGroup` | Max group admins |
45
+ | `maxParticipantsPerGroup` | Max group members |
46
+ | `maxPinnedMessagesPerConversation` | Max pins |
47
+
48
+ ### Calls check order
49
+
50
+ 1. `calls.allowedByPlatform`
51
+ 2. `calls.configured`
52
+ 3. `calls.enabled`
53
+ 4. `audioCallEnabled` / `videoCallEnabled`
54
+
55
+ Same rules apply in **DM and group** chats.
56
+
57
+ ---
58
+
59
+ ## Exports you need
60
+
61
+ ```ts
62
+ import {
63
+ useAdminFeatureGate,
64
+ useEffectiveSettingsOptional,
65
+ AdminPermissionTooltip,
66
+ ADMIN_PERMISSION_DENIED_MESSAGE,
67
+ type CallFeatureGate,
68
+ type AdminFeatureKey,
69
+ } from "@realtimexsco/live-chat";
70
+ ```
71
+
72
+ ### Feature keys (`useAdminFeatureGate`)
73
+
74
+ | Key | Gate |
75
+ |-----|------|
76
+ | `"audioCall"` | Voice call |
77
+ | `"videoCall"` | Video call |
78
+ | `"pushNotification"` | Push toggle |
79
+ | `"blockUsers"` | Block user |
80
+ | `"attachments"` | Attach files |
81
+ | `"groupCreation"` | Create group |
82
+
83
+ ### Gate shape (`CallFeatureGate`)
84
+
85
+ ```ts
86
+ {
87
+ visible: boolean; // keep UI visible
88
+ enabled: boolean; // allow click / toggle
89
+ disabledReason: string | null; // tooltip text when disabled
90
+ }
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Two ways to customize (important)
96
+
97
+ ### Option A — Prefer small slots (recommended)
98
+
99
+ Override only the look of a control. Let the package wrapper apply gates + tooltips.
100
+
101
+ ```ts
102
+ // ✅ Good — package HeaderCallActions still applies permissions
103
+ components: {
104
+ PhoneCallButton: MyPhoneButton,
105
+ VideoCallButton: MyVideoButton,
106
+ }
107
+
108
+ // ❌ Avoid unless you wire gates yourself
109
+ components: {
110
+ HeaderCallActions: MyCallActions, // replaces gated wrapper
111
+ }
112
+ ```
113
+
114
+ Your button should still honor props the package passes:
115
+
116
+ ```tsx
117
+ function MyVideoButton({
118
+ onVideoCall,
119
+ disabled,
120
+ disabledReason,
121
+ className,
122
+ }: VideoCallButtonSlotProps) {
123
+ return (
124
+ <button
125
+ type="button"
126
+ className={className}
127
+ disabled={disabled}
128
+ aria-disabled={disabled}
129
+ onClick={disabled ? undefined : onVideoCall}
130
+ >
131
+ Video
132
+ </button>
133
+ );
134
+ }
135
+ ```
136
+
137
+ (The package wraps this with `AdminPermissionTooltip` when used via default `HeaderCallActions`.)
138
+
139
+ ### Option B — Full custom slot
140
+
141
+ If you replace a whole slot (e.g. `HeaderCallActions`, custom attach button, custom push row), **you** must read the gate and apply disable + tooltip.
142
+
143
+ ```tsx
144
+ import {
145
+ useAdminFeatureGate,
146
+ AdminPermissionTooltip,
147
+ } from "@realtimexsco/live-chat";
148
+
149
+ function MyVideoCallButton({ onVideoCall, className }) {
150
+ const gate = useAdminFeatureGate("videoCall");
151
+
152
+ return (
153
+ <AdminPermissionTooltip
154
+ disabled={!gate.enabled}
155
+ message={gate.disabledReason}
156
+ side="bottom"
157
+ >
158
+ <button
159
+ type="button"
160
+ className={className}
161
+ disabled={!gate.enabled}
162
+ onClick={gate.enabled ? onVideoCall : undefined}
163
+ >
164
+ Video
165
+ </button>
166
+ </AdminPermissionTooltip>
167
+ );
168
+ }
169
+ ```
170
+
171
+ Same pattern for other features — change the key only:
172
+
173
+ ```ts
174
+ useAdminFeatureGate("audioCall");
175
+ useAdminFeatureGate("pushNotification");
176
+ useAdminFeatureGate("blockUsers");
177
+ useAdminFeatureGate("attachments");
178
+ useAdminFeatureGate("groupCreation");
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Contract for every custom permission-aware control
184
+
185
+ | Do | Don’t |
186
+ |----|--------|
187
+ | Keep the control **visible** when denied | Hide the button when permission is off |
188
+ | Set `disabled={!gate.enabled}` | Leave it clickable |
189
+ | Wrap with `AdminPermissionTooltip` | Skip the tooltip |
190
+ | Use `useAdminFeatureGate` / gate props | Hardcode your own permission logic |
191
+ | Clear `onClick` when disabled | Fire the action when disabled |
192
+
193
+ ---
194
+
195
+ ## Advanced: raw settings
196
+
197
+ For limits (file size, max participants, timers, locked group policy), use:
198
+
199
+ ```ts
200
+ const { settings, loading, isGroupPermissionLocked } =
201
+ useEffectiveSettingsOptional();
202
+
203
+ settings.attachments.maxFileSizeMB;
204
+ settings.maxParticipantsPerGroup;
205
+ settings.messageTimers.dmEditMinutes;
206
+ isGroupPermissionLocked("onlyAdminCanSendMessage");
207
+ ```
208
+
209
+ While `loading` is `true`, show a spinner instead of flashing the wrong enabled state (same idea as the push settings row).
210
+
211
+ ---
212
+
213
+ ## Loading state
214
+
215
+ Effective settings and feature-specific APIs (e.g. push config) may load after the modal/UI opens. Recommended pattern:
216
+
217
+ 1. If `loading` → show row + spinner
218
+ 2. When ready → show control with correct enabled/disabled state
219
+ 3. Never flash “enabled” then suddenly disable without explanation
220
+
221
+ ---
222
+
223
+ ## Checklist for host apps
224
+
225
+ - [ ] Default UI: no extra work — gates are built in
226
+ - [ ] Custom `PhoneCallButton` / `VideoCallButton`: honor `disabled` (+ optional `disabledReason`)
227
+ - [ ] Custom `HeaderCallActions` / full call UI: use `useAdminFeatureGate` + `AdminPermissionTooltip`
228
+ - [ ] Custom push / attach / block / create-group UI: same gate + tooltip pattern
229
+ - [ ] Do not override `HeaderCallActions` only for styling — override `PhoneCallButton` / `VideoCallButton` instead
230
+
231
+ ---
232
+
233
+ ## Related
234
+
235
+ - Package docs: [https://realtimex.softwareco.com/documentations/package](https://realtimex.softwareco.com/documentations/package)
236
+ - npm: [https://www.npmjs.com/package/@realtimexsco/live-chat](https://www.npmjs.com/package/@realtimexsco/live-chat)
package/README.md CHANGED
@@ -2,22 +2,26 @@
2
2
 
3
3
  > Drop-in real-time chat UI for React and Next.js — direct messages, group chats, reactions, replies, forwarding, pinned/starred messages, and more. Built on Socket.IO, Zustand, Tailwind CSS v4, and Radix UI.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
6
- [![npm downloads](https://img.shields.io/npm/dm/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
7
- [![license](https://img.shields.io/npm/l/@realtimexsco/live-chat.svg)](#license)
8
-
9
- | | |
10
- |---|---|
11
- | **Package** | [`@realtimexsco/live-chat`](https://www.npmjs.com/package/@realtimexsco/live-chat) |
12
- | **Styles** | `@realtimexsco/live-chat/styles.css` |
13
- | **Requires** | React 17+, Tailwind CSS **v4**, Zustand 4+ |
14
- | **Docs** | [Package documentation](https://realtimex.softwareco.com/documentations/package) |
15
- | **License** | [MIT](#license) |
5
+ [npm version](https://www.npmjs.com/package/@realtimexsco/live-chat)
6
+ [npm downloads](https://www.npmjs.com/package/@realtimexsco/live-chat)
7
+ [license](#license)
8
+
9
+
10
+ | | |
11
+ | ------------ | ---------------------------------------------------------------------------------- |
12
+ | **Package** | `[@realtimexsco/live-chat](https://www.npmjs.com/package/@realtimexsco/live-chat)` |
13
+ | **Styles** | `@realtimexsco/live-chat/styles.css` |
14
+ | **Requires** | React 17+, Tailwind CSS **v4**, Zustand 4+ |
15
+ | **Docs** | [Package documentation](https://realtimex.softwareco.com/documentations/package) |
16
+ | **License** | [MIT](#license) |
17
+
16
18
 
17
19
  Socket.IO, REST client, Zustand store, Radix UI, emoji picker, and chat UI are **bundled**. Your app only installs peer dependencies.
18
20
 
19
21
  ---
20
22
 
23
+
24
+
21
25
  ## Install
22
26
 
23
27
  ```bash
@@ -49,23 +53,27 @@ export default function ChatPage() {
49
53
 
50
54
  ---
51
55
 
56
+
57
+
52
58
  ## Features
53
59
 
54
60
  DMs · groups · Socket.IO · reactions · replies · forward · edit/delete · pin/star · attachments · search · typing · read receipts · light/dark · locale/timezone · full UI customization
55
61
 
56
62
  ---
57
63
 
64
+
65
+
58
66
  ## More information
59
67
 
60
- | Topic | Link |
61
- |-------|------|
68
+
69
+ | Topic | Link |
70
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
62
71
  | Full setup & docs | [https://realtimex.softwareco.com/documentations/package](https://realtimex.softwareco.com/documentations/package) |
63
- | npm package | [npmjs.com/package/@realtimexsco/live-chat](https://www.npmjs.com/package/@realtimexsco/live-chat) |
64
- | License | [MIT](#license) |
72
+ | npm package | [npmjs.com/package/@realtimexsco/live-chat](https://www.npmjs.com/package/@realtimexsco/live-chat) |
73
+ | License | [MIT](#license) |
65
74
 
66
- ---
67
75
 
68
- <a id="license"></a>
76
+ ---
69
77
 
70
78
  ## License
71
79
 
@@ -77,4 +85,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
77
85
 
78
86
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
79
87
 
80
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
88
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/dist/index.cjs CHANGED
@@ -6906,8 +6906,21 @@ function AdminPermissionTooltip({
6906
6906
  if (!disabled || !message) {
6907
6907
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
6908
6908
  }
6909
+ const gatedChildren = React3__namespace.default.Children.map(children, (child) => {
6910
+ if (!React3__namespace.default.isValidElement(child)) return child;
6911
+ return React3__namespace.default.cloneElement(child, {
6912
+ className: cn(child.props.className, "pointer-events-none")
6913
+ });
6914
+ });
6909
6915
  return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6910
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("inline-flex", className), children }) }),
6916
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6917
+ "span",
6918
+ {
6919
+ className: cn("inline-flex cursor-not-allowed", className),
6920
+ tabIndex: 0,
6921
+ children: gatedChildren
6922
+ }
6923
+ ) }),
6911
6924
  /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side, className: "max-w-[240px] text-xs", children: message })
6912
6925
  ] });
6913
6926
  }
@@ -7320,9 +7333,34 @@ var PushNotificationToggle = ({
7320
7333
  children
7321
7334
  }) => {
7322
7335
  const push = usePushNotifications();
7323
- const { getPushNotificationGate } = useEffectiveSettingsOptional();
7336
+ const { loading: settingsLoading, getPushNotificationGate } = useEffectiveSettingsOptional();
7324
7337
  const adminGate = getPushNotificationGate();
7325
7338
  const [isToggling, setIsToggling] = React3.useState(false);
7339
+ const isBootstrapping = push.loading || settingsLoading;
7340
+ if (isBootstrapping && !push.supported) {
7341
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7342
+ "div",
7343
+ {
7344
+ className: cn(
7345
+ "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-surface) px-4 py-3",
7346
+ className
7347
+ ),
7348
+ children: [
7349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
7350
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-(--chat-text)", children: label }),
7351
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-(--chat-muted)", children: "Loading\u2026" })
7352
+ ] }),
7353
+ /* @__PURE__ */ jsxRuntime.jsx(
7354
+ lucideReact.Loader2,
7355
+ {
7356
+ className: "size-5 shrink-0 animate-spin text-(--chat-theme)",
7357
+ "aria-label": "Loading push notification settings"
7358
+ }
7359
+ )
7360
+ ]
7361
+ }
7362
+ );
7363
+ }
7326
7364
  if (!push.supported) {
7327
7365
  return null;
7328
7366
  }
@@ -7330,8 +7368,8 @@ var PushNotificationToggle = ({
7330
7368
  const permissionBlocked = push.permission === "denied";
7331
7369
  const adminBlocked = !adminGate.enabled;
7332
7370
  const notConfigured = !push.configured;
7333
- const showLoader = isToggling;
7334
- const isDisabled = showLoader || permissionBlocked || push.loading || adminBlocked || notConfigured;
7371
+ const showLoader = isToggling || isBootstrapping;
7372
+ const isDisabled = showLoader || permissionBlocked || push.loading || settingsLoading || adminBlocked || notConfigured;
7335
7373
  const handleChange = async (checked) => {
7336
7374
  if (adminBlocked || notConfigured) return;
7337
7375
  setIsToggling(true);
@@ -9001,35 +9039,37 @@ var DefaultHeaderCallActions = ({
9001
9039
  showVideoCall = true,
9002
9040
  onPhoneCall,
9003
9041
  onVideoCall,
9042
+ phoneCallDisabled = false,
9043
+ videoCallDisabled = false,
9044
+ phoneCallDisabledReason = null,
9045
+ videoCallDisabledReason = null,
9004
9046
  className
9005
9047
  }) => {
9006
- const { components, slotClassName, features } = useChatCustomizationOptional();
9007
- const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
9048
+ const { components, slotClassName } = useChatCustomizationOptional();
9008
9049
  const PhoneSlot = components.PhoneCallButton;
9009
9050
  const VideoSlot = components.VideoCallButton;
9010
- const hideInGroups = features.hideCallActionsInGroupChats ?? false;
9011
- const audioGate = getAudioCallGate();
9012
- const videoGate = getVideoCallGate();
9013
- if (hideInGroups && activeChannel.isGroup) return null;
9014
9051
  if (!showPhoneCall && !showVideoCall) return null;
9015
- const phoneEnabled = showPhoneCall && audioGate.enabled;
9016
- const videoEnabled = showVideoCall && videoGate.enabled;
9052
+ const phoneEnabled = showPhoneCall && !phoneCallDisabled;
9053
+ const videoEnabled = showVideoCall && !videoCallDisabled;
9017
9054
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9018
9055
  showPhoneCall && (PhoneSlot ? /* @__PURE__ */ jsxRuntime.jsx(
9019
9056
  AdminPermissionTooltip,
9020
9057
  {
9021
- disabled: !audioGate.enabled,
9022
- message: audioGate.disabledReason,
9058
+ disabled: phoneCallDisabled,
9059
+ message: phoneCallDisabledReason,
9060
+ side: "bottom",
9023
9061
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
9024
9062
  PhoneSlot,
9025
9063
  {
9026
9064
  activeChannel,
9027
9065
  conversationId,
9028
9066
  onPhoneCall: phoneEnabled ? onPhoneCall : void 0,
9067
+ disabled: phoneCallDisabled,
9068
+ disabledReason: phoneCallDisabledReason,
9029
9069
  className: cn(
9030
9070
  className,
9031
9071
  slotClassName("phoneCallButton"),
9032
- !audioGate.enabled && "pointer-events-none opacity-40"
9072
+ phoneCallDisabled && "opacity-40"
9033
9073
  )
9034
9074
  }
9035
9075
  ) })
@@ -9037,8 +9077,9 @@ var DefaultHeaderCallActions = ({
9037
9077
  ) : /* @__PURE__ */ jsxRuntime.jsx(
9038
9078
  AdminPermissionTooltip,
9039
9079
  {
9040
- disabled: !audioGate.enabled,
9041
- message: audioGate.disabledReason,
9080
+ disabled: phoneCallDisabled,
9081
+ message: phoneCallDisabledReason,
9082
+ side: "bottom",
9042
9083
  children: /* @__PURE__ */ jsxRuntime.jsx(
9043
9084
  Button,
9044
9085
  {
@@ -9046,7 +9087,7 @@ var DefaultHeaderCallActions = ({
9046
9087
  size: "icon",
9047
9088
  className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
9048
9089
  "aria-label": "Voice call",
9049
- disabled: !audioGate.enabled,
9090
+ disabled: phoneCallDisabled,
9050
9091
  onClick: phoneEnabled ? onPhoneCall : void 0,
9051
9092
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 15 })
9052
9093
  }
@@ -9056,18 +9097,21 @@ var DefaultHeaderCallActions = ({
9056
9097
  showVideoCall && (VideoSlot ? /* @__PURE__ */ jsxRuntime.jsx(
9057
9098
  AdminPermissionTooltip,
9058
9099
  {
9059
- disabled: !videoGate.enabled,
9060
- message: videoGate.disabledReason,
9100
+ disabled: videoCallDisabled,
9101
+ message: videoCallDisabledReason,
9102
+ side: "bottom",
9061
9103
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
9062
9104
  VideoSlot,
9063
9105
  {
9064
9106
  activeChannel,
9065
9107
  conversationId,
9066
9108
  onVideoCall: videoEnabled ? onVideoCall : void 0,
9109
+ disabled: videoCallDisabled,
9110
+ disabledReason: videoCallDisabledReason,
9067
9111
  className: cn(
9068
9112
  className,
9069
9113
  slotClassName("videoCallButton"),
9070
- !videoGate.enabled && "pointer-events-none opacity-40"
9114
+ videoCallDisabled && "opacity-40"
9071
9115
  )
9072
9116
  }
9073
9117
  ) })
@@ -9075,8 +9119,9 @@ var DefaultHeaderCallActions = ({
9075
9119
  ) : /* @__PURE__ */ jsxRuntime.jsx(
9076
9120
  AdminPermissionTooltip,
9077
9121
  {
9078
- disabled: !videoGate.enabled,
9079
- message: videoGate.disabledReason,
9122
+ disabled: videoCallDisabled,
9123
+ message: videoCallDisabledReason,
9124
+ side: "bottom",
9080
9125
  children: /* @__PURE__ */ jsxRuntime.jsx(
9081
9126
  Button,
9082
9127
  {
@@ -9084,7 +9129,7 @@ var DefaultHeaderCallActions = ({
9084
9129
  size: "icon",
9085
9130
  className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
9086
9131
  "aria-label": "Video call",
9087
- disabled: !videoGate.enabled,
9132
+ disabled: videoCallDisabled,
9088
9133
  onClick: videoEnabled ? onVideoCall : void 0,
9089
9134
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 15 })
9090
9135
  }
@@ -9094,16 +9139,28 @@ var DefaultHeaderCallActions = ({
9094
9139
  ] });
9095
9140
  };
9096
9141
  var HeaderCallActions = (props) => {
9097
- const { components, slotClassName, features } = useChatCustomizationOptional();
9142
+ const { components, slotClassName } = useChatCustomizationOptional();
9143
+ const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
9098
9144
  const Custom = components.HeaderCallActions;
9099
- if (!Custom) {
9100
- const hideInGroups = features.hideCallActionsInGroupChats ?? false;
9101
- const showPhoneCall = props.showPhoneCall ?? true;
9102
- const showVideoCall = props.showVideoCall ?? true;
9103
- if (hideInGroups && props.activeChannel.isGroup) return null;
9104
- if (!showPhoneCall && !showVideoCall) return null;
9105
- }
9106
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex items-center gap-0.5", slotClassName("headerCallActions")), children: Custom ? /* @__PURE__ */ jsxRuntime.jsx(Custom, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultHeaderCallActions, { ...props }) });
9145
+ const showPhoneCall = props.showPhoneCall ?? true;
9146
+ const showVideoCall = props.showVideoCall ?? true;
9147
+ const audioGate = getAudioCallGate();
9148
+ const videoGate = getVideoCallGate();
9149
+ if (!showPhoneCall && !showVideoCall) return null;
9150
+ const gatedProps = {
9151
+ ...props,
9152
+ showPhoneCall,
9153
+ showVideoCall,
9154
+ onPhoneCall: audioGate.enabled ? props.onPhoneCall : void 0,
9155
+ onVideoCall: videoGate.enabled ? props.onVideoCall : void 0,
9156
+ phoneCallDisabled: !audioGate.enabled,
9157
+ videoCallDisabled: !videoGate.enabled,
9158
+ phoneCallDisabledReason: audioGate.disabledReason,
9159
+ videoCallDisabledReason: videoGate.disabledReason
9160
+ };
9161
+ const hasButtonSlots = components.PhoneCallButton != null || components.VideoCallButton != null;
9162
+ const useCustom = Custom != null && Custom !== HeaderCallActions && !hasButtonSlots;
9163
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex items-center gap-0.5", slotClassName("headerCallActions")), children: useCustom ? /* @__PURE__ */ jsxRuntime.jsx(Custom, { ...gatedProps }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultHeaderCallActions, { ...gatedProps }) });
9107
9164
  };
9108
9165
  var HeaderCallActions_default = HeaderCallActions;
9109
9166
  function OptionsMenuItem({
@@ -13501,8 +13558,8 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13501
13558
  { key: "onlyAdminCanSendMessage", label: "Only Admin Can Send Message", desc: "Restricts message sending to group administrators only." },
13502
13559
  { key: "onlyAdminCanEditInfo", label: "Only Admin Can Edit Info", desc: "Only admins can change group name, description, and avatar." },
13503
13560
  { key: "senderCanEditMessage", label: "Sender Can Edit Message", desc: "Allows users to edit their own sent messages." },
13504
- { key: "allowMemberAdd", label: "Only Admin Can Add Members", desc: "Only administrators can add new participants to the group.", inverted: true },
13505
- { key: "allowMemberRemove", label: "Only Admin Can Remove Members", desc: "Only administrators can remove other participants.", inverted: true },
13561
+ { key: "allowMemberAdd", label: "Allow Members to Add", desc: "Allows group members to add new participants." },
13562
+ { key: "allowMemberRemove", label: "Allow Members to Remove", desc: "Allows group members to remove other participants." },
13506
13563
  { key: "moderationEnabled", label: "Moderation Enabled", desc: "Enables advanced message moderation features." }
13507
13564
  ];
13508
13565
  return /* @__PURE__ */ jsxRuntime.jsx(Sheet, { open: isOpen, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ChatSheetContent, { showCloseButton: true, className: "sm:max-w-[380px]", children: [
@@ -13518,7 +13575,7 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
13518
13575
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-1 px-0.5 text-[10px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "Permissions" }),
13519
13576
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13520
13577
  const locked = isGroupPermissionLocked(item.key);
13521
- const checked = item.inverted ? !permissions[item.key] : permissions[item.key];
13578
+ const checked = permissions[item.key];
13522
13579
  return /* @__PURE__ */ jsxRuntime.jsxs(
13523
13580
  "div",
13524
13581
  {
@@ -19699,11 +19756,35 @@ var packageDefaultComponents = {
19699
19756
  ChatSocketStatus: ChatSocketStatus_default
19700
19757
  };
19701
19758
 
19759
+ // src/hooks/useAdminFeatureGate.ts
19760
+ function useAdminFeatureGate(feature) {
19761
+ const ctx = useEffectiveSettingsOptional();
19762
+ switch (feature) {
19763
+ case "audioCall":
19764
+ return ctx.getAudioCallGate();
19765
+ case "videoCall":
19766
+ return ctx.getVideoCallGate();
19767
+ case "pushNotification":
19768
+ return ctx.getPushNotificationGate();
19769
+ case "blockUsers":
19770
+ return ctx.getBlockUsersGate();
19771
+ case "attachments":
19772
+ return ctx.getAttachmentsGate();
19773
+ case "groupCreation":
19774
+ return ctx.getGroupCreationGate();
19775
+ default: {
19776
+ const _exhaustive = feature;
19777
+ return _exhaustive;
19778
+ }
19779
+ }
19780
+ }
19781
+
19702
19782
  // src/index.ts
19703
19783
  init_settings_types();
19704
19784
  init_api_service();
19705
19785
  var index_default = ChatMain_default;
19706
19786
 
19787
+ exports.AdminPermissionTooltip = AdminPermissionTooltip;
19707
19788
  exports.Calendar = Calendar;
19708
19789
  exports.CalendarDayButton = CalendarDayButton;
19709
19790
  exports.Channel = Channel;
@@ -19786,6 +19867,7 @@ exports.smoothScrollToMessage = smoothScrollToMessage;
19786
19867
  exports.startOfLocalDay = startOfLocalDay;
19787
19868
  exports.toDateInputValue = toDateInputValue;
19788
19869
  exports.uniqueList = uniqueList;
19870
+ exports.useAdminFeatureGate = useAdminFeatureGate;
19789
19871
  exports.useChatAlerts = useChatAlerts;
19790
19872
  exports.useChatContext = useChatContext;
19791
19873
  exports.useChatController = useChatController;