@realtimexsco/live-chat 1.4.16 → 1.4.18
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 +236 -0
- package/README.md +23 -15
- package/dist/index.cjs +91 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.mjs +90 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +42 -10
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
|
@@ -4,20 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@realtimexsco/live-chat)
|
|
6
6
|
[](https://www.npmjs.com/package/@realtimexsco/live-chat)
|
|
7
|
-
[](#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) |
|
|
8
17
|
|
|
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) |
|
|
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
|
-
|
|
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
|
|
64
|
-
| 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
|
-
|
|
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
|
@@ -9039,23 +9039,24 @@ var DefaultHeaderCallActions = ({
|
|
|
9039
9039
|
showVideoCall = true,
|
|
9040
9040
|
onPhoneCall,
|
|
9041
9041
|
onVideoCall,
|
|
9042
|
+
phoneCallDisabled = false,
|
|
9043
|
+
videoCallDisabled = false,
|
|
9044
|
+
phoneCallDisabledReason = null,
|
|
9045
|
+
videoCallDisabledReason = null,
|
|
9042
9046
|
className
|
|
9043
9047
|
}) => {
|
|
9044
9048
|
const { components, slotClassName } = useChatCustomizationOptional();
|
|
9045
|
-
const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
|
|
9046
9049
|
const PhoneSlot = components.PhoneCallButton;
|
|
9047
9050
|
const VideoSlot = components.VideoCallButton;
|
|
9048
|
-
const audioGate = getAudioCallGate();
|
|
9049
|
-
const videoGate = getVideoCallGate();
|
|
9050
9051
|
if (!showPhoneCall && !showVideoCall) return null;
|
|
9051
|
-
const phoneEnabled = showPhoneCall &&
|
|
9052
|
-
const videoEnabled = showVideoCall &&
|
|
9052
|
+
const phoneEnabled = showPhoneCall && !phoneCallDisabled;
|
|
9053
|
+
const videoEnabled = showVideoCall && !videoCallDisabled;
|
|
9053
9054
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9054
9055
|
showPhoneCall && (PhoneSlot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9055
9056
|
AdminPermissionTooltip,
|
|
9056
9057
|
{
|
|
9057
|
-
disabled:
|
|
9058
|
-
message:
|
|
9058
|
+
disabled: phoneCallDisabled,
|
|
9059
|
+
message: phoneCallDisabledReason,
|
|
9059
9060
|
side: "bottom",
|
|
9060
9061
|
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9061
9062
|
PhoneSlot,
|
|
@@ -9063,10 +9064,12 @@ var DefaultHeaderCallActions = ({
|
|
|
9063
9064
|
activeChannel,
|
|
9064
9065
|
conversationId,
|
|
9065
9066
|
onPhoneCall: phoneEnabled ? onPhoneCall : void 0,
|
|
9067
|
+
disabled: phoneCallDisabled,
|
|
9068
|
+
disabledReason: phoneCallDisabledReason,
|
|
9066
9069
|
className: cn(
|
|
9067
9070
|
className,
|
|
9068
9071
|
slotClassName("phoneCallButton"),
|
|
9069
|
-
|
|
9072
|
+
phoneCallDisabled && "opacity-40"
|
|
9070
9073
|
)
|
|
9071
9074
|
}
|
|
9072
9075
|
) })
|
|
@@ -9074,8 +9077,8 @@ var DefaultHeaderCallActions = ({
|
|
|
9074
9077
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9075
9078
|
AdminPermissionTooltip,
|
|
9076
9079
|
{
|
|
9077
|
-
disabled:
|
|
9078
|
-
message:
|
|
9080
|
+
disabled: phoneCallDisabled,
|
|
9081
|
+
message: phoneCallDisabledReason,
|
|
9079
9082
|
side: "bottom",
|
|
9080
9083
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9081
9084
|
Button,
|
|
@@ -9084,7 +9087,7 @@ var DefaultHeaderCallActions = ({
|
|
|
9084
9087
|
size: "icon",
|
|
9085
9088
|
className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
|
|
9086
9089
|
"aria-label": "Voice call",
|
|
9087
|
-
disabled:
|
|
9090
|
+
disabled: phoneCallDisabled,
|
|
9088
9091
|
onClick: phoneEnabled ? onPhoneCall : void 0,
|
|
9089
9092
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 15 })
|
|
9090
9093
|
}
|
|
@@ -9094,8 +9097,8 @@ var DefaultHeaderCallActions = ({
|
|
|
9094
9097
|
showVideoCall && (VideoSlot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9095
9098
|
AdminPermissionTooltip,
|
|
9096
9099
|
{
|
|
9097
|
-
disabled:
|
|
9098
|
-
message:
|
|
9100
|
+
disabled: videoCallDisabled,
|
|
9101
|
+
message: videoCallDisabledReason,
|
|
9099
9102
|
side: "bottom",
|
|
9100
9103
|
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9101
9104
|
VideoSlot,
|
|
@@ -9103,10 +9106,12 @@ var DefaultHeaderCallActions = ({
|
|
|
9103
9106
|
activeChannel,
|
|
9104
9107
|
conversationId,
|
|
9105
9108
|
onVideoCall: videoEnabled ? onVideoCall : void 0,
|
|
9109
|
+
disabled: videoCallDisabled,
|
|
9110
|
+
disabledReason: videoCallDisabledReason,
|
|
9106
9111
|
className: cn(
|
|
9107
9112
|
className,
|
|
9108
9113
|
slotClassName("videoCallButton"),
|
|
9109
|
-
|
|
9114
|
+
videoCallDisabled && "opacity-40"
|
|
9110
9115
|
)
|
|
9111
9116
|
}
|
|
9112
9117
|
) })
|
|
@@ -9114,8 +9119,8 @@ var DefaultHeaderCallActions = ({
|
|
|
9114
9119
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
9115
9120
|
AdminPermissionTooltip,
|
|
9116
9121
|
{
|
|
9117
|
-
disabled:
|
|
9118
|
-
message:
|
|
9122
|
+
disabled: videoCallDisabled,
|
|
9123
|
+
message: videoCallDisabledReason,
|
|
9119
9124
|
side: "bottom",
|
|
9120
9125
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9121
9126
|
Button,
|
|
@@ -9124,7 +9129,7 @@ var DefaultHeaderCallActions = ({
|
|
|
9124
9129
|
size: "icon",
|
|
9125
9130
|
className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
|
|
9126
9131
|
"aria-label": "Video call",
|
|
9127
|
-
disabled:
|
|
9132
|
+
disabled: videoCallDisabled,
|
|
9128
9133
|
onClick: videoEnabled ? onVideoCall : void 0,
|
|
9129
9134
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 15 })
|
|
9130
9135
|
}
|
|
@@ -9135,12 +9140,27 @@ var DefaultHeaderCallActions = ({
|
|
|
9135
9140
|
};
|
|
9136
9141
|
var HeaderCallActions = (props) => {
|
|
9137
9142
|
const { components, slotClassName } = useChatCustomizationOptional();
|
|
9143
|
+
const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
|
|
9138
9144
|
const Custom = components.HeaderCallActions;
|
|
9139
9145
|
const showPhoneCall = props.showPhoneCall ?? true;
|
|
9140
9146
|
const showVideoCall = props.showVideoCall ?? true;
|
|
9147
|
+
const audioGate = getAudioCallGate();
|
|
9148
|
+
const videoGate = getVideoCallGate();
|
|
9141
9149
|
if (!showPhoneCall && !showVideoCall) return null;
|
|
9142
|
-
const
|
|
9143
|
-
|
|
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 }) });
|
|
9144
9164
|
};
|
|
9145
9165
|
var HeaderCallActions_default = HeaderCallActions;
|
|
9146
9166
|
function OptionsMenuItem({
|
|
@@ -9803,10 +9823,10 @@ function AudioAttachmentPlayer({
|
|
|
9803
9823
|
"div",
|
|
9804
9824
|
{
|
|
9805
9825
|
className: cn(
|
|
9806
|
-
"chat-audio-player w-full min-w-[220px] max-w-[280px] rounded-xl border p-2.5",
|
|
9826
|
+
"chat-audio-player w-full min-w-[220px] max-w-[280px] rounded-xl border p-2.5 transition-shadow duration-150",
|
|
9807
9827
|
onThemeBubble && "chat-audio-player-on-dark border-white/20 bg-white/12 text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] backdrop-blur-[2px]",
|
|
9808
|
-
onIncomingBubble && "border-(--chat-border)/70 bg-(--chat-elevated) text-(--chat-text) shadow-sm [.dark_&]:bg-(--chat-surface)",
|
|
9809
|
-
!inBubble && "border-(--chat-border) bg-(--chat-elevated) text-(--chat-text) shadow-sm",
|
|
9828
|
+
onIncomingBubble && "border-(--chat-border)/70 bg-(--chat-elevated) text-(--chat-text) shadow-sm hover:shadow-md [.dark_&]:bg-(--chat-surface)",
|
|
9829
|
+
!inBubble && "border-(--chat-border) bg-(--chat-elevated) text-(--chat-text) shadow-sm hover:shadow-md",
|
|
9810
9830
|
className
|
|
9811
9831
|
),
|
|
9812
9832
|
children: [
|
|
@@ -9842,7 +9862,7 @@ function AudioAttachmentPlayer({
|
|
|
9842
9862
|
onClick: () => void togglePlay(),
|
|
9843
9863
|
disabled: !url,
|
|
9844
9864
|
className: cn(
|
|
9845
|
-
"flex size-8 shrink-0 items-center justify-center rounded-full border transition-
|
|
9865
|
+
"flex size-8 shrink-0 items-center justify-center rounded-full border transition-all duration-150 hover:scale-105",
|
|
9846
9866
|
onThemeBubble ? "border-white/30 bg-white/10 text-white hover:bg-white/15" : "border-(--chat-border) bg-(--chat-input-bg) text-(--chat-text) hover:bg-(--chat-hover)",
|
|
9847
9867
|
!url && "opacity-40"
|
|
9848
9868
|
),
|
|
@@ -9896,10 +9916,10 @@ function getMediaCardClasses(inBubble, isSender) {
|
|
|
9896
9916
|
const onThemeBubble = inBubble && isSender;
|
|
9897
9917
|
const onIncomingBubble = inBubble && !isSender;
|
|
9898
9918
|
return cn(
|
|
9899
|
-
"w-full min-w-[220px] max-w-[280px] rounded-xl border p-2.5",
|
|
9919
|
+
"w-full min-w-[220px] max-w-[280px] rounded-xl border p-2.5 transition-shadow duration-150",
|
|
9900
9920
|
onThemeBubble && "border-white/20 bg-white/12 text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] backdrop-blur-[2px]",
|
|
9901
|
-
onIncomingBubble && "border-(--chat-border)/70 bg-(--chat-elevated) text-(--chat-text) shadow-sm [.dark_&]:bg-(--chat-surface)",
|
|
9902
|
-
!inBubble && "border-(--chat-border) bg-(--chat-elevated) text-(--chat-text) shadow-sm"
|
|
9921
|
+
onIncomingBubble && "border-(--chat-border)/70 bg-(--chat-elevated) text-(--chat-text) shadow-sm hover:shadow-md [.dark_&]:bg-(--chat-surface)",
|
|
9922
|
+
!inBubble && "border-(--chat-border) bg-(--chat-elevated) text-(--chat-text) shadow-sm hover:shadow-md"
|
|
9903
9923
|
);
|
|
9904
9924
|
}
|
|
9905
9925
|
function VideoAttachmentPlayer({
|
|
@@ -10023,10 +10043,10 @@ function VideoAttachmentPlayer({
|
|
|
10023
10043
|
{
|
|
10024
10044
|
type: "button",
|
|
10025
10045
|
onClick: () => void togglePlay(),
|
|
10026
|
-
className: "absolute inset-0 flex items-center justify-center bg-black/20 transition-colors hover:bg-black/30",
|
|
10046
|
+
className: "group/play absolute inset-0 flex items-center justify-center bg-black/20 transition-colors hover:bg-black/30",
|
|
10027
10047
|
"aria-label": `Play ${name}`,
|
|
10028
10048
|
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
|
|
10029
|
-
"flex items-center justify-center rounded-full shadow-md",
|
|
10049
|
+
"flex items-center justify-center rounded-full shadow-md transition-transform duration-150 group-hover/play:scale-110",
|
|
10030
10050
|
compact ? "size-9" : "size-11",
|
|
10031
10051
|
onThemeBubble ? "bg-white/95 text-(--chat-theme)" : "bg-(--chat-surface)/95 text-(--chat-text)"
|
|
10032
10052
|
), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { size: compact ? 15 : 18, className: "ml-0.5 fill-current" }) })
|
|
@@ -10069,13 +10089,13 @@ function getAttachmentIcon(fileName, type, className, size = 20) {
|
|
|
10069
10089
|
}
|
|
10070
10090
|
function getAttachmentIconBoxClass(fileName, type) {
|
|
10071
10091
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10072
|
-
if (["mp3", "wav", "ogg", "m4a"].includes(ext)) return "bg-violet-500";
|
|
10073
|
-
if (["mp4", "mov", "webm"].includes(ext)) return "bg-pink-500";
|
|
10074
|
-
if (ext === "pdf") return "bg-red-500";
|
|
10075
|
-
if (["doc", "docx"].includes(ext)) return "bg-blue-500";
|
|
10076
|
-
if (["xls", "xlsx", "csv"].includes(ext)) return "bg-emerald-600";
|
|
10077
|
-
if (["ppt", "pptx"].includes(ext)) return "bg-orange-500";
|
|
10078
|
-
return "bg-slate-500";
|
|
10092
|
+
if (["mp3", "wav", "ogg", "m4a"].includes(ext)) return "bg-linear-to-br from-violet-500 to-fuchsia-500";
|
|
10093
|
+
if (["mp4", "mov", "webm"].includes(ext)) return "bg-linear-to-br from-pink-500 to-rose-500";
|
|
10094
|
+
if (ext === "pdf") return "bg-linear-to-br from-red-500 to-rose-600";
|
|
10095
|
+
if (["doc", "docx"].includes(ext)) return "bg-linear-to-br from-blue-500 to-indigo-500";
|
|
10096
|
+
if (["xls", "xlsx", "csv"].includes(ext)) return "bg-linear-to-br from-emerald-500 to-teal-600";
|
|
10097
|
+
if (["ppt", "pptx"].includes(ext)) return "bg-linear-to-br from-orange-500 to-amber-500";
|
|
10098
|
+
return "bg-linear-to-br from-slate-500 to-slate-600";
|
|
10079
10099
|
}
|
|
10080
10100
|
var MessageStatus = ({ status, isPending, className }) => {
|
|
10081
10101
|
const resolvedStatus = status || "sent";
|
|
@@ -11225,28 +11245,28 @@ function ImageTile({ att, className, fill, embeddedInBubble, showImageCaption, o
|
|
|
11225
11245
|
{
|
|
11226
11246
|
type: "button",
|
|
11227
11247
|
className: cn(
|
|
11228
|
-
"relative block w-full overflow-hidden bg-(--chat-elevated)",
|
|
11248
|
+
"group relative block w-full overflow-hidden bg-(--chat-elevated)",
|
|
11229
11249
|
fill ? "h-full min-h-0" : "",
|
|
11230
11250
|
className
|
|
11231
11251
|
),
|
|
11232
11252
|
onClick: onOpen,
|
|
11233
11253
|
"aria-label": `Open ${att.name || "image"}`,
|
|
11234
11254
|
children: [
|
|
11235
|
-
mediaUrl ? (
|
|
11236
|
-
// eslint-disable-next-line @next/next/no-img-element -- remote chat attachment URLs are dynamic/user-supplied
|
|
11255
|
+
mediaUrl ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11237
11256
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11238
11257
|
"img",
|
|
11239
11258
|
{
|
|
11240
11259
|
src: mediaUrl,
|
|
11241
11260
|
alt: att.name || "Image",
|
|
11242
11261
|
className: cn(
|
|
11243
|
-
"block",
|
|
11262
|
+
"block transition-transform duration-300 ease-out group-hover:scale-[1.04]",
|
|
11244
11263
|
fill || embeddedInBubble ? "size-full object-cover" : "max-h-[300px] w-full rounded-xl object-contain"
|
|
11245
11264
|
),
|
|
11246
11265
|
loading: "lazy"
|
|
11247
11266
|
}
|
|
11248
|
-
)
|
|
11249
|
-
|
|
11267
|
+
),
|
|
11268
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute inset-0 bg-black/0 transition-colors duration-200 group-hover:bg-black/10", "aria-hidden": true })
|
|
11269
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-full min-h-[80px] items-center justify-center text-xs text-(--chat-muted)", children: "Image unavailable" }),
|
|
11250
11270
|
showImageCaption && imageCaption ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 z-10 bg-linear-to-t from-black/75 via-black/35 to-transparent px-1.5 pb-1 pt-5 text-left", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-3 text-[10px] leading-snug text-white", children: imageCaption }) }) : null
|
|
11251
11271
|
]
|
|
11252
11272
|
}
|
|
@@ -11681,14 +11701,14 @@ function FileAttachmentRow({
|
|
|
11681
11701
|
"div",
|
|
11682
11702
|
{
|
|
11683
11703
|
className: cn(
|
|
11684
|
-
"group w-full transition-
|
|
11704
|
+
"group w-full transition-all duration-150",
|
|
11685
11705
|
!stacked && "flex min-w-[220px] items-center gap-2",
|
|
11686
11706
|
stacked && "py-1.5",
|
|
11687
11707
|
stacked && showDivider && (onThemeBubble ? "border-t border-white/15" : "border-t border-(--chat-border)/35"),
|
|
11688
11708
|
!stacked && "rounded-xl border p-2.5",
|
|
11689
|
-
!stacked && onThemeBubble && "border-white/20 bg-white/12 text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] backdrop-blur-[2px]",
|
|
11690
|
-
!stacked && onIncomingBubble && "border-(--chat-border)/70 bg-(--chat-elevated) text-(--chat-text) shadow-sm [.dark_&]:bg-(--chat-surface)",
|
|
11691
|
-
!stacked && !inBubble && "border-(--chat-border) bg-(--chat-surface) shadow-sm",
|
|
11709
|
+
!stacked && onThemeBubble && "border-white/20 bg-white/12 text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] backdrop-blur-[2px] hover:bg-white/16",
|
|
11710
|
+
!stacked && onIncomingBubble && "border-(--chat-border)/70 bg-(--chat-elevated) text-(--chat-text) shadow-sm hover:border-(--chat-theme)/30 hover:shadow-md [.dark_&]:bg-(--chat-surface)",
|
|
11711
|
+
!stacked && !inBubble && "border-(--chat-border) bg-(--chat-surface) shadow-sm hover:border-(--chat-theme)/30 hover:shadow-md",
|
|
11692
11712
|
!fileUrl && "pointer-events-none opacity-60",
|
|
11693
11713
|
isCaptionActive && "opacity-90"
|
|
11694
11714
|
),
|
|
@@ -11703,7 +11723,7 @@ function FileAttachmentRow({
|
|
|
11703
11723
|
className: "flex min-w-0 flex-1 items-center gap-2.5 text-left disabled:cursor-default",
|
|
11704
11724
|
children: [
|
|
11705
11725
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
|
|
11706
|
-
"flex size-10 shrink-0 items-center justify-center rounded-lg text-white shadow-sm",
|
|
11726
|
+
"flex size-10 shrink-0 items-center justify-center rounded-lg text-white shadow-sm transition-transform duration-150 group-hover:scale-105",
|
|
11707
11727
|
getAttachmentIconBoxClass(rawName)
|
|
11708
11728
|
), children: getAttachmentIcon(rawName, "file", "text-white", 18) }),
|
|
11709
11729
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -19736,11 +19756,35 @@ var packageDefaultComponents = {
|
|
|
19736
19756
|
ChatSocketStatus: ChatSocketStatus_default
|
|
19737
19757
|
};
|
|
19738
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
|
+
|
|
19739
19782
|
// src/index.ts
|
|
19740
19783
|
init_settings_types();
|
|
19741
19784
|
init_api_service();
|
|
19742
19785
|
var index_default = ChatMain_default;
|
|
19743
19786
|
|
|
19787
|
+
exports.AdminPermissionTooltip = AdminPermissionTooltip;
|
|
19744
19788
|
exports.Calendar = Calendar;
|
|
19745
19789
|
exports.CalendarDayButton = CalendarDayButton;
|
|
19746
19790
|
exports.Channel = Channel;
|
|
@@ -19823,6 +19867,7 @@ exports.smoothScrollToMessage = smoothScrollToMessage;
|
|
|
19823
19867
|
exports.startOfLocalDay = startOfLocalDay;
|
|
19824
19868
|
exports.toDateInputValue = toDateInputValue;
|
|
19825
19869
|
exports.uniqueList = uniqueList;
|
|
19870
|
+
exports.useAdminFeatureGate = useAdminFeatureGate;
|
|
19826
19871
|
exports.useChatAlerts = useChatAlerts;
|
|
19827
19872
|
exports.useChatContext = useChatContext;
|
|
19828
19873
|
exports.useChatController = useChatController;
|