@realtimexsco/live-chat 1.4.10 → 1.4.12

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/dist/index.mjs CHANGED
@@ -2,14 +2,14 @@ import { io } from 'socket.io-client';
2
2
  import axios from 'axios';
3
3
  import { create } from 'zustand';
4
4
  import { persist } from 'zustand/middleware';
5
- import * as React2 from 'react';
6
- import React2__default, { createContext, useMemo, useContext, useState, useCallback, useRef, useEffect, useLayoutEffect, useSyncExternalStore } from 'react';
7
- import { Tooltip as Tooltip$1, Slot, Switch as Switch$1, Avatar as Avatar$1, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, Dialog as Dialog$1, AlertDialog as AlertDialog$1 } from 'radix-ui';
5
+ import * as React3 from 'react';
6
+ import React3__default, { createContext, useMemo, useContext, useState, useCallback, useEffect, useRef, useLayoutEffect, Fragment as Fragment$1, useSyncExternalStore } from 'react';
7
+ import { Tooltip as Tooltip$1, Slot, Switch as Switch$1, Dialog as Dialog$1, Avatar as Avatar$1, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, AlertDialog as AlertDialog$1 } from 'radix-ui';
8
8
  import { clsx } from 'clsx';
9
9
  import { twMerge } from 'tailwind-merge';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import { flushSync } from 'react-dom';
12
- import { Sun, Moon, Search, X, Loader2, UserPlus, ChevronDown, ArrowLeft, Users, WifiOff, Wifi, Sparkles, MessageSquareText, ChevronRight, Pin, Star, MoreVertical, Info, UserKey, ShieldUser, Paperclip, Clock, MessageSquareX, Trash2, LogOut, Ban, CheckCheck, Check, Camera, Save, Pencil, Mail, CornerUpLeft, CheckSquare, Smile, ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon, MessageCircleMore, History, Shield, Send, AlertCircle, ShieldCheck, UserMinus, Settings, MessageCircle, Phone, Video, ExternalLink, Download, ChevronLeft, ZoomOut, ZoomIn, RotateCcw, Music, ArrowRight, MoreHorizontal, PinOff, StarOff, XIcon, ImageIcon, Film, Play, Pause, FileText, FileSpreadsheet, FileIcon, MessageSquare, Eye, Captions } from 'lucide-react';
12
+ import { Sun, Moon, Loader2, Settings, Search, X, UserPlus, ChevronDown, ArrowLeft, Users, WifiOff, Wifi, Sparkles, MessageSquareText, ChevronRight, Pin, Star, MoreVertical, Info, UserKey, ShieldUser, Paperclip, Clock, MessageSquareX, Trash2, LogOut, Ban, CheckCheck, Check, Camera, Save, Pencil, Mail, CornerUpLeft, CheckSquare, Smile, ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon, MessageCircleMore, History, Shield, Send, AlertCircle, ShieldCheck, UserMinus, MessageCircle, Phone, Video, ExternalLink, Download, ChevronLeft, ZoomOut, ZoomIn, RotateCcw, Music, XIcon, ArrowRight, MoreHorizontal, PinOff, StarOff, ImageIcon, Film, Play, Pause, FileText, FileSpreadsheet, FileIcon, MessageSquare, Eye, Captions } from 'lucide-react';
13
13
  import { cva } from 'class-variance-authority';
14
14
  import EmojiPicker, { Theme } from 'emoji-picker-react';
15
15
  import { getDefaultClassNames, DayPicker } from 'react-day-picker';
@@ -1162,6 +1162,7 @@ function resolveChatApiKey(url) {
1162
1162
  }
1163
1163
  if (path.includes("/message/search-messages")) return "searchMessages";
1164
1164
  if (path.includes("/message/searched-message/context")) return "searchMessagesContext";
1165
+ if (path.includes("/settings/effective")) return "effectiveSettings";
1165
1166
  if (path.includes("/conversation/group/create")) return "createGroup";
1166
1167
  if (path.includes("/conversation/group/permission/update")) return "updateGroupPermissions";
1167
1168
  if (path.includes("/conversation/group/information/update")) return "updateGroupInfo";
@@ -1205,7 +1206,8 @@ var init_query_params = __esm({
1205
1206
  "pinnedMessages",
1206
1207
  "starredMessages",
1207
1208
  "searchMessages",
1208
- "searchMessagesContext"
1209
+ "searchMessagesContext",
1210
+ "effectiveSettings"
1209
1211
  ];
1210
1212
  CHAT_API_KEYS = [
1211
1213
  "all",
@@ -1223,6 +1225,64 @@ var init_query_params = __esm({
1223
1225
  ];
1224
1226
  }
1225
1227
  });
1228
+
1229
+ // src/services/api/api-version.ts
1230
+ var DEFAULT_VERSIONS, apiVersions, normalizeApiVersion, resolveApiRoot, buildVersionedApiUrl, setChatApiVersions, getChatApiVersions, setChatPushApiVersion, syncDefaultApiVersionFromBaseUrl;
1231
+ var init_api_version = __esm({
1232
+ "src/services/api/api-version.ts"() {
1233
+ init_client();
1234
+ DEFAULT_VERSIONS = {
1235
+ v1: "v1",
1236
+ v2: "v2"
1237
+ };
1238
+ apiVersions = { ...DEFAULT_VERSIONS };
1239
+ normalizeApiVersion = (version) => {
1240
+ const raw = String(version || "v1").trim().replace(/^\/+|\/+$/g, "");
1241
+ if (!raw) return "v1";
1242
+ return /^v\d+$/i.test(raw) ? raw.toLowerCase() : `v${raw}`;
1243
+ };
1244
+ resolveApiRoot = (baseUrl) => {
1245
+ const raw = String(
1246
+ baseUrl || getChatBaseUrl() || apiClient.defaults.baseURL || ""
1247
+ ).trim().replace(/\/$/, "");
1248
+ if (!raw) return "";
1249
+ const withoutVersion = raw.replace(/\/v\d+$/i, "");
1250
+ return withoutVersion || raw;
1251
+ };
1252
+ buildVersionedApiUrl = (path, options) => {
1253
+ const root = resolveApiRoot(options?.baseUrl);
1254
+ const version = normalizeApiVersion(
1255
+ options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.v1)
1256
+ );
1257
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1258
+ if (!root) {
1259
+ return `/${version}${normalizedPath}`;
1260
+ }
1261
+ return `${root}/${version}${normalizedPath}`;
1262
+ };
1263
+ setChatApiVersions = (config) => {
1264
+ if (!config) return;
1265
+ if (config.v1) {
1266
+ apiVersions.v1 = normalizeApiVersion(config.v1);
1267
+ }
1268
+ if (config.v2) {
1269
+ apiVersions.v2 = normalizeApiVersion(config.v2);
1270
+ }
1271
+ };
1272
+ getChatApiVersions = () => ({
1273
+ ...apiVersions
1274
+ });
1275
+ setChatPushApiVersion = (version) => {
1276
+ apiVersions.v2 = normalizeApiVersion(version);
1277
+ };
1278
+ syncDefaultApiVersionFromBaseUrl = (baseUrl) => {
1279
+ const match = String(baseUrl || "").trim().match(/\/(v\d+)\/?$/i);
1280
+ if (match?.[1]) {
1281
+ apiVersions.v1 = normalizeApiVersion(match[1]);
1282
+ }
1283
+ };
1284
+ }
1285
+ });
1226
1286
  function resolveParamsForRequest(url, method) {
1227
1287
  const apiKey = resolveChatApiKey(url);
1228
1288
  const httpMethod = (method || "get").toLowerCase();
@@ -1239,7 +1299,9 @@ var chatClientId, chatAccessToken, chatBaseUrl, chatSocketUrl, chatQueryParams,
1239
1299
  var init_client = __esm({
1240
1300
  "src/services/api/client.ts"() {
1241
1301
  init_query_params();
1302
+ init_api_version();
1242
1303
  init_query_params();
1304
+ init_api_version();
1243
1305
  chatClientId = "";
1244
1306
  chatAccessToken = "";
1245
1307
  chatBaseUrl = "";
@@ -1257,6 +1319,7 @@ var init_client = __esm({
1257
1319
  setChatBaseURL = (url) => {
1258
1320
  chatBaseUrl = url.trim();
1259
1321
  apiClient.defaults.baseURL = chatBaseUrl;
1322
+ syncDefaultApiVersionFromBaseUrl(chatBaseUrl);
1260
1323
  };
1261
1324
  getChatBaseUrl = () => chatBaseUrl;
1262
1325
  setChatSocketUrl = (url) => {
@@ -1360,6 +1423,9 @@ var init_api_constants = __esm({
1360
1423
  SEARCH_MESSAGES: "/message/search-messages",
1361
1424
  SEARCH_MESSAGES_CONTEXT: (messageId2) => `/message/searched-message/context?messageId=${messageId2}`,
1362
1425
  BLOCK_UNBLOCK_USER: "/user/block-unblock"
1426
+ },
1427
+ SETTINGS: {
1428
+ PERMISSIONS: "/settings/effective"
1363
1429
  }
1364
1430
  };
1365
1431
  }
@@ -1396,13 +1462,11 @@ var init_auth_api = __esm({
1396
1462
  });
1397
1463
 
1398
1464
  // src/constants/chat.constants.ts
1399
- var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, DM_EDIT_WINDOW_MS, GROUP_EDIT_WINDOW_MS, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1465
+ var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1400
1466
  var init_chat_constants = __esm({
1401
1467
  "src/constants/chat.constants.ts"() {
1402
1468
  DEFAULT_MESSAGES_LIMIT = 20;
1403
1469
  DEFAULT_CONVERSATIONS_LIMIT = 20;
1404
- DM_EDIT_WINDOW_MS = 5 * 60 * 1e3;
1405
- GROUP_EDIT_WINDOW_MS = 10 * 60 * 1e3;
1406
1470
  MAX_ATTACHMENT_CAPTION_LENGTH = 100;
1407
1471
  ATTACHMENT_FILENAME_TIMESTAMP_LENGTH = 13;
1408
1472
  }
@@ -1605,6 +1669,206 @@ var init_uploads_api = __esm({
1605
1669
  }
1606
1670
  });
1607
1671
 
1672
+ // src/types/settings.types.ts
1673
+ var DEFAULT_EFFECTIVE_SETTINGS, ADMIN_PERMISSION_DENIED_MESSAGE, CALLS_NOT_CONFIGURED_MESSAGE;
1674
+ var init_settings_types = __esm({
1675
+ "src/types/settings.types.ts"() {
1676
+ DEFAULT_EFFECTIVE_SETTINGS = {
1677
+ pushNotificationEnabled: true,
1678
+ videoCallEnabled: true,
1679
+ audioCallEnabled: true,
1680
+ blockUsersEnabled: true,
1681
+ calls: {
1682
+ enabled: true,
1683
+ configured: true
1684
+ },
1685
+ groupPolicy: {
1686
+ defaults: {
1687
+ onlyAdminCanSendMessage: false,
1688
+ onlyAdminCanEditInfo: false,
1689
+ senderCanEditMessage: true,
1690
+ allowMemberAdd: true,
1691
+ allowMemberRemove: false,
1692
+ moderationEnabled: false
1693
+ },
1694
+ locked: []
1695
+ },
1696
+ messageTimers: {
1697
+ dmEditMinutes: 5,
1698
+ dmDeleteMinutes: 5,
1699
+ groupEditMinutes: 10,
1700
+ groupDeleteMinutes: 10
1701
+ },
1702
+ attachments: {
1703
+ enabled: true,
1704
+ maxFileSizeMB: 10,
1705
+ allowedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
1706
+ maxAttachmentsPerMessage: 10
1707
+ },
1708
+ maxAdminsPerGroup: 5,
1709
+ maxParticipantsPerGroup: 100,
1710
+ maxPinnedMessagesPerConversation: 10,
1711
+ groupCreationEnabled: true
1712
+ };
1713
+ ADMIN_PERMISSION_DENIED_MESSAGE = "This feature is disabled by your administrator.";
1714
+ CALLS_NOT_CONFIGURED_MESSAGE = "Calls are not configured. Please contact your administrator.";
1715
+ }
1716
+ });
1717
+
1718
+ // src/services/api/settings.api.ts
1719
+ function asBoolean(value, fallback) {
1720
+ return typeof value === "boolean" ? value : fallback;
1721
+ }
1722
+ function asPositiveNumber(value, fallback) {
1723
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
1724
+ }
1725
+ function asStringArray(value, fallback) {
1726
+ if (!Array.isArray(value)) return fallback;
1727
+ const next = value.filter((item) => typeof item === "string");
1728
+ return next.length > 0 ? next : fallback;
1729
+ }
1730
+ function normalizeGroupPermissions(value, fallback) {
1731
+ if (!value || typeof value !== "object") return { ...fallback };
1732
+ const raw = value;
1733
+ return {
1734
+ onlyAdminCanSendMessage: asBoolean(
1735
+ raw.onlyAdminCanSendMessage,
1736
+ fallback.onlyAdminCanSendMessage
1737
+ ),
1738
+ onlyAdminCanEditInfo: asBoolean(
1739
+ raw.onlyAdminCanEditInfo,
1740
+ fallback.onlyAdminCanEditInfo
1741
+ ),
1742
+ senderCanEditMessage: asBoolean(
1743
+ raw.senderCanEditMessage,
1744
+ fallback.senderCanEditMessage
1745
+ ),
1746
+ allowMemberAdd: asBoolean(raw.allowMemberAdd, fallback.allowMemberAdd),
1747
+ allowMemberRemove: asBoolean(
1748
+ raw.allowMemberRemove,
1749
+ fallback.allowMemberRemove
1750
+ ),
1751
+ moderationEnabled: asBoolean(
1752
+ raw.moderationEnabled,
1753
+ fallback.moderationEnabled
1754
+ )
1755
+ };
1756
+ }
1757
+ function normalizeCalls(value, fallback) {
1758
+ if (!value || typeof value !== "object") return { ...fallback };
1759
+ const raw = value;
1760
+ return {
1761
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1762
+ configured: asBoolean(raw.configured, fallback.configured)
1763
+ };
1764
+ }
1765
+ function normalizeGroupPolicy(value, fallback) {
1766
+ if (!value || typeof value !== "object") {
1767
+ return {
1768
+ defaults: { ...fallback.defaults },
1769
+ locked: [...fallback.locked]
1770
+ };
1771
+ }
1772
+ const raw = value;
1773
+ return {
1774
+ defaults: normalizeGroupPermissions(raw.defaults, fallback.defaults),
1775
+ locked: asStringArray(raw.locked, fallback.locked)
1776
+ };
1777
+ }
1778
+ function normalizeMessageTimers(value, fallback) {
1779
+ if (!value || typeof value !== "object") return { ...fallback };
1780
+ const raw = value;
1781
+ return {
1782
+ dmEditMinutes: asPositiveNumber(raw.dmEditMinutes, fallback.dmEditMinutes),
1783
+ dmDeleteMinutes: asPositiveNumber(
1784
+ raw.dmDeleteMinutes,
1785
+ fallback.dmDeleteMinutes
1786
+ ),
1787
+ groupEditMinutes: asPositiveNumber(
1788
+ raw.groupEditMinutes,
1789
+ fallback.groupEditMinutes
1790
+ ),
1791
+ groupDeleteMinutes: asPositiveNumber(
1792
+ raw.groupDeleteMinutes,
1793
+ fallback.groupDeleteMinutes
1794
+ )
1795
+ };
1796
+ }
1797
+ function normalizeAttachments(value, fallback) {
1798
+ if (!value || typeof value !== "object") return { ...fallback };
1799
+ const raw = value;
1800
+ return {
1801
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1802
+ maxFileSizeMB: asPositiveNumber(raw.maxFileSizeMB, fallback.maxFileSizeMB),
1803
+ allowedMimeTypes: asStringArray(
1804
+ raw.allowedMimeTypes,
1805
+ fallback.allowedMimeTypes
1806
+ ),
1807
+ maxAttachmentsPerMessage: asPositiveNumber(
1808
+ raw.maxAttachmentsPerMessage,
1809
+ fallback.maxAttachmentsPerMessage
1810
+ )
1811
+ };
1812
+ }
1813
+ function normalizeEffectiveSettings(raw) {
1814
+ const payload = raw && typeof raw === "object" ? raw.data ?? raw : null;
1815
+ const source = payload && typeof payload === "object" ? payload : {};
1816
+ const defaults = DEFAULT_EFFECTIVE_SETTINGS;
1817
+ return {
1818
+ pushNotificationEnabled: asBoolean(
1819
+ source.pushNotificationEnabled,
1820
+ defaults.pushNotificationEnabled
1821
+ ),
1822
+ videoCallEnabled: asBoolean(
1823
+ source.videoCallEnabled,
1824
+ defaults.videoCallEnabled
1825
+ ),
1826
+ audioCallEnabled: asBoolean(
1827
+ source.audioCallEnabled,
1828
+ defaults.audioCallEnabled
1829
+ ),
1830
+ blockUsersEnabled: asBoolean(
1831
+ source.blockUsersEnabled,
1832
+ defaults.blockUsersEnabled
1833
+ ),
1834
+ calls: normalizeCalls(source.calls, defaults.calls),
1835
+ groupPolicy: normalizeGroupPolicy(source.groupPolicy, defaults.groupPolicy),
1836
+ messageTimers: normalizeMessageTimers(
1837
+ source.messageTimers,
1838
+ defaults.messageTimers
1839
+ ),
1840
+ attachments: normalizeAttachments(source.attachments, defaults.attachments),
1841
+ maxAdminsPerGroup: asPositiveNumber(
1842
+ source.maxAdminsPerGroup,
1843
+ defaults.maxAdminsPerGroup
1844
+ ),
1845
+ maxParticipantsPerGroup: asPositiveNumber(
1846
+ source.maxParticipantsPerGroup,
1847
+ defaults.maxParticipantsPerGroup
1848
+ ),
1849
+ maxPinnedMessagesPerConversation: asPositiveNumber(
1850
+ source.maxPinnedMessagesPerConversation,
1851
+ defaults.maxPinnedMessagesPerConversation
1852
+ ),
1853
+ groupCreationEnabled: asBoolean(
1854
+ source.groupCreationEnabled,
1855
+ defaults.groupCreationEnabled
1856
+ )
1857
+ };
1858
+ }
1859
+ var fetchEffectiveSettings;
1860
+ var init_settings_api = __esm({
1861
+ "src/services/api/settings.api.ts"() {
1862
+ init_api_constants();
1863
+ init_client();
1864
+ init_settings_types();
1865
+ fetchEffectiveSettings = async () => {
1866
+ const response = await apiClient.get(API_ENDPOINTS.SETTINGS.PERMISSIONS);
1867
+ return normalizeEffectiveSettings(response.data);
1868
+ };
1869
+ }
1870
+ });
1871
+
1608
1872
  // src/services/api/index.ts
1609
1873
  var init_api = __esm({
1610
1874
  "src/services/api/index.ts"() {
@@ -1613,6 +1877,7 @@ var init_api = __esm({
1613
1877
  init_messages_api();
1614
1878
  init_groups_api();
1615
1879
  init_uploads_api();
1880
+ init_settings_api();
1616
1881
  }
1617
1882
  });
1618
1883
 
@@ -3722,7 +3987,7 @@ function resolveApiUrl(apiUrl) {
3722
3987
  function resolveSocketUrl(socketUrl, apiUrl) {
3723
3988
  const explicit = (socketUrl ?? "").trim();
3724
3989
  if (explicit) return explicit;
3725
- return resolveApiUrl(apiUrl).replace(/\/api\/v1\/?$/, "");
3990
+ return resolveApiUrl(apiUrl).replace(/\/api\/v\d+\/?$/i, "");
3726
3991
  }
3727
3992
 
3728
3993
  // src/hooks/chat-sync/useChatSync.ts
@@ -4444,6 +4709,143 @@ var useChatCustomizationOptional = () => {
4444
4709
  };
4445
4710
  var useChatFeatures = () => useChatCustomization().features;
4446
4711
  var useChatFeaturesOptional = () => useChatCustomizationOptional().features;
4712
+
4713
+ // src/context/ChatEffectiveSettingsContext.tsx
4714
+ init_settings_api();
4715
+ init_settings_types();
4716
+ var ChatEffectiveSettingsContext = createContext(null);
4717
+ function buildCallGate(settings, featureEnabled) {
4718
+ if (!settings.calls.configured) {
4719
+ return {
4720
+ visible: true,
4721
+ enabled: false,
4722
+ disabledReason: CALLS_NOT_CONFIGURED_MESSAGE
4723
+ };
4724
+ }
4725
+ if (!settings.calls.enabled) {
4726
+ return {
4727
+ visible: true,
4728
+ enabled: false,
4729
+ disabledReason: ADMIN_PERMISSION_DENIED_MESSAGE
4730
+ };
4731
+ }
4732
+ if (!featureEnabled) {
4733
+ return {
4734
+ visible: true,
4735
+ enabled: false,
4736
+ disabledReason: ADMIN_PERMISSION_DENIED_MESSAGE
4737
+ };
4738
+ }
4739
+ return { visible: true, enabled: true, disabledReason: null };
4740
+ }
4741
+ function ChatEffectiveSettingsProvider({
4742
+ children,
4743
+ enabled = true
4744
+ }) {
4745
+ const [settings, setSettings] = useState(
4746
+ DEFAULT_EFFECTIVE_SETTINGS
4747
+ );
4748
+ const [loading, setLoading] = useState(true);
4749
+ const [error, setError] = useState(null);
4750
+ const refresh = useCallback(async () => {
4751
+ if (!enabled) {
4752
+ setLoading(false);
4753
+ return;
4754
+ }
4755
+ setLoading(true);
4756
+ try {
4757
+ const next = await fetchEffectiveSettings();
4758
+ setSettings(next);
4759
+ setError(null);
4760
+ } catch (err) {
4761
+ console.error("Failed to fetch effective settings:", err);
4762
+ setError(
4763
+ err instanceof Error ? err.message : "Could not load chat permissions"
4764
+ );
4765
+ setSettings(DEFAULT_EFFECTIVE_SETTINGS);
4766
+ } finally {
4767
+ setLoading(false);
4768
+ }
4769
+ }, [enabled]);
4770
+ useEffect(() => {
4771
+ void refresh();
4772
+ }, [refresh]);
4773
+ const value = useMemo(() => {
4774
+ const isGroupPermissionLocked = (key) => settings.groupPolicy.locked.includes(key);
4775
+ return {
4776
+ settings,
4777
+ loading,
4778
+ error,
4779
+ refresh,
4780
+ isGroupPermissionLocked,
4781
+ getAudioCallGate: () => buildCallGate(settings, settings.audioCallEnabled),
4782
+ getVideoCallGate: () => buildCallGate(settings, settings.videoCallEnabled),
4783
+ getPushNotificationGate: () => ({
4784
+ visible: true,
4785
+ enabled: settings.pushNotificationEnabled,
4786
+ disabledReason: settings.pushNotificationEnabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4787
+ }),
4788
+ getBlockUsersGate: () => ({
4789
+ visible: true,
4790
+ enabled: settings.blockUsersEnabled,
4791
+ disabledReason: settings.blockUsersEnabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4792
+ }),
4793
+ getAttachmentsGate: () => ({
4794
+ visible: true,
4795
+ enabled: settings.attachments.enabled,
4796
+ disabledReason: settings.attachments.enabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4797
+ }),
4798
+ getGroupCreationGate: () => ({
4799
+ visible: true,
4800
+ enabled: settings.groupCreationEnabled,
4801
+ disabledReason: settings.groupCreationEnabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4802
+ })
4803
+ };
4804
+ }, [settings, loading, error, refresh]);
4805
+ return /* @__PURE__ */ jsx(ChatEffectiveSettingsContext.Provider, { value, children });
4806
+ }
4807
+ function useEffectiveSettings() {
4808
+ const ctx = useContext(ChatEffectiveSettingsContext);
4809
+ if (!ctx) {
4810
+ throw new Error(
4811
+ "useEffectiveSettings must be used within ChatEffectiveSettingsProvider"
4812
+ );
4813
+ }
4814
+ return ctx;
4815
+ }
4816
+ function useEffectiveSettingsOptional() {
4817
+ const ctx = useContext(ChatEffectiveSettingsContext);
4818
+ if (ctx) return ctx;
4819
+ return {
4820
+ settings: DEFAULT_EFFECTIVE_SETTINGS,
4821
+ loading: false,
4822
+ error: null,
4823
+ refresh: async () => void 0,
4824
+ isGroupPermissionLocked: () => false,
4825
+ getAudioCallGate: () => buildCallGate(DEFAULT_EFFECTIVE_SETTINGS, true),
4826
+ getVideoCallGate: () => buildCallGate(DEFAULT_EFFECTIVE_SETTINGS, true),
4827
+ getPushNotificationGate: () => ({
4828
+ visible: true,
4829
+ enabled: true,
4830
+ disabledReason: null
4831
+ }),
4832
+ getBlockUsersGate: () => ({
4833
+ visible: true,
4834
+ enabled: true,
4835
+ disabledReason: null
4836
+ }),
4837
+ getAttachmentsGate: () => ({
4838
+ visible: true,
4839
+ enabled: true,
4840
+ disabledReason: null
4841
+ }),
4842
+ getGroupCreationGate: () => ({
4843
+ visible: true,
4844
+ enabled: true,
4845
+ disabledReason: null
4846
+ })
4847
+ };
4848
+ }
4447
4849
  var ChatContext = createContext(void 0);
4448
4850
  var useChatContext = () => {
4449
4851
  const context = useContext(ChatContext);
@@ -4469,6 +4871,7 @@ var Chat = ({
4469
4871
  queryParams,
4470
4872
  queryParamApis,
4471
4873
  queryParamsByApi,
4874
+ apiVersions: apiVersions2,
4472
4875
  channelsData,
4473
4876
  messagesData,
4474
4877
  onMessageReceived,
@@ -4482,7 +4885,7 @@ var Chat = ({
4482
4885
  const status = useStore((s) => s.status);
4483
4886
  const error = useStore((s) => s.error);
4484
4887
  useChatSync();
4485
- React2__default.useEffect(() => {
4888
+ React3__default.useEffect(() => {
4486
4889
  const clientId = client?.id;
4487
4890
  const initializeChat = async () => {
4488
4891
  if (!clientId || !loggedUserDetails) return;
@@ -4494,6 +4897,7 @@ var Chat = ({
4494
4897
  const resolvedApiUrl = resolveApiUrl(apiUrl);
4495
4898
  const resolvedSocketUrl = resolveSocketUrl(socketUrl, resolvedApiUrl);
4496
4899
  if (resolvedApiUrl) setChatBaseURL(resolvedApiUrl);
4900
+ setChatApiVersions(apiVersions2);
4497
4901
  if (resolvedSocketUrl) setChatSocketUrl(resolvedSocketUrl);
4498
4902
  let currentToken = accessToken || "";
4499
4903
  let sessionUser = resolveUserFromAccessToken(currentToken, loggedUserDetails);
@@ -4571,28 +4975,28 @@ var Chat = ({
4571
4975
  queryParamApis: queryParamApis ?? ["get"],
4572
4976
  queryParamsByApi: queryParamsByApi ?? {}
4573
4977
  });
4574
- React2__default.useEffect(() => {
4978
+ React3__default.useEffect(() => {
4575
4979
  setChatQueryParams(queryParams);
4576
4980
  setChatQueryParamApis(queryParamApis);
4577
4981
  setChatQueryParamsByApi(queryParamsByApi);
4578
4982
  }, [queryConfigKey]);
4579
- React2__default.useEffect(() => {
4983
+ React3__default.useEffect(() => {
4580
4984
  if (lastDM && onMessageReceived) {
4581
4985
  onMessageReceived(lastDM);
4582
4986
  }
4583
4987
  }, [lastDM, onMessageReceived]);
4584
- React2__default.useEffect(() => {
4988
+ React3__default.useEffect(() => {
4585
4989
  if (status === "connected" && onSocketConnected) {
4586
4990
  const socketId = useStore.getState().socketId;
4587
4991
  if (socketId) onSocketConnected(socketId);
4588
4992
  }
4589
4993
  }, [status, onSocketConnected]);
4590
- React2__default.useEffect(() => {
4994
+ React3__default.useEffect(() => {
4591
4995
  if (status === "error" && error && onSocketError) {
4592
4996
  onSocketError(error);
4593
4997
  }
4594
4998
  }, [status, error, onSocketError]);
4595
- const contextValue = React2__default.useMemo(
4999
+ const contextValue = React3__default.useMemo(
4596
5000
  () => ({
4597
5001
  onSendMessage,
4598
5002
  client,
@@ -4612,7 +5016,7 @@ var Chat = ({
4612
5016
  messagesData
4613
5017
  ]
4614
5018
  );
4615
- const customization = React2__default.useMemo(
5019
+ const customization = React3__default.useMemo(
4616
5020
  () => ({
4617
5021
  components: uiConfig.components,
4618
5022
  classNames: uiConfig.classNames,
@@ -4620,7 +5024,7 @@ var Chat = ({
4620
5024
  }),
4621
5025
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
4622
5026
  );
4623
- return /* @__PURE__ */ jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? children : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
5027
+ return /* @__PURE__ */ jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsx(ChatEffectiveSettingsProvider, { enabled: true, children }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
4624
5028
  };
4625
5029
 
4626
5030
  // src/hooks/useChatController.ts
@@ -5611,6 +6015,18 @@ function formatMemberName(name) {
5611
6015
  function formatLoggedUserName(name) {
5612
6016
  return name ? name.charAt(0).toUpperCase() + name.slice(1) : "User";
5613
6017
  }
6018
+
6019
+ // src/chat/sidebar/build-logged-user-profile.ts
6020
+ var buildLoggedUserProfile = (loggedUserDetails) => ({
6021
+ _id: loggedUserDetails?._id || "1",
6022
+ name: formatLoggedUserName(loggedUserDetails?.name),
6023
+ email: loggedUserDetails?.email || "user@example.com",
6024
+ role: loggedUserDetails?.role || "user",
6025
+ isActive: loggedUserDetails?.isActive ?? true,
6026
+ createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6027
+ updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6028
+ image: loggedUserDetails?.image || void 0
6029
+ });
5614
6030
  function Dialog({
5615
6031
  ...props
5616
6032
  }) {
@@ -6011,6 +6427,8 @@ function useCreateChatDialog({
6011
6427
  const fetchAllUsers = useStore((storeState) => storeState.fetchAllUsers);
6012
6428
  const accessToken = useStore((storeState) => storeState.accessToken);
6013
6429
  const fetchConversations2 = useStore((storeState) => storeState.fetchConversations);
6430
+ const { settings } = useEffectiveSettingsOptional();
6431
+ const maxParticipants = settings.maxParticipantsPerGroup;
6014
6432
  const fileInputRef = useRef(null);
6015
6433
  useEffect(() => {
6016
6434
  if (accessToken) {
@@ -6024,9 +6442,18 @@ function useCreateChatDialog({
6024
6442
  setState((prev) => {
6025
6443
  const memberId = String(member._id);
6026
6444
  const exists = prev.selectedMembers.some((m) => String(m._id) === memberId);
6445
+ if (exists) {
6446
+ return {
6447
+ ...prev,
6448
+ selectedMembers: prev.selectedMembers.filter((m) => String(m._id) !== memberId)
6449
+ };
6450
+ }
6451
+ if (prev.selectedMembers.length + 1 >= maxParticipants) {
6452
+ return prev;
6453
+ }
6027
6454
  return {
6028
6455
  ...prev,
6029
- selectedMembers: exists ? prev.selectedMembers.filter((m) => String(m._id) !== memberId) : [...prev.selectedMembers, member]
6456
+ selectedMembers: [...prev.selectedMembers, member]
6030
6457
  };
6031
6458
  });
6032
6459
  };
@@ -6410,8 +6837,25 @@ function GroupCreateTab({
6410
6837
  ) })
6411
6838
  ] });
6412
6839
  }
6840
+ function AdminPermissionTooltip({
6841
+ disabled,
6842
+ message,
6843
+ side = "top",
6844
+ className,
6845
+ children
6846
+ }) {
6847
+ if (!disabled || !message) {
6848
+ return /* @__PURE__ */ jsx(Fragment, { children });
6849
+ }
6850
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
6851
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: cn("inline-flex", className), children }) }),
6852
+ /* @__PURE__ */ jsx(TooltipContent, { side, className: "max-w-[240px] text-xs", children: message })
6853
+ ] });
6854
+ }
6413
6855
  var CreateChatDialog = (props) => {
6414
6856
  const { isCreateModalOpen = false, setIsCreateModalOpen } = props;
6857
+ const { getGroupCreationGate } = useEffectiveSettingsOptional();
6858
+ const groupCreationGate = getGroupCreationGate();
6415
6859
  const {
6416
6860
  state,
6417
6861
  updateState,
@@ -6465,24 +6909,39 @@ var CreateChatDialog = (props) => {
6465
6909
  ) })
6466
6910
  ] }),
6467
6911
  /* @__PURE__ */ jsx("div", { className: "grid shrink-0 grid-cols-2 gap-2 px-5", children: [
6468
- { id: "dm", label: "Message", icon: MessageCircle },
6469
- { id: "group", label: "New group", icon: Users }
6470
- ].map(({ id, label, icon: Icon }) => /* @__PURE__ */ jsxs(
6471
- "button",
6912
+ { id: "dm", label: "Message", icon: MessageCircle, disabled: false, reason: null },
6472
6913
  {
6473
- type: "button",
6474
- onClick: () => updateState({ activeTab: id }),
6475
- className: cn(
6476
- "flex items-center justify-center gap-2 rounded-full border py-2 text-sm transition-colors",
6477
- state.activeTab === id ? "border-(--chat-theme-20) bg-(--chat-theme-5) text-(--chat-theme)" : "border-(--chat-border) text-(--chat-muted) hover:border-(--chat-theme-20) hover:text-(--chat-text)"
6478
- ),
6479
- children: [
6480
- /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
6481
- label
6482
- ]
6483
- },
6484
- id
6485
- )) }),
6914
+ id: "group",
6915
+ label: "New group",
6916
+ icon: Users,
6917
+ disabled: !groupCreationGate.enabled,
6918
+ reason: groupCreationGate.disabledReason
6919
+ }
6920
+ ].map(({ id, label, icon: Icon, disabled, reason }) => {
6921
+ const tabButton = /* @__PURE__ */ jsxs(
6922
+ "button",
6923
+ {
6924
+ type: "button",
6925
+ disabled,
6926
+ onClick: () => {
6927
+ if (disabled) return;
6928
+ updateState({ activeTab: id });
6929
+ },
6930
+ className: cn(
6931
+ "flex w-full items-center justify-center gap-2 rounded-full border py-2 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40",
6932
+ state.activeTab === id ? "border-(--chat-theme-20) bg-(--chat-theme-5) text-(--chat-theme)" : "border-(--chat-border) text-(--chat-muted) hover:border-(--chat-theme-20) hover:text-(--chat-text)"
6933
+ ),
6934
+ children: [
6935
+ /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
6936
+ label
6937
+ ]
6938
+ }
6939
+ );
6940
+ if (!disabled || !reason) {
6941
+ return /* @__PURE__ */ jsx(Fragment$1, { children: tabButton }, id);
6942
+ }
6943
+ return /* @__PURE__ */ jsx(AdminPermissionTooltip, { disabled: true, message: reason, children: tabButton }, id);
6944
+ }) }),
6486
6945
  state.activeTab === "dm" && /* @__PURE__ */ jsx(
6487
6946
  DmContactsTab,
6488
6947
  {
@@ -6493,7 +6952,7 @@ var CreateChatDialog = (props) => {
6493
6952
  onStartDM: handleStartDM
6494
6953
  }
6495
6954
  ),
6496
- state.activeTab === "group" && /* @__PURE__ */ jsx(
6955
+ state.activeTab === "group" && groupCreationGate.enabled && /* @__PURE__ */ jsx(
6497
6956
  GroupCreateTab,
6498
6957
  {
6499
6958
  state,
@@ -6545,6 +7004,7 @@ function Switch({
6545
7004
 
6546
7005
  // src/notifications/push.service.ts
6547
7006
  init_client();
7007
+ init_api_version();
6548
7008
  var PUSH_TOKEN_STORAGE_KEY = "realtimex-push-token";
6549
7009
  var SERVICE_WORKER_PATH = "/firebase-messaging-sw.js";
6550
7010
  var PUSH_CHANGED_EVENT = "realtimex:push-changed";
@@ -6556,33 +7016,33 @@ var emitPushChanged = (enabled) => {
6556
7016
  } catch {
6557
7017
  }
6558
7018
  };
6559
- var v2Url = "https://realtimex.softwareco.com/api/v2";
7019
+ var pushApiUrl = (path) => buildVersionedApiUrl(path, { service: "v2" });
6560
7020
  var apiGetPushConfig = async () => {
6561
- const { data } = await apiClient.get(`${v2Url}/notifications/push/config`);
7021
+ const { data } = await apiClient.get(pushApiUrl("/notifications/push/config"));
6562
7022
  return data?.data;
6563
7023
  };
6564
7024
  var apiRegisterPushToken = async (token, deviceLabel) => {
6565
- const { data } = await apiClient.post(`${v2Url}/notifications/push/token`, {
7025
+ const { data } = await apiClient.post(pushApiUrl("/notifications/push/token"), {
6566
7026
  token,
6567
7027
  deviceLabel
6568
7028
  });
6569
7029
  return data?.data;
6570
7030
  };
6571
7031
  var apiRemovePushToken = async (token) => {
6572
- const { data } = await apiClient.delete(`${v2Url}/notifications/push/token`, {
7032
+ const { data } = await apiClient.delete(pushApiUrl("/notifications/push/token"), {
6573
7033
  data: { token }
6574
7034
  });
6575
7035
  return data;
6576
7036
  };
6577
7037
  var apiGetPushPreference = async () => {
6578
7038
  const { data } = await apiClient.get(
6579
- `${v2Url}/notifications/push/preference`
7039
+ pushApiUrl("/notifications/push/preference")
6580
7040
  );
6581
7041
  return data?.data;
6582
7042
  };
6583
7043
  var apiUpdatePushPreference = async (enabled) => {
6584
7044
  const { data } = await apiClient.patch(
6585
- `${v2Url}/notifications/push/preference`,
7045
+ pushApiUrl("/notifications/push/preference"),
6586
7046
  { enabled }
6587
7047
  );
6588
7048
  return data?.data;
@@ -6777,13 +7237,15 @@ var usePushNotifications = () => {
6777
7237
  }, [refresh]);
6778
7238
  const setUserEnabled = useCallback(
6779
7239
  async (enabled) => {
6780
- setState((prev) => ({ ...prev, userEnabled: enabled }));
7240
+ setState((prev) => ({ ...prev, loading: true, error: null }));
6781
7241
  try {
6782
7242
  await apiUpdatePushPreference(enabled);
7243
+ setState((prev) => ({ ...prev, userEnabled: enabled, loading: false }));
6783
7244
  } catch (error) {
6784
7245
  setState((prev) => ({
6785
7246
  ...prev,
6786
7247
  userEnabled: !enabled,
7248
+ loading: false,
6787
7249
  error: error instanceof Error ? error.message : "Could not update push preference"
6788
7250
  }));
6789
7251
  }
@@ -6795,95 +7257,229 @@ var usePushNotifications = () => {
6795
7257
  var PushNotificationToggle = ({
6796
7258
  className,
6797
7259
  label = "Push notifications",
6798
- description = "Get notified about new messages on this device"
7260
+ description = "Get notified about new messages on this device",
7261
+ children
6799
7262
  }) => {
6800
7263
  const push = usePushNotifications();
6801
- if (!push.supported || !push.allowedByWorkspace || !push.configured) {
7264
+ const { getPushNotificationGate } = useEffectiveSettingsOptional();
7265
+ const adminGate = getPushNotificationGate();
7266
+ const [isToggling, setIsToggling] = useState(false);
7267
+ if (!push.supported) {
6802
7268
  return null;
6803
7269
  }
6804
7270
  const isOn = push.deviceEnabled && push.userEnabled;
6805
7271
  const permissionBlocked = push.permission === "denied";
7272
+ const adminBlocked = !adminGate.enabled;
7273
+ const notConfigured = !push.configured;
7274
+ const showLoader = isToggling;
7275
+ const isDisabled = showLoader || permissionBlocked || push.loading || adminBlocked || notConfigured;
6806
7276
  const handleChange = async (checked) => {
6807
- if (checked) {
6808
- if (!push.userEnabled) await push.setUserEnabled(true);
6809
- if (!push.deviceEnabled) await push.enable();
6810
- } else {
6811
- await push.disable();
7277
+ if (adminBlocked || notConfigured) return;
7278
+ setIsToggling(true);
7279
+ try {
7280
+ if (checked) {
7281
+ if (!push.userEnabled) await push.setUserEnabled(true);
7282
+ if (!push.deviceEnabled) await push.enable();
7283
+ } else {
7284
+ await push.disable();
7285
+ }
7286
+ } finally {
7287
+ setIsToggling(false);
6812
7288
  }
6813
7289
  };
7290
+ const descriptionText = adminBlocked ? adminGate.disabledReason || description : notConfigured ? "Push notifications are not configured. Please contact your administrator." : permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description;
7291
+ const renderProps = {
7292
+ label,
7293
+ description: descriptionText,
7294
+ checked: adminBlocked || notConfigured ? false : isOn,
7295
+ disabled: isDisabled,
7296
+ loading: showLoader || push.loading,
7297
+ permissionBlocked,
7298
+ error: push.error,
7299
+ onCheckedChange: (checked) => void handleChange(checked)
7300
+ };
7301
+ if (children) {
7302
+ return /* @__PURE__ */ jsx(Fragment, { children: children(renderProps) });
7303
+ }
6814
7304
  return /* @__PURE__ */ jsxs(
6815
7305
  "div",
6816
7306
  {
6817
- className: className ?? "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-surface) px-4 py-3",
7307
+ className: cn(
7308
+ "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-surface) px-4 py-3",
7309
+ className
7310
+ ),
6818
7311
  children: [
6819
7312
  /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
6820
7313
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-(--chat-text)", children: label }),
6821
- /* @__PURE__ */ jsx("p", { className: "text-xs text-(--chat-muted)", children: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description })
7314
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-(--chat-muted)", children: renderProps.description })
6822
7315
  ] }),
6823
- /* @__PURE__ */ jsx(
6824
- Switch,
7316
+ showLoader ? /* @__PURE__ */ jsx(
7317
+ Loader2,
7318
+ {
7319
+ className: "size-5 shrink-0 animate-spin text-(--chat-theme)",
7320
+ "aria-label": "Updating push notifications"
7321
+ }
7322
+ ) : /* @__PURE__ */ jsx(
7323
+ AdminPermissionTooltip,
6825
7324
  {
6826
- checked: isOn,
6827
- disabled: push.loading || permissionBlocked,
6828
- onCheckedChange: (checked) => void handleChange(checked),
6829
- "aria-label": `Toggle ${label}`
7325
+ disabled: adminBlocked || notConfigured,
7326
+ message: adminBlocked ? adminGate.disabledReason : "Push notifications are not configured. Please contact your administrator.",
7327
+ children: /* @__PURE__ */ jsx(
7328
+ Switch,
7329
+ {
7330
+ checked: adminBlocked || notConfigured ? false : isOn,
7331
+ disabled: isDisabled,
7332
+ onCheckedChange: (checked) => void handleChange(checked),
7333
+ "aria-label": `Toggle ${label}`
7334
+ }
7335
+ )
6830
7336
  }
6831
7337
  )
6832
7338
  ]
6833
7339
  }
6834
7340
  );
6835
7341
  };
6836
- var LoggedUserSettingsDialog = ({
6837
- loggedUserDetails
7342
+ var LoggedUserSettingsContent = ({
7343
+ user,
7344
+ title = "Account settings",
7345
+ showProfile = true,
7346
+ showPushToggle = true,
7347
+ className
6838
7348
  }) => {
6839
- const [open, setOpen] = useState(false);
6840
- const user = {
6841
- _id: loggedUserDetails?._id || "1",
6842
- name: formatLoggedUserName(loggedUserDetails?.name),
6843
- email: loggedUserDetails?.email || "user@example.com",
6844
- role: loggedUserDetails?.role || "user",
6845
- isActive: loggedUserDetails?.isActive ?? true,
6846
- createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6847
- updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6848
- image: loggedUserDetails?.image || void 0
6849
- };
6850
- return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange: setOpen, children: [
6851
- /* @__PURE__ */ jsxs(Tooltip, { children: [
6852
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
6853
- Button,
6854
- {
6855
- type: "button",
6856
- variant: "ghost",
6857
- size: "icon",
6858
- className: sidebarActionBtnClass,
6859
- "aria-label": "Account settings",
6860
- onClick: () => setOpen(true),
6861
- children: /* @__PURE__ */ jsx(Settings, { className: "size-3.5", strokeWidth: 2 })
6862
- }
6863
- ) }),
6864
- /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Settings" })
6865
- ] }),
6866
- /* @__PURE__ */ jsxs(ChatDialogContent, { className: "gap-0 overflow-hidden p-0 sm:max-w-[380px]", children: [
6867
- /* @__PURE__ */ jsx("div", { className: "border-b border-(--chat-border) px-5 py-4", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-left text-base font-semibold text-(--chat-text)", children: "Account settings" }) }),
6868
- /* @__PURE__ */ jsxs("div", { className: "space-y-5 px-5 py-5", children: [
6869
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center", children: [
6870
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
6871
- /* @__PURE__ */ jsxs(Avatar, { className: "size-16 ring-2 ring-(--chat-theme-20)", children: [
6872
- user.image ? /* @__PURE__ */ jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
6873
- /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-lg font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
7349
+ const { resolveComponent, slotClassName } = useChatCustomization();
7350
+ const PushToggleSlot = resolveComponent(
7351
+ "PushNotificationToggle",
7352
+ PushNotificationToggle
7353
+ );
7354
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7355
+ /* @__PURE__ */ jsx(
7356
+ "div",
7357
+ {
7358
+ className: cn(
7359
+ "border-b border-(--chat-border) px-5 py-4",
7360
+ slotClassName("loggedUserSettingsHeader")
7361
+ ),
7362
+ children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-left text-base font-semibold text-(--chat-text)", children: title })
7363
+ }
7364
+ ),
7365
+ /* @__PURE__ */ jsxs(
7366
+ "div",
7367
+ {
7368
+ className: cn(
7369
+ "space-y-5 px-5 py-5",
7370
+ slotClassName("loggedUserSettingsContent", className)
7371
+ ),
7372
+ children: [
7373
+ showProfile ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center", children: [
7374
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
7375
+ /* @__PURE__ */ jsxs(
7376
+ Avatar,
7377
+ {
7378
+ className: cn(
7379
+ "size-16 ring-2 ring-(--chat-theme-20)",
7380
+ slotClassName("loggedUserSettingsAvatar")
7381
+ ),
7382
+ children: [
7383
+ user.image ? /* @__PURE__ */ jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
7384
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-lg font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
7385
+ ]
7386
+ }
7387
+ ),
7388
+ /* @__PURE__ */ jsx("span", { className: "absolute bottom-0.5 right-0.5 size-3 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6874
7389
  ] }),
6875
- /* @__PURE__ */ jsx("span", { className: "absolute bottom-0.5 right-0.5 size-3 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6876
- ] }),
6877
- /* @__PURE__ */ jsx("h3", { className: "mt-3 text-base font-semibold text-(--chat-text)", children: user.name }),
6878
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-(--chat-muted)", children: user.email }),
6879
- /* @__PURE__ */ jsxs("p", { className: "mt-2 inline-flex items-center gap-1.5 rounded-full bg-(--chat-theme-10) px-2.5 py-0.5 text-xs font-medium text-(--chat-theme)", children: [
6880
- /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-(--chat-online)" }),
6881
- "Online"
6882
- ] })
6883
- ] }),
6884
- /* @__PURE__ */ jsx("div", { className: "border-t border-(--chat-border) pt-4", children: /* @__PURE__ */ jsx(PushNotificationToggle, { className: "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-theme-10) px-4 py-3" }) })
6885
- ] })
6886
- ] })
7390
+ /* @__PURE__ */ jsx("h3", { className: "mt-3 text-base font-semibold text-(--chat-text)", children: user.name }),
7391
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-(--chat-muted)", children: user.email }),
7392
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 inline-flex items-center gap-1.5 rounded-full bg-(--chat-theme-10) px-2.5 py-0.5 text-xs font-medium text-(--chat-theme)", children: [
7393
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-(--chat-online)" }),
7394
+ "Online"
7395
+ ] })
7396
+ ] }) : null,
7397
+ showPushToggle ? /* @__PURE__ */ jsx("div", { className: "border-t border-(--chat-border) pt-4", children: /* @__PURE__ */ jsx(
7398
+ PushToggleSlot,
7399
+ {
7400
+ className: cn(
7401
+ "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-theme-10) px-4 py-3",
7402
+ slotClassName("pushNotificationToggle")
7403
+ )
7404
+ }
7405
+ ) }) : null
7406
+ ]
7407
+ }
7408
+ )
7409
+ ] });
7410
+ };
7411
+ var LoggedUserSettingsContent_default = LoggedUserSettingsContent;
7412
+ var LoggedUserSettingsTrigger = ({
7413
+ onClick,
7414
+ className,
7415
+ tooltip = "Settings",
7416
+ ariaLabel = "Account settings"
7417
+ }) => {
7418
+ const { slotClassName } = useChatCustomization();
7419
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
7420
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
7421
+ Button,
7422
+ {
7423
+ type: "button",
7424
+ variant: "ghost",
7425
+ size: "icon",
7426
+ className: cn(
7427
+ sidebarActionBtnClass,
7428
+ slotClassName("loggedUserSettingsTrigger", className)
7429
+ ),
7430
+ "aria-label": ariaLabel,
7431
+ onClick,
7432
+ children: /* @__PURE__ */ jsx(Settings, { className: "size-3.5", strokeWidth: 2 })
7433
+ }
7434
+ ) }),
7435
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: tooltip })
7436
+ ] });
7437
+ };
7438
+ var LoggedUserSettingsTrigger_default = LoggedUserSettingsTrigger;
7439
+ var LoggedUserSettingsDialog = ({
7440
+ loggedUserDetails,
7441
+ open: controlledOpen,
7442
+ onOpenChange: controlledOnOpenChange,
7443
+ title,
7444
+ showProfile = true,
7445
+ showPushToggle = true,
7446
+ className
7447
+ }) => {
7448
+ const { resolveComponent, slotClassName } = useChatCustomization();
7449
+ const [internalOpen, setInternalOpen] = useState(false);
7450
+ const open = controlledOpen ?? internalOpen;
7451
+ const onOpenChange = controlledOnOpenChange ?? setInternalOpen;
7452
+ const TriggerSlot = resolveComponent(
7453
+ "LoggedUserSettingsTrigger",
7454
+ LoggedUserSettingsTrigger_default
7455
+ );
7456
+ const ContentSlot = resolveComponent(
7457
+ "LoggedUserSettingsContent",
7458
+ LoggedUserSettingsContent_default
7459
+ );
7460
+ const user = buildLoggedUserProfile(loggedUserDetails);
7461
+ return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange, children: [
7462
+ /* @__PURE__ */ jsx(TriggerSlot, { onClick: () => onOpenChange(true) }),
7463
+ /* @__PURE__ */ jsx(
7464
+ ChatDialogContent,
7465
+ {
7466
+ className: cn(
7467
+ "gap-0 overflow-hidden p-0 sm:max-w-[380px]",
7468
+ slotClassName("loggedUserSettingsDialog", className)
7469
+ ),
7470
+ children: /* @__PURE__ */ jsx(
7471
+ ContentSlot,
7472
+ {
7473
+ loggedUserDetails,
7474
+ user,
7475
+ onClose: () => onOpenChange(false),
7476
+ title,
7477
+ showProfile,
7478
+ showPushToggle
7479
+ }
7480
+ )
7481
+ }
7482
+ )
6887
7483
  ] });
6888
7484
  };
6889
7485
  var LoggedUserSettingsDialog_default = LoggedUserSettingsDialog;
@@ -6896,16 +7492,12 @@ var LoggedUserDetails = ({
6896
7492
  setSearchQuery
6897
7493
  }) => {
6898
7494
  const { showColorModeToggle } = useChatTheme();
6899
- const user = {
6900
- _id: loggedUserDetails?._id || "1",
6901
- name: formatLoggedUserName(loggedUserDetails?.name),
6902
- email: loggedUserDetails?.email || "user@example.com",
6903
- role: loggedUserDetails?.role || "user",
6904
- isActive: loggedUserDetails?.isActive ?? true,
6905
- createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6906
- updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6907
- image: loggedUserDetails?.image || void 0
6908
- };
7495
+ const { resolveComponent } = useChatCustomization();
7496
+ const SettingsDialogSlot = resolveComponent(
7497
+ "LoggedUserSettingsDialog",
7498
+ LoggedUserSettingsDialog_default
7499
+ );
7500
+ const user = buildLoggedUserProfile(loggedUserDetails);
6909
7501
  return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b border-(--chat-border) bg-(--chat-surface) px-3 pb-3 pt-3", children: [
6910
7502
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [
6911
7503
  /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
@@ -6916,14 +7508,11 @@ var LoggedUserDetails = ({
6916
7508
  /* @__PURE__ */ jsx("span", { className: "absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6917
7509
  ] }),
6918
7510
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
6919
- /* @__PURE__ */ jsxs("h2", { className: "truncate text-xs font-medium leading-tight text-(--chat-text)", children: [
6920
- user.name,
6921
- "123"
6922
- ] }),
7511
+ /* @__PURE__ */ jsx("h2", { className: "truncate text-xs font-medium leading-tight text-(--chat-text)", children: user.name }),
6923
7512
  /* @__PURE__ */ jsx("p", { className: "truncate text-[10px] text-(--chat-muted)", children: user.email })
6924
7513
  ] }),
6925
7514
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
6926
- /* @__PURE__ */ jsx(LoggedUserSettingsDialog_default, { loggedUserDetails }),
7515
+ /* @__PURE__ */ jsx(SettingsDialogSlot, { loggedUserDetails }),
6927
7516
  showColorModeToggle ? /* @__PURE__ */ jsx(ChatThemeToggle_default, {}) : null
6928
7517
  ] })
6929
7518
  ] }),
@@ -8164,7 +8753,7 @@ function highlightSearchMatch(text, query) {
8164
8753
  children: part
8165
8754
  },
8166
8755
  `${part}-${index}`
8167
- ) : /* @__PURE__ */ jsx(React2__default.Fragment, { children: part }, `${part}-${index}`)
8756
+ ) : /* @__PURE__ */ jsx(React3__default.Fragment, { children: part }, `${part}-${index}`)
8168
8757
  );
8169
8758
  }
8170
8759
  function getMessagePreview(message) {
@@ -8344,7 +8933,7 @@ var HeaderSearchPopover = ({
8344
8933
  ] });
8345
8934
  };
8346
8935
  var HeaderSearchPopover_default = HeaderSearchPopover;
8347
- var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text)";
8936
+ var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text) disabled:pointer-events-none disabled:opacity-40";
8348
8937
  var DefaultHeaderCallActions = ({
8349
8938
  activeChannel,
8350
8939
  conversationId,
@@ -8355,48 +8944,91 @@ var DefaultHeaderCallActions = ({
8355
8944
  className
8356
8945
  }) => {
8357
8946
  const { components, slotClassName, features } = useChatCustomizationOptional();
8947
+ const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
8358
8948
  const PhoneSlot = components.PhoneCallButton;
8359
8949
  const VideoSlot = components.VideoCallButton;
8360
8950
  const hideInGroups = features.hideCallActionsInGroupChats ?? false;
8951
+ const audioGate = getAudioCallGate();
8952
+ const videoGate = getVideoCallGate();
8361
8953
  if (hideInGroups && activeChannel.isGroup) return null;
8362
8954
  if (!showPhoneCall && !showVideoCall) return null;
8955
+ const phoneEnabled = showPhoneCall && audioGate.enabled;
8956
+ const videoEnabled = showVideoCall && videoGate.enabled;
8363
8957
  return /* @__PURE__ */ jsxs(Fragment, { children: [
8364
8958
  showPhoneCall && (PhoneSlot ? /* @__PURE__ */ jsx(
8365
- PhoneSlot,
8959
+ AdminPermissionTooltip,
8366
8960
  {
8367
- activeChannel,
8368
- conversationId,
8369
- onPhoneCall,
8370
- className: cn(className, slotClassName("phoneCallButton"))
8961
+ disabled: !audioGate.enabled,
8962
+ message: audioGate.disabledReason,
8963
+ children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
8964
+ PhoneSlot,
8965
+ {
8966
+ activeChannel,
8967
+ conversationId,
8968
+ onPhoneCall: phoneEnabled ? onPhoneCall : void 0,
8969
+ className: cn(
8970
+ className,
8971
+ slotClassName("phoneCallButton"),
8972
+ !audioGate.enabled && "pointer-events-none opacity-40"
8973
+ )
8974
+ }
8975
+ ) })
8371
8976
  }
8372
8977
  ) : /* @__PURE__ */ jsx(
8373
- Button,
8978
+ AdminPermissionTooltip,
8374
8979
  {
8375
- variant: "ghost",
8376
- size: "icon",
8377
- className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
8378
- "aria-label": "Voice call",
8379
- onClick: onPhoneCall,
8380
- children: /* @__PURE__ */ jsx(Phone, { size: 15 })
8980
+ disabled: !audioGate.enabled,
8981
+ message: audioGate.disabledReason,
8982
+ children: /* @__PURE__ */ jsx(
8983
+ Button,
8984
+ {
8985
+ variant: "ghost",
8986
+ size: "icon",
8987
+ className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
8988
+ "aria-label": "Voice call",
8989
+ disabled: !audioGate.enabled,
8990
+ onClick: phoneEnabled ? onPhoneCall : void 0,
8991
+ children: /* @__PURE__ */ jsx(Phone, { size: 15 })
8992
+ }
8993
+ )
8381
8994
  }
8382
8995
  )),
8383
8996
  showVideoCall && (VideoSlot ? /* @__PURE__ */ jsx(
8384
- VideoSlot,
8997
+ AdminPermissionTooltip,
8385
8998
  {
8386
- activeChannel,
8387
- conversationId,
8388
- onVideoCall,
8389
- className: cn(className, slotClassName("videoCallButton"))
8999
+ disabled: !videoGate.enabled,
9000
+ message: videoGate.disabledReason,
9001
+ children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
9002
+ VideoSlot,
9003
+ {
9004
+ activeChannel,
9005
+ conversationId,
9006
+ onVideoCall: videoEnabled ? onVideoCall : void 0,
9007
+ className: cn(
9008
+ className,
9009
+ slotClassName("videoCallButton"),
9010
+ !videoGate.enabled && "pointer-events-none opacity-40"
9011
+ )
9012
+ }
9013
+ ) })
8390
9014
  }
8391
9015
  ) : /* @__PURE__ */ jsx(
8392
- Button,
9016
+ AdminPermissionTooltip,
8393
9017
  {
8394
- variant: "ghost",
8395
- size: "icon",
8396
- className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
8397
- "aria-label": "Video call",
8398
- onClick: onVideoCall,
8399
- children: /* @__PURE__ */ jsx(Video, { size: 15 })
9018
+ disabled: !videoGate.enabled,
9019
+ message: videoGate.disabledReason,
9020
+ children: /* @__PURE__ */ jsx(
9021
+ Button,
9022
+ {
9023
+ variant: "ghost",
9024
+ size: "icon",
9025
+ className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
9026
+ "aria-label": "Video call",
9027
+ disabled: !videoGate.enabled,
9028
+ onClick: videoEnabled ? onVideoCall : void 0,
9029
+ children: /* @__PURE__ */ jsx(Video, { size: 15 })
9030
+ }
9031
+ )
8400
9032
  }
8401
9033
  ))
8402
9034
  ] });
@@ -8418,19 +9050,38 @@ function OptionsMenuItem({
8418
9050
  icon: Icon,
8419
9051
  label,
8420
9052
  onSelect,
8421
- destructive = false
9053
+ destructive = false,
9054
+ disabled = false,
9055
+ disabledReason
8422
9056
  }) {
8423
- return /* @__PURE__ */ jsxs(
9057
+ const item = /* @__PURE__ */ jsxs(
8424
9058
  DropdownMenuItem,
8425
9059
  {
8426
- className: destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
8427
- onSelect,
9060
+ className: cn(
9061
+ destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
9062
+ disabled && "pointer-events-none opacity-40"
9063
+ ),
9064
+ disabled,
9065
+ onSelect: (event) => {
9066
+ if (disabled) {
9067
+ event.preventDefault();
9068
+ return;
9069
+ }
9070
+ onSelect?.();
9071
+ },
8428
9072
  children: [
8429
9073
  /* @__PURE__ */ jsx(Icon, { strokeWidth: 1.75 }),
8430
9074
  /* @__PURE__ */ jsx("span", { className: "flex-1", children: label })
8431
9075
  ]
8432
9076
  }
8433
9077
  );
9078
+ if (!disabled || !disabledReason) {
9079
+ return item;
9080
+ }
9081
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
9082
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "w-full", children: item }) }),
9083
+ /* @__PURE__ */ jsx(TooltipContent, { side: "left", className: "max-w-[220px] text-xs", children: disabledReason })
9084
+ ] });
8434
9085
  }
8435
9086
  var HeaderRightSide = ({
8436
9087
  activeChannel,
@@ -8465,6 +9116,8 @@ var HeaderRightSide = ({
8465
9116
  const { scopeClassName, themeStyles, showColorModeToggle } = useChatTheme();
8466
9117
  const { showDateTimeSettings } = useChatLocale();
8467
9118
  const features = useChatFeaturesOptional();
9119
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
9120
+ const blockUsersGate = getBlockUsersGate();
8468
9121
  const showPhoneCall = features.showPhoneCall ?? defaultChatFeatures.showPhoneCall;
8469
9122
  const showVideoCall = features.showVideoCall ?? defaultChatFeatures.showVideoCall;
8470
9123
  const handlePhoneCall = () => {
@@ -8613,7 +9266,9 @@ var HeaderRightSide = ({
8613
9266
  icon: Ban,
8614
9267
  label: isBlocked ? "Unblock User" : "Block User",
8615
9268
  onSelect: onToggleBlock,
8616
- destructive: true
9269
+ destructive: true,
9270
+ disabled: !blockUsersGate.enabled,
9271
+ disabledReason: blockUsersGate.disabledReason
8617
9272
  }
8618
9273
  )
8619
9274
  ]
@@ -12653,17 +13308,24 @@ var ChannelDetailsDrawer = (props) => {
12653
13308
  };
12654
13309
  var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12655
13310
  init_useStore();
13311
+ init_settings_types();
12656
13312
  var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
12657
13313
  const updateGroupPermissions = useStore((s) => s.updateGroupPermissions);
13314
+ const { settings, isGroupPermissionLocked } = useEffectiveSettingsOptional();
12658
13315
  const [permissions, setPermissions] = useState(null);
12659
13316
  const [isSaving, setIsSaving] = useState(false);
12660
13317
  useEffect(() => {
12661
13318
  if (conversationInfo?.groupPermissions) {
12662
13319
  setPermissions(conversationInfo.groupPermissions);
13320
+ return;
12663
13321
  }
12664
- }, [conversationInfo]);
13322
+ if (isOpen) {
13323
+ setPermissions({ ...settings.groupPolicy.defaults });
13324
+ }
13325
+ }, [conversationInfo, isOpen, settings.groupPolicy.defaults]);
12665
13326
  if (!conversationId || !permissions) return null;
12666
13327
  const handleToggle = (key) => {
13328
+ if (isGroupPermissionLocked(key)) return;
12667
13329
  setPermissions((prev) => prev ? { ...prev, [key]: !prev[key] } : null);
12668
13330
  };
12669
13331
  const handleSave = async () => {
@@ -12695,31 +13357,43 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
12695
13357
  /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "flex flex-col px-2.5 py-2"), children: [
12696
13358
  /* @__PURE__ */ jsxs("section", { className: "mb-2.5 flex-1", children: [
12697
13359
  /* @__PURE__ */ jsx("h3", { className: "mb-1 px-0.5 text-[10px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "Permissions" }),
12698
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => /* @__PURE__ */ jsxs(
12699
- "div",
12700
- {
12701
- className: cn(
12702
- "flex items-start justify-between gap-3 py-2.5",
12703
- index < permissionItems.length - 1 && "border-b border-(--chat-border)"
12704
- ),
12705
- children: [
12706
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-0.5", children: [
12707
- /* @__PURE__ */ jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
12708
- /* @__PURE__ */ jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc })
12709
- ] }),
12710
- /* @__PURE__ */ jsx(
12711
- Switch,
12712
- {
12713
- size: "sm",
12714
- className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
12715
- checked: "inverted" in item && item.inverted ? !permissions[item.key] : permissions[item.key],
12716
- onCheckedChange: () => handleToggle(item.key)
12717
- }
12718
- )
12719
- ]
12720
- },
12721
- item.key
12722
- )) })
13360
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13361
+ const locked = isGroupPermissionLocked(item.key);
13362
+ return /* @__PURE__ */ jsxs(
13363
+ "div",
13364
+ {
13365
+ className: cn(
13366
+ "flex items-start justify-between gap-3 py-2.5",
13367
+ index < permissionItems.length - 1 && "border-b border-(--chat-border)"
13368
+ ),
13369
+ children: [
13370
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-0.5", children: [
13371
+ /* @__PURE__ */ jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
13372
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc }),
13373
+ locked ? /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-(--chat-theme)", children: "Locked by administrator" }) : null
13374
+ ] }),
13375
+ /* @__PURE__ */ jsx(
13376
+ AdminPermissionTooltip,
13377
+ {
13378
+ disabled: locked,
13379
+ message: ADMIN_PERMISSION_DENIED_MESSAGE,
13380
+ children: /* @__PURE__ */ jsx(
13381
+ Switch,
13382
+ {
13383
+ size: "sm",
13384
+ className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
13385
+ disabled: locked,
13386
+ checked: item.inverted ? !permissions[item.key] : permissions[item.key],
13387
+ onCheckedChange: () => handleToggle(item.key)
13388
+ }
13389
+ )
13390
+ }
13391
+ )
13392
+ ]
13393
+ },
13394
+ item.key
13395
+ );
13396
+ }) })
12723
13397
  ] }),
12724
13398
  /* @__PURE__ */ jsx("div", { className: "sticky bottom-0 border-t border-(--chat-border) bg-(--chat-surface) px-0.5 pt-2.5 pb-1", children: /* @__PURE__ */ jsx(
12725
13399
  Button,
@@ -12749,6 +13423,8 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12749
13423
  const [isManaging, setIsManaging] = useState(null);
12750
13424
  const manageGroupAdmins = useStore((s) => s.manageGroupAdmins);
12751
13425
  const { isOwner } = useGroupInfo(conversationId);
13426
+ const { settings } = useEffectiveSettingsOptional();
13427
+ const maxAdmins = settings.maxAdminsPerGroup;
12752
13428
  const participants = useMemo(
12753
13429
  () => conversationInfo?.participants ?? [],
12754
13430
  [conversationInfo?.participants]
@@ -12788,8 +13464,16 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12788
13464
  }),
12789
13465
  [filteredParticipants, adminIds, ownerId]
12790
13466
  );
13467
+ const adminCount = admins.length + (ownerId ? 1 : 0);
13468
+ const atAdminLimit = adminCount >= maxAdmins;
12791
13469
  const handleManageAdmin = async (userId, type) => {
12792
13470
  if (!conversationId) return;
13471
+ if (type === "add" && adminCount >= maxAdmins) {
13472
+ useStore.getState().handleAppError(
13473
+ `You can have up to ${maxAdmins} admins in this group.`
13474
+ );
13475
+ return;
13476
+ }
12793
13477
  setIsManaging(userId);
12794
13478
  try {
12795
13479
  await manageGroupAdmins({
@@ -12801,7 +13485,6 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12801
13485
  setIsManaging(null);
12802
13486
  }
12803
13487
  };
12804
- const adminCount = admins.length + (ownerId ? 1 : 0);
12805
13488
  return {
12806
13489
  searchQuery,
12807
13490
  setSearchQuery,
@@ -12813,6 +13496,8 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12813
13496
  promotableParticipants,
12814
13497
  handleManageAdmin,
12815
13498
  adminCount,
13499
+ maxAdmins,
13500
+ atAdminLimit,
12816
13501
  hasRequiredData: Boolean(conversationId && conversationInfo)
12817
13502
  };
12818
13503
  }
@@ -12825,6 +13510,8 @@ function AdminParticipantRow({
12825
13510
  isAdmin,
12826
13511
  showPromote,
12827
13512
  showDemote,
13513
+ promoteDisabled = false,
13514
+ promoteDisabledReason,
12828
13515
  isOwnerViewer,
12829
13516
  isManaging,
12830
13517
  onManageAdmin
@@ -12865,14 +13552,24 @@ function AdminParticipantRow({
12865
13552
  }
12866
13553
  ),
12867
13554
  showPromote && isOwnerViewer && /* @__PURE__ */ jsx(
12868
- Button,
13555
+ AdminPermissionTooltip,
12869
13556
  {
12870
- variant: "ghost",
12871
- size: "sm",
12872
- className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme)",
12873
- onClick: () => onManageAdmin(memberId, "add"),
12874
- disabled: isManaging === memberId,
12875
- children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13557
+ disabled: promoteDisabled,
13558
+ message: promoteDisabledReason,
13559
+ children: /* @__PURE__ */ jsx(
13560
+ Button,
13561
+ {
13562
+ variant: "ghost",
13563
+ size: "sm",
13564
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13565
+ onClick: () => {
13566
+ if (promoteDisabled) return;
13567
+ onManageAdmin(memberId, "add");
13568
+ },
13569
+ disabled: isManaging === memberId || promoteDisabled,
13570
+ children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13571
+ }
13572
+ )
12876
13573
  }
12877
13574
  )
12878
13575
  ] })
@@ -12896,7 +13593,9 @@ function AdminParticipantList({
12896
13593
  isAdmin,
12897
13594
  onManageAdmin,
12898
13595
  showPromote,
12899
- showDemoteForAdmins
13596
+ showDemoteForAdmins,
13597
+ promoteDisabled = false,
13598
+ promoteDisabledReason
12900
13599
  }) {
12901
13600
  return /* @__PURE__ */ jsx(Fragment, { children: members.map((member, index) => {
12902
13601
  const { memberId, memberName, memberEmail, memberImage } = getMemberFields(member);
@@ -12914,6 +13613,8 @@ function AdminParticipantList({
12914
13613
  isAdmin: userIsAdmin,
12915
13614
  showPromote: showPromote && !userIsAdmin && !userIsOwner,
12916
13615
  showDemote: showDemoteForAdmins ? userIsAdmin && !userIsOwner : false,
13616
+ promoteDisabled,
13617
+ promoteDisabledReason,
12917
13618
  isOwnerViewer,
12918
13619
  isManaging,
12919
13620
  onManageAdmin
@@ -12933,6 +13634,8 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
12933
13634
  promotableParticipants,
12934
13635
  handleManageAdmin,
12935
13636
  adminCount,
13637
+ maxAdmins,
13638
+ atAdminLimit,
12936
13639
  hasRequiredData
12937
13640
  } = useAdminsDrawer({ conversationId, conversationInfo });
12938
13641
  if (!hasRequiredData) return null;
@@ -12952,7 +13655,10 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
12952
13655
  /* @__PURE__ */ jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Administrators" }),
12953
13656
  /* @__PURE__ */ jsx("span", { className: "rounded-full bg-(--chat-theme-10) px-2 py-0.5 text-[11px] font-medium leading-none text-(--chat-theme)", children: adminCount })
12954
13657
  ] }),
12955
- /* @__PURE__ */ jsx("p", { className: chatSheetSubtitleClass, children: "Manage who can adjust group settings and members." })
13658
+ /* @__PURE__ */ jsxs("p", { className: chatSheetSubtitleClass, children: [
13659
+ "Manage who can adjust group settings and members",
13660
+ maxAdmins ? ` (max ${maxAdmins}).` : "."
13661
+ ] })
12956
13662
  ] }),
12957
13663
  /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
12958
13664
  /* @__PURE__ */ jsx("div", { className: "mb-2.5 px-0.5", children: /* @__PURE__ */ jsx(
@@ -12995,7 +13701,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
12995
13701
  ownerId,
12996
13702
  isAdmin,
12997
13703
  onManageAdmin: handleManageAdmin,
12998
- showPromote: true
13704
+ showPromote: true,
13705
+ promoteDisabled: atAdminLimit,
13706
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
12999
13707
  }
13000
13708
  ) })
13001
13709
  ] }),
@@ -13012,7 +13720,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13012
13720
  isAdmin,
13013
13721
  onManageAdmin: handleManageAdmin,
13014
13722
  showPromote: true,
13015
- showDemoteForAdmins: true
13723
+ showDemoteForAdmins: true,
13724
+ promoteDisabled: atAdminLimit,
13725
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13016
13726
  }
13017
13727
  ) })
13018
13728
  ] }),
@@ -13040,9 +13750,12 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13040
13750
  );
13041
13751
  }, [allUsers, searchQuery]);
13042
13752
  const { userPermissions } = useGroupInfo(conversationId);
13753
+ const { settings } = useEffectiveSettingsOptional();
13754
+ const maxParticipants = settings.maxParticipantsPerGroup;
13043
13755
  if (!conversationId || !conversationInfo) return null;
13044
13756
  const participants = conversationInfo.participants || [];
13045
13757
  const admins = conversationInfo.groupAdmins || [];
13758
+ const atParticipantLimit = participants.length >= maxParticipants;
13046
13759
  let ownerId = null;
13047
13760
  if (conversationInfo.owner) {
13048
13761
  ownerId = typeof conversationInfo.owner === "object" && conversationInfo.owner !== null ? conversationInfo.owner._id || conversationInfo.owner.id : conversationInfo.owner;
@@ -13096,14 +13809,24 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13096
13809
  }
13097
13810
  ),
13098
13811
  options.showAdd && /* @__PURE__ */ jsx(
13099
- Button,
13812
+ AdminPermissionTooltip,
13100
13813
  {
13101
- variant: "ghost",
13102
- size: "sm",
13103
- className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme)",
13104
- onClick: () => handleManageMember(memberId, "add"),
13105
- disabled: isManaging === memberId,
13106
- children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13814
+ disabled: atParticipantLimit,
13815
+ message: `Maximum ${maxParticipants} participants allowed in this group.`,
13816
+ children: /* @__PURE__ */ jsx(
13817
+ Button,
13818
+ {
13819
+ variant: "ghost",
13820
+ size: "sm",
13821
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13822
+ onClick: () => {
13823
+ if (atParticipantLimit) return;
13824
+ handleManageMember(memberId, "add");
13825
+ },
13826
+ disabled: isManaging === memberId || atParticipantLimit,
13827
+ children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13828
+ }
13829
+ )
13107
13830
  }
13108
13831
  )
13109
13832
  ] })
@@ -13125,7 +13848,11 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13125
13848
  /* @__PURE__ */ jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Management" }),
13126
13849
  /* @__PURE__ */ jsx("span", { className: "rounded-full bg-(--chat-theme-10) px-2 py-0.5 text-[11px] font-medium leading-none text-(--chat-theme)", children: participants.length })
13127
13850
  ] }),
13128
- /* @__PURE__ */ jsx("p", { className: chatSheetSubtitleClass, children: "Manage members and invite people." })
13851
+ /* @__PURE__ */ jsxs("p", { className: chatSheetSubtitleClass, children: [
13852
+ "Manage members and invite people (max ",
13853
+ maxParticipants,
13854
+ ")."
13855
+ ] })
13129
13856
  ] }),
13130
13857
  /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13131
13858
  !searchQuery && /* @__PURE__ */ jsxs("section", { className: "mb-2.5", children: [
@@ -13267,6 +13994,8 @@ init_store_helpers();
13267
13994
  function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13268
13995
  const loggedUserDetails = useStore((s) => s.loggedUserDetails);
13269
13996
  const composerDisabledByConversation = useStore((s) => s.composerDisabledByConversation);
13997
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
13998
+ const blockUsersGate = getBlockUsersGate();
13270
13999
  const [isBlocking, setIsBlocking] = useState(false);
13271
14000
  const resolvedConversationId = conversationId ?? activeChannel?.conversationId ?? null;
13272
14001
  const composerDisabledReason = resolvedConversationId ? composerDisabledByConversation[resolvedConversationId] : void 0;
@@ -13288,6 +14017,12 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13288
14017
  const toggleBlock = useCallback(async () => {
13289
14018
  if (!activeChannel?.id || activeChannel.isGroup) return false;
13290
14019
  const nextBlocked = !isBlocked;
14020
+ if (nextBlocked && !blockUsersGate.enabled) {
14021
+ useStore.getState().handleAppError(
14022
+ blockUsersGate.disabledReason || "Blocking users is disabled by your administrator."
14023
+ );
14024
+ return false;
14025
+ }
13291
14026
  const type = nextBlocked ? "block" : "unblock";
13292
14027
  try {
13293
14028
  setIsBlocking(true);
@@ -13308,8 +14043,8 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13308
14043
  } finally {
13309
14044
  setIsBlocking(false);
13310
14045
  }
13311
- }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId]);
13312
- return { isBlocked, isBlocking, toggleBlock };
14046
+ }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId, blockUsersGate]);
14047
+ return { isBlocked, isBlocking, toggleBlock, blockUsersGate };
13313
14048
  }
13314
14049
 
13315
14050
  // src/chat/channel-header/useChannelHeader.ts
@@ -13845,6 +14580,7 @@ var MessageReactions_default = MessageReactions;
13845
14580
  var MessageToolbar = ({
13846
14581
  isSender,
13847
14582
  isRecent,
14583
+ isWithinDeleteWindow = isRecent,
13848
14584
  isPinned,
13849
14585
  isStarred,
13850
14586
  isPinPending,
@@ -13868,7 +14604,7 @@ var MessageToolbar = ({
13868
14604
  }) => {
13869
14605
  const { isDark, scopeClassName, themeStyles } = useChatTheme();
13870
14606
  const showDeleteMenu = isSender || hasAttachments || attachmentDeleteMode;
13871
- const showDeleteForEveryone = isSender && isRecent;
14607
+ const showDeleteForEveryone = isSender && isWithinDeleteWindow;
13872
14608
  return /* @__PURE__ */ jsxs(
13873
14609
  "div",
13874
14610
  {
@@ -14190,16 +14926,59 @@ function resolveForwardItems(selectedKeys, messages) {
14190
14926
  }
14191
14927
 
14192
14928
  // src/chat/lib/chat-edit-window.ts
14193
- init_chat_constants();
14194
- function getMessageEditWindowMs(isGroup) {
14195
- return isGroup ? GROUP_EDIT_WINDOW_MS : DM_EDIT_WINDOW_MS;
14929
+ init_settings_types();
14930
+
14931
+ // src/lib/effective-settings.helpers.ts
14932
+ function minutesToMs(minutes) {
14933
+ return Math.max(0, minutes) * 60 * 1e3;
14934
+ }
14935
+ function getEditWindowMs(timers, isGroup) {
14936
+ return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
14937
+ }
14938
+ function getDeleteWindowMs(timers, isGroup) {
14939
+ return minutesToMs(
14940
+ isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
14941
+ );
14196
14942
  }
14197
- function isWithinMessageEditWindow(createdAt, isGroup) {
14943
+ function isWithinWindow(createdAt, windowMs) {
14198
14944
  if (!createdAt) return false;
14199
14945
  const date = new Date(createdAt);
14200
14946
  if (isNaN(date.getTime())) return false;
14201
14947
  const diff = Date.now() - date.getTime();
14202
- return diff >= 0 && diff < getMessageEditWindowMs(isGroup);
14948
+ return diff >= 0 && diff < windowMs;
14949
+ }
14950
+ function isWithinMessageEditWindow(createdAt, timers, isGroup) {
14951
+ return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
14952
+ }
14953
+ function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
14954
+ return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
14955
+ }
14956
+ function matchesAllowedMimeType(fileType, allowedMimeTypes) {
14957
+ const mime = (fileType || "application/octet-stream").toLowerCase();
14958
+ return allowedMimeTypes.some((pattern) => {
14959
+ const normalized = pattern.trim().toLowerCase();
14960
+ if (!normalized) return false;
14961
+ if (normalized.endsWith("/*")) {
14962
+ const prefix = normalized.slice(0, -1);
14963
+ return mime.startsWith(prefix);
14964
+ }
14965
+ return mime === normalized;
14966
+ });
14967
+ }
14968
+ function buildAcceptFromMimeTypes(allowedMimeTypes) {
14969
+ return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
14970
+ }
14971
+ function formatFileSizeMB(sizeBytes) {
14972
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
14973
+ }
14974
+
14975
+ // src/chat/lib/chat-edit-window.ts
14976
+ var FALLBACK_TIMERS = DEFAULT_EFFECTIVE_SETTINGS.messageTimers;
14977
+ function isWithinMessageEditWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
14978
+ return isWithinMessageEditWindow(createdAt, timers, isGroup);
14979
+ }
14980
+ function isWithinMessageDeleteWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
14981
+ return isWithinMessageDeleteWindow(createdAt, timers, isGroup);
14203
14982
  }
14204
14983
 
14205
14984
  // src/chat/message-item/useMessageItemActions.ts
@@ -14277,6 +15056,9 @@ function useMessageItemActions({
14277
15056
  const canUnpin = !isPinned || isPinnedByMe;
14278
15057
  const { userPermissions } = useGroupInfo(conversationId || null);
14279
15058
  const { formatMessageTime } = useChatLocale();
15059
+ const { settings } = useEffectiveSettingsOptional();
15060
+ const messageTimers = settings.messageTimers;
15061
+ const maxPinnedMessages = settings.maxPinnedMessagesPerConversation;
14280
15062
  const visibleAttachments = getVisibleAttachments(attachments);
14281
15063
  const hasAttachments = visibleAttachments.length > 0;
14282
15064
  const hasMultiAttachments = hasAttachments && messageUsesAttachmentSelection({ attachments: visibleAttachments });
@@ -14293,7 +15075,8 @@ function useMessageItemActions({
14293
15075
  const hasText = Boolean(message?.trim());
14294
15076
  const isSelected = !!mid && !!selectedKeys && isMessageWhollySelected(selectedKeys, mid) && !hasMultiAttachments;
14295
15077
  const canEdit = (!isGroup || userPermissions.canEditMessage) && hasText;
14296
- const isWithinEditWindow = () => isWithinMessageEditWindow(createdAt, isGroup);
15078
+ const isWithinEditWindow = () => isWithinMessageEditWindow2(createdAt, isGroup, messageTimers);
15079
+ const isWithinDeleteWindow = () => isWithinMessageDeleteWindow2(createdAt, isGroup, messageTimers);
14297
15080
  const canEditAttachmentCaption = isSender && isWithinEditWindow() && (!isGroup || userPermissions.canEditMessage) && !isDeletedForEveryone && !!mid;
14298
15081
  const handleAttachmentCaptionClick = (attachment) => {
14299
15082
  if (!canEditAttachmentCaption) return;
@@ -14462,6 +15245,15 @@ function useMessageItemActions({
14462
15245
  });
14463
15246
  }
14464
15247
  } else {
15248
+ if (conversationId) {
15249
+ const pinnedCount = (pinnedMessagesByConversation[conversationId] || []).length || useStore.getState().liveConversationCounts[conversationId]?.pinnedCount || 0;
15250
+ if (pinnedCount >= maxPinnedMessages) {
15251
+ useStore.getState().handleAppError(
15252
+ `You can pin up to ${maxPinnedMessages} messages in this conversation.`
15253
+ );
15254
+ return;
15255
+ }
15256
+ }
14465
15257
  if (isGroup) {
14466
15258
  if (!conversationId) return;
14467
15259
  emitGroupPin({
@@ -14520,6 +15312,7 @@ function useMessageItemActions({
14520
15312
  canUnpin,
14521
15313
  canEdit,
14522
15314
  isWithinEditWindow,
15315
+ isWithinDeleteWindow,
14523
15316
  canEditAttachmentCaption,
14524
15317
  visibleAttachments,
14525
15318
  hasAttachments,
@@ -14551,7 +15344,7 @@ function useMessageItemActions({
14551
15344
  }
14552
15345
 
14553
15346
  // src/chat/lib/reply-preview.ts
14554
- function normalizeAttachments(replyTo, loggedUserId) {
15347
+ function normalizeAttachments2(replyTo, loggedUserId) {
14555
15348
  const raw = replyTo?.attachments;
14556
15349
  if (!Array.isArray(raw) || raw.length === 0) return [];
14557
15350
  return raw.map((att, index) => {
@@ -14564,7 +15357,7 @@ function resolveReplyToMessage(replyTo, messagesById, loggedUserId) {
14564
15357
  if (typeof replyTo === "object" && (replyTo.content?.trim() || replyTo.attachments?.length)) {
14565
15358
  return {
14566
15359
  ...replyTo,
14567
- attachments: normalizeAttachments(replyTo, loggedUserId)
15360
+ attachments: normalizeAttachments2(replyTo, loggedUserId)
14568
15361
  };
14569
15362
  }
14570
15363
  const replyId = String(
@@ -15001,6 +15794,7 @@ function MessageItemView(props) {
15001
15794
  canUnpin,
15002
15795
  canEdit,
15003
15796
  isWithinEditWindow,
15797
+ isWithinDeleteWindow,
15004
15798
  canEditAttachmentCaption,
15005
15799
  visibleAttachments,
15006
15800
  hasAttachments,
@@ -15092,6 +15886,7 @@ function MessageItemView(props) {
15092
15886
  {
15093
15887
  isSender,
15094
15888
  isRecent: isWithinEditWindow(),
15889
+ isWithinDeleteWindow: isWithinDeleteWindow(),
15095
15890
  messageId: mid,
15096
15891
  isPinned,
15097
15892
  isStarred,
@@ -15386,6 +16181,7 @@ function MessageSelectionBar({
15386
16181
  }
15387
16182
 
15388
16183
  // src/chat/active-channel-messages/utils.ts
16184
+ init_settings_types();
15389
16185
  var getMessageSenderId2 = (msg) => msg ? String(msg.senderId || msg.sender?._id || "") : "";
15390
16186
  var isLikelyObjectId = (value) => /^[a-f\d]{24}$/i.test(value);
15391
16187
  function resolveGroupSenderDisplay(message, allUsers = [], participants = []) {
@@ -15417,7 +16213,7 @@ var isSameMessageSide = (a, b, isGroupChat) => {
15417
16213
  const bId = getMessageSenderId2(b);
15418
16214
  return aId !== "" && aId === bId;
15419
16215
  };
15420
- function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
16216
+ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup, timers = DEFAULT_EFFECTIVE_SETTINGS.messageTimers) {
15421
16217
  if (selectedKeys.size === 0) return false;
15422
16218
  const parsed = [...selectedKeys].map((key) => parseSelectionKey(key));
15423
16219
  const messageIds = new Set(parsed.map((entry) => entry.messageId));
@@ -15425,7 +16221,7 @@ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
15425
16221
  const messageId2 = [...messageIds][0];
15426
16222
  const message = messages.find((entry) => String(entry._id || entry.id) === messageId2);
15427
16223
  if (!message?.isOwnMessage) return false;
15428
- if (!isWithinMessageEditWindow(message.timestamp, isGroup)) return false;
16224
+ if (!isWithinMessageDeleteWindow2(message.timestamp, isGroup, timers)) return false;
15429
16225
  const hasAttachmentKey = parsed.some((entry) => entry.attachmentId);
15430
16226
  if (!hasAttachmentKey) {
15431
16227
  return selectedKeys.size === 1;
@@ -15594,6 +16390,8 @@ function useActiveChannelMessages({
15594
16390
  const emitDmDelete = useStore((s) => s.emitDmDelete);
15595
16391
  const emitGroupDelete = useStore((s) => s.emitGroupDelete);
15596
16392
  const loggedUserDetails = useStore((s) => s.loggedUserDetails);
16393
+ const { settings } = useEffectiveSettingsOptional();
16394
+ const messageTimers = settings.messageTimers;
15597
16395
  const scrollContainerRef = useRef(null);
15598
16396
  const bottomRef = useRef(null);
15599
16397
  const messageListInitializedRef = useRef(false);
@@ -15712,8 +16510,13 @@ function useActiveChannelMessages({
15712
16510
  }, []);
15713
16511
  const selectedForwardItems = resolveForwardItems(selectedKeys, displayMessages);
15714
16512
  const canDeleteForEveryone = useMemo(
15715
- () => canDeleteSelectionForEveryone(selectedKeys, displayMessages, !!activeChannel?.isGroup),
15716
- [selectedKeys, displayMessages, activeChannel?.isGroup]
16513
+ () => canDeleteSelectionForEveryone(
16514
+ selectedKeys,
16515
+ displayMessages,
16516
+ !!activeChannel?.isGroup,
16517
+ messageTimers
16518
+ ),
16519
+ [selectedKeys, displayMessages, activeChannel?.isGroup, messageTimers]
15717
16520
  );
15718
16521
  const emitSelectionDelete = useCallback(
15719
16522
  (deleteType) => {
@@ -15721,7 +16524,8 @@ function useActiveChannelMessages({
15721
16524
  if (deleteType === "everyone" && !canDeleteSelectionForEveryone(
15722
16525
  selectedKeys,
15723
16526
  displayMessages,
15724
- !!activeChannel?.isGroup
16527
+ !!activeChannel?.isGroup,
16528
+ messageTimers
15725
16529
  )) {
15726
16530
  return;
15727
16531
  }
@@ -15762,7 +16566,8 @@ function useActiveChannelMessages({
15762
16566
  conversationId,
15763
16567
  emitGroupDelete,
15764
16568
  emitDmDelete,
15765
- clearSelection
16569
+ clearSelection,
16570
+ messageTimers
15766
16571
  ]
15767
16572
  );
15768
16573
  const handleBatchDeleteForMe = useCallback(() => {
@@ -16100,8 +16905,8 @@ function CalendarDayButton({
16100
16905
  ...props
16101
16906
  }) {
16102
16907
  const defaultClassNames = getDefaultClassNames();
16103
- const ref = React2.useRef(null);
16104
- React2.useEffect(() => {
16908
+ const ref = React3.useRef(null);
16909
+ React3.useEffect(() => {
16105
16910
  if (modifiers.focused) ref.current?.focus();
16106
16911
  }, [modifiers.focused]);
16107
16912
  return /* @__PURE__ */ jsx(
@@ -17005,7 +17810,6 @@ var getAttachmentType = (file) => {
17005
17810
  };
17006
17811
 
17007
17812
  // src/chat/channel-message-box/useChannelMessageBox.ts
17008
- var MAX_ATTACHMENTS = COMPOSER_MAX_ATTACHMENTS;
17009
17813
  function useChannelMessageBox({
17010
17814
  activeChannel,
17011
17815
  conversationId,
@@ -17019,6 +17823,12 @@ function useChannelMessageBox({
17019
17823
  conversationId,
17020
17824
  onActiveChannelPatch
17021
17825
  );
17826
+ const { settings, getAttachmentsGate } = useEffectiveSettingsOptional();
17827
+ const attachmentsGate = getAttachmentsGate();
17828
+ const maxAttachments = settings.attachments.maxAttachmentsPerMessage;
17829
+ const maxFileSizeBytes = settings.attachments.maxFileSizeMB * 1024 * 1024;
17830
+ const allowedMimeTypes = settings.attachments.allowedMimeTypes;
17831
+ const acceptAttribute = buildAcceptFromMimeTypes(allowedMimeTypes);
17022
17832
  const [state, setState] = useState({
17023
17833
  messageInput: "",
17024
17834
  attachments: [],
@@ -17043,10 +17853,10 @@ function useChannelMessageBox({
17043
17853
  const composerDisabledReason = conversationId ? composerDisabledByConversation[conversationId] : void 0;
17044
17854
  const fileInputRef = useRef(null);
17045
17855
  const attachmentsCountRef = useRef(0);
17046
- React2__default.useEffect(() => {
17856
+ React3__default.useEffect(() => {
17047
17857
  attachmentsCountRef.current = state.attachments.length;
17048
17858
  }, [state.attachments.length]);
17049
- React2__default.useEffect(() => {
17859
+ React3__default.useEffect(() => {
17050
17860
  const draft = {
17051
17861
  messageInput: state.messageInput,
17052
17862
  attachments: state.attachments
@@ -17054,7 +17864,7 @@ function useChannelMessageBox({
17054
17864
  syncComposerRef(draft);
17055
17865
  updateConversationDraft(conversationIdRef.current, draft);
17056
17866
  }, [state.messageInput, state.attachments, syncComposerRef]);
17057
- React2__default.useEffect(() => {
17867
+ React3__default.useEffect(() => {
17058
17868
  if (typingTimeoutRef.current) {
17059
17869
  clearTimeout(typingTimeoutRef.current);
17060
17870
  typingTimeoutRef.current = null;
@@ -17114,21 +17924,52 @@ function useChannelMessageBox({
17114
17924
  const handleFileChange = (e) => {
17115
17925
  const files = e.target.files ? Array.from(e.target.files) : [];
17116
17926
  if (!files.length) return;
17927
+ if (!attachmentsGate.enabled) {
17928
+ updateState({
17929
+ attachmentError: attachmentsGate.disabledReason || "Attachments are disabled by your administrator."
17930
+ });
17931
+ if (fileInputRef.current) fileInputRef.current.value = "";
17932
+ return;
17933
+ }
17117
17934
  const currentCount = attachmentsCountRef.current;
17118
- const remaining = MAX_ATTACHMENTS - currentCount;
17935
+ const remaining = maxAttachments - currentCount;
17119
17936
  if (remaining <= 0) {
17120
- updateState({ attachmentError: `Maximum ${MAX_ATTACHMENTS} files allowed` });
17937
+ updateState({ attachmentError: `Maximum ${maxAttachments} files allowed` });
17938
+ if (fileInputRef.current) fileInputRef.current.value = "";
17939
+ return;
17940
+ }
17941
+ const validFiles = [];
17942
+ const rejectionMessages = [];
17943
+ for (const file of files) {
17944
+ if (!matchesAllowedMimeType(file.type, allowedMimeTypes)) {
17945
+ rejectionMessages.push(`"${file.name}" type is not allowed`);
17946
+ continue;
17947
+ }
17948
+ if (file.size > maxFileSizeBytes) {
17949
+ rejectionMessages.push(
17950
+ `"${file.name}" exceeds ${settings.attachments.maxFileSizeMB} MB (got ${formatFileSizeMB(file.size)})`
17951
+ );
17952
+ continue;
17953
+ }
17954
+ validFiles.push(file);
17955
+ }
17956
+ if (validFiles.length === 0) {
17957
+ updateState({
17958
+ attachmentError: rejectionMessages[0] || "Selected files are not allowed"
17959
+ });
17121
17960
  if (fileInputRef.current) fileInputRef.current.value = "";
17122
17961
  return;
17123
17962
  }
17124
- if (files.length > remaining) {
17963
+ if (validFiles.length > remaining) {
17125
17964
  updateState({
17126
17965
  attachmentError: `You can upload only ${remaining} more file${remaining > 1 ? "s" : ""}`
17127
17966
  });
17967
+ } else if (rejectionMessages.length > 0) {
17968
+ updateState({ attachmentError: rejectionMessages[0] });
17128
17969
  } else {
17129
17970
  updateState({ attachmentError: "" });
17130
17971
  }
17131
- const filesToAdd = files.slice(0, remaining);
17972
+ const filesToAdd = validFiles.slice(0, remaining);
17132
17973
  attachmentsCountRef.current = currentCount + filesToAdd.length;
17133
17974
  const newAttachments = filesToAdd.map((file) => ({
17134
17975
  id: `att-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
@@ -17185,7 +18026,7 @@ function useChannelMessageBox({
17185
18026
  return {
17186
18027
  ...prev,
17187
18028
  attachments: nextAttachments,
17188
- attachmentError: nextAttachments.length < MAX_ATTACHMENTS ? "" : prev.attachmentError
18029
+ attachmentError: nextAttachments.length < maxAttachments ? "" : prev.attachmentError
17189
18030
  };
17190
18031
  });
17191
18032
  };
@@ -17292,7 +18133,10 @@ function useChannelMessageBox({
17292
18133
  onlyAdminCanSendMessage,
17293
18134
  fileInputRef,
17294
18135
  hasUploadingAttachments,
17295
- maxAttachments: MAX_ATTACHMENTS,
18136
+ maxAttachments,
18137
+ acceptAttribute,
18138
+ attachmentsEnabled: attachmentsGate.enabled,
18139
+ attachmentsDisabledReason: attachmentsGate.disabledReason,
17296
18140
  handleInputChange,
17297
18141
  handleFileChange,
17298
18142
  handleRemoveAttachment,
@@ -17320,6 +18164,9 @@ var ChannelMessageBoxView = (props) => {
17320
18164
  fileInputRef,
17321
18165
  hasUploadingAttachments,
17322
18166
  maxAttachments,
18167
+ acceptAttribute,
18168
+ attachmentsEnabled,
18169
+ attachmentsDisabledReason,
17323
18170
  handleInputChange,
17324
18171
  handleFileChange,
17325
18172
  handleRemoveAttachment,
@@ -17417,16 +18264,23 @@ var ChannelMessageBoxView = (props) => {
17417
18264
  ),
17418
18265
  /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5 p-1.5", children: [
17419
18266
  /* @__PURE__ */ jsx(
17420
- Button,
18267
+ AdminPermissionTooltip,
17421
18268
  {
17422
- type: "button",
17423
- variant: "ghost",
17424
- size: "icon",
17425
- className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text)",
17426
- onClick: () => !hasUploadingAttachments && fileInputRef.current?.click(),
17427
- disabled: hasUploadingAttachments,
17428
- "aria-label": "Attach files",
17429
- children: /* @__PURE__ */ jsx(Paperclip, { size: 16 })
18269
+ disabled: !attachmentsEnabled,
18270
+ message: attachmentsDisabledReason,
18271
+ children: /* @__PURE__ */ jsx(
18272
+ Button,
18273
+ {
18274
+ type: "button",
18275
+ variant: "ghost",
18276
+ size: "icon",
18277
+ className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text) disabled:opacity-40",
18278
+ onClick: () => attachmentsEnabled && !hasUploadingAttachments && fileInputRef.current?.click(),
18279
+ disabled: !attachmentsEnabled || hasUploadingAttachments,
18280
+ "aria-label": "Attach files",
18281
+ children: /* @__PURE__ */ jsx(Paperclip, { size: 16 })
18282
+ }
18283
+ )
17430
18284
  }
17431
18285
  ),
17432
18286
  /* @__PURE__ */ jsx(
@@ -17436,8 +18290,9 @@ var ChannelMessageBoxView = (props) => {
17436
18290
  ref: fileInputRef,
17437
18291
  className: "hidden",
17438
18292
  multiple: true,
17439
- accept: "image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx",
17440
- onChange: handleFileChange
18293
+ accept: acceptAttribute || void 0,
18294
+ onChange: handleFileChange,
18295
+ disabled: !attachmentsEnabled
17441
18296
  }
17442
18297
  ),
17443
18298
  /* @__PURE__ */ jsxs(Popover, { children: [
@@ -18325,6 +19180,7 @@ var ChatMain = ({
18325
19180
  queryParams,
18326
19181
  queryParamApis,
18327
19182
  queryParamsByApi,
19183
+ apiVersions: apiVersions2,
18328
19184
  themeColor = "#7494ec",
18329
19185
  theme,
18330
19186
  uiConfig,
@@ -18351,8 +19207,8 @@ var ChatMain = ({
18351
19207
  viewportHeight = "full",
18352
19208
  viewportClassName
18353
19209
  }) => {
18354
- const clientObj = React2__default.useMemo(() => ({ id: clientId }), [clientId]);
18355
- const uiConfigObj = React2__default.useMemo(
19210
+ const clientObj = React3__default.useMemo(() => ({ id: clientId }), [clientId]);
19211
+ const uiConfigObj = React3__default.useMemo(
18356
19212
  () => ({
18357
19213
  ...uiConfig || {},
18358
19214
  components: { ...uiConfig?.components, ...components },
@@ -18394,6 +19250,7 @@ var ChatMain = ({
18394
19250
  queryParams,
18395
19251
  queryParamApis,
18396
19252
  queryParamsByApi,
19253
+ apiVersions: apiVersions2,
18397
19254
  children: [
18398
19255
  /* @__PURE__ */ jsx(ForegroundPushBridge, {}),
18399
19256
  /* @__PURE__ */ jsx(
@@ -18686,6 +19543,10 @@ var ChannelListItem_default = ChannelListItem2;
18686
19543
  var packageDefaultComponents = {
18687
19544
  ChatLayout: DefaultChatLayout_default,
18688
19545
  LoggedUserDetails: LoggedUserDetails_default,
19546
+ LoggedUserSettingsDialog: LoggedUserSettingsDialog_default,
19547
+ LoggedUserSettingsTrigger: LoggedUserSettingsTrigger_default,
19548
+ LoggedUserSettingsContent: LoggedUserSettingsContent_default,
19549
+ PushNotificationToggle,
18689
19550
  ChannelList: ChannelListView_default,
18690
19551
  ChannelListItem: DefaultChannelListItem,
18691
19552
  ChannelHeader: ChannelHeaderView_default,
@@ -18718,8 +19579,10 @@ var packageDefaultComponents = {
18718
19579
  };
18719
19580
 
18720
19581
  // src/index.ts
19582
+ init_settings_types();
19583
+ init_api_service();
18721
19584
  var index_default = ChatMain_default;
18722
19585
 
18723
- export { CHAT_API_KEYS, CHAT_GET_API_KEYS, Calendar, CalendarDayButton, Channel, ChannelHeaderView_default as ChannelHeader, ChannelListView_default as ChannelList, ChannelListItem_default as ChannelListItem, Chat, ChatAlertsProvider, ChatAvatar_default as ChatAvatar, ChatCustomizationProvider, ChatDateJumpMenu, ChatLayout_default as ChatLayout, ChatLocaleProvider, ChatMain_default as ChatMain, ChatThemeProvider, ChatViewport, DefaultChannelListItem, DefaultChatAvatar, DefaultChatLayout_default as DefaultChatLayout, ForegroundPushBridge, ForwardModalView as ForwardModal, HeaderCallActions_default as HeaderCallActions, LoggedUserDetails_default as LoggedUserDetails, ChannelMessageBoxView_default as MessageInput, MessageItemView as MessageItem, ActiveChannelMessagesView_default as MessageList, NOTIFICATION_CLICK_SW_TYPE, OPEN_CONVERSATION_EVENT, PUSH_BROADCAST_CHANNEL, PUSH_CHANGED_EVENT, PUSH_DATA_CACHE_NAME, PushNotificationToggle, apiGetPushConfig, apiGetPushPreference, apiRegisterPushToken, apiRemovePushToken, apiUpdatePushPreference, buildChannelFromPushRequest, capitalizeFirst, clampJumpDate, consumePendingOpenConversation, consumePendingOpenFromPushCache, consumePendingOpenRequest, index_default as default, defaultChatClassNames, defaultChatComponents, defaultChatFeatures, defaultChatLayoutConfig, disablePushOnThisDevice, downloadFile, enablePushOnThisDevice, getChatBaseUrl, getChatQueryParamApis, getChatQueryParams, getChatQueryParamsByApi, getChatSocketUrl, getFirstAndLastNameOneCharacter, getResponsiveWidth, getSocketConfig, getStoredPushToken, installNotificationClickBridge, isEmpty, isPushSupported, mergeChatCustomization, mergeChatLayoutConfig, onForegroundPush, packageDefaultComponents, peekPendingOpenConversation, peekPendingOpenRequest, requestOpenConversation, resolveApiUrl, resolveChatApiKey, resolveConversationIdFromPushData, resolveJumpDateBounds, resolveJumpPreset, resolveJumpStep, resolveSocketUrl, setChatAccessToken, setChatBaseURL, setChatClientId, setChatQueryParamApis, setChatQueryParams, setChatQueryParamsByApi, setChatSocketUrl, showNotification, smoothScrollToMessage, socket_service_default as socketService, startOfLocalDay, toDateInputValue, uniqueList, useChatAlerts, useChatContext, useChatController, useChatCustomization, useChatCustomizationOptional, useChatFeatures, useChatFeaturesOptional, useChatLocale, useStore as useChatStore, useChatTheme, usePushNotifications };
19586
+ export { ADMIN_PERMISSION_DENIED_MESSAGE, CALLS_NOT_CONFIGURED_MESSAGE, CHAT_API_KEYS, CHAT_GET_API_KEYS, Calendar, CalendarDayButton, Channel, ChannelHeaderView_default as ChannelHeader, ChannelListView_default as ChannelList, ChannelListItem_default as ChannelListItem, Chat, ChatAlertsProvider, ChatAvatar_default as ChatAvatar, ChatCustomizationProvider, ChatDateJumpMenu, ChatEffectiveSettingsProvider, ChatLayout_default as ChatLayout, ChatLocaleProvider, ChatMain_default as ChatMain, ChatThemeProvider, ChatViewport, DEFAULT_EFFECTIVE_SETTINGS, DefaultChannelListItem, DefaultChatAvatar, DefaultChatLayout_default as DefaultChatLayout, ForegroundPushBridge, ForwardModalView as ForwardModal, HeaderCallActions_default as HeaderCallActions, LoggedUserDetails_default as LoggedUserDetails, LoggedUserSettingsContent_default as LoggedUserSettingsContent, LoggedUserSettingsDialog_default as LoggedUserSettingsDialog, LoggedUserSettingsTrigger_default as LoggedUserSettingsTrigger, ChannelMessageBoxView_default as MessageInput, MessageItemView as MessageItem, ActiveChannelMessagesView_default as MessageList, NOTIFICATION_CLICK_SW_TYPE, OPEN_CONVERSATION_EVENT, PUSH_BROADCAST_CHANNEL, PUSH_CHANGED_EVENT, PUSH_DATA_CACHE_NAME, PushNotificationToggle, apiGetPushConfig, apiGetPushPreference, apiRegisterPushToken, apiRemovePushToken, apiUpdatePushPreference, buildChannelFromPushRequest, buildLoggedUserProfile, buildVersionedApiUrl, capitalizeFirst, clampJumpDate, consumePendingOpenConversation, consumePendingOpenFromPushCache, consumePendingOpenRequest, index_default as default, defaultChatClassNames, defaultChatComponents, defaultChatFeatures, defaultChatLayoutConfig, disablePushOnThisDevice, downloadFile, enablePushOnThisDevice, fetchEffectiveSettings, getChatApiVersions, getChatBaseUrl, getChatQueryParamApis, getChatQueryParams, getChatQueryParamsByApi, getChatSocketUrl, getFirstAndLastNameOneCharacter, getResponsiveWidth, getSocketConfig, getStoredPushToken, installNotificationClickBridge, isEmpty, isPushSupported, mergeChatCustomization, mergeChatLayoutConfig, onForegroundPush, packageDefaultComponents, peekPendingOpenConversation, peekPendingOpenRequest, requestOpenConversation, resolveApiRoot, resolveApiUrl, resolveChatApiKey, resolveConversationIdFromPushData, resolveJumpDateBounds, resolveJumpPreset, resolveJumpStep, resolveSocketUrl, setChatAccessToken, setChatApiVersions, setChatBaseURL, setChatClientId, setChatPushApiVersion, setChatQueryParamApis, setChatQueryParams, setChatQueryParamsByApi, setChatSocketUrl, showNotification, smoothScrollToMessage, socket_service_default as socketService, startOfLocalDay, toDateInputValue, uniqueList, useChatAlerts, useChatContext, useChatController, useChatCustomization, useChatCustomizationOptional, useChatFeatures, useChatFeaturesOptional, useChatLocale, useStore as useChatStore, useChatTheme, useEffectiveSettings, useEffectiveSettingsOptional, usePushNotifications };
18724
19587
  //# sourceMappingURL=index.mjs.map
18725
19588
  //# sourceMappingURL=index.mjs.map