@realtimexsco/live-chat 1.4.11 → 1.4.13

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.cjs CHANGED
@@ -6,7 +6,7 @@ var socket_ioClient = require('socket.io-client');
6
6
  var axios = require('axios');
7
7
  var zustand = require('zustand');
8
8
  var middleware = require('zustand/middleware');
9
- var React2 = require('react');
9
+ var React3 = require('react');
10
10
  var radixUi = require('radix-ui');
11
11
  var clsx = require('clsx');
12
12
  var tailwindMerge = require('tailwind-merge');
@@ -39,7 +39,7 @@ function _interopNamespace(e) {
39
39
  }
40
40
 
41
41
  var axios__default = /*#__PURE__*/_interopDefault(axios);
42
- var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
42
+ var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
43
43
  var EmojiPicker__default = /*#__PURE__*/_interopDefault(EmojiPicker);
44
44
  var toast__default = /*#__PURE__*/_interopDefault(toast);
45
45
 
@@ -1082,16 +1082,27 @@ var init_connection_actions = __esm({
1082
1082
  setActiveConversationId: (conversationId) => {
1083
1083
  set((state) => {
1084
1084
  const nextId = conversationId ?? null;
1085
- if (String(state.activeConversationId ?? "") === String(nextId ?? "")) {
1085
+ const prevId = state.activeConversationId ?? null;
1086
+ if (String(prevId ?? "") === String(nextId ?? "")) {
1086
1087
  return state;
1087
1088
  }
1089
+ const nextErrors = { ...state.conversationErrors };
1090
+ if (prevId) {
1091
+ delete nextErrors[String(prevId)];
1092
+ }
1088
1093
  if (!nextId) {
1089
- return { activeConversationId: null };
1094
+ return {
1095
+ activeConversationId: null,
1096
+ globalAppError: null,
1097
+ conversationErrors: nextErrors
1098
+ };
1090
1099
  }
1091
1100
  const target = state.conversations.find((c) => String(c._id) === String(nextId));
1092
1101
  const unreadAlreadyZero = !normalizeUnreadCount(target?.unreadCount, state.loggedUserDetails?._id);
1093
1102
  return {
1094
1103
  activeConversationId: nextId,
1104
+ globalAppError: null,
1105
+ conversationErrors: nextErrors,
1095
1106
  conversations: unreadAlreadyZero ? state.conversations : state.conversations.map(
1096
1107
  (c) => String(c._id) === String(nextId) ? { ...c, unreadCount: 0 } : c
1097
1108
  )
@@ -1132,25 +1143,32 @@ var init_connection_actions = __esm({
1132
1143
  type = "rate_limit";
1133
1144
  return;
1134
1145
  }
1146
+ if (conversationId) {
1147
+ set((state) => ({
1148
+ conversationErrors: {
1149
+ ...state.conversationErrors,
1150
+ [conversationId]: { message, type }
1151
+ },
1152
+ globalAppError: null
1153
+ }));
1154
+ return;
1155
+ }
1135
1156
  set({ globalAppError: { message, type } });
1136
1157
  },
1137
1158
  clearAppError: (conversationId) => {
1138
1159
  set((state) => {
1139
- const targetId = conversationId ?? state.activeConversationId;
1140
- if (targetId) {
1160
+ if (conversationId) {
1141
1161
  const nextErrors = { ...state.conversationErrors };
1142
1162
  const nextComposer = { ...state.composerDisabledByConversation };
1143
- delete nextErrors[targetId];
1144
- delete nextComposer[targetId];
1163
+ delete nextErrors[conversationId];
1164
+ delete nextComposer[conversationId];
1145
1165
  return {
1146
1166
  conversationErrors: nextErrors,
1147
1167
  composerDisabledByConversation: nextComposer
1148
1168
  };
1149
1169
  }
1150
1170
  return {
1151
- globalAppError: null,
1152
- conversationErrors: {},
1153
- composerDisabledByConversation: {}
1171
+ globalAppError: null
1154
1172
  };
1155
1173
  });
1156
1174
  },
@@ -1190,6 +1208,7 @@ function resolveChatApiKey(url) {
1190
1208
  }
1191
1209
  if (path.includes("/message/search-messages")) return "searchMessages";
1192
1210
  if (path.includes("/message/searched-message/context")) return "searchMessagesContext";
1211
+ if (path.includes("/settings/effective")) return "effectiveSettings";
1193
1212
  if (path.includes("/conversation/group/create")) return "createGroup";
1194
1213
  if (path.includes("/conversation/group/permission/update")) return "updateGroupPermissions";
1195
1214
  if (path.includes("/conversation/group/information/update")) return "updateGroupInfo";
@@ -1233,7 +1252,8 @@ var init_query_params = __esm({
1233
1252
  "pinnedMessages",
1234
1253
  "starredMessages",
1235
1254
  "searchMessages",
1236
- "searchMessagesContext"
1255
+ "searchMessagesContext",
1256
+ "effectiveSettings"
1237
1257
  ];
1238
1258
  exports.CHAT_API_KEYS = [
1239
1259
  "all",
@@ -1258,8 +1278,8 @@ var init_api_version = __esm({
1258
1278
  "src/services/api/api-version.ts"() {
1259
1279
  init_client();
1260
1280
  DEFAULT_VERSIONS = {
1261
- default: "v1",
1262
- push: "v2"
1281
+ v1: "v1",
1282
+ v2: "v2"
1263
1283
  };
1264
1284
  apiVersions = { ...DEFAULT_VERSIONS };
1265
1285
  normalizeApiVersion = (version) => {
@@ -1278,7 +1298,7 @@ var init_api_version = __esm({
1278
1298
  exports.buildVersionedApiUrl = (path, options) => {
1279
1299
  const root = exports.resolveApiRoot(options?.baseUrl);
1280
1300
  const version = normalizeApiVersion(
1281
- options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.default)
1301
+ options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.v1)
1282
1302
  );
1283
1303
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1284
1304
  if (!root) {
@@ -1288,23 +1308,23 @@ var init_api_version = __esm({
1288
1308
  };
1289
1309
  exports.setChatApiVersions = (config) => {
1290
1310
  if (!config) return;
1291
- if (config.default) {
1292
- apiVersions.default = normalizeApiVersion(config.default);
1311
+ if (config.v1) {
1312
+ apiVersions.v1 = normalizeApiVersion(config.v1);
1293
1313
  }
1294
- if (config.push) {
1295
- apiVersions.push = normalizeApiVersion(config.push);
1314
+ if (config.v2) {
1315
+ apiVersions.v2 = normalizeApiVersion(config.v2);
1296
1316
  }
1297
1317
  };
1298
1318
  exports.getChatApiVersions = () => ({
1299
1319
  ...apiVersions
1300
1320
  });
1301
1321
  exports.setChatPushApiVersion = (version) => {
1302
- apiVersions.push = normalizeApiVersion(version);
1322
+ apiVersions.v2 = normalizeApiVersion(version);
1303
1323
  };
1304
1324
  syncDefaultApiVersionFromBaseUrl = (baseUrl) => {
1305
1325
  const match = String(baseUrl || "").trim().match(/\/(v\d+)\/?$/i);
1306
1326
  if (match?.[1]) {
1307
- apiVersions.default = normalizeApiVersion(match[1]);
1327
+ apiVersions.v1 = normalizeApiVersion(match[1]);
1308
1328
  }
1309
1329
  };
1310
1330
  }
@@ -1449,6 +1469,9 @@ var init_api_constants = __esm({
1449
1469
  SEARCH_MESSAGES: "/message/search-messages",
1450
1470
  SEARCH_MESSAGES_CONTEXT: (messageId2) => `/message/searched-message/context?messageId=${messageId2}`,
1451
1471
  BLOCK_UNBLOCK_USER: "/user/block-unblock"
1472
+ },
1473
+ SETTINGS: {
1474
+ PERMISSIONS: "/settings/effective"
1452
1475
  }
1453
1476
  };
1454
1477
  }
@@ -1485,13 +1508,11 @@ var init_auth_api = __esm({
1485
1508
  });
1486
1509
 
1487
1510
  // src/constants/chat.constants.ts
1488
- var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, DM_EDIT_WINDOW_MS, GROUP_EDIT_WINDOW_MS, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1511
+ var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1489
1512
  var init_chat_constants = __esm({
1490
1513
  "src/constants/chat.constants.ts"() {
1491
1514
  DEFAULT_MESSAGES_LIMIT = 20;
1492
1515
  DEFAULT_CONVERSATIONS_LIMIT = 20;
1493
- DM_EDIT_WINDOW_MS = 5 * 60 * 1e3;
1494
- GROUP_EDIT_WINDOW_MS = 10 * 60 * 1e3;
1495
1516
  MAX_ATTACHMENT_CAPTION_LENGTH = 100;
1496
1517
  ATTACHMENT_FILENAME_TIMESTAMP_LENGTH = 13;
1497
1518
  }
@@ -1694,6 +1715,212 @@ var init_uploads_api = __esm({
1694
1715
  }
1695
1716
  });
1696
1717
 
1718
+ // src/types/settings.types.ts
1719
+ exports.DEFAULT_EFFECTIVE_SETTINGS = void 0; exports.ADMIN_PERMISSION_DENIED_MESSAGE = void 0; exports.CALLS_NOT_ALLOWED_BY_PLATFORM_MESSAGE = void 0; exports.CALLS_NOT_CONFIGURED_MESSAGE = void 0;
1720
+ var init_settings_types = __esm({
1721
+ "src/types/settings.types.ts"() {
1722
+ exports.DEFAULT_EFFECTIVE_SETTINGS = {
1723
+ pushNotificationEnabled: true,
1724
+ videoCallEnabled: true,
1725
+ audioCallEnabled: true,
1726
+ blockUsersEnabled: true,
1727
+ calls: {
1728
+ allowedByPlatform: true,
1729
+ enabled: true,
1730
+ configured: true
1731
+ },
1732
+ groupPolicy: {
1733
+ defaults: {
1734
+ onlyAdminCanSendMessage: false,
1735
+ onlyAdminCanEditInfo: false,
1736
+ senderCanEditMessage: true,
1737
+ allowMemberAdd: true,
1738
+ allowMemberRemove: false,
1739
+ moderationEnabled: false
1740
+ },
1741
+ locked: []
1742
+ },
1743
+ messageTimers: {
1744
+ dmEditMinutes: 5,
1745
+ dmDeleteMinutes: 5,
1746
+ groupEditMinutes: 10,
1747
+ groupDeleteMinutes: 10
1748
+ },
1749
+ attachments: {
1750
+ enabled: true,
1751
+ maxFileSizeMB: 10,
1752
+ allowedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
1753
+ maxAttachmentsPerMessage: 10
1754
+ },
1755
+ maxAdminsPerGroup: 5,
1756
+ maxParticipantsPerGroup: 100,
1757
+ maxPinnedMessagesPerConversation: 10,
1758
+ groupCreationEnabled: true
1759
+ };
1760
+ exports.ADMIN_PERMISSION_DENIED_MESSAGE = "This feature is disabled by your administrator.";
1761
+ exports.CALLS_NOT_ALLOWED_BY_PLATFORM_MESSAGE = "Calls are not allowed on this platform. Please contact your administrator.";
1762
+ exports.CALLS_NOT_CONFIGURED_MESSAGE = "Calls are not configured. Please contact your administrator.";
1763
+ }
1764
+ });
1765
+
1766
+ // src/services/api/settings.api.ts
1767
+ function asBoolean(value, fallback) {
1768
+ return typeof value === "boolean" ? value : fallback;
1769
+ }
1770
+ function asPositiveNumber(value, fallback) {
1771
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
1772
+ }
1773
+ function asStringArray(value, fallback) {
1774
+ if (!Array.isArray(value)) return fallback;
1775
+ const next = value.filter((item) => typeof item === "string");
1776
+ return next.length > 0 ? next : fallback;
1777
+ }
1778
+ function normalizeGroupPermissions(value, fallback) {
1779
+ if (!value || typeof value !== "object") return { ...fallback };
1780
+ const raw = value;
1781
+ return {
1782
+ onlyAdminCanSendMessage: asBoolean(
1783
+ raw.onlyAdminCanSendMessage,
1784
+ fallback.onlyAdminCanSendMessage
1785
+ ),
1786
+ onlyAdminCanEditInfo: asBoolean(
1787
+ raw.onlyAdminCanEditInfo,
1788
+ fallback.onlyAdminCanEditInfo
1789
+ ),
1790
+ senderCanEditMessage: asBoolean(
1791
+ raw.senderCanEditMessage,
1792
+ fallback.senderCanEditMessage
1793
+ ),
1794
+ allowMemberAdd: asBoolean(raw.allowMemberAdd, fallback.allowMemberAdd),
1795
+ allowMemberRemove: asBoolean(
1796
+ raw.allowMemberRemove,
1797
+ fallback.allowMemberRemove
1798
+ ),
1799
+ moderationEnabled: asBoolean(
1800
+ raw.moderationEnabled,
1801
+ fallback.moderationEnabled
1802
+ )
1803
+ };
1804
+ }
1805
+ function normalizeCalls(value, fallback) {
1806
+ if (!value || typeof value !== "object") return { ...fallback };
1807
+ const raw = value;
1808
+ return {
1809
+ allowedByPlatform: asBoolean(
1810
+ raw.allowedByPlatform,
1811
+ fallback.allowedByPlatform
1812
+ ),
1813
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1814
+ configured: asBoolean(raw.configured, fallback.configured)
1815
+ };
1816
+ }
1817
+ function normalizeGroupPolicy(value, fallback) {
1818
+ if (!value || typeof value !== "object") {
1819
+ return {
1820
+ defaults: { ...fallback.defaults },
1821
+ locked: [...fallback.locked]
1822
+ };
1823
+ }
1824
+ const raw = value;
1825
+ return {
1826
+ defaults: normalizeGroupPermissions(raw.defaults, fallback.defaults),
1827
+ locked: asStringArray(raw.locked, fallback.locked)
1828
+ };
1829
+ }
1830
+ function normalizeMessageTimers(value, fallback) {
1831
+ if (!value || typeof value !== "object") return { ...fallback };
1832
+ const raw = value;
1833
+ return {
1834
+ dmEditMinutes: asPositiveNumber(raw.dmEditMinutes, fallback.dmEditMinutes),
1835
+ dmDeleteMinutes: asPositiveNumber(
1836
+ raw.dmDeleteMinutes,
1837
+ fallback.dmDeleteMinutes
1838
+ ),
1839
+ groupEditMinutes: asPositiveNumber(
1840
+ raw.groupEditMinutes,
1841
+ fallback.groupEditMinutes
1842
+ ),
1843
+ groupDeleteMinutes: asPositiveNumber(
1844
+ raw.groupDeleteMinutes,
1845
+ fallback.groupDeleteMinutes
1846
+ )
1847
+ };
1848
+ }
1849
+ function normalizeAttachments(value, fallback) {
1850
+ if (!value || typeof value !== "object") return { ...fallback };
1851
+ const raw = value;
1852
+ return {
1853
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1854
+ maxFileSizeMB: asPositiveNumber(raw.maxFileSizeMB, fallback.maxFileSizeMB),
1855
+ allowedMimeTypes: asStringArray(
1856
+ raw.allowedMimeTypes,
1857
+ fallback.allowedMimeTypes
1858
+ ),
1859
+ maxAttachmentsPerMessage: asPositiveNumber(
1860
+ raw.maxAttachmentsPerMessage,
1861
+ fallback.maxAttachmentsPerMessage
1862
+ )
1863
+ };
1864
+ }
1865
+ function normalizeEffectiveSettings(raw) {
1866
+ const payload = raw && typeof raw === "object" ? raw.data ?? raw : null;
1867
+ const source = payload && typeof payload === "object" ? payload : {};
1868
+ const defaults = exports.DEFAULT_EFFECTIVE_SETTINGS;
1869
+ return {
1870
+ pushNotificationEnabled: asBoolean(
1871
+ source.pushNotificationEnabled,
1872
+ defaults.pushNotificationEnabled
1873
+ ),
1874
+ videoCallEnabled: asBoolean(
1875
+ source.videoCallEnabled,
1876
+ defaults.videoCallEnabled
1877
+ ),
1878
+ audioCallEnabled: asBoolean(
1879
+ source.audioCallEnabled,
1880
+ defaults.audioCallEnabled
1881
+ ),
1882
+ blockUsersEnabled: asBoolean(
1883
+ source.blockUsersEnabled,
1884
+ defaults.blockUsersEnabled
1885
+ ),
1886
+ calls: normalizeCalls(source.calls, defaults.calls),
1887
+ groupPolicy: normalizeGroupPolicy(source.groupPolicy, defaults.groupPolicy),
1888
+ messageTimers: normalizeMessageTimers(
1889
+ source.messageTimers,
1890
+ defaults.messageTimers
1891
+ ),
1892
+ attachments: normalizeAttachments(source.attachments, defaults.attachments),
1893
+ maxAdminsPerGroup: asPositiveNumber(
1894
+ source.maxAdminsPerGroup,
1895
+ defaults.maxAdminsPerGroup
1896
+ ),
1897
+ maxParticipantsPerGroup: asPositiveNumber(
1898
+ source.maxParticipantsPerGroup,
1899
+ defaults.maxParticipantsPerGroup
1900
+ ),
1901
+ maxPinnedMessagesPerConversation: asPositiveNumber(
1902
+ source.maxPinnedMessagesPerConversation,
1903
+ defaults.maxPinnedMessagesPerConversation
1904
+ ),
1905
+ groupCreationEnabled: asBoolean(
1906
+ source.groupCreationEnabled,
1907
+ defaults.groupCreationEnabled
1908
+ )
1909
+ };
1910
+ }
1911
+ exports.fetchEffectiveSettings = void 0;
1912
+ var init_settings_api = __esm({
1913
+ "src/services/api/settings.api.ts"() {
1914
+ init_api_constants();
1915
+ init_client();
1916
+ init_settings_types();
1917
+ exports.fetchEffectiveSettings = async () => {
1918
+ const response = await apiClient.get(API_ENDPOINTS.SETTINGS.PERMISSIONS);
1919
+ return normalizeEffectiveSettings(response.data);
1920
+ };
1921
+ }
1922
+ });
1923
+
1697
1924
  // src/services/api/index.ts
1698
1925
  var init_api = __esm({
1699
1926
  "src/services/api/index.ts"() {
@@ -1702,6 +1929,7 @@ var init_api = __esm({
1702
1929
  init_messages_api();
1703
1930
  init_groups_api();
1704
1931
  init_uploads_api();
1932
+ init_settings_api();
1705
1933
  }
1706
1934
  });
1707
1935
 
@@ -4385,7 +4613,7 @@ var syncLocalMessagesOnDmMarkRead = (prev, lastDMMarkRead, conversations, logged
4385
4613
 
4386
4614
  // src/hooks/chat-sync/useChatSync.ts
4387
4615
  var useChatSync = () => {
4388
- const [localMessages, setLocalMessages] = React2.useState({});
4616
+ const [localMessages, setLocalMessages] = React3.useState({});
4389
4617
  const lastDM = useSocketStore((s) => s.lastDM);
4390
4618
  const lastEditDM = useSocketStore((s) => s.lastEditDM);
4391
4619
  const lastDeleteDM = useSocketStore((s) => s.lastDeleteDM);
@@ -4398,17 +4626,17 @@ var useChatSync = () => {
4398
4626
  const messagesByConversation = useSocketStore((s) => s.messagesByConversation);
4399
4627
  const messagesMetadata = useSocketStore((s) => s.messagesMetadata);
4400
4628
  const loggedUserDetails = useAuthStore((s) => s.loggedUserDetails);
4401
- React2.useEffect(() => {
4629
+ React3.useEffect(() => {
4402
4630
  setLocalMessages(
4403
4631
  (prev) => syncLocalMessagesOnStoreClear(prev, messagesByConversation, messagesMetadata)
4404
4632
  );
4405
4633
  }, [messagesByConversation, messagesMetadata]);
4406
- React2.useEffect(() => {
4634
+ React3.useEffect(() => {
4407
4635
  if (lastPinUpdate) {
4408
4636
  setLocalMessages((prev) => syncLocalMessagesOnPinUpdate(prev, lastPinUpdate));
4409
4637
  }
4410
4638
  }, [lastPinUpdate]);
4411
- React2.useEffect(() => {
4639
+ React3.useEffect(() => {
4412
4640
  if (lastStarUpdate) {
4413
4641
  setLocalMessages((prev) => {
4414
4642
  const next = syncLocalMessagesOnStarUpdate(
@@ -4420,18 +4648,18 @@ var useChatSync = () => {
4420
4648
  });
4421
4649
  }
4422
4650
  }, [lastStarUpdate, loggedUserDetails?._id]);
4423
- React2.useEffect(() => {
4651
+ React3.useEffect(() => {
4424
4652
  if (!lastReactionUpdate) return;
4425
4653
  setLocalMessages(
4426
4654
  (prev) => syncLocalMessagesOnReactionUpdate(prev, lastReactionUpdate, messagesByConversation)
4427
4655
  );
4428
4656
  }, [lastReactionUpdate, messagesByConversation]);
4429
- React2.useEffect(() => {
4657
+ React3.useEffect(() => {
4430
4658
  if (lastDeleteDM) {
4431
4659
  setLocalMessages((prev) => syncLocalMessagesOnDelete(prev, lastDeleteDM));
4432
4660
  }
4433
4661
  }, [lastDeleteDM]);
4434
- React2.useEffect(() => {
4662
+ React3.useEffect(() => {
4435
4663
  if (lastEditDM) {
4436
4664
  setLocalMessages((prev) => {
4437
4665
  const next = syncLocalMessagesOnEdit(prev, lastEditDM);
@@ -4439,7 +4667,7 @@ var useChatSync = () => {
4439
4667
  });
4440
4668
  }
4441
4669
  }, [lastEditDM]);
4442
- React2.useEffect(() => {
4670
+ React3.useEffect(() => {
4443
4671
  if (!lastDM) return;
4444
4672
  const result = buildIncomingMessageFromDM(
4445
4673
  lastDM,
@@ -4451,14 +4679,14 @@ var useChatSync = () => {
4451
4679
  (prev) => applyMessageUpdate(prev, result.storageKey, result.incomingMsg, result.isMe)
4452
4680
  );
4453
4681
  }, [lastDM, loggedUserDetails, conversations]);
4454
- React2.useEffect(() => {
4682
+ React3.useEffect(() => {
4455
4683
  if (lastStatusUpdate) {
4456
4684
  setLocalMessages(
4457
4685
  (prev) => syncLocalMessagesOnStatusUpdate(prev, lastStatusUpdate, loggedUserDetails)
4458
4686
  );
4459
4687
  }
4460
4688
  }, [lastStatusUpdate, loggedUserDetails]);
4461
- React2.useEffect(() => {
4689
+ React3.useEffect(() => {
4462
4690
  if (lastDMMarkRead) {
4463
4691
  setLocalMessages((prev) => {
4464
4692
  const next = syncLocalMessagesOnDmMarkRead(
@@ -4488,16 +4716,16 @@ var mergeChatCustomization = (config) => ({
4488
4716
  classNames: { ...defaultChatClassNames, ...config?.classNames ?? {} },
4489
4717
  features: { ...defaultChatFeatures, ...config?.features ?? {} }
4490
4718
  });
4491
- var ChatCustomizationContext = React2.createContext(void 0);
4719
+ var ChatCustomizationContext = React3.createContext(void 0);
4492
4720
  var ChatCustomizationProvider = ({
4493
4721
  children,
4494
4722
  customization
4495
4723
  }) => {
4496
- const merged = React2.useMemo(
4724
+ const merged = React3.useMemo(
4497
4725
  () => mergeChatCustomization(customization),
4498
4726
  [customization]
4499
4727
  );
4500
- const value = React2.useMemo(
4728
+ const value = React3.useMemo(
4501
4729
  () => ({
4502
4730
  components: merged.components,
4503
4731
  classNames: merged.classNames,
@@ -4513,7 +4741,7 @@ var ChatCustomizationProvider = ({
4513
4741
  return /* @__PURE__ */ jsxRuntime.jsx(ChatCustomizationContext.Provider, { value, children });
4514
4742
  };
4515
4743
  var useChatCustomization = () => {
4516
- const context = React2.useContext(ChatCustomizationContext);
4744
+ const context = React3.useContext(ChatCustomizationContext);
4517
4745
  if (!context) {
4518
4746
  throw new Error(
4519
4747
  "useChatCustomization must be used within ChatCustomizationProvider"
@@ -4522,7 +4750,7 @@ var useChatCustomization = () => {
4522
4750
  return context;
4523
4751
  };
4524
4752
  var useChatCustomizationOptional = () => {
4525
- const context = React2.useContext(ChatCustomizationContext);
4753
+ const context = React3.useContext(ChatCustomizationContext);
4526
4754
  return context ?? {
4527
4755
  components: {},
4528
4756
  classNames: {},
@@ -4533,9 +4761,153 @@ var useChatCustomizationOptional = () => {
4533
4761
  };
4534
4762
  var useChatFeatures = () => useChatCustomization().features;
4535
4763
  var useChatFeaturesOptional = () => useChatCustomizationOptional().features;
4536
- var ChatContext = React2.createContext(void 0);
4764
+
4765
+ // src/context/ChatEffectiveSettingsContext.tsx
4766
+ init_settings_api();
4767
+ init_settings_types();
4768
+ var ChatEffectiveSettingsContext = React3.createContext(null);
4769
+ function buildCallGate(settings, featureEnabled) {
4770
+ if (!settings.calls.allowedByPlatform) {
4771
+ return {
4772
+ visible: true,
4773
+ enabled: false,
4774
+ disabledReason: exports.CALLS_NOT_ALLOWED_BY_PLATFORM_MESSAGE
4775
+ };
4776
+ }
4777
+ if (!settings.calls.configured) {
4778
+ return {
4779
+ visible: true,
4780
+ enabled: false,
4781
+ disabledReason: exports.CALLS_NOT_CONFIGURED_MESSAGE
4782
+ };
4783
+ }
4784
+ if (!settings.calls.enabled) {
4785
+ return {
4786
+ visible: true,
4787
+ enabled: false,
4788
+ disabledReason: exports.ADMIN_PERMISSION_DENIED_MESSAGE
4789
+ };
4790
+ }
4791
+ if (!featureEnabled) {
4792
+ return {
4793
+ visible: true,
4794
+ enabled: false,
4795
+ disabledReason: exports.ADMIN_PERMISSION_DENIED_MESSAGE
4796
+ };
4797
+ }
4798
+ return { visible: true, enabled: true, disabledReason: null };
4799
+ }
4800
+ function ChatEffectiveSettingsProvider({
4801
+ children,
4802
+ enabled = true
4803
+ }) {
4804
+ const [settings, setSettings] = React3.useState(
4805
+ exports.DEFAULT_EFFECTIVE_SETTINGS
4806
+ );
4807
+ const [loading, setLoading] = React3.useState(true);
4808
+ const [error, setError] = React3.useState(null);
4809
+ const refresh = React3.useCallback(async () => {
4810
+ if (!enabled) {
4811
+ setLoading(false);
4812
+ return;
4813
+ }
4814
+ setLoading(true);
4815
+ try {
4816
+ const next = await exports.fetchEffectiveSettings();
4817
+ setSettings(next);
4818
+ setError(null);
4819
+ } catch (err) {
4820
+ console.error("Failed to fetch effective settings:", err);
4821
+ setError(
4822
+ err instanceof Error ? err.message : "Could not load chat permissions"
4823
+ );
4824
+ setSettings(exports.DEFAULT_EFFECTIVE_SETTINGS);
4825
+ } finally {
4826
+ setLoading(false);
4827
+ }
4828
+ }, [enabled]);
4829
+ React3.useEffect(() => {
4830
+ void refresh();
4831
+ }, [refresh]);
4832
+ const value = React3.useMemo(() => {
4833
+ const isGroupPermissionLocked = (key) => settings.groupPolicy.locked.includes(key);
4834
+ return {
4835
+ settings,
4836
+ loading,
4837
+ error,
4838
+ refresh,
4839
+ isGroupPermissionLocked,
4840
+ getAudioCallGate: () => buildCallGate(settings, settings.audioCallEnabled),
4841
+ getVideoCallGate: () => buildCallGate(settings, settings.videoCallEnabled),
4842
+ getPushNotificationGate: () => ({
4843
+ visible: true,
4844
+ enabled: settings.pushNotificationEnabled,
4845
+ disabledReason: settings.pushNotificationEnabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4846
+ }),
4847
+ getBlockUsersGate: () => ({
4848
+ visible: true,
4849
+ enabled: settings.blockUsersEnabled,
4850
+ disabledReason: settings.blockUsersEnabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4851
+ }),
4852
+ getAttachmentsGate: () => ({
4853
+ visible: true,
4854
+ enabled: settings.attachments.enabled,
4855
+ disabledReason: settings.attachments.enabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4856
+ }),
4857
+ getGroupCreationGate: () => ({
4858
+ visible: true,
4859
+ enabled: settings.groupCreationEnabled,
4860
+ disabledReason: settings.groupCreationEnabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4861
+ })
4862
+ };
4863
+ }, [settings, loading, error, refresh]);
4864
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatEffectiveSettingsContext.Provider, { value, children });
4865
+ }
4866
+ function useEffectiveSettings() {
4867
+ const ctx = React3.useContext(ChatEffectiveSettingsContext);
4868
+ if (!ctx) {
4869
+ throw new Error(
4870
+ "useEffectiveSettings must be used within ChatEffectiveSettingsProvider"
4871
+ );
4872
+ }
4873
+ return ctx;
4874
+ }
4875
+ function useEffectiveSettingsOptional() {
4876
+ const ctx = React3.useContext(ChatEffectiveSettingsContext);
4877
+ if (ctx) return ctx;
4878
+ return {
4879
+ settings: exports.DEFAULT_EFFECTIVE_SETTINGS,
4880
+ loading: false,
4881
+ error: null,
4882
+ refresh: async () => void 0,
4883
+ isGroupPermissionLocked: () => false,
4884
+ getAudioCallGate: () => buildCallGate(exports.DEFAULT_EFFECTIVE_SETTINGS, true),
4885
+ getVideoCallGate: () => buildCallGate(exports.DEFAULT_EFFECTIVE_SETTINGS, true),
4886
+ getPushNotificationGate: () => ({
4887
+ visible: true,
4888
+ enabled: true,
4889
+ disabledReason: null
4890
+ }),
4891
+ getBlockUsersGate: () => ({
4892
+ visible: true,
4893
+ enabled: true,
4894
+ disabledReason: null
4895
+ }),
4896
+ getAttachmentsGate: () => ({
4897
+ visible: true,
4898
+ enabled: true,
4899
+ disabledReason: null
4900
+ }),
4901
+ getGroupCreationGate: () => ({
4902
+ visible: true,
4903
+ enabled: true,
4904
+ disabledReason: null
4905
+ })
4906
+ };
4907
+ }
4908
+ var ChatContext = React3.createContext(void 0);
4537
4909
  var useChatContext = () => {
4538
- const context = React2.useContext(ChatContext);
4910
+ const context = React3.useContext(ChatContext);
4539
4911
  if (!context) {
4540
4912
  throw new Error("useChatContext must be used within a Chat provider");
4541
4913
  }
@@ -4572,7 +4944,7 @@ var Chat = ({
4572
4944
  const status = exports.useChatStore((s) => s.status);
4573
4945
  const error = exports.useChatStore((s) => s.error);
4574
4946
  useChatSync();
4575
- React2__namespace.default.useEffect(() => {
4947
+ React3__namespace.default.useEffect(() => {
4576
4948
  const clientId = client?.id;
4577
4949
  const initializeChat = async () => {
4578
4950
  if (!clientId || !loggedUserDetails) return;
@@ -4662,28 +5034,28 @@ var Chat = ({
4662
5034
  queryParamApis: queryParamApis ?? ["get"],
4663
5035
  queryParamsByApi: queryParamsByApi ?? {}
4664
5036
  });
4665
- React2__namespace.default.useEffect(() => {
5037
+ React3__namespace.default.useEffect(() => {
4666
5038
  exports.setChatQueryParams(queryParams);
4667
5039
  exports.setChatQueryParamApis(queryParamApis);
4668
5040
  exports.setChatQueryParamsByApi(queryParamsByApi);
4669
5041
  }, [queryConfigKey]);
4670
- React2__namespace.default.useEffect(() => {
5042
+ React3__namespace.default.useEffect(() => {
4671
5043
  if (lastDM && onMessageReceived) {
4672
5044
  onMessageReceived(lastDM);
4673
5045
  }
4674
5046
  }, [lastDM, onMessageReceived]);
4675
- React2__namespace.default.useEffect(() => {
5047
+ React3__namespace.default.useEffect(() => {
4676
5048
  if (status === "connected" && onSocketConnected) {
4677
5049
  const socketId = exports.useChatStore.getState().socketId;
4678
5050
  if (socketId) onSocketConnected(socketId);
4679
5051
  }
4680
5052
  }, [status, onSocketConnected]);
4681
- React2__namespace.default.useEffect(() => {
5053
+ React3__namespace.default.useEffect(() => {
4682
5054
  if (status === "error" && error && onSocketError) {
4683
5055
  onSocketError(error);
4684
5056
  }
4685
5057
  }, [status, error, onSocketError]);
4686
- const contextValue = React2__namespace.default.useMemo(
5058
+ const contextValue = React3__namespace.default.useMemo(
4687
5059
  () => ({
4688
5060
  onSendMessage,
4689
5061
  client,
@@ -4703,7 +5075,7 @@ var Chat = ({
4703
5075
  messagesData
4704
5076
  ]
4705
5077
  );
4706
- const customization = React2__namespace.default.useMemo(
5078
+ const customization = React3__namespace.default.useMemo(
4707
5079
  () => ({
4708
5080
  components: uiConfig.components,
4709
5081
  classNames: uiConfig.classNames,
@@ -4711,7 +5083,7 @@ var Chat = ({
4711
5083
  }),
4712
5084
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
4713
5085
  );
4714
- return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? children : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
5086
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsxRuntime.jsx(ChatEffectiveSettingsProvider, { enabled: true, children }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
4715
5087
  };
4716
5088
 
4717
5089
  // src/hooks/useChatController.ts
@@ -5009,7 +5381,7 @@ function toDisplayAttachments(attachments) {
5009
5381
  }));
5010
5382
  }
5011
5383
  var useChatController = () => {
5012
- const [state, setState] = React2.useState({
5384
+ const [state, setState] = React3.useState({
5013
5385
  activeChannel: null,
5014
5386
  activeConversationId: null,
5015
5387
  replyToMessage: null,
@@ -5019,15 +5391,15 @@ var useChatController = () => {
5019
5391
  debouncedSearch: "",
5020
5392
  isCreateModalOpen: false
5021
5393
  });
5022
- const updateState = React2.useCallback((updates) => {
5394
+ const updateState = React3.useCallback((updates) => {
5023
5395
  setState((prev) => ({ ...prev, ...updates }));
5024
5396
  }, []);
5025
- const handleSelectionModeChange = React2.useCallback((active) => {
5397
+ const handleSelectionModeChange = React3.useCallback((active) => {
5026
5398
  setState(
5027
5399
  (prev) => prev.isMessageSelectionMode === active ? prev : { ...prev, isMessageSelectionMode: active }
5028
5400
  );
5029
5401
  }, []);
5030
- const handleActiveChannelPatch = React2.useCallback((patch) => {
5402
+ const handleActiveChannelPatch = React3.useCallback((patch) => {
5031
5403
  setState(
5032
5404
  (prev) => prev.activeChannel ? { ...prev, activeChannel: { ...prev.activeChannel, ...patch } } : prev
5033
5405
  );
@@ -5051,11 +5423,11 @@ var useChatController = () => {
5051
5423
  const allUsers = exports.useChatStore((s) => s.allUsers);
5052
5424
  const isFetchingConversations = exports.useChatStore((s) => s.isFetchingConversations);
5053
5425
  const conversations = exports.useChatStore((s) => s.conversations);
5054
- const [pendingOpenRequest, setPendingOpenRequest] = React2.useState(() => peekPendingOpenRequest());
5426
+ const [pendingOpenRequest, setPendingOpenRequest] = React3.useState(() => peekPendingOpenRequest());
5055
5427
  const conversationsLength = exports.useChatStore(
5056
5428
  (s) => state.activeChannel?.conversationId ? 0 : s.conversations.length
5057
5429
  );
5058
- const lastConversationActionsRef = React2.useRef(null);
5430
+ const lastConversationActionsRef = React3.useRef(null);
5059
5431
  const activeChannelId = state.activeChannel?.id;
5060
5432
  const activeChannelConvId = state.activeChannel?.conversationId;
5061
5433
  const activeChannelIsGroup = !!state.activeChannel?.isGroup;
@@ -5080,7 +5452,7 @@ var useChatController = () => {
5080
5452
  isGroupConversation(latestInfo)
5081
5453
  ].join("|");
5082
5454
  });
5083
- React2.useEffect(() => {
5455
+ React3.useEffect(() => {
5084
5456
  const onOpen = (event) => {
5085
5457
  const detail = event.detail;
5086
5458
  if (detail?.conversationId) {
@@ -5097,7 +5469,7 @@ var useChatController = () => {
5097
5469
  window.addEventListener(OPEN_CONVERSATION_EVENT, onOpen);
5098
5470
  return () => window.removeEventListener(OPEN_CONVERSATION_EVENT, onOpen);
5099
5471
  }, []);
5100
- React2.useEffect(() => {
5472
+ React3.useEffect(() => {
5101
5473
  if (!pendingOpenRequest?.conversationId || !loggedUserDetails) return;
5102
5474
  let cancelled = false;
5103
5475
  const openPending = async () => {
@@ -5163,13 +5535,13 @@ var useChatController = () => {
5163
5535
  allUsers,
5164
5536
  fetchConversationInfo2
5165
5537
  ]);
5166
- React2.useEffect(() => {
5538
+ React3.useEffect(() => {
5167
5539
  const handler = setTimeout(() => {
5168
5540
  updateState({ debouncedSearch: state.searchQuery });
5169
5541
  }, 400);
5170
5542
  return () => clearTimeout(handler);
5171
5543
  }, [state.searchQuery, updateState]);
5172
- React2.useEffect(() => {
5544
+ React3.useEffect(() => {
5173
5545
  if (!state.activeChannel || !loggedUserDetails) {
5174
5546
  setState(
5175
5547
  (prev) => prev.activeConversationId === null ? prev : { ...prev, activeConversationId: null }
@@ -5185,7 +5557,7 @@ var useChatController = () => {
5185
5557
  (prev) => prev.activeConversationId === nextId ? prev : { ...prev, activeConversationId: nextId }
5186
5558
  );
5187
5559
  }, [state.activeChannel, loggedUserDetails, conversationsLength]);
5188
- React2.useEffect(() => {
5560
+ React3.useEffect(() => {
5189
5561
  if (!lastDM?.conversationId || !loggedUserDetails) return;
5190
5562
  const convId = String(lastDM.conversationId);
5191
5563
  const senderId = String(lastDM.sender?._id || lastDM.sender || "");
@@ -5218,7 +5590,7 @@ var useChatController = () => {
5218
5590
  };
5219
5591
  });
5220
5592
  }, [lastDM, loggedUserDetails]);
5221
- const resolvedLocalMessages = React2.useMemo(
5593
+ const resolvedLocalMessages = React3.useMemo(
5222
5594
  () => resolveLocalMessagesForChannel(
5223
5595
  localMessages,
5224
5596
  state.activeConversationId,
@@ -5226,7 +5598,7 @@ var useChatController = () => {
5226
5598
  ),
5227
5599
  [localMessages, state.activeConversationId, state.activeChannel?.id]
5228
5600
  );
5229
- const resolvedConversationMessages = React2.useMemo(() => {
5601
+ const resolvedConversationMessages = React3.useMemo(() => {
5230
5602
  if (state.activeConversationId) {
5231
5603
  return messagesByConversation[state.activeConversationId] || [];
5232
5604
  }
@@ -5234,7 +5606,7 @@ var useChatController = () => {
5234
5606
  const groupConversationId = String(state.activeChannel.id);
5235
5607
  return messagesByConversation[groupConversationId] || [];
5236
5608
  }, [messagesByConversation, state.activeConversationId, state.activeChannel]);
5237
- React2.useEffect(() => {
5609
+ React3.useEffect(() => {
5238
5610
  if (!activeChannelSyncKey || !state.activeChannel) return;
5239
5611
  const [convId, pinned, starred, isLeft, isBlocked, isGroup] = activeChannelSyncKey.split("|");
5240
5612
  setState((prev) => {
@@ -5257,10 +5629,10 @@ var useChatController = () => {
5257
5629
  };
5258
5630
  });
5259
5631
  }, [activeChannelSyncKey, state.activeChannel]);
5260
- React2.useEffect(() => {
5632
+ React3.useEffect(() => {
5261
5633
  setActiveConversationId(state.activeConversationId);
5262
5634
  }, [state.activeConversationId, setActiveConversationId]);
5263
- React2.useEffect(() => {
5635
+ React3.useEffect(() => {
5264
5636
  if (!state.activeConversationId || !state.activeChannel || !loggedUserDetails) {
5265
5637
  lastConversationActionsRef.current = null;
5266
5638
  return;
@@ -5307,7 +5679,7 @@ var useChatController = () => {
5307
5679
  emitGroupMarkRead,
5308
5680
  emitMarkAsRead
5309
5681
  ]);
5310
- React2.useEffect(() => {
5682
+ React3.useEffect(() => {
5311
5683
  if (globalAppError?.type === "participant" && state.activeChannel) {
5312
5684
  updateState({ activeChannel: null, activeConversationId: null });
5313
5685
  }
@@ -5484,7 +5856,7 @@ function withThemeTransition(apply) {
5484
5856
  }
5485
5857
  reactDom.flushSync(apply);
5486
5858
  }
5487
- var ChatThemeContext = React2.createContext(null);
5859
+ var ChatThemeContext = React3.createContext(null);
5488
5860
  var ChatThemeProvider = ({
5489
5861
  children,
5490
5862
  themeColor,
@@ -5494,10 +5866,10 @@ var ChatThemeProvider = ({
5494
5866
  showColorModeToggle = true
5495
5867
  }) => {
5496
5868
  const isControlled = colorMode !== void 0;
5497
- const [internalMode, setInternalMode] = React2.useState(defaultColorMode);
5498
- const [isThemeTransitionReady, setIsThemeTransitionReady] = React2.useState(false);
5499
- const skipThemeTransitionRef = React2.useRef(true);
5500
- React2.useLayoutEffect(() => {
5869
+ const [internalMode, setInternalMode] = React3.useState(defaultColorMode);
5870
+ const [isThemeTransitionReady, setIsThemeTransitionReady] = React3.useState(false);
5871
+ const skipThemeTransitionRef = React3.useRef(true);
5872
+ React3.useLayoutEffect(() => {
5501
5873
  if (!isControlled) {
5502
5874
  const stored = loadChatColorMode();
5503
5875
  if (stored) {
@@ -5512,7 +5884,7 @@ var ChatThemeProvider = ({
5512
5884
  }, [isControlled]);
5513
5885
  const resolvedMode = isControlled ? colorMode : internalMode;
5514
5886
  const isDark = resolvedMode === "dark";
5515
- const setColorMode = React2.useCallback(
5887
+ const setColorMode = React3.useCallback(
5516
5888
  (mode) => {
5517
5889
  const apply = () => {
5518
5890
  if (!isControlled) {
@@ -5529,10 +5901,10 @@ var ChatThemeProvider = ({
5529
5901
  },
5530
5902
  [isControlled, onColorModeChange]
5531
5903
  );
5532
- const toggleColorMode = React2.useCallback(() => {
5904
+ const toggleColorMode = React3.useCallback(() => {
5533
5905
  setColorMode(resolvedMode === "dark" ? "light" : "dark");
5534
5906
  }, [resolvedMode, setColorMode]);
5535
- const value = React2.useMemo(
5907
+ const value = React3.useMemo(
5536
5908
  () => ({
5537
5909
  colorMode: resolvedMode,
5538
5910
  isDark,
@@ -5560,7 +5932,7 @@ var ChatThemeProvider = ({
5560
5932
  return /* @__PURE__ */ jsxRuntime.jsx(ChatThemeContext.Provider, { value, children });
5561
5933
  };
5562
5934
  var useChatTheme = () => {
5563
- const context = React2.useContext(ChatThemeContext);
5935
+ const context = React3.useContext(ChatThemeContext);
5564
5936
  if (!context) {
5565
5937
  throw new Error("useChatTheme must be used within ChatThemeProvider");
5566
5938
  }
@@ -6105,7 +6477,7 @@ function useCreateChatDialog({
6105
6477
  onUserSelect,
6106
6478
  setIsCreateModalOpen
6107
6479
  }) {
6108
- const [state, setState] = React2.useState(initialState);
6480
+ const [state, setState] = React3.useState(initialState);
6109
6481
  const updateState = (updates) => {
6110
6482
  setState((prev) => ({ ...prev, ...updates }));
6111
6483
  };
@@ -6114,8 +6486,10 @@ function useCreateChatDialog({
6114
6486
  const fetchAllUsers = exports.useChatStore((storeState) => storeState.fetchAllUsers);
6115
6487
  const accessToken = exports.useChatStore((storeState) => storeState.accessToken);
6116
6488
  const fetchConversations2 = exports.useChatStore((storeState) => storeState.fetchConversations);
6117
- const fileInputRef = React2.useRef(null);
6118
- React2.useEffect(() => {
6489
+ const { settings } = useEffectiveSettingsOptional();
6490
+ const maxParticipants = settings.maxParticipantsPerGroup;
6491
+ const fileInputRef = React3.useRef(null);
6492
+ React3.useEffect(() => {
6119
6493
  if (accessToken) {
6120
6494
  fetchAllUsers();
6121
6495
  }
@@ -6127,9 +6501,18 @@ function useCreateChatDialog({
6127
6501
  setState((prev) => {
6128
6502
  const memberId = String(member._id);
6129
6503
  const exists = prev.selectedMembers.some((m) => String(m._id) === memberId);
6504
+ if (exists) {
6505
+ return {
6506
+ ...prev,
6507
+ selectedMembers: prev.selectedMembers.filter((m) => String(m._id) !== memberId)
6508
+ };
6509
+ }
6510
+ if (prev.selectedMembers.length + 1 >= maxParticipants) {
6511
+ return prev;
6512
+ }
6130
6513
  return {
6131
6514
  ...prev,
6132
- selectedMembers: exists ? prev.selectedMembers.filter((m) => String(m._id) !== memberId) : [...prev.selectedMembers, member]
6515
+ selectedMembers: [...prev.selectedMembers, member]
6133
6516
  };
6134
6517
  });
6135
6518
  };
@@ -6513,8 +6896,25 @@ function GroupCreateTab({
6513
6896
  ) })
6514
6897
  ] });
6515
6898
  }
6899
+ function AdminPermissionTooltip({
6900
+ disabled,
6901
+ message,
6902
+ side = "top",
6903
+ className,
6904
+ children
6905
+ }) {
6906
+ if (!disabled || !message) {
6907
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
6908
+ }
6909
+ return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6910
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("inline-flex", className), children }) }),
6911
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side, className: "max-w-[240px] text-xs", children: message })
6912
+ ] });
6913
+ }
6516
6914
  var CreateChatDialog = (props) => {
6517
6915
  const { isCreateModalOpen = false, setIsCreateModalOpen } = props;
6916
+ const { getGroupCreationGate } = useEffectiveSettingsOptional();
6917
+ const groupCreationGate = getGroupCreationGate();
6518
6918
  const {
6519
6919
  state,
6520
6920
  updateState,
@@ -6568,24 +6968,39 @@ var CreateChatDialog = (props) => {
6568
6968
  ) })
6569
6969
  ] }),
6570
6970
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid shrink-0 grid-cols-2 gap-2 px-5", children: [
6571
- { id: "dm", label: "Message", icon: lucideReact.MessageCircle },
6572
- { id: "group", label: "New group", icon: lucideReact.Users }
6573
- ].map(({ id, label, icon: Icon }) => /* @__PURE__ */ jsxRuntime.jsxs(
6574
- "button",
6971
+ { id: "dm", label: "Message", icon: lucideReact.MessageCircle, disabled: false, reason: null },
6575
6972
  {
6576
- type: "button",
6577
- onClick: () => updateState({ activeTab: id }),
6578
- className: cn(
6579
- "flex items-center justify-center gap-2 rounded-full border py-2 text-sm transition-colors",
6580
- 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)"
6581
- ),
6582
- children: [
6583
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }),
6584
- label
6585
- ]
6586
- },
6587
- id
6588
- )) }),
6973
+ id: "group",
6974
+ label: "New group",
6975
+ icon: lucideReact.Users,
6976
+ disabled: !groupCreationGate.enabled,
6977
+ reason: groupCreationGate.disabledReason
6978
+ }
6979
+ ].map(({ id, label, icon: Icon, disabled, reason }) => {
6980
+ const tabButton = /* @__PURE__ */ jsxRuntime.jsxs(
6981
+ "button",
6982
+ {
6983
+ type: "button",
6984
+ disabled,
6985
+ onClick: () => {
6986
+ if (disabled) return;
6987
+ updateState({ activeTab: id });
6988
+ },
6989
+ className: cn(
6990
+ "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",
6991
+ 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)"
6992
+ ),
6993
+ children: [
6994
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }),
6995
+ label
6996
+ ]
6997
+ }
6998
+ );
6999
+ if (!disabled || !reason) {
7000
+ return /* @__PURE__ */ jsxRuntime.jsx(React3.Fragment, { children: tabButton }, id);
7001
+ }
7002
+ return /* @__PURE__ */ jsxRuntime.jsx(AdminPermissionTooltip, { disabled: true, message: reason, children: tabButton }, id);
7003
+ }) }),
6589
7004
  state.activeTab === "dm" && /* @__PURE__ */ jsxRuntime.jsx(
6590
7005
  DmContactsTab,
6591
7006
  {
@@ -6596,7 +7011,7 @@ var CreateChatDialog = (props) => {
6596
7011
  onStartDM: handleStartDM
6597
7012
  }
6598
7013
  ),
6599
- state.activeTab === "group" && /* @__PURE__ */ jsxRuntime.jsx(
7014
+ state.activeTab === "group" && groupCreationGate.enabled && /* @__PURE__ */ jsxRuntime.jsx(
6600
7015
  GroupCreateTab,
6601
7016
  {
6602
7017
  state,
@@ -6660,7 +7075,7 @@ var emitPushChanged = (enabled) => {
6660
7075
  } catch {
6661
7076
  }
6662
7077
  };
6663
- var pushApiUrl = (path) => exports.buildVersionedApiUrl(path, { service: "push" });
7078
+ var pushApiUrl = (path) => exports.buildVersionedApiUrl(path, { service: "v2" });
6664
7079
  var apiGetPushConfig = async () => {
6665
7080
  const { data } = await apiClient.get(pushApiUrl("/notifications/push/config"));
6666
7081
  return data?.data;
@@ -6809,7 +7224,7 @@ var onForegroundPush = async (callback) => {
6809
7224
 
6810
7225
  // src/notifications/usePushNotifications.ts
6811
7226
  var usePushNotifications = () => {
6812
- const [state, setState] = React2.useState({
7227
+ const [state, setState] = React3.useState({
6813
7228
  supported: false,
6814
7229
  allowedByWorkspace: false,
6815
7230
  configured: false,
@@ -6819,7 +7234,7 @@ var usePushNotifications = () => {
6819
7234
  loading: true,
6820
7235
  error: null
6821
7236
  });
6822
- const refresh = React2.useCallback(async () => {
7237
+ const refresh = React3.useCallback(async () => {
6823
7238
  if (!isPushSupported()) {
6824
7239
  setState((prev) => ({
6825
7240
  ...prev,
@@ -6852,10 +7267,10 @@ var usePushNotifications = () => {
6852
7267
  }));
6853
7268
  }
6854
7269
  }, []);
6855
- React2.useEffect(() => {
7270
+ React3.useEffect(() => {
6856
7271
  void refresh();
6857
7272
  }, [refresh]);
6858
- const enable = React2.useCallback(async () => {
7273
+ const enable = React3.useCallback(async () => {
6859
7274
  setState((prev) => ({ ...prev, loading: true, error: null }));
6860
7275
  try {
6861
7276
  await enablePushOnThisDevice();
@@ -6871,7 +7286,7 @@ var usePushNotifications = () => {
6871
7286
  return false;
6872
7287
  }
6873
7288
  }, [refresh]);
6874
- const disable = React2.useCallback(async () => {
7289
+ const disable = React3.useCallback(async () => {
6875
7290
  setState((prev) => ({ ...prev, loading: true, error: null }));
6876
7291
  try {
6877
7292
  await disablePushOnThisDevice();
@@ -6879,7 +7294,7 @@ var usePushNotifications = () => {
6879
7294
  await refresh();
6880
7295
  }
6881
7296
  }, [refresh]);
6882
- const setUserEnabled = React2.useCallback(
7297
+ const setUserEnabled = React3.useCallback(
6883
7298
  async (enabled) => {
6884
7299
  setState((prev) => ({ ...prev, loading: true, error: null }));
6885
7300
  try {
@@ -6905,14 +7320,20 @@ var PushNotificationToggle = ({
6905
7320
  children
6906
7321
  }) => {
6907
7322
  const push = usePushNotifications();
6908
- const [isToggling, setIsToggling] = React2.useState(false);
6909
- if (!push.supported || !push.allowedByWorkspace || !push.configured) {
7323
+ const { getPushNotificationGate } = useEffectiveSettingsOptional();
7324
+ const adminGate = getPushNotificationGate();
7325
+ const [isToggling, setIsToggling] = React3.useState(false);
7326
+ if (!push.supported) {
6910
7327
  return null;
6911
7328
  }
6912
7329
  const isOn = push.deviceEnabled && push.userEnabled;
6913
7330
  const permissionBlocked = push.permission === "denied";
7331
+ const adminBlocked = !adminGate.enabled;
7332
+ const notConfigured = !push.configured;
6914
7333
  const showLoader = isToggling;
7334
+ const isDisabled = showLoader || permissionBlocked || push.loading || adminBlocked || notConfigured;
6915
7335
  const handleChange = async (checked) => {
7336
+ if (adminBlocked || notConfigured) return;
6916
7337
  setIsToggling(true);
6917
7338
  try {
6918
7339
  if (checked) {
@@ -6925,11 +7346,12 @@ var PushNotificationToggle = ({
6925
7346
  setIsToggling(false);
6926
7347
  }
6927
7348
  };
7349
+ 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;
6928
7350
  const renderProps = {
6929
7351
  label,
6930
- description: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description,
6931
- checked: isOn,
6932
- disabled: showLoader || permissionBlocked || push.loading,
7352
+ description: descriptionText,
7353
+ checked: adminBlocked || notConfigured ? false : isOn,
7354
+ disabled: isDisabled,
6933
7355
  loading: showLoader || push.loading,
6934
7356
  permissionBlocked,
6935
7357
  error: push.error,
@@ -6957,12 +7379,20 @@ var PushNotificationToggle = ({
6957
7379
  "aria-label": "Updating push notifications"
6958
7380
  }
6959
7381
  ) : /* @__PURE__ */ jsxRuntime.jsx(
6960
- Switch,
7382
+ AdminPermissionTooltip,
6961
7383
  {
6962
- checked: isOn,
6963
- disabled: permissionBlocked || push.loading,
6964
- onCheckedChange: (checked) => void handleChange(checked),
6965
- "aria-label": `Toggle ${label}`
7384
+ disabled: adminBlocked || notConfigured,
7385
+ message: adminBlocked ? adminGate.disabledReason : "Push notifications are not configured. Please contact your administrator.",
7386
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7387
+ Switch,
7388
+ {
7389
+ checked: adminBlocked || notConfigured ? false : isOn,
7390
+ disabled: isDisabled,
7391
+ onCheckedChange: (checked) => void handleChange(checked),
7392
+ "aria-label": `Toggle ${label}`,
7393
+ className: "data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)"
7394
+ }
7395
+ )
6966
7396
  }
6967
7397
  )
6968
7398
  ]
@@ -7076,7 +7506,7 @@ var LoggedUserSettingsDialog = ({
7076
7506
  className
7077
7507
  }) => {
7078
7508
  const { resolveComponent, slotClassName } = useChatCustomization();
7079
- const [internalOpen, setInternalOpen] = React2.useState(false);
7509
+ const [internalOpen, setInternalOpen] = React3.useState(false);
7080
7510
  const open = controlledOpen ?? internalOpen;
7081
7511
  const onOpenChange = controlledOnOpenChange ?? setInternalOpen;
7082
7512
  const TriggerSlot = resolveComponent(
@@ -7264,7 +7694,7 @@ function getDraftRevision() {
7264
7694
  return draftRevision;
7265
7695
  }
7266
7696
  function useDraftRevision() {
7267
- return React2.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
7697
+ return React3.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
7268
7698
  }
7269
7699
  function getConversationDraft(conversationId) {
7270
7700
  if (!conversationId) return emptyDraft();
@@ -7302,11 +7732,11 @@ function getConversationDraftPreview(conversationId) {
7302
7732
  return null;
7303
7733
  }
7304
7734
  function useConversationDraft(conversationId) {
7305
- const composerRef = React2.useRef(emptyDraft());
7306
- const syncComposerRef = React2.useCallback((draft) => {
7735
+ const composerRef = React3.useRef(emptyDraft());
7736
+ const syncComposerRef = React3.useCallback((draft) => {
7307
7737
  composerRef.current = draft;
7308
7738
  }, []);
7309
- React2.useEffect(() => {
7739
+ React3.useEffect(() => {
7310
7740
  const id = conversationId;
7311
7741
  return () => {
7312
7742
  if (!id) return;
@@ -7525,7 +7955,7 @@ var saveChatDateTimePreferences = (prefs) => {
7525
7955
  } catch {
7526
7956
  }
7527
7957
  };
7528
- var ChatLocaleContext = React2.createContext(null);
7958
+ var ChatLocaleContext = React3.createContext(null);
7529
7959
  var resolveInitialPrefs = (defaults) => {
7530
7960
  const stored = loadChatDateTimePreferences();
7531
7961
  const fallback = resolveStoredPrefs(defaults);
@@ -7552,7 +7982,7 @@ var ChatLocaleProvider = ({
7552
7982
  const localeControlled = controlledLocale !== void 0;
7553
7983
  const timeZoneControlled = controlledTimeZone !== void 0;
7554
7984
  const hour12Controlled = controlledHour12 !== void 0;
7555
- const [internalPrefs, setInternalPrefs] = React2.useState(
7985
+ const [internalPrefs, setInternalPrefs] = React3.useState(
7556
7986
  () => resolveInitialPrefs({
7557
7987
  locale: defaultLocale,
7558
7988
  timeZone: defaultTimeZone,
@@ -7562,7 +7992,7 @@ var ChatLocaleProvider = ({
7562
7992
  const resolvedLocale = localeControlled ? controlledLocale : internalPrefs.locale;
7563
7993
  const resolvedTimeZone = timeZoneControlled ? controlledTimeZone : internalPrefs.timeZone;
7564
7994
  const resolvedHour12 = hour12Controlled ? controlledHour12 ?? getDefaultHour12() : internalPrefs.hour12 ?? getDefaultHour12();
7565
- const updatePrefs = React2.useCallback(
7995
+ const updatePrefs = React3.useCallback(
7566
7996
  (updates) => {
7567
7997
  const next = {
7568
7998
  locale: updates.locale !== void 0 ? updates.locale : resolvedLocale,
@@ -7591,19 +8021,19 @@ var ChatLocaleProvider = ({
7591
8021
  onDateTimePreferencesChange
7592
8022
  ]
7593
8023
  );
7594
- const setLocale = React2.useCallback(
8024
+ const setLocale = React3.useCallback(
7595
8025
  (locale) => updatePrefs({ locale }),
7596
8026
  [updatePrefs]
7597
8027
  );
7598
- const setTimeZone = React2.useCallback(
8028
+ const setTimeZone = React3.useCallback(
7599
8029
  (timeZone) => updatePrefs({ timeZone }),
7600
8030
  [updatePrefs]
7601
8031
  );
7602
- const setHour12 = React2.useCallback(
8032
+ const setHour12 = React3.useCallback(
7603
8033
  (hour12) => updatePrefs({ hour12 }),
7604
8034
  [updatePrefs]
7605
8035
  );
7606
- const prefs = React2.useMemo(
8036
+ const prefs = React3.useMemo(
7607
8037
  () => ({
7608
8038
  locale: resolvedLocale,
7609
8039
  timeZone: resolvedTimeZone,
@@ -7611,8 +8041,8 @@ var ChatLocaleProvider = ({
7611
8041
  }),
7612
8042
  [resolvedLocale, resolvedTimeZone, resolvedHour12]
7613
8043
  );
7614
- const formatters = React2.useMemo(() => createChatDateTimeFormatters(prefs), [prefs]);
7615
- const value = React2.useMemo(
8044
+ const formatters = React3.useMemo(() => createChatDateTimeFormatters(prefs), [prefs]);
8045
+ const value = React3.useMemo(
7616
8046
  () => ({
7617
8047
  ...formatters,
7618
8048
  locale: resolvedLocale,
@@ -7637,7 +8067,7 @@ var ChatLocaleProvider = ({
7637
8067
  return /* @__PURE__ */ jsxRuntime.jsx(ChatLocaleContext.Provider, { value, children });
7638
8068
  };
7639
8069
  var useChatLocale = () => {
7640
- const context = React2.useContext(ChatLocaleContext);
8070
+ const context = React3.useContext(ChatLocaleContext);
7641
8071
  if (!context) {
7642
8072
  throw new Error("useChatLocale must be used within ChatLocaleProvider");
7643
8073
  }
@@ -7759,15 +8189,15 @@ function useChannelList({ debouncedSearch = "" }) {
7759
8189
  const conversationsPage = exports.useChatStore((s) => s.conversationsPage);
7760
8190
  const accessToken = exports.useChatStore((s) => s.accessToken);
7761
8191
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
7762
- const lastFetchKeyRef = React2.useRef(null);
8192
+ const lastFetchKeyRef = React3.useRef(null);
7763
8193
  useDraftRevision();
7764
8194
  const { formatSidebarTime } = useChatLocale();
7765
- React2.useEffect(() => {
8195
+ React3.useEffect(() => {
7766
8196
  if (accessToken) {
7767
8197
  exports.useChatStore.getState().fetchAllUsers();
7768
8198
  }
7769
8199
  }, [accessToken]);
7770
- React2.useEffect(() => {
8200
+ React3.useEffect(() => {
7771
8201
  if (!accessToken) return;
7772
8202
  const fetchKey = debouncedSearch;
7773
8203
  if (lastFetchKeyRef.current === fetchKey) return;
@@ -7779,7 +8209,7 @@ function useChannelList({ debouncedSearch = "" }) {
7779
8209
  }
7780
8210
  fetchConversations2(DEFAULT_CONVERSATIONS_LIMIT, 1, debouncedSearch, true);
7781
8211
  }, [accessToken, debouncedSearch]);
7782
- const mappedChannels = React2.useMemo(() => {
8212
+ const mappedChannels = React3.useMemo(() => {
7783
8213
  const safeConversations = Array.isArray(conversations) ? conversations : [];
7784
8214
  const seenConversationIds = /* @__PURE__ */ new Set();
7785
8215
  return safeConversations.map((c) => {
@@ -8383,7 +8813,7 @@ function highlightSearchMatch(text, query) {
8383
8813
  children: part
8384
8814
  },
8385
8815
  `${part}-${index}`
8386
- ) : /* @__PURE__ */ jsxRuntime.jsx(React2__namespace.default.Fragment, { children: part }, `${part}-${index}`)
8816
+ ) : /* @__PURE__ */ jsxRuntime.jsx(React3__namespace.default.Fragment, { children: part }, `${part}-${index}`)
8387
8817
  );
8388
8818
  }
8389
8819
  function getMessagePreview(message) {
@@ -8430,14 +8860,14 @@ var HeaderSearchPopover = ({
8430
8860
  }) => {
8431
8861
  const { scopeClassName, themeStyles } = useChatTheme();
8432
8862
  const { formatMessageTime, formatShortDate } = useChatLocale();
8433
- const inputRef = React2.useRef(null);
8434
- React2.useEffect(() => {
8863
+ const inputRef = React3.useRef(null);
8864
+ React3.useEffect(() => {
8435
8865
  if (!open) return;
8436
8866
  const timer = window.setTimeout(() => inputRef.current?.focus(), 0);
8437
8867
  return () => window.clearTimeout(timer);
8438
8868
  }, [open]);
8439
8869
  const showResults = searchQuery.trim().length > 0;
8440
- const resultCountLabel = React2.useMemo(() => {
8870
+ const resultCountLabel = React3.useMemo(() => {
8441
8871
  if (!searchResults.length) return "0 results";
8442
8872
  return `${searchResults.length}${hasMore ? "+" : ""} result${searchResults.length === 1 ? "" : "s"}`;
8443
8873
  }, [hasMore, searchResults.length]);
@@ -8563,7 +8993,7 @@ var HeaderSearchPopover = ({
8563
8993
  ] });
8564
8994
  };
8565
8995
  var HeaderSearchPopover_default = HeaderSearchPopover;
8566
- var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text)";
8996
+ var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text) disabled:opacity-40";
8567
8997
  var DefaultHeaderCallActions = ({
8568
8998
  activeChannel,
8569
8999
  conversationId,
@@ -8574,48 +9004,91 @@ var DefaultHeaderCallActions = ({
8574
9004
  className
8575
9005
  }) => {
8576
9006
  const { components, slotClassName, features } = useChatCustomizationOptional();
9007
+ const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
8577
9008
  const PhoneSlot = components.PhoneCallButton;
8578
9009
  const VideoSlot = components.VideoCallButton;
8579
9010
  const hideInGroups = features.hideCallActionsInGroupChats ?? false;
9011
+ const audioGate = getAudioCallGate();
9012
+ const videoGate = getVideoCallGate();
8580
9013
  if (hideInGroups && activeChannel.isGroup) return null;
8581
9014
  if (!showPhoneCall && !showVideoCall) return null;
9015
+ const phoneEnabled = showPhoneCall && audioGate.enabled;
9016
+ const videoEnabled = showVideoCall && videoGate.enabled;
8582
9017
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8583
9018
  showPhoneCall && (PhoneSlot ? /* @__PURE__ */ jsxRuntime.jsx(
8584
- PhoneSlot,
9019
+ AdminPermissionTooltip,
8585
9020
  {
8586
- activeChannel,
8587
- conversationId,
8588
- onPhoneCall,
8589
- className: cn(className, slotClassName("phoneCallButton"))
9021
+ disabled: !audioGate.enabled,
9022
+ message: audioGate.disabledReason,
9023
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
9024
+ PhoneSlot,
9025
+ {
9026
+ activeChannel,
9027
+ conversationId,
9028
+ onPhoneCall: phoneEnabled ? onPhoneCall : void 0,
9029
+ className: cn(
9030
+ className,
9031
+ slotClassName("phoneCallButton"),
9032
+ !audioGate.enabled && "pointer-events-none opacity-40"
9033
+ )
9034
+ }
9035
+ ) })
8590
9036
  }
8591
9037
  ) : /* @__PURE__ */ jsxRuntime.jsx(
8592
- Button,
9038
+ AdminPermissionTooltip,
8593
9039
  {
8594
- variant: "ghost",
8595
- size: "icon",
8596
- className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
8597
- "aria-label": "Voice call",
8598
- onClick: onPhoneCall,
8599
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 15 })
9040
+ disabled: !audioGate.enabled,
9041
+ message: audioGate.disabledReason,
9042
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9043
+ Button,
9044
+ {
9045
+ variant: "ghost",
9046
+ size: "icon",
9047
+ className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
9048
+ "aria-label": "Voice call",
9049
+ disabled: !audioGate.enabled,
9050
+ onClick: phoneEnabled ? onPhoneCall : void 0,
9051
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 15 })
9052
+ }
9053
+ )
8600
9054
  }
8601
9055
  )),
8602
9056
  showVideoCall && (VideoSlot ? /* @__PURE__ */ jsxRuntime.jsx(
8603
- VideoSlot,
9057
+ AdminPermissionTooltip,
8604
9058
  {
8605
- activeChannel,
8606
- conversationId,
8607
- onVideoCall,
8608
- className: cn(className, slotClassName("videoCallButton"))
9059
+ disabled: !videoGate.enabled,
9060
+ message: videoGate.disabledReason,
9061
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
9062
+ VideoSlot,
9063
+ {
9064
+ activeChannel,
9065
+ conversationId,
9066
+ onVideoCall: videoEnabled ? onVideoCall : void 0,
9067
+ className: cn(
9068
+ className,
9069
+ slotClassName("videoCallButton"),
9070
+ !videoGate.enabled && "pointer-events-none opacity-40"
9071
+ )
9072
+ }
9073
+ ) })
8609
9074
  }
8610
9075
  ) : /* @__PURE__ */ jsxRuntime.jsx(
8611
- Button,
9076
+ AdminPermissionTooltip,
8612
9077
  {
8613
- variant: "ghost",
8614
- size: "icon",
8615
- className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
8616
- "aria-label": "Video call",
8617
- onClick: onVideoCall,
8618
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 15 })
9078
+ disabled: !videoGate.enabled,
9079
+ message: videoGate.disabledReason,
9080
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9081
+ Button,
9082
+ {
9083
+ variant: "ghost",
9084
+ size: "icon",
9085
+ className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
9086
+ "aria-label": "Video call",
9087
+ disabled: !videoGate.enabled,
9088
+ onClick: videoEnabled ? onVideoCall : void 0,
9089
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 15 })
9090
+ }
9091
+ )
8619
9092
  }
8620
9093
  ))
8621
9094
  ] });
@@ -8637,19 +9110,38 @@ function OptionsMenuItem({
8637
9110
  icon: Icon,
8638
9111
  label,
8639
9112
  onSelect,
8640
- destructive = false
9113
+ destructive = false,
9114
+ disabled = false,
9115
+ disabledReason
8641
9116
  }) {
8642
- return /* @__PURE__ */ jsxRuntime.jsxs(
9117
+ const item = /* @__PURE__ */ jsxRuntime.jsxs(
8643
9118
  DropdownMenuItem,
8644
9119
  {
8645
- className: destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
8646
- onSelect,
9120
+ className: cn(
9121
+ destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
9122
+ disabled && "pointer-events-none opacity-40"
9123
+ ),
9124
+ disabled,
9125
+ onSelect: (event) => {
9126
+ if (disabled) {
9127
+ event.preventDefault();
9128
+ return;
9129
+ }
9130
+ onSelect?.();
9131
+ },
8647
9132
  children: [
8648
9133
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { strokeWidth: 1.75 }),
8649
9134
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: label })
8650
9135
  ]
8651
9136
  }
8652
9137
  );
9138
+ if (!disabled || !disabledReason) {
9139
+ return item;
9140
+ }
9141
+ return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
9142
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: item }) }),
9143
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "left", className: "max-w-[220px] text-xs", children: disabledReason })
9144
+ ] });
8653
9145
  }
8654
9146
  var HeaderRightSide = ({
8655
9147
  activeChannel,
@@ -8684,6 +9176,8 @@ var HeaderRightSide = ({
8684
9176
  const { scopeClassName, themeStyles, showColorModeToggle } = useChatTheme();
8685
9177
  const { showDateTimeSettings } = useChatLocale();
8686
9178
  const features = useChatFeaturesOptional();
9179
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
9180
+ const blockUsersGate = getBlockUsersGate();
8687
9181
  const showPhoneCall = features.showPhoneCall ?? defaultChatFeatures.showPhoneCall;
8688
9182
  const showVideoCall = features.showVideoCall ?? defaultChatFeatures.showVideoCall;
8689
9183
  const handlePhoneCall = () => {
@@ -8832,7 +9326,9 @@ var HeaderRightSide = ({
8832
9326
  icon: lucideReact.Ban,
8833
9327
  label: isBlocked ? "Unblock User" : "Block User",
8834
9328
  onSelect: onToggleBlock,
8835
- destructive: true
9329
+ destructive: true,
9330
+ disabled: !blockUsersGate.enabled,
9331
+ disabledReason: blockUsersGate.disabledReason
8836
9332
  }
8837
9333
  )
8838
9334
  ]
@@ -9203,13 +9699,13 @@ function AudioAttachmentPlayer({
9203
9699
  showDownload = true,
9204
9700
  downloadName
9205
9701
  }) {
9206
- const audioRef = React2.useRef(null);
9207
- const [isPlaying, setIsPlaying] = React2.useState(false);
9208
- const [currentTime, setCurrentTime] = React2.useState(0);
9209
- const [duration, setDuration] = React2.useState(durationProp ?? 0);
9702
+ const audioRef = React3.useRef(null);
9703
+ const [isPlaying, setIsPlaying] = React3.useState(false);
9704
+ const [currentTime, setCurrentTime] = React3.useState(0);
9705
+ const [duration, setDuration] = React3.useState(durationProp ?? 0);
9210
9706
  const resolvedDuration = duration > 0 ? duration : 0;
9211
9707
  const progressPercent = resolvedDuration > 0 ? Math.min(100, currentTime / resolvedDuration * 100) : 0;
9212
- const togglePlay = React2.useCallback(async () => {
9708
+ const togglePlay = React3.useCallback(async () => {
9213
9709
  const audio = audioRef.current;
9214
9710
  if (!audio || !url) return;
9215
9711
  if (audio.paused) {
@@ -9231,7 +9727,7 @@ function AudioAttachmentPlayer({
9231
9727
  audio.currentTime = next;
9232
9728
  setCurrentTime(next);
9233
9729
  };
9234
- React2.useEffect(() => {
9730
+ React3.useEffect(() => {
9235
9731
  const audio = audioRef.current;
9236
9732
  if (!audio) return;
9237
9733
  const onTimeUpdate = () => setCurrentTime(audio.currentTime);
@@ -9259,7 +9755,7 @@ function AudioAttachmentPlayer({
9259
9755
  audio.removeEventListener("play", onPlay);
9260
9756
  };
9261
9757
  }, [url]);
9262
- React2.useEffect(() => {
9758
+ React3.useEffect(() => {
9263
9759
  if (durationProp && durationProp > 0) {
9264
9760
  setDuration(durationProp);
9265
9761
  }
@@ -9381,12 +9877,12 @@ function VideoAttachmentPlayer({
9381
9877
  downloadName,
9382
9878
  onOpenPreview
9383
9879
  }) {
9384
- const videoRef = React2.useRef(null);
9385
- const [isPlaying, setIsPlaying] = React2.useState(false);
9386
- const [duration, setDuration] = React2.useState(durationProp ?? 0);
9880
+ const videoRef = React3.useRef(null);
9881
+ const [isPlaying, setIsPlaying] = React3.useState(false);
9882
+ const [duration, setDuration] = React3.useState(durationProp ?? 0);
9387
9883
  const onThemeBubble = inBubble && isSender;
9388
9884
  const resolvedDuration = duration > 0 ? duration : 0;
9389
- const togglePlay = React2.useCallback(async () => {
9885
+ const togglePlay = React3.useCallback(async () => {
9390
9886
  const video = videoRef.current;
9391
9887
  if (!video || !url) return;
9392
9888
  if (video.paused) {
@@ -9401,7 +9897,7 @@ function VideoAttachmentPlayer({
9401
9897
  setIsPlaying(false);
9402
9898
  }
9403
9899
  }, [url]);
9404
- React2.useEffect(() => {
9900
+ React3.useEffect(() => {
9405
9901
  if (durationProp && durationProp > 0) {
9406
9902
  setDuration(durationProp);
9407
9903
  }
@@ -9679,10 +10175,10 @@ async function fetchAuthenticatedBlob(url) {
9679
10175
  return response.blob();
9680
10176
  }
9681
10177
  function PdfPreviewPanel({ url, name }) {
9682
- const [blobUrl, setBlobUrl] = React2.useState(null);
9683
- const [fallbackUrl, setFallbackUrl] = React2.useState(null);
9684
- const [loading, setLoading] = React2.useState(true);
9685
- React2.useEffect(() => {
10178
+ const [blobUrl, setBlobUrl] = React3.useState(null);
10179
+ const [fallbackUrl, setFallbackUrl] = React3.useState(null);
10180
+ const [loading, setLoading] = React3.useState(true);
10181
+ React3.useEffect(() => {
9686
10182
  let active = true;
9687
10183
  let objectUrl = null;
9688
10184
  const loadPdf = async () => {
@@ -10186,60 +10682,60 @@ function AttachmentImageLightbox({
10186
10682
  onSaveCaption,
10187
10683
  initialCaptionEditing = false
10188
10684
  }) {
10189
- const [index, setIndex] = React2.useState(initialIndex);
10190
- const [zoom, setZoom] = React2.useState(1);
10191
- const [pan, setPan] = React2.useState({ x: 0, y: 0 });
10192
- const [imageVersion, setImageVersion] = React2.useState(0);
10193
- const [localCaptions, setLocalCaptions] = React2.useState({});
10194
- const [captionDraft, setCaptionDraft] = React2.useState("");
10195
- const [isCaptionEditing, setIsCaptionEditing] = React2.useState(false);
10196
- const thumbRefs = React2.useRef([]);
10197
- const captionInputRef = React2.useRef(null);
10198
- const touchStartX = React2.useRef(null);
10199
- const isPanning = React2.useRef(false);
10200
- const panStart = React2.useRef({ x: 0, y: 0, originX: 0, originY: 0 });
10685
+ const [index, setIndex] = React3.useState(initialIndex);
10686
+ const [zoom, setZoom] = React3.useState(1);
10687
+ const [pan, setPan] = React3.useState({ x: 0, y: 0 });
10688
+ const [imageVersion, setImageVersion] = React3.useState(0);
10689
+ const [localCaptions, setLocalCaptions] = React3.useState({});
10690
+ const [captionDraft, setCaptionDraft] = React3.useState("");
10691
+ const [isCaptionEditing, setIsCaptionEditing] = React3.useState(false);
10692
+ const thumbRefs = React3.useRef([]);
10693
+ const captionInputRef = React3.useRef(null);
10694
+ const touchStartX = React3.useRef(null);
10695
+ const isPanning = React3.useRef(false);
10696
+ const panStart = React3.useRef({ x: 0, y: 0, originX: 0, originY: 0 });
10201
10697
  const canEdit = canEditCaption && !!onSaveCaption;
10202
- React2.useEffect(() => {
10698
+ React3.useEffect(() => {
10203
10699
  setIndex(initialIndex);
10204
10700
  }, [initialIndex]);
10205
- const resetView = React2.useCallback(() => {
10701
+ const resetView = React3.useCallback(() => {
10206
10702
  setZoom(1);
10207
10703
  setPan({ x: 0, y: 0 });
10208
10704
  }, []);
10209
- const reloadImage = React2.useCallback(() => {
10705
+ const reloadImage = React3.useCallback(() => {
10210
10706
  resetView();
10211
10707
  setImageVersion((version) => version + 1);
10212
10708
  }, [resetView]);
10213
- React2.useEffect(() => {
10709
+ React3.useEffect(() => {
10214
10710
  resetView();
10215
10711
  setIsCaptionEditing(false);
10216
10712
  }, [index, resetView]);
10217
- React2.useEffect(() => {
10713
+ React3.useEffect(() => {
10218
10714
  if (!initialCaptionEditing || !canEdit) return;
10219
10715
  setIsCaptionEditing(true);
10220
10716
  }, [initialCaptionEditing, canEdit]);
10221
- React2.useEffect(() => {
10717
+ React3.useEffect(() => {
10222
10718
  if (!isCaptionEditing) return;
10223
10719
  const id = window.setTimeout(() => captionInputRef.current?.focus(), 40);
10224
10720
  return () => window.clearTimeout(id);
10225
10721
  }, [isCaptionEditing]);
10226
- const goPrev = React2.useCallback(() => {
10722
+ const goPrev = React3.useCallback(() => {
10227
10723
  setIndex((current2) => current2 > 0 ? current2 - 1 : images.length - 1);
10228
10724
  }, [images.length]);
10229
- const goNext = React2.useCallback(() => {
10725
+ const goNext = React3.useCallback(() => {
10230
10726
  setIndex((current2) => current2 < images.length - 1 ? current2 + 1 : 0);
10231
10727
  }, [images.length]);
10232
- const zoomIn = React2.useCallback(() => {
10728
+ const zoomIn = React3.useCallback(() => {
10233
10729
  setZoom((current2) => clampZoom(Number((current2 + ZOOM_STEP).toFixed(2))));
10234
10730
  }, []);
10235
- const zoomOut = React2.useCallback(() => {
10731
+ const zoomOut = React3.useCallback(() => {
10236
10732
  setZoom((current2) => {
10237
10733
  const next = clampZoom(Number((current2 - ZOOM_STEP).toFixed(2)));
10238
10734
  if (next <= MIN_ZOOM) setPan({ x: 0, y: 0 });
10239
10735
  return next;
10240
10736
  });
10241
10737
  }, []);
10242
- React2.useEffect(() => {
10738
+ React3.useEffect(() => {
10243
10739
  const onKeyDown = (event) => {
10244
10740
  const tag = event.target?.tagName;
10245
10741
  const isTyping = tag === "INPUT" || tag === "TEXTAREA";
@@ -10253,14 +10749,14 @@ function AttachmentImageLightbox({
10253
10749
  window.addEventListener("keydown", onKeyDown);
10254
10750
  return () => window.removeEventListener("keydown", onKeyDown);
10255
10751
  }, [goPrev, goNext, zoomIn, zoomOut, resetView]);
10256
- React2.useEffect(() => {
10752
+ React3.useEffect(() => {
10257
10753
  thumbRefs.current[index]?.scrollIntoView({
10258
10754
  behavior: "smooth",
10259
10755
  block: "nearest",
10260
10756
  inline: "center"
10261
10757
  });
10262
10758
  }, [index]);
10263
- React2.useEffect(() => {
10759
+ React3.useEffect(() => {
10264
10760
  const attachment = images[index];
10265
10761
  if (!attachment) return;
10266
10762
  setCaptionDraft(getCaptionForAttachment(attachment, localCaptions));
@@ -10731,7 +11227,7 @@ function MessageAttachmentGrid({
10731
11227
  onToggleAttachmentSelect,
10732
11228
  onAttachmentHover
10733
11229
  }) {
10734
- const [lightboxIndex, setLightboxIndex] = React2.useState(null);
11230
+ const [lightboxIndex, setLightboxIndex] = React3.useState(null);
10735
11231
  if (!images.length) return null;
10736
11232
  const visible = images.slice(0, MAX_VISIBLE);
10737
11233
  const overflow = images.length - MAX_VISIBLE;
@@ -10951,7 +11447,7 @@ function MessageAttachmentsImageSection({
10951
11447
  hoveredAttachmentId,
10952
11448
  onAttachmentHover
10953
11449
  }) {
10954
- const [lightboxIndex, setLightboxIndex] = React2.useState(null);
11450
+ const [lightboxIndex, setLightboxIndex] = React3.useState(null);
10955
11451
  if (images.length === 0) return null;
10956
11452
  const renderMediaMetaOverlay = () => /* @__PURE__ */ jsxRuntime.jsx(
10957
11453
  MediaMetaOverlay,
@@ -11426,7 +11922,7 @@ function MessageAttachmentsView({
11426
11922
  senderName,
11427
11923
  senderId
11428
11924
  }) {
11429
- const [documentPreview, setDocumentPreview] = React2.useState(null);
11925
+ const [documentPreview, setDocumentPreview] = React3.useState(null);
11430
11926
  const visibleAttachments = getVisibleAttachments(attachments);
11431
11927
  if (isDeletedForEveryone || visibleAttachments.length === 0) return null;
11432
11928
  const messageCaption = caption?.trim() || "";
@@ -11811,8 +12307,8 @@ var SavedMessagesDrawer = ({
11811
12307
  const conversationMessages = exports.useChatStore(
11812
12308
  (s) => conversationId ? s.messagesByConversation[conversationId] ?? EMPTY_CONVERSATION_MESSAGES : EMPTY_CONVERSATION_MESSAGES
11813
12309
  );
11814
- const [searchQuery, setSearchQuery] = React2.useState("");
11815
- const parsedEntries = React2.useMemo(() => {
12310
+ const [searchQuery, setSearchQuery] = React3.useState("");
12311
+ const parsedEntries = React3.useMemo(() => {
11816
12312
  return messages.map((msg) => ({
11817
12313
  msg,
11818
12314
  item: parseSavedMessage(msg, {
@@ -11833,7 +12329,7 @@ var SavedMessagesDrawer = ({
11833
12329
  variant,
11834
12330
  prefs
11835
12331
  ]);
11836
- const groupedEntries = React2.useMemo(() => {
12332
+ const groupedEntries = React3.useMemo(() => {
11837
12333
  const query = searchQuery.trim().toLowerCase();
11838
12334
  const filtered = parsedEntries.filter(({ item, msg }) => {
11839
12335
  if (!query) return true;
@@ -12106,15 +12602,15 @@ function SectionScrollArea({
12106
12602
  onLoadMore,
12107
12603
  dependencyKey
12108
12604
  }) {
12109
- const scrollRef = React2.useRef(null);
12110
- const [showArrow, setShowArrow] = React2.useState(false);
12111
- const updateArrow = React2.useCallback(() => {
12605
+ const scrollRef = React3.useRef(null);
12606
+ const [showArrow, setShowArrow] = React3.useState(false);
12607
+ const updateArrow = React3.useCallback(() => {
12112
12608
  const el = scrollRef.current;
12113
12609
  if (!el) return;
12114
12610
  const canScrollDown = el.scrollHeight - el.scrollTop - el.clientHeight > 6;
12115
12611
  setShowArrow(canScrollDown || hasMoreToLoad);
12116
12612
  }, [hasMoreToLoad]);
12117
- React2.useEffect(() => {
12613
+ React3.useEffect(() => {
12118
12614
  updateArrow();
12119
12615
  const el = scrollRef.current;
12120
12616
  if (!el) return;
@@ -12290,31 +12786,31 @@ var AttachmentsDrawer = ({ isOpen, onOpenChange, conversationId }) => {
12290
12786
  const messagesByConversation = exports.useChatStore((s) => s.messagesByConversation);
12291
12787
  const messagesMetadata = exports.useChatStore((s) => s.messagesMetadata);
12292
12788
  const fetchMessages2 = exports.useChatStore((s) => s.fetchMessages);
12293
- const [sectionVisibleCount, setSectionVisibleCount] = React2.useState({});
12294
- const [documentPreview, setDocumentPreview] = React2.useState(null);
12295
- const [imageLightboxIndex, setImageLightboxIndex] = React2.useState(null);
12789
+ const [sectionVisibleCount, setSectionVisibleCount] = React3.useState({});
12790
+ const [documentPreview, setDocumentPreview] = React3.useState(null);
12791
+ const [imageLightboxIndex, setImageLightboxIndex] = React3.useState(null);
12296
12792
  const hasLoadedMessages = conversationId ? Boolean(messagesMetadata[conversationId]?.isFirstPage) : false;
12297
12793
  const isLoading = isOpen && !!conversationId && !hasLoadedMessages;
12298
- React2.useEffect(() => {
12794
+ React3.useEffect(() => {
12299
12795
  if (!isOpen) {
12300
12796
  setSectionVisibleCount({});
12301
12797
  setDocumentPreview(null);
12302
12798
  setImageLightboxIndex(null);
12303
12799
  }
12304
12800
  }, [isOpen]);
12305
- React2.useEffect(() => {
12801
+ React3.useEffect(() => {
12306
12802
  setSectionVisibleCount({});
12307
12803
  }, [conversationId]);
12308
- React2.useEffect(() => {
12804
+ React3.useEffect(() => {
12309
12805
  if (!isOpen || !conversationId || hasLoadedMessages) return;
12310
12806
  fetchMessages2(conversationId);
12311
12807
  }, [isOpen, conversationId, hasLoadedMessages, fetchMessages2]);
12312
- const attachments = React2.useMemo(() => {
12808
+ const attachments = React3.useMemo(() => {
12313
12809
  if (!conversationId) return [];
12314
12810
  const messages = messagesByConversation[conversationId] || [];
12315
12811
  return collectConversationAttachments(messages);
12316
12812
  }, [conversationId, messagesByConversation]);
12317
- const imageLightboxItems = React2.useMemo(
12813
+ const imageLightboxItems = React3.useMemo(
12318
12814
  () => attachments.filter((item) => item.type === "image").map((item) => ({
12319
12815
  id: item.id,
12320
12816
  url: item.url,
@@ -12323,14 +12819,14 @@ var AttachmentsDrawer = ({ isOpen, onOpenChange, conversationId }) => {
12323
12819
  })),
12324
12820
  [attachments]
12325
12821
  );
12326
- const openImagePreview = React2.useCallback(
12822
+ const openImagePreview = React3.useCallback(
12327
12823
  (item) => {
12328
12824
  const index = imageLightboxItems.findIndex((entry) => entry.id === item.id);
12329
12825
  if (index >= 0) setImageLightboxIndex(index);
12330
12826
  },
12331
12827
  [imageLightboxItems]
12332
12828
  );
12333
- const grouped = React2.useMemo(() => {
12829
+ const grouped = React3.useMemo(() => {
12334
12830
  const map = /* @__PURE__ */ new Map();
12335
12831
  for (const item of attachments) {
12336
12832
  const bucket = map.get(item.type) ?? [];
@@ -12430,13 +12926,13 @@ var useGroupInfo = (conversationId) => {
12430
12926
  const conversationInfos = exports.useChatStore((s) => s.conversationInfos);
12431
12927
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
12432
12928
  const info = conversationId ? conversationInfos[conversationId] : null;
12433
- const isAdmin = React2.useMemo(() => {
12929
+ const isAdmin = React3.useMemo(() => {
12434
12930
  if (!info || !loggedUserDetails) return false;
12435
12931
  return info.groupAdmins?.some(
12436
12932
  (admin) => (typeof admin === "string" ? admin : admin._id) === String(loggedUserDetails._id)
12437
12933
  ) || false;
12438
12934
  }, [info, loggedUserDetails]);
12439
- const isOwner = React2.useMemo(() => {
12935
+ const isOwner = React3.useMemo(() => {
12440
12936
  if (!info || !loggedUserDetails) return false;
12441
12937
  let ownerId = null;
12442
12938
  if (info.owner) {
@@ -12445,7 +12941,7 @@ var useGroupInfo = (conversationId) => {
12445
12941
  return String(ownerId) === String(loggedUserDetails._id);
12446
12942
  }, [info, loggedUserDetails]);
12447
12943
  const permissions = info?.groupPermissions || null;
12448
- const userPermissions = React2.useMemo(() => {
12944
+ const userPermissions = React3.useMemo(() => {
12449
12945
  return {
12450
12946
  canSendMessage: isOwner || isAdmin || !permissions?.onlyAdminCanSendMessage,
12451
12947
  canEditInfo: isOwner || isAdmin || !permissions?.onlyAdminCanEditInfo,
@@ -12494,21 +12990,21 @@ function useChannelDetailsDrawer({
12494
12990
  const conversationId = conversationInfo?._id || activeChannel?.conversationId || storeActiveConversationId || null;
12495
12991
  const { userPermissions } = useGroupInfo(conversationId);
12496
12992
  const { formatLongDate } = useChatLocale();
12497
- const storeConversation = React2.useMemo(
12993
+ const storeConversation = React3.useMemo(
12498
12994
  () => conversations.find((c) => conversationId && String(c._id) === String(conversationId)),
12499
12995
  [conversations, conversationId]
12500
12996
  );
12501
- const isGroupChannel = React2.useMemo(() => {
12997
+ const isGroupChannel = React3.useMemo(() => {
12502
12998
  if (storeConversation) {
12503
12999
  return storeConversation.conversationType === "group" || !!storeConversation.isGroup;
12504
13000
  }
12505
13001
  return !!activeChannel?.isGroup;
12506
13002
  }, [storeConversation, activeChannel?.isGroup]);
12507
- const participantsSource = React2.useMemo(
13003
+ const participantsSource = React3.useMemo(
12508
13004
  () => conversationInfo?.participants || storeConversation?.participants || [],
12509
13005
  [conversationInfo?.participants, storeConversation?.participants]
12510
13006
  );
12511
- const resolvedOtherParticipant = React2.useMemo(() => {
13007
+ const resolvedOtherParticipant = React3.useMemo(() => {
12512
13008
  if (isGroupChannel) return { id: "", user: null };
12513
13009
  return resolveOtherParticipant(
12514
13010
  {
@@ -12527,7 +13023,7 @@ function useChannelDetailsDrawer({
12527
13023
  allUsers
12528
13024
  ]);
12529
13025
  const otherUser = resolvedOtherParticipant.user;
12530
- const otherUserEmail = React2.useMemo(() => {
13026
+ const otherUserEmail = React3.useMemo(() => {
12531
13027
  if (isGroupChannel) return null;
12532
13028
  const fromResolved = resolvedOtherParticipant.user?.email?.trim();
12533
13029
  if (fromResolved) return fromResolved;
@@ -12535,7 +13031,7 @@ function useChannelDetailsDrawer({
12535
13031
  if (!otherId) return null;
12536
13032
  return allUsers.find((user) => String(user._id) === String(otherId))?.email?.trim() || activeChannel?.email?.trim() || null;
12537
13033
  }, [isGroupChannel, resolvedOtherParticipant.id, resolvedOtherParticipant.user?.email, allUsers, activeChannel?.email]);
12538
- const resolvedCreatedAt = React2.useMemo(
13034
+ const resolvedCreatedAt = React3.useMemo(
12539
13035
  () => pickDate(
12540
13036
  conversationInfo?.createdAt,
12541
13037
  conversationInfo?.updatedAt,
@@ -12548,9 +13044,9 @@ function useChannelDetailsDrawer({
12548
13044
  ),
12549
13045
  [conversationInfo, storeConversation, otherUser, conversationId, messagesByConversation]
12550
13046
  );
12551
- const [detailsLoading, setDetailsLoading] = React2.useState(false);
12552
- const fetchStartedRef = React2.useRef(null);
12553
- React2.useEffect(() => {
13047
+ const [detailsLoading, setDetailsLoading] = React3.useState(false);
13048
+ const fetchStartedRef = React3.useRef(null);
13049
+ React3.useEffect(() => {
12554
13050
  if (!isOpen) {
12555
13051
  fetchStartedRef.current = null;
12556
13052
  setDetailsLoading(false);
@@ -12592,16 +13088,16 @@ function useChannelDetailsDrawer({
12592
13088
  };
12593
13089
  }, [isOpen, conversationId, isGroupChannel, fetchAllUsers]);
12594
13090
  const canEditInfo = activeChannel ? userPermissions.canEditInfo : false;
12595
- const [isEditing, setIsEditing] = React2.useState(false);
12596
- const [name, setName] = React2.useState("");
12597
- const [description, setDescription] = React2.useState("");
12598
- const [image, setImage] = React2.useState(null);
12599
- const [uploadedImageUrl, setUploadedImageUrl] = React2.useState(null);
12600
- const [imageUploadStatus, setImageUploadStatus] = React2.useState("idle");
12601
- const [imageUploadError, setImageUploadError] = React2.useState(null);
12602
- const [isSaving, setIsSaving] = React2.useState(false);
12603
- const fileInputRef = React2.useRef(null);
12604
- React2.useEffect(() => {
13091
+ const [isEditing, setIsEditing] = React3.useState(false);
13092
+ const [name, setName] = React3.useState("");
13093
+ const [description, setDescription] = React3.useState("");
13094
+ const [image, setImage] = React3.useState(null);
13095
+ const [uploadedImageUrl, setUploadedImageUrl] = React3.useState(null);
13096
+ const [imageUploadStatus, setImageUploadStatus] = React3.useState("idle");
13097
+ const [imageUploadError, setImageUploadError] = React3.useState(null);
13098
+ const [isSaving, setIsSaving] = React3.useState(false);
13099
+ const fileInputRef = React3.useRef(null);
13100
+ React3.useEffect(() => {
12605
13101
  if (conversationInfo) {
12606
13102
  setName(conversationInfo.groupName || activeChannel?.name || "");
12607
13103
  setDescription(conversationInfo.groupDescription || "");
@@ -12872,17 +13368,24 @@ var ChannelDetailsDrawer = (props) => {
12872
13368
  };
12873
13369
  var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12874
13370
  init_useStore();
13371
+ init_settings_types();
12875
13372
  var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
12876
13373
  const updateGroupPermissions = exports.useChatStore((s) => s.updateGroupPermissions);
12877
- const [permissions, setPermissions] = React2.useState(null);
12878
- const [isSaving, setIsSaving] = React2.useState(false);
12879
- React2.useEffect(() => {
13374
+ const { settings, isGroupPermissionLocked } = useEffectiveSettingsOptional();
13375
+ const [permissions, setPermissions] = React3.useState(null);
13376
+ const [isSaving, setIsSaving] = React3.useState(false);
13377
+ React3.useEffect(() => {
12880
13378
  if (conversationInfo?.groupPermissions) {
12881
13379
  setPermissions(conversationInfo.groupPermissions);
13380
+ return;
13381
+ }
13382
+ if (isOpen) {
13383
+ setPermissions({ ...settings.groupPolicy.defaults });
12882
13384
  }
12883
- }, [conversationInfo]);
13385
+ }, [conversationInfo, isOpen, settings.groupPolicy.defaults]);
12884
13386
  if (!conversationId || !permissions) return null;
12885
13387
  const handleToggle = (key) => {
13388
+ if (isGroupPermissionLocked(key)) return;
12886
13389
  setPermissions((prev) => prev ? { ...prev, [key]: !prev[key] } : null);
12887
13390
  };
12888
13391
  const handleSave = async () => {
@@ -12914,31 +13417,43 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
12914
13417
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "flex flex-col px-2.5 py-2"), children: [
12915
13418
  /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mb-2.5 flex-1", children: [
12916
13419
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-1 px-0.5 text-[10px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "Permissions" }),
12917
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(
12918
- "div",
12919
- {
12920
- className: cn(
12921
- "flex items-start justify-between gap-3 py-2.5",
12922
- index < permissionItems.length - 1 && "border-b border-(--chat-border)"
12923
- ),
12924
- children: [
12925
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-0.5", children: [
12926
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
12927
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc })
12928
- ] }),
12929
- /* @__PURE__ */ jsxRuntime.jsx(
12930
- Switch,
12931
- {
12932
- size: "sm",
12933
- className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
12934
- checked: "inverted" in item && item.inverted ? !permissions[item.key] : permissions[item.key],
12935
- onCheckedChange: () => handleToggle(item.key)
12936
- }
12937
- )
12938
- ]
12939
- },
12940
- item.key
12941
- )) })
13420
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13421
+ const locked = isGroupPermissionLocked(item.key);
13422
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13423
+ "div",
13424
+ {
13425
+ className: cn(
13426
+ "flex items-start justify-between gap-3 py-2.5",
13427
+ index < permissionItems.length - 1 && "border-b border-(--chat-border)"
13428
+ ),
13429
+ children: [
13430
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-0.5", children: [
13431
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
13432
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc }),
13433
+ locked ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-medium text-(--chat-theme)", children: "Locked by administrator" }) : null
13434
+ ] }),
13435
+ /* @__PURE__ */ jsxRuntime.jsx(
13436
+ AdminPermissionTooltip,
13437
+ {
13438
+ disabled: locked,
13439
+ message: exports.ADMIN_PERMISSION_DENIED_MESSAGE,
13440
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13441
+ Switch,
13442
+ {
13443
+ size: "sm",
13444
+ className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
13445
+ disabled: locked,
13446
+ checked: item.inverted ? !permissions[item.key] : permissions[item.key],
13447
+ onCheckedChange: () => handleToggle(item.key)
13448
+ }
13449
+ )
13450
+ }
13451
+ )
13452
+ ]
13453
+ },
13454
+ item.key
13455
+ );
13456
+ }) })
12942
13457
  ] }),
12943
13458
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky bottom-0 border-t border-(--chat-border) bg-(--chat-surface) px-0.5 pt-2.5 pb-1", children: /* @__PURE__ */ jsxRuntime.jsx(
12944
13459
  Button,
@@ -12964,51 +13479,61 @@ var PermissionDrawer_default = PermissionDrawer;
12964
13479
  // src/chat/header/admins-drawer/useAdminsDrawer.ts
12965
13480
  init_useStore();
12966
13481
  function useAdminsDrawer({ conversationId, conversationInfo }) {
12967
- const [searchQuery, setSearchQuery] = React2.useState("");
12968
- const [isManaging, setIsManaging] = React2.useState(null);
13482
+ const [searchQuery, setSearchQuery] = React3.useState("");
13483
+ const [isManaging, setIsManaging] = React3.useState(null);
12969
13484
  const manageGroupAdmins = exports.useChatStore((s) => s.manageGroupAdmins);
12970
13485
  const { isOwner } = useGroupInfo(conversationId);
12971
- const participants = React2.useMemo(
13486
+ const { settings } = useEffectiveSettingsOptional();
13487
+ const maxAdmins = settings.maxAdminsPerGroup;
13488
+ const participants = React3.useMemo(
12972
13489
  () => conversationInfo?.participants ?? [],
12973
13490
  [conversationInfo?.participants]
12974
13491
  );
12975
- const admins = React2.useMemo(
13492
+ const admins = React3.useMemo(
12976
13493
  () => conversationInfo?.groupAdmins ?? [],
12977
13494
  [conversationInfo?.groupAdmins]
12978
13495
  );
12979
- const ownerId = React2.useMemo(() => {
13496
+ const ownerId = React3.useMemo(() => {
12980
13497
  if (!conversationInfo?.owner) return null;
12981
13498
  return typeof conversationInfo.owner === "object" && conversationInfo.owner !== null ? conversationInfo.owner._id || conversationInfo.owner.id : conversationInfo.owner;
12982
13499
  }, [conversationInfo?.owner]);
12983
- const adminIds = React2.useMemo(
13500
+ const adminIds = React3.useMemo(
12984
13501
  () => new Set(
12985
13502
  admins.map((a) => typeof a === "string" ? a : a._id).filter((id) => Boolean(id))
12986
13503
  ),
12987
13504
  [admins]
12988
13505
  );
12989
- const isAdmin = React2.useCallback((userId) => adminIds.has(userId), [adminIds]);
12990
- const filteredParticipants = React2.useMemo(() => {
13506
+ const isAdmin = React3.useCallback((userId) => adminIds.has(userId), [adminIds]);
13507
+ const filteredParticipants = React3.useMemo(() => {
12991
13508
  if (!searchQuery.trim()) return participants;
12992
13509
  return participants.filter(
12993
13510
  (p) => p.name?.toLowerCase().includes(searchQuery.toLowerCase()) || p.email?.toLowerCase().includes(searchQuery.toLowerCase())
12994
13511
  );
12995
13512
  }, [participants, searchQuery]);
12996
- const adminParticipants = React2.useMemo(
13513
+ const adminParticipants = React3.useMemo(
12997
13514
  () => filteredParticipants.filter((p) => {
12998
13515
  const id = typeof p === "string" ? p : p._id;
12999
13516
  return id && (adminIds.has(id) || id === ownerId);
13000
13517
  }),
13001
13518
  [filteredParticipants, adminIds, ownerId]
13002
13519
  );
13003
- const promotableParticipants = React2.useMemo(
13520
+ const promotableParticipants = React3.useMemo(
13004
13521
  () => filteredParticipants.filter((p) => {
13005
13522
  const id = typeof p === "string" ? p : p._id;
13006
13523
  return id && id !== ownerId && !adminIds.has(id);
13007
13524
  }),
13008
13525
  [filteredParticipants, adminIds, ownerId]
13009
13526
  );
13527
+ const adminCount = admins.length + (ownerId ? 1 : 0);
13528
+ const atAdminLimit = adminCount >= maxAdmins;
13010
13529
  const handleManageAdmin = async (userId, type) => {
13011
13530
  if (!conversationId) return;
13531
+ if (type === "add" && adminCount >= maxAdmins) {
13532
+ exports.useChatStore.getState().handleAppError(
13533
+ `You can have up to ${maxAdmins} admins in this group.`
13534
+ );
13535
+ return;
13536
+ }
13012
13537
  setIsManaging(userId);
13013
13538
  try {
13014
13539
  await manageGroupAdmins({
@@ -13020,7 +13545,6 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
13020
13545
  setIsManaging(null);
13021
13546
  }
13022
13547
  };
13023
- const adminCount = admins.length + (ownerId ? 1 : 0);
13024
13548
  return {
13025
13549
  searchQuery,
13026
13550
  setSearchQuery,
@@ -13032,6 +13556,8 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
13032
13556
  promotableParticipants,
13033
13557
  handleManageAdmin,
13034
13558
  adminCount,
13559
+ maxAdmins,
13560
+ atAdminLimit,
13035
13561
  hasRequiredData: Boolean(conversationId && conversationInfo)
13036
13562
  };
13037
13563
  }
@@ -13044,6 +13570,8 @@ function AdminParticipantRow({
13044
13570
  isAdmin,
13045
13571
  showPromote,
13046
13572
  showDemote,
13573
+ promoteDisabled = false,
13574
+ promoteDisabledReason,
13047
13575
  isOwnerViewer,
13048
13576
  isManaging,
13049
13577
  onManageAdmin
@@ -13084,14 +13612,24 @@ function AdminParticipantRow({
13084
13612
  }
13085
13613
  ),
13086
13614
  showPromote && isOwnerViewer && /* @__PURE__ */ jsxRuntime.jsx(
13087
- Button,
13615
+ AdminPermissionTooltip,
13088
13616
  {
13089
- variant: "ghost",
13090
- size: "sm",
13091
- className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme)",
13092
- onClick: () => onManageAdmin(memberId, "add"),
13093
- disabled: isManaging === memberId,
13094
- children: isManaging === memberId ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { size: 13 })
13617
+ disabled: promoteDisabled,
13618
+ message: promoteDisabledReason,
13619
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13620
+ Button,
13621
+ {
13622
+ variant: "ghost",
13623
+ size: "sm",
13624
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13625
+ onClick: () => {
13626
+ if (promoteDisabled) return;
13627
+ onManageAdmin(memberId, "add");
13628
+ },
13629
+ disabled: isManaging === memberId || promoteDisabled,
13630
+ children: isManaging === memberId ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { size: 13 })
13631
+ }
13632
+ )
13095
13633
  }
13096
13634
  )
13097
13635
  ] })
@@ -13115,7 +13653,9 @@ function AdminParticipantList({
13115
13653
  isAdmin,
13116
13654
  onManageAdmin,
13117
13655
  showPromote,
13118
- showDemoteForAdmins
13656
+ showDemoteForAdmins,
13657
+ promoteDisabled = false,
13658
+ promoteDisabledReason
13119
13659
  }) {
13120
13660
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: members.map((member, index) => {
13121
13661
  const { memberId, memberName, memberEmail, memberImage } = getMemberFields(member);
@@ -13133,6 +13673,8 @@ function AdminParticipantList({
13133
13673
  isAdmin: userIsAdmin,
13134
13674
  showPromote: showPromote && !userIsAdmin && !userIsOwner,
13135
13675
  showDemote: showDemoteForAdmins ? userIsAdmin && !userIsOwner : false,
13676
+ promoteDisabled,
13677
+ promoteDisabledReason,
13136
13678
  isOwnerViewer,
13137
13679
  isManaging,
13138
13680
  onManageAdmin
@@ -13152,6 +13694,8 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13152
13694
  promotableParticipants,
13153
13695
  handleManageAdmin,
13154
13696
  adminCount,
13697
+ maxAdmins,
13698
+ atAdminLimit,
13155
13699
  hasRequiredData
13156
13700
  } = useAdminsDrawer({ conversationId, conversationInfo });
13157
13701
  if (!hasRequiredData) return null;
@@ -13171,7 +13715,10 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13171
13715
  /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Administrators" }),
13172
13716
  /* @__PURE__ */ jsxRuntime.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 })
13173
13717
  ] }),
13174
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: chatSheetSubtitleClass, children: "Manage who can adjust group settings and members." })
13718
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: chatSheetSubtitleClass, children: [
13719
+ "Manage who can adjust group settings and members",
13720
+ maxAdmins ? ` (max ${maxAdmins}).` : "."
13721
+ ] })
13175
13722
  ] }),
13176
13723
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13177
13724
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2.5 px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -13214,7 +13761,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13214
13761
  ownerId,
13215
13762
  isAdmin,
13216
13763
  onManageAdmin: handleManageAdmin,
13217
- showPromote: true
13764
+ showPromote: true,
13765
+ promoteDisabled: atAdminLimit,
13766
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13218
13767
  }
13219
13768
  ) })
13220
13769
  ] }),
@@ -13231,7 +13780,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13231
13780
  isAdmin,
13232
13781
  onManageAdmin: handleManageAdmin,
13233
13782
  showPromote: true,
13234
- showDemoteForAdmins: true
13783
+ showDemoteForAdmins: true,
13784
+ promoteDisabled: atAdminLimit,
13785
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13235
13786
  }
13236
13787
  ) })
13237
13788
  ] }),
@@ -13247,21 +13798,24 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13247
13798
  var AdminsDrawer_default = AdminsDrawer;
13248
13799
  init_useStore();
13249
13800
  var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
13250
- const [searchQuery, setSearchQuery] = React2.useState("");
13251
- const [isManaging, setIsManaging] = React2.useState(null);
13801
+ const [searchQuery, setSearchQuery] = React3.useState("");
13802
+ const [isManaging, setIsManaging] = React3.useState(null);
13252
13803
  const allUsers = exports.useChatStore((s) => s.allUsers);
13253
13804
  const manageGroupMembers = exports.useChatStore((s) => s.manageGroupMembers);
13254
13805
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
13255
- const filteredUsers = React2.useMemo(() => {
13806
+ const filteredUsers = React3.useMemo(() => {
13256
13807
  if (!searchQuery.trim()) return allUsers;
13257
13808
  return allUsers.filter(
13258
13809
  (u) => u.name?.toLowerCase().includes(searchQuery.toLowerCase()) || u.email?.toLowerCase().includes(searchQuery.toLowerCase())
13259
13810
  );
13260
13811
  }, [allUsers, searchQuery]);
13261
13812
  const { userPermissions } = useGroupInfo(conversationId);
13813
+ const { settings } = useEffectiveSettingsOptional();
13814
+ const maxParticipants = settings.maxParticipantsPerGroup;
13262
13815
  if (!conversationId || !conversationInfo) return null;
13263
13816
  const participants = conversationInfo.participants || [];
13264
13817
  const admins = conversationInfo.groupAdmins || [];
13818
+ const atParticipantLimit = participants.length >= maxParticipants;
13265
13819
  let ownerId = null;
13266
13820
  if (conversationInfo.owner) {
13267
13821
  ownerId = typeof conversationInfo.owner === "object" && conversationInfo.owner !== null ? conversationInfo.owner._id || conversationInfo.owner.id : conversationInfo.owner;
@@ -13315,14 +13869,24 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13315
13869
  }
13316
13870
  ),
13317
13871
  options.showAdd && /* @__PURE__ */ jsxRuntime.jsx(
13318
- Button,
13872
+ AdminPermissionTooltip,
13319
13873
  {
13320
- variant: "ghost",
13321
- size: "sm",
13322
- className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme)",
13323
- onClick: () => handleManageMember(memberId, "add"),
13324
- disabled: isManaging === memberId,
13325
- children: isManaging === memberId ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { size: 13 })
13874
+ disabled: atParticipantLimit,
13875
+ message: `Maximum ${maxParticipants} participants allowed in this group.`,
13876
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13877
+ Button,
13878
+ {
13879
+ variant: "ghost",
13880
+ size: "sm",
13881
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13882
+ onClick: () => {
13883
+ if (atParticipantLimit) return;
13884
+ handleManageMember(memberId, "add");
13885
+ },
13886
+ disabled: isManaging === memberId || atParticipantLimit,
13887
+ children: isManaging === memberId ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { size: 13 })
13888
+ }
13889
+ )
13326
13890
  }
13327
13891
  )
13328
13892
  ] })
@@ -13344,7 +13908,11 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13344
13908
  /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Management" }),
13345
13909
  /* @__PURE__ */ jsxRuntime.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 })
13346
13910
  ] }),
13347
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: chatSheetSubtitleClass, children: "Manage members and invite people." })
13911
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: chatSheetSubtitleClass, children: [
13912
+ "Manage members and invite people (max ",
13913
+ maxParticipants,
13914
+ ")."
13915
+ ] })
13348
13916
  ] }),
13349
13917
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13350
13918
  !searchQuery && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mb-2.5", children: [
@@ -13465,8 +14033,8 @@ var ChatActionModal_default = ChatActionModal;
13465
14033
  // src/chat/channel-header/useChannelHeader.ts
13466
14034
  init_useStore();
13467
14035
  function useDebounce(value, delay) {
13468
- const [debouncedValue, setDebouncedValue] = React2.useState(value);
13469
- React2.useEffect(() => {
14036
+ const [debouncedValue, setDebouncedValue] = React3.useState(value);
14037
+ React3.useEffect(() => {
13470
14038
  const timer = setTimeout(() => setDebouncedValue(value), delay);
13471
14039
  return () => {
13472
14040
  clearTimeout(timer);
@@ -13486,7 +14054,9 @@ init_store_helpers();
13486
14054
  function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13487
14055
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
13488
14056
  const composerDisabledByConversation = exports.useChatStore((s) => s.composerDisabledByConversation);
13489
- const [isBlocking, setIsBlocking] = React2.useState(false);
14057
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
14058
+ const blockUsersGate = getBlockUsersGate();
14059
+ const [isBlocking, setIsBlocking] = React3.useState(false);
13490
14060
  const resolvedConversationId = conversationId ?? activeChannel?.conversationId ?? null;
13491
14061
  const composerDisabledReason = resolvedConversationId ? composerDisabledByConversation[resolvedConversationId] : void 0;
13492
14062
  const isBlockedFromConversation = exports.useChatStore((s) => {
@@ -13504,9 +14074,15 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13504
14074
  return !!conv?.isBlocked || !!activeChannel.isBlocked;
13505
14075
  });
13506
14076
  const isBlocked = isBlockedFromConversation || !activeChannel?.isGroup && !!composerDisabledReason && /block/i.test(composerDisabledReason);
13507
- const toggleBlock = React2.useCallback(async () => {
14077
+ const toggleBlock = React3.useCallback(async () => {
13508
14078
  if (!activeChannel?.id || activeChannel.isGroup) return false;
13509
14079
  const nextBlocked = !isBlocked;
14080
+ if (nextBlocked && !blockUsersGate.enabled) {
14081
+ exports.useChatStore.getState().handleAppError(
14082
+ blockUsersGate.disabledReason || "Blocking users is disabled by your administrator."
14083
+ );
14084
+ return false;
14085
+ }
13510
14086
  const type = nextBlocked ? "block" : "unblock";
13511
14087
  try {
13512
14088
  setIsBlocking(true);
@@ -13527,8 +14103,8 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13527
14103
  } finally {
13528
14104
  setIsBlocking(false);
13529
14105
  }
13530
- }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId]);
13531
- return { isBlocked, isBlocking, toggleBlock };
14106
+ }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId, blockUsersGate]);
14107
+ return { isBlocked, isBlocking, toggleBlock, blockUsersGate };
13532
14108
  }
13533
14109
 
13534
14110
  // src/chat/channel-header/useChannelHeader.ts
@@ -13561,8 +14137,8 @@ function useChannelHeader({
13561
14137
  onBack,
13562
14138
  onActiveChannelPatch
13563
14139
  }) {
13564
- const [state, setState] = React2.useState(initialState2);
13565
- const updateState = React2.useCallback((updates) => {
14140
+ const [state, setState] = React3.useState(initialState2);
14141
+ const updateState = React3.useCallback((updates) => {
13566
14142
  setState((prev) => ({ ...prev, ...updates }));
13567
14143
  }, []);
13568
14144
  const { formatLongDate } = useChatLocale();
@@ -13602,7 +14178,7 @@ function useChannelHeader({
13602
14178
  const setSearchedMessageId = exports.useChatStore((s) => s.setSearchedMessageId);
13603
14179
  const isSearchActive = exports.useChatStore((s) => s.isSearchActive);
13604
14180
  const debouncedSearch = useDebounce(state.searchQuery, 800);
13605
- const handleSearch = React2.useCallback(async (query, cursor) => {
14181
+ const handleSearch = React3.useCallback(async (query, cursor) => {
13606
14182
  if (!conversationId) return;
13607
14183
  const isFirstPage = !cursor;
13608
14184
  updateState({ isSearching: isFirstPage, isLoadingMoreSearch: !isFirstPage });
@@ -13623,7 +14199,7 @@ function useChannelHeader({
13623
14199
  updateState({ isSearching: false, isLoadingMoreSearch: false });
13624
14200
  }
13625
14201
  }, [conversationId, updateState]);
13626
- React2.useEffect(() => {
14202
+ React3.useEffect(() => {
13627
14203
  if (debouncedSearch && conversationId) {
13628
14204
  updateState({ searchCursor: null, searchHasMore: false });
13629
14205
  handleSearch(debouncedSearch);
@@ -13657,32 +14233,32 @@ function useChannelHeader({
13657
14233
  handleCloseSearch();
13658
14234
  };
13659
14235
  const canViewMembers = isAdmin || userPermissions.canAddMember || userPermissions.canRemoveMember;
13660
- const prevIsClearingRef = React2.useRef(isClearingChat);
13661
- React2.useEffect(() => {
14236
+ const prevIsClearingRef = React3.useRef(isClearingChat);
14237
+ React3.useEffect(() => {
13662
14238
  if (prevIsClearingRef.current === true && !isClearingChat) {
13663
14239
  updateState({ isClearChatAlertOpen: false });
13664
14240
  }
13665
14241
  prevIsClearingRef.current = isClearingChat;
13666
14242
  }, [isClearingChat, updateState]);
13667
- const prevIsDeletingRef = React2.useRef(isDeletingConversation);
13668
- React2.useEffect(() => {
14243
+ const prevIsDeletingRef = React3.useRef(isDeletingConversation);
14244
+ React3.useEffect(() => {
13669
14245
  if (prevIsDeletingRef.current === true && !isDeletingConversation) {
13670
14246
  updateState({ isDeletingConversationAlertOpen: false });
13671
14247
  onBack?.();
13672
14248
  }
13673
14249
  prevIsDeletingRef.current = isDeletingConversation;
13674
14250
  }, [isDeletingConversation, onBack, updateState]);
13675
- React2.useEffect(() => {
14251
+ React3.useEffect(() => {
13676
14252
  if (state.isPinOpen && conversationId) {
13677
14253
  fetchPinnedMessages2(conversationId);
13678
14254
  }
13679
14255
  }, [state.isPinOpen, conversationId, fetchPinnedMessages2]);
13680
- React2.useEffect(() => {
14256
+ React3.useEffect(() => {
13681
14257
  if (state.isFavoriteOpen && conversationId) {
13682
14258
  fetchStarredMessages2(conversationId);
13683
14259
  }
13684
14260
  }, [state.isFavoriteOpen, conversationId, fetchStarredMessages2]);
13685
- React2.useEffect(() => {
14261
+ React3.useEffect(() => {
13686
14262
  if (state.isAttachmentsOpen && conversationId) {
13687
14263
  const meta = exports.useChatStore.getState().messagesMetadata[conversationId];
13688
14264
  if (!meta?.isFirstPage) {
@@ -14064,6 +14640,7 @@ var MessageReactions_default = MessageReactions;
14064
14640
  var MessageToolbar = ({
14065
14641
  isSender,
14066
14642
  isRecent,
14643
+ isWithinDeleteWindow = isRecent,
14067
14644
  isPinned,
14068
14645
  isStarred,
14069
14646
  isPinPending,
@@ -14087,7 +14664,7 @@ var MessageToolbar = ({
14087
14664
  }) => {
14088
14665
  const { isDark, scopeClassName, themeStyles } = useChatTheme();
14089
14666
  const showDeleteMenu = isSender || hasAttachments || attachmentDeleteMode;
14090
- const showDeleteForEveryone = isSender && isRecent;
14667
+ const showDeleteForEveryone = isSender && isWithinDeleteWindow;
14091
14668
  return /* @__PURE__ */ jsxRuntime.jsxs(
14092
14669
  "div",
14093
14670
  {
@@ -14242,8 +14819,8 @@ function SentAttachmentCaptionModal({
14242
14819
  canEdit = false,
14243
14820
  onSave
14244
14821
  }) {
14245
- const [draft, setDraft] = React2.useState("");
14246
- React2.useEffect(() => {
14822
+ const [draft, setDraft] = React3.useState("");
14823
+ React3.useEffect(() => {
14247
14824
  if (open && attachment) {
14248
14825
  setDraft(attachment.caption || "");
14249
14826
  }
@@ -14409,16 +14986,68 @@ function resolveForwardItems(selectedKeys, messages) {
14409
14986
  }
14410
14987
 
14411
14988
  // src/chat/lib/chat-edit-window.ts
14412
- init_chat_constants();
14413
- function getMessageEditWindowMs(isGroup) {
14414
- return isGroup ? GROUP_EDIT_WINDOW_MS : DM_EDIT_WINDOW_MS;
14989
+ init_settings_types();
14990
+
14991
+ // src/lib/effective-settings.helpers.ts
14992
+ function minutesToMs(minutes) {
14993
+ return Math.max(0, minutes) * 60 * 1e3;
14994
+ }
14995
+ function getEditWindowMs(timers, isGroup) {
14996
+ return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
14415
14997
  }
14416
- function isWithinMessageEditWindow(createdAt, isGroup) {
14998
+ function getDeleteWindowMs(timers, isGroup) {
14999
+ return minutesToMs(
15000
+ isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
15001
+ );
15002
+ }
15003
+ function isWithinWindow(createdAt, windowMs) {
14417
15004
  if (!createdAt) return false;
14418
15005
  const date = new Date(createdAt);
14419
15006
  if (isNaN(date.getTime())) return false;
14420
15007
  const diff = Date.now() - date.getTime();
14421
- return diff >= 0 && diff < getMessageEditWindowMs(isGroup);
15008
+ return diff >= 0 && diff < windowMs;
15009
+ }
15010
+ function isWithinMessageEditWindow(createdAt, timers, isGroup) {
15011
+ return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
15012
+ }
15013
+ function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
15014
+ return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
15015
+ }
15016
+ function allowsAllMimeTypes(allowedMimeTypes) {
15017
+ return allowedMimeTypes.some((pattern) => {
15018
+ const normalized = pattern.trim().toLowerCase();
15019
+ return normalized === "*" || normalized === "*/*";
15020
+ });
15021
+ }
15022
+ function matchesAllowedMimeType(fileType, allowedMimeTypes) {
15023
+ if (!allowedMimeTypes.length) return false;
15024
+ if (allowsAllMimeTypes(allowedMimeTypes)) return true;
15025
+ const mime = (fileType || "application/octet-stream").toLowerCase();
15026
+ return allowedMimeTypes.some((pattern) => {
15027
+ const normalized = pattern.trim().toLowerCase();
15028
+ if (!normalized) return false;
15029
+ if (normalized.endsWith("/*")) {
15030
+ const prefix = normalized.slice(0, -1);
15031
+ return mime.startsWith(prefix);
15032
+ }
15033
+ return mime === normalized;
15034
+ });
15035
+ }
15036
+ function buildAcceptFromMimeTypes(allowedMimeTypes) {
15037
+ if (allowsAllMimeTypes(allowedMimeTypes)) return "";
15038
+ return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
15039
+ }
15040
+ function formatFileSizeMB(sizeBytes) {
15041
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
15042
+ }
15043
+
15044
+ // src/chat/lib/chat-edit-window.ts
15045
+ var FALLBACK_TIMERS = exports.DEFAULT_EFFECTIVE_SETTINGS.messageTimers;
15046
+ function isWithinMessageEditWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
15047
+ return isWithinMessageEditWindow(createdAt, timers, isGroup);
15048
+ }
15049
+ function isWithinMessageDeleteWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
15050
+ return isWithinMessageDeleteWindow(createdAt, timers, isGroup);
14422
15051
  }
14423
15052
 
14424
15053
  // src/chat/message-item/useMessageItemActions.ts
@@ -14443,7 +15072,7 @@ function useMessageItemActions({
14443
15072
  selectionMode = false,
14444
15073
  selectedKeys
14445
15074
  }) {
14446
- const [state, setState] = React2.useState({
15075
+ const [state, setState] = React3.useState({
14447
15076
  isHoverMessage: false,
14448
15077
  isEditing: false,
14449
15078
  editValue: String(message || ""),
@@ -14456,7 +15085,7 @@ function useMessageItemActions({
14456
15085
  const updateState = (updates) => {
14457
15086
  setState((prev) => ({ ...prev, ...updates }));
14458
15087
  };
14459
- React2.useEffect(() => {
15088
+ React3.useEffect(() => {
14460
15089
  setState((prev) => ({ ...prev, editValue: String(message || "") }));
14461
15090
  }, [message]);
14462
15091
  const emitDmEdit = exports.useChatStore((s) => s.emitDmEdit);
@@ -14496,6 +15125,9 @@ function useMessageItemActions({
14496
15125
  const canUnpin = !isPinned || isPinnedByMe;
14497
15126
  const { userPermissions } = useGroupInfo(conversationId || null);
14498
15127
  const { formatMessageTime } = useChatLocale();
15128
+ const { settings } = useEffectiveSettingsOptional();
15129
+ const messageTimers = settings.messageTimers;
15130
+ const maxPinnedMessages = settings.maxPinnedMessagesPerConversation;
14499
15131
  const visibleAttachments = getVisibleAttachments(attachments);
14500
15132
  const hasAttachments = visibleAttachments.length > 0;
14501
15133
  const hasMultiAttachments = hasAttachments && messageUsesAttachmentSelection({ attachments: visibleAttachments });
@@ -14512,7 +15144,8 @@ function useMessageItemActions({
14512
15144
  const hasText = Boolean(message?.trim());
14513
15145
  const isSelected = !!mid && !!selectedKeys && isMessageWhollySelected(selectedKeys, mid) && !hasMultiAttachments;
14514
15146
  const canEdit = (!isGroup || userPermissions.canEditMessage) && hasText;
14515
- const isWithinEditWindow = () => isWithinMessageEditWindow(createdAt, isGroup);
15147
+ const isWithinEditWindow = () => isWithinMessageEditWindow2(createdAt, isGroup, messageTimers);
15148
+ const isWithinDeleteWindow = () => isWithinMessageDeleteWindow2(createdAt, isGroup, messageTimers);
14516
15149
  const canEditAttachmentCaption = isSender && isWithinEditWindow() && (!isGroup || userPermissions.canEditMessage) && !isDeletedForEveryone && !!mid;
14517
15150
  const handleAttachmentCaptionClick = (attachment) => {
14518
15151
  if (!canEditAttachmentCaption) return;
@@ -14681,6 +15314,15 @@ function useMessageItemActions({
14681
15314
  });
14682
15315
  }
14683
15316
  } else {
15317
+ if (conversationId) {
15318
+ const pinnedCount = (pinnedMessagesByConversation[conversationId] || []).length || exports.useChatStore.getState().liveConversationCounts[conversationId]?.pinnedCount || 0;
15319
+ if (pinnedCount >= maxPinnedMessages) {
15320
+ exports.useChatStore.getState().handleAppError(
15321
+ `You can pin up to ${maxPinnedMessages} messages in this conversation.`
15322
+ );
15323
+ return;
15324
+ }
15325
+ }
14684
15326
  if (isGroup) {
14685
15327
  if (!conversationId) return;
14686
15328
  emitGroupPin({
@@ -14739,6 +15381,7 @@ function useMessageItemActions({
14739
15381
  canUnpin,
14740
15382
  canEdit,
14741
15383
  isWithinEditWindow,
15384
+ isWithinDeleteWindow,
14742
15385
  canEditAttachmentCaption,
14743
15386
  visibleAttachments,
14744
15387
  hasAttachments,
@@ -14770,7 +15413,7 @@ function useMessageItemActions({
14770
15413
  }
14771
15414
 
14772
15415
  // src/chat/lib/reply-preview.ts
14773
- function normalizeAttachments(replyTo, loggedUserId) {
15416
+ function normalizeAttachments2(replyTo, loggedUserId) {
14774
15417
  const raw = replyTo?.attachments;
14775
15418
  if (!Array.isArray(raw) || raw.length === 0) return [];
14776
15419
  return raw.map((att, index) => {
@@ -14783,7 +15426,7 @@ function resolveReplyToMessage(replyTo, messagesById, loggedUserId) {
14783
15426
  if (typeof replyTo === "object" && (replyTo.content?.trim() || replyTo.attachments?.length)) {
14784
15427
  return {
14785
15428
  ...replyTo,
14786
- attachments: normalizeAttachments(replyTo, loggedUserId)
15429
+ attachments: normalizeAttachments2(replyTo, loggedUserId)
14787
15430
  };
14788
15431
  }
14789
15432
  const replyId = String(
@@ -15220,6 +15863,7 @@ function MessageItemView(props) {
15220
15863
  canUnpin,
15221
15864
  canEdit,
15222
15865
  isWithinEditWindow,
15866
+ isWithinDeleteWindow,
15223
15867
  canEditAttachmentCaption,
15224
15868
  visibleAttachments,
15225
15869
  hasAttachments,
@@ -15311,6 +15955,7 @@ function MessageItemView(props) {
15311
15955
  {
15312
15956
  isSender,
15313
15957
  isRecent: isWithinEditWindow(),
15958
+ isWithinDeleteWindow: isWithinDeleteWindow(),
15314
15959
  messageId: mid,
15315
15960
  isPinned,
15316
15961
  isStarred,
@@ -15500,7 +16145,7 @@ function MessageSelectionBar({
15500
16145
  className
15501
16146
  }) {
15502
16147
  const { scopeClassName, themeStyles } = useChatTheme();
15503
- const [isDeleteOpen, setIsDeleteOpen] = React2.useState(false);
16148
+ const [isDeleteOpen, setIsDeleteOpen] = React3.useState(false);
15504
16149
  if (count === 0) return null;
15505
16150
  const countLabel = selectionLabel === "files" ? count === 1 ? "file" : "files" : count === 1 ? "message" : "messages";
15506
16151
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -15605,6 +16250,7 @@ function MessageSelectionBar({
15605
16250
  }
15606
16251
 
15607
16252
  // src/chat/active-channel-messages/utils.ts
16253
+ init_settings_types();
15608
16254
  var getMessageSenderId2 = (msg) => msg ? String(msg.senderId || msg.sender?._id || "") : "";
15609
16255
  var isLikelyObjectId = (value) => /^[a-f\d]{24}$/i.test(value);
15610
16256
  function resolveGroupSenderDisplay(message, allUsers = [], participants = []) {
@@ -15636,7 +16282,7 @@ var isSameMessageSide = (a, b, isGroupChat) => {
15636
16282
  const bId = getMessageSenderId2(b);
15637
16283
  return aId !== "" && aId === bId;
15638
16284
  };
15639
- function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
16285
+ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup, timers = exports.DEFAULT_EFFECTIVE_SETTINGS.messageTimers) {
15640
16286
  if (selectedKeys.size === 0) return false;
15641
16287
  const parsed = [...selectedKeys].map((key) => parseSelectionKey(key));
15642
16288
  const messageIds = new Set(parsed.map((entry) => entry.messageId));
@@ -15644,7 +16290,7 @@ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
15644
16290
  const messageId2 = [...messageIds][0];
15645
16291
  const message = messages.find((entry) => String(entry._id || entry.id) === messageId2);
15646
16292
  if (!message?.isOwnMessage) return false;
15647
- if (!isWithinMessageEditWindow(message.timestamp, isGroup)) return false;
16293
+ if (!isWithinMessageDeleteWindow2(message.timestamp, isGroup, timers)) return false;
15648
16294
  const hasAttachmentKey = parsed.some((entry) => entry.attachmentId);
15649
16295
  if (!hasAttachmentKey) {
15650
16296
  return selectedKeys.size === 1;
@@ -15808,22 +16454,24 @@ function useActiveChannelMessages({
15808
16454
  onSelectionModeChange,
15809
16455
  conversationInfo
15810
16456
  }) {
15811
- const [selectionMode, setSelectionMode] = React2.useState(false);
15812
- const [selectedKeys, setSelectedKeys] = React2.useState(/* @__PURE__ */ new Set());
16457
+ const [selectionMode, setSelectionMode] = React3.useState(false);
16458
+ const [selectedKeys, setSelectedKeys] = React3.useState(/* @__PURE__ */ new Set());
15813
16459
  const emitDmDelete = exports.useChatStore((s) => s.emitDmDelete);
15814
16460
  const emitGroupDelete = exports.useChatStore((s) => s.emitGroupDelete);
15815
16461
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
15816
- const scrollContainerRef = React2.useRef(null);
15817
- const bottomRef = React2.useRef(null);
15818
- const messageListInitializedRef = React2.useRef(false);
15819
- const previousMessageCountRef = React2.useRef(0);
15820
- const previousLastMessageIdRef = React2.useRef(null);
15821
- const [enteringMessageIds, setEnteringMessageIds] = React2.useState(/* @__PURE__ */ new Set());
15822
- const [showScrollToBottom, setShowScrollToBottom] = React2.useState(false);
15823
- const stickToBottomRef = React2.useRef(true);
15824
- const pendingJumpRef = React2.useRef(null);
15825
- const jumpPagesRef = React2.useRef(0);
15826
- const [jumpNonce, setJumpNonce] = React2.useState(0);
16462
+ const { settings } = useEffectiveSettingsOptional();
16463
+ const messageTimers = settings.messageTimers;
16464
+ const scrollContainerRef = React3.useRef(null);
16465
+ const bottomRef = React3.useRef(null);
16466
+ const messageListInitializedRef = React3.useRef(false);
16467
+ const previousMessageCountRef = React3.useRef(0);
16468
+ const previousLastMessageIdRef = React3.useRef(null);
16469
+ const [enteringMessageIds, setEnteringMessageIds] = React3.useState(/* @__PURE__ */ new Set());
16470
+ const [showScrollToBottom, setShowScrollToBottom] = React3.useState(false);
16471
+ const stickToBottomRef = React3.useRef(true);
16472
+ const pendingJumpRef = React3.useRef(null);
16473
+ const jumpPagesRef = React3.useRef(0);
16474
+ const [jumpNonce, setJumpNonce] = React3.useState(0);
15827
16475
  const { fetchMessages: fetchMessages2, messagesMetadata, deletedForMeIds, searchedMessageId, isSearchActive, setSearchActive, setSearchedMessageId, fetchSearchMessageContext } = exports.useChatStore();
15828
16476
  const loggedUserImage = exports.useChatStore((s) => s.loggedUserDetails?.image);
15829
16477
  const conversations = exports.useChatStore((s) => s.conversations);
@@ -15832,29 +16480,29 @@ function useActiveChannelMessages({
15832
16480
  const mappedApiMessages = conversationMessages.map(
15833
16481
  (m) => mapApiMessage(m, activeChannel, conversationId, loggedUserId, activeConversation)
15834
16482
  );
15835
- const displayMessages = React2.useMemo(
16483
+ const displayMessages = React3.useMemo(
15836
16484
  () => mergeChatMessages(mappedApiMessages, localMessages, loggedUserId).filter((m) => !deletedForMeIds.includes(String(m._id || m.id)) && !m.isDeletedForMe).filter((m) => !conversationId || String(m.conversationId || "") === String(conversationId)).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()),
15837
16485
  [mappedApiMessages, localMessages, loggedUserId, deletedForMeIds, conversationId]
15838
16486
  );
15839
- const messagesById = React2.useMemo(
16487
+ const messagesById = React3.useMemo(
15840
16488
  () => new Map(displayMessages.map((message) => [String(message._id || message.id), message])),
15841
16489
  [displayMessages]
15842
16490
  );
15843
- const messagesWithResolvedReplies = React2.useMemo(
16491
+ const messagesWithResolvedReplies = React3.useMemo(
15844
16492
  () => displayMessages.map((message) => ({
15845
16493
  ...message,
15846
16494
  replyTo: resolveReplyToMessage(message.replyTo, messagesById, loggedUserId)
15847
16495
  })),
15848
16496
  [displayMessages, messagesById, loggedUserId]
15849
16497
  );
15850
- const jumpDateBounds = React2.useMemo(
16498
+ const jumpDateBounds = React3.useMemo(
15851
16499
  () => resolveJumpDateBounds(
15852
16500
  conversationInfo?.createdAt,
15853
16501
  displayMessages[0]?.timestamp
15854
16502
  ),
15855
16503
  [conversationInfo?.createdAt, displayMessages]
15856
16504
  );
15857
- React2.useEffect(() => {
16505
+ React3.useEffect(() => {
15858
16506
  const messages = messagesWithResolvedReplies;
15859
16507
  const lastMessage = messages[messages.length - 1];
15860
16508
  const lastMessageId = lastMessage ? String(lastMessage._id || lastMessage.id) : null;
@@ -15881,11 +16529,11 @@ function useActiveChannelMessages({
15881
16529
  }, [messagesWithResolvedReplies]);
15882
16530
  const isInitialLoading = isLoading && currentMeta?.isFirstPage;
15883
16531
  const isLoadingMore = isLoading && !currentMeta?.isFirstPage;
15884
- const clearSelection = React2.useCallback(() => {
16532
+ const clearSelection = React3.useCallback(() => {
15885
16533
  setSelectionMode(false);
15886
16534
  setSelectedKeys(/* @__PURE__ */ new Set());
15887
16535
  }, []);
15888
- React2.useEffect(() => {
16536
+ React3.useEffect(() => {
15889
16537
  clearSelection();
15890
16538
  messageListInitializedRef.current = false;
15891
16539
  previousMessageCountRef.current = 0;
@@ -15896,14 +16544,14 @@ function useActiveChannelMessages({
15896
16544
  stickToBottomRef.current = true;
15897
16545
  setShowScrollToBottom(false);
15898
16546
  }, [conversationId, clearSelection]);
15899
- const onSelectionModeChangeRef = React2.useRef(onSelectionModeChange);
15900
- React2.useEffect(() => {
16547
+ const onSelectionModeChangeRef = React3.useRef(onSelectionModeChange);
16548
+ React3.useEffect(() => {
15901
16549
  onSelectionModeChangeRef.current = onSelectionModeChange;
15902
16550
  }, [onSelectionModeChange]);
15903
- React2.useEffect(() => {
16551
+ React3.useEffect(() => {
15904
16552
  onSelectionModeChangeRef.current?.(selectionMode);
15905
16553
  }, [selectionMode]);
15906
- const toggleSelection = React2.useCallback((message, attachmentId) => {
16554
+ const toggleSelection = React3.useCallback((message, attachmentId) => {
15907
16555
  const messageId2 = String(message._id || message.id);
15908
16556
  if (!messageId2) return;
15909
16557
  const useAttachmentSelection = messageUsesAttachmentSelection(message);
@@ -15916,7 +16564,7 @@ function useActiveChannelMessages({
15916
16564
  return next;
15917
16565
  });
15918
16566
  }, []);
15919
- const enterSelectionMode = React2.useCallback((message, attachmentId) => {
16567
+ const enterSelectionMode = React3.useCallback((message, attachmentId) => {
15920
16568
  const messageId2 = String(message._id || message.id);
15921
16569
  if (!messageId2) return;
15922
16570
  setSelectionMode(true);
@@ -15930,17 +16578,23 @@ function useActiveChannelMessages({
15930
16578
  setSelectedKeys((prev) => new Set(prev).add(messageId2));
15931
16579
  }, []);
15932
16580
  const selectedForwardItems = resolveForwardItems(selectedKeys, displayMessages);
15933
- const canDeleteForEveryone = React2.useMemo(
15934
- () => canDeleteSelectionForEveryone(selectedKeys, displayMessages, !!activeChannel?.isGroup),
15935
- [selectedKeys, displayMessages, activeChannel?.isGroup]
16581
+ const canDeleteForEveryone = React3.useMemo(
16582
+ () => canDeleteSelectionForEveryone(
16583
+ selectedKeys,
16584
+ displayMessages,
16585
+ !!activeChannel?.isGroup,
16586
+ messageTimers
16587
+ ),
16588
+ [selectedKeys, displayMessages, activeChannel?.isGroup, messageTimers]
15936
16589
  );
15937
- const emitSelectionDelete = React2.useCallback(
16590
+ const emitSelectionDelete = React3.useCallback(
15938
16591
  (deleteType) => {
15939
16592
  if (!loggedUserDetails || selectedKeys.size === 0) return;
15940
16593
  if (deleteType === "everyone" && !canDeleteSelectionForEveryone(
15941
16594
  selectedKeys,
15942
16595
  displayMessages,
15943
- !!activeChannel?.isGroup
16596
+ !!activeChannel?.isGroup,
16597
+ messageTimers
15944
16598
  )) {
15945
16599
  return;
15946
16600
  }
@@ -15981,13 +16635,14 @@ function useActiveChannelMessages({
15981
16635
  conversationId,
15982
16636
  emitGroupDelete,
15983
16637
  emitDmDelete,
15984
- clearSelection
16638
+ clearSelection,
16639
+ messageTimers
15985
16640
  ]
15986
16641
  );
15987
- const handleBatchDeleteForMe = React2.useCallback(() => {
16642
+ const handleBatchDeleteForMe = React3.useCallback(() => {
15988
16643
  emitSelectionDelete("me");
15989
16644
  }, [emitSelectionDelete]);
15990
- const handleBatchDeleteForEveryone = React2.useCallback(() => {
16645
+ const handleBatchDeleteForEveryone = React3.useCallback(() => {
15991
16646
  emitSelectionDelete("everyone");
15992
16647
  }, [emitSelectionDelete]);
15993
16648
  const handleBatchForward = () => {
@@ -16011,7 +16666,7 @@ function useActiveChannelMessages({
16011
16666
  }
16012
16667
  await fetchSearchMessageContext(messageId2, { searchMode: false });
16013
16668
  };
16014
- const handleJumpTo = React2.useCallback((preset, specificDate) => {
16669
+ const handleJumpTo = React3.useCallback((preset, specificDate) => {
16015
16670
  stickToBottomRef.current = false;
16016
16671
  if (preset === "beginning") {
16017
16672
  pendingJumpRef.current = { kind: "beginning" };
@@ -16032,7 +16687,7 @@ function useActiveChannelMessages({
16032
16687
  jumpPagesRef.current = 0;
16033
16688
  setJumpNonce((n) => n + 1);
16034
16689
  }, [jumpDateBounds.minDay, jumpDateBounds.maxDay]);
16035
- React2.useEffect(() => {
16690
+ React3.useEffect(() => {
16036
16691
  const pending = pendingJumpRef.current;
16037
16692
  if (!pending || isLoading) return;
16038
16693
  const step = resolveJumpStep(
@@ -16071,13 +16726,13 @@ function useActiveChannelMessages({
16071
16726
  fetchMessages2,
16072
16727
  setSearchedMessageId
16073
16728
  ]);
16074
- const updateScrollToBottomVisibility = React2.useCallback((el) => {
16729
+ const updateScrollToBottomVisibility = React3.useCallback((el) => {
16075
16730
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
16076
16731
  const nearBottom = distanceFromBottom <= 96;
16077
16732
  stickToBottomRef.current = nearBottom;
16078
16733
  setShowScrollToBottom(!nearBottom);
16079
16734
  }, []);
16080
- const handleScrollToBottom = React2.useCallback(() => {
16735
+ const handleScrollToBottom = React3.useCallback(() => {
16081
16736
  stickToBottomRef.current = true;
16082
16737
  if (isSearchActive) {
16083
16738
  setSearchActive(false);
@@ -16104,12 +16759,12 @@ function useActiveChannelMessages({
16104
16759
  });
16105
16760
  }
16106
16761
  };
16107
- React2.useLayoutEffect(() => {
16762
+ React3.useLayoutEffect(() => {
16108
16763
  if (!isInitialLoading && stickToBottomRef.current && !pendingJumpRef.current) {
16109
16764
  bottomRef.current?.scrollIntoView({ behavior: "smooth" });
16110
16765
  }
16111
16766
  }, [messagesWithResolvedReplies.length, isInitialLoading]);
16112
- React2.useEffect(() => {
16767
+ React3.useEffect(() => {
16113
16768
  if (searchedMessageId && !isLoading) {
16114
16769
  stickToBottomRef.current = false;
16115
16770
  const container = scrollContainerRef.current;
@@ -16319,8 +16974,8 @@ function CalendarDayButton({
16319
16974
  ...props
16320
16975
  }) {
16321
16976
  const defaultClassNames = reactDayPicker.getDefaultClassNames();
16322
- const ref = React2__namespace.useRef(null);
16323
- React2__namespace.useEffect(() => {
16977
+ const ref = React3__namespace.useRef(null);
16978
+ React3__namespace.useEffect(() => {
16324
16979
  if (modifiers.focused) ref.current?.focus();
16325
16980
  }, [modifiers.focused]);
16326
16981
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -16352,12 +17007,12 @@ var PRESETS = [
16352
17007
  ];
16353
17008
  function ChatDateJumpMenu({ label, minDay, maxDay, onJump }) {
16354
17009
  const { scopeClassName } = useChatTheme();
16355
- const [open, setOpen] = React2.useState(false);
16356
- const [showCalendar, setShowCalendar] = React2.useState(false);
16357
- const [calendarMonth, setCalendarMonth] = React2.useState(() => new Date(maxDay));
17010
+ const [open, setOpen] = React3.useState(false);
17011
+ const [showCalendar, setShowCalendar] = React3.useState(false);
17012
+ const [calendarMonth, setCalendarMonth] = React3.useState(() => new Date(maxDay));
16358
17013
  const minDate = new Date(minDay);
16359
17014
  const maxDate = new Date(maxDay);
16360
- React2.useEffect(() => {
17015
+ React3.useEffect(() => {
16361
17016
  if (showCalendar) setCalendarMonth(new Date(maxDay));
16362
17017
  }, [showCalendar, maxDay]);
16363
17018
  const queueJump = (preset, specificDate) => {
@@ -16691,8 +17346,8 @@ function AttachmentCaptionModal({
16691
17346
  attachment,
16692
17347
  onSave
16693
17348
  }) {
16694
- const [draft, setDraft] = React2.useState("");
16695
- React2.useEffect(() => {
17349
+ const [draft, setDraft] = React3.useState("");
17350
+ React3.useEffect(() => {
16696
17351
  if (open && attachment) {
16697
17352
  setDraft(attachment.caption || "");
16698
17353
  }
@@ -17069,9 +17724,9 @@ function ComposerAttachmentPreview({
17069
17724
  embedded = false,
17070
17725
  maxAttachments = COMPOSER_MAX_ATTACHMENTS
17071
17726
  }) {
17072
- const [captionTargetId, setCaptionTargetId] = React2.useState(null);
17073
- const [lightboxState, setLightboxState] = React2.useState(null);
17074
- const { media, files, imageMedia, lightboxImages } = React2.useMemo(() => {
17727
+ const [captionTargetId, setCaptionTargetId] = React3.useState(null);
17728
+ const [lightboxState, setLightboxState] = React3.useState(null);
17729
+ const { media, files, imageMedia, lightboxImages } = React3.useMemo(() => {
17075
17730
  const mediaItems = attachments.filter(
17076
17731
  (item) => (item.type === "image" || item.type === "video") && item.previewUrl
17077
17732
  );
@@ -17224,7 +17879,6 @@ var getAttachmentType = (file) => {
17224
17879
  };
17225
17880
 
17226
17881
  // src/chat/channel-message-box/useChannelMessageBox.ts
17227
- var MAX_ATTACHMENTS = COMPOSER_MAX_ATTACHMENTS;
17228
17882
  function useChannelMessageBox({
17229
17883
  activeChannel,
17230
17884
  conversationId,
@@ -17238,7 +17892,13 @@ function useChannelMessageBox({
17238
17892
  conversationId,
17239
17893
  onActiveChannelPatch
17240
17894
  );
17241
- const [state, setState] = React2.useState({
17895
+ const { settings, getAttachmentsGate } = useEffectiveSettingsOptional();
17896
+ const attachmentsGate = getAttachmentsGate();
17897
+ const maxAttachments = settings.attachments.maxAttachmentsPerMessage;
17898
+ const maxFileSizeBytes = settings.attachments.maxFileSizeMB * 1024 * 1024;
17899
+ const allowedMimeTypes = settings.attachments.allowedMimeTypes;
17900
+ const acceptAttribute = buildAcceptFromMimeTypes(allowedMimeTypes);
17901
+ const [state, setState] = React3.useState({
17242
17902
  messageInput: "",
17243
17903
  attachments: [],
17244
17904
  isFocused: false,
@@ -17248,8 +17908,8 @@ function useChannelMessageBox({
17248
17908
  const updateState = (updates) => {
17249
17909
  setState((prev) => ({ ...prev, ...updates }));
17250
17910
  };
17251
- const typingTimeoutRef = React2.useRef(null);
17252
- const conversationIdRef = React2.useRef(conversationId);
17911
+ const typingTimeoutRef = React3.useRef(null);
17912
+ const conversationIdRef = React3.useRef(conversationId);
17253
17913
  conversationIdRef.current = conversationId;
17254
17914
  const { syncComposerRef } = useConversationDraft(conversationId);
17255
17915
  const emitTypingStart = exports.useChatStore((s) => s.emitTypingStart);
@@ -17260,12 +17920,12 @@ function useChannelMessageBox({
17260
17920
  const allUsers = exports.useChatStore((s) => s.allUsers);
17261
17921
  const composerDisabledByConversation = exports.useChatStore((s) => s.composerDisabledByConversation);
17262
17922
  const composerDisabledReason = conversationId ? composerDisabledByConversation[conversationId] : void 0;
17263
- const fileInputRef = React2.useRef(null);
17264
- const attachmentsCountRef = React2.useRef(0);
17265
- React2__namespace.default.useEffect(() => {
17923
+ const fileInputRef = React3.useRef(null);
17924
+ const attachmentsCountRef = React3.useRef(0);
17925
+ React3__namespace.default.useEffect(() => {
17266
17926
  attachmentsCountRef.current = state.attachments.length;
17267
17927
  }, [state.attachments.length]);
17268
- React2__namespace.default.useEffect(() => {
17928
+ React3__namespace.default.useEffect(() => {
17269
17929
  const draft = {
17270
17930
  messageInput: state.messageInput,
17271
17931
  attachments: state.attachments
@@ -17273,7 +17933,7 @@ function useChannelMessageBox({
17273
17933
  syncComposerRef(draft);
17274
17934
  updateConversationDraft(conversationIdRef.current, draft);
17275
17935
  }, [state.messageInput, state.attachments, syncComposerRef]);
17276
- React2__namespace.default.useEffect(() => {
17936
+ React3__namespace.default.useEffect(() => {
17277
17937
  if (typingTimeoutRef.current) {
17278
17938
  clearTimeout(typingTimeoutRef.current);
17279
17939
  typingTimeoutRef.current = null;
@@ -17333,21 +17993,52 @@ function useChannelMessageBox({
17333
17993
  const handleFileChange = (e) => {
17334
17994
  const files = e.target.files ? Array.from(e.target.files) : [];
17335
17995
  if (!files.length) return;
17996
+ if (!attachmentsGate.enabled) {
17997
+ updateState({
17998
+ attachmentError: attachmentsGate.disabledReason || "Attachments are disabled by your administrator."
17999
+ });
18000
+ if (fileInputRef.current) fileInputRef.current.value = "";
18001
+ return;
18002
+ }
17336
18003
  const currentCount = attachmentsCountRef.current;
17337
- const remaining = MAX_ATTACHMENTS - currentCount;
18004
+ const remaining = maxAttachments - currentCount;
17338
18005
  if (remaining <= 0) {
17339
- updateState({ attachmentError: `Maximum ${MAX_ATTACHMENTS} files allowed` });
18006
+ updateState({ attachmentError: `Maximum ${maxAttachments} files allowed` });
18007
+ if (fileInputRef.current) fileInputRef.current.value = "";
18008
+ return;
18009
+ }
18010
+ const validFiles = [];
18011
+ const rejectionMessages = [];
18012
+ for (const file of files) {
18013
+ if (!matchesAllowedMimeType(file.type, allowedMimeTypes)) {
18014
+ rejectionMessages.push(`"${file.name}" type is not allowed`);
18015
+ continue;
18016
+ }
18017
+ if (file.size > maxFileSizeBytes) {
18018
+ rejectionMessages.push(
18019
+ `"${file.name}" exceeds ${settings.attachments.maxFileSizeMB} MB (got ${formatFileSizeMB(file.size)})`
18020
+ );
18021
+ continue;
18022
+ }
18023
+ validFiles.push(file);
18024
+ }
18025
+ if (validFiles.length === 0) {
18026
+ updateState({
18027
+ attachmentError: rejectionMessages[0] || "Selected files are not allowed"
18028
+ });
17340
18029
  if (fileInputRef.current) fileInputRef.current.value = "";
17341
18030
  return;
17342
18031
  }
17343
- if (files.length > remaining) {
18032
+ if (validFiles.length > remaining) {
17344
18033
  updateState({
17345
18034
  attachmentError: `You can upload only ${remaining} more file${remaining > 1 ? "s" : ""}`
17346
18035
  });
18036
+ } else if (rejectionMessages.length > 0) {
18037
+ updateState({ attachmentError: rejectionMessages[0] });
17347
18038
  } else {
17348
18039
  updateState({ attachmentError: "" });
17349
18040
  }
17350
- const filesToAdd = files.slice(0, remaining);
18041
+ const filesToAdd = validFiles.slice(0, remaining);
17351
18042
  attachmentsCountRef.current = currentCount + filesToAdd.length;
17352
18043
  const newAttachments = filesToAdd.map((file) => ({
17353
18044
  id: `att-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
@@ -17404,7 +18095,7 @@ function useChannelMessageBox({
17404
18095
  return {
17405
18096
  ...prev,
17406
18097
  attachments: nextAttachments,
17407
- attachmentError: nextAttachments.length < MAX_ATTACHMENTS ? "" : prev.attachmentError
18098
+ attachmentError: nextAttachments.length < maxAttachments ? "" : prev.attachmentError
17408
18099
  };
17409
18100
  });
17410
18101
  };
@@ -17511,7 +18202,10 @@ function useChannelMessageBox({
17511
18202
  onlyAdminCanSendMessage,
17512
18203
  fileInputRef,
17513
18204
  hasUploadingAttachments,
17514
- maxAttachments: MAX_ATTACHMENTS,
18205
+ maxAttachments,
18206
+ acceptAttribute,
18207
+ attachmentsEnabled: attachmentsGate.enabled,
18208
+ attachmentsDisabledReason: attachmentsGate.disabledReason,
17515
18209
  handleInputChange,
17516
18210
  handleFileChange,
17517
18211
  handleRemoveAttachment,
@@ -17539,6 +18233,9 @@ var ChannelMessageBoxView = (props) => {
17539
18233
  fileInputRef,
17540
18234
  hasUploadingAttachments,
17541
18235
  maxAttachments,
18236
+ acceptAttribute,
18237
+ attachmentsEnabled,
18238
+ attachmentsDisabledReason,
17542
18239
  handleInputChange,
17543
18240
  handleFileChange,
17544
18241
  handleRemoveAttachment,
@@ -17636,16 +18333,23 @@ var ChannelMessageBoxView = (props) => {
17636
18333
  ),
17637
18334
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-1.5 p-1.5", children: [
17638
18335
  /* @__PURE__ */ jsxRuntime.jsx(
17639
- Button,
18336
+ AdminPermissionTooltip,
17640
18337
  {
17641
- type: "button",
17642
- variant: "ghost",
17643
- size: "icon",
17644
- className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text)",
17645
- onClick: () => !hasUploadingAttachments && fileInputRef.current?.click(),
17646
- disabled: hasUploadingAttachments,
17647
- "aria-label": "Attach files",
17648
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { size: 16 })
18338
+ disabled: !attachmentsEnabled,
18339
+ message: attachmentsDisabledReason,
18340
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18341
+ Button,
18342
+ {
18343
+ type: "button",
18344
+ variant: "ghost",
18345
+ size: "icon",
18346
+ className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text) disabled:opacity-40",
18347
+ onClick: () => attachmentsEnabled && !hasUploadingAttachments && fileInputRef.current?.click(),
18348
+ disabled: !attachmentsEnabled || hasUploadingAttachments,
18349
+ "aria-label": "Attach files",
18350
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { size: 16 })
18351
+ }
18352
+ )
17649
18353
  }
17650
18354
  ),
17651
18355
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -17655,8 +18359,9 @@ var ChannelMessageBoxView = (props) => {
17655
18359
  ref: fileInputRef,
17656
18360
  className: "hidden",
17657
18361
  multiple: true,
17658
- accept: "image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx",
17659
- onChange: handleFileChange
18362
+ accept: acceptAttribute || void 0,
18363
+ onChange: handleFileChange,
18364
+ disabled: !attachmentsEnabled
17660
18365
  }
17661
18366
  ),
17662
18367
  /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
@@ -17735,11 +18440,11 @@ var ChannelMessageBoxView_default = ChannelMessageBoxView;
17735
18440
  // src/chat/forward-modal/useForwardModal.ts
17736
18441
  init_useStore();
17737
18442
  function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17738
- const [search, setSearch] = React2.useState("");
17739
- const [debouncedSearch, setDebouncedSearch] = React2.useState("");
17740
- const targetChannelRef = React2.useRef(null);
17741
- const wasForwardingRef = React2.useRef(false);
17742
- const completionHandledRef = React2.useRef(false);
18443
+ const [search, setSearch] = React3.useState("");
18444
+ const [debouncedSearch, setDebouncedSearch] = React3.useState("");
18445
+ const targetChannelRef = React3.useRef(null);
18446
+ const wasForwardingRef = React3.useRef(false);
18447
+ const completionHandledRef = React3.useRef(false);
17743
18448
  const emitDmForward = exports.useChatStore((s) => s.emitDmForward);
17744
18449
  const emitGroupForward = exports.useChatStore((s) => s.emitGroupForward);
17745
18450
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
@@ -17747,23 +18452,23 @@ function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17747
18452
  const storeConversations = exports.useChatStore((s) => s.conversations);
17748
18453
  const allUsers = exports.useChatStore((s) => s.allUsers);
17749
18454
  const sourceMessage = items[0]?.message ?? null;
17750
- const mids = React2.useMemo(
18455
+ const mids = React3.useMemo(
17751
18456
  () => items.map((item) => String(item.message._id || item.message.id || "")).filter(Boolean),
17752
18457
  [items]
17753
18458
  );
17754
18459
  const isForwarding = mids.some((id) => pendingActions[id]?.includes("forward"));
17755
- const onCloseRef = React2.useRef(onClose);
17756
- const onForwardCompleteRef = React2.useRef(onForwardComplete);
17757
- React2.useEffect(() => {
18460
+ const onCloseRef = React3.useRef(onClose);
18461
+ const onForwardCompleteRef = React3.useRef(onForwardComplete);
18462
+ React3.useEffect(() => {
17758
18463
  onCloseRef.current = onClose;
17759
18464
  }, [onClose]);
17760
- React2.useEffect(() => {
18465
+ React3.useEffect(() => {
17761
18466
  onForwardCompleteRef.current = onForwardComplete;
17762
18467
  }, [onForwardComplete]);
17763
- const handleOpenChange = React2.useCallback((open) => {
18468
+ const handleOpenChange = React3.useCallback((open) => {
17764
18469
  if (!open) onCloseRef.current();
17765
18470
  }, []);
17766
- React2.useEffect(() => {
18471
+ React3.useEffect(() => {
17767
18472
  if (isForwarding) {
17768
18473
  wasForwardingRef.current = true;
17769
18474
  const timeout = setTimeout(() => {
@@ -17781,7 +18486,7 @@ function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17781
18486
  queueMicrotask(() => onForwardCompleteRef.current(target));
17782
18487
  }
17783
18488
  }, [isForwarding, mids]);
17784
- React2.useEffect(() => {
18489
+ React3.useEffect(() => {
17785
18490
  if (!isOpen) {
17786
18491
  setSearch("");
17787
18492
  setDebouncedSearch("");
@@ -17790,11 +18495,11 @@ function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17790
18495
  completionHandledRef.current = false;
17791
18496
  }
17792
18497
  }, [isOpen]);
17793
- React2.useEffect(() => {
18498
+ React3.useEffect(() => {
17794
18499
  const handler = setTimeout(() => setDebouncedSearch(search), 400);
17795
18500
  return () => clearTimeout(handler);
17796
18501
  }, [search]);
17797
- const mappedChannels = React2.useMemo(() => {
18502
+ const mappedChannels = React3.useMemo(() => {
17798
18503
  const sourceConversationId = sourceMessage?.conversationId ? String(sourceMessage.conversationId) : null;
17799
18504
  const query = debouncedSearch.trim().toLowerCase();
17800
18505
  return storeConversations.filter((c) => !sourceConversationId || String(c._id) !== sourceConversationId).map((c) => {
@@ -17922,7 +18627,7 @@ function ForwardModalView(props) {
17922
18627
 
17923
18628
  // src/chat/ChatPanelAlerts.tsx
17924
18629
  init_useStore();
17925
- var ChatAlertsContext = React2.createContext({});
18630
+ var ChatAlertsContext = React3.createContext({});
17926
18631
  function ChatAlertsProvider({
17927
18632
  children,
17928
18633
  value
@@ -17930,7 +18635,7 @@ function ChatAlertsProvider({
17930
18635
  return /* @__PURE__ */ jsxRuntime.jsx(ChatAlertsContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 h-full w-full flex-1 flex-col overflow-hidden", children }) });
17931
18636
  }
17932
18637
  function useChatAlerts() {
17933
- return React2.useContext(ChatAlertsContext);
18638
+ return React3.useContext(ChatAlertsContext);
17934
18639
  }
17935
18640
  var normalizeMessage = (message) => message.trim().toLowerCase();
17936
18641
  var alertStyles = {
@@ -17944,15 +18649,16 @@ var ChatPanelAlerts = () => {
17944
18649
  const globalAppError = exports.useChatStore((s) => s.globalAppError);
17945
18650
  const conversationErrors = exports.useChatStore((s) => s.conversationErrors);
17946
18651
  const clearAppError = exports.useChatStore((s) => s.clearAppError);
18652
+ const setRateLimitResetAt = exports.useChatStore((s) => s.setRateLimitResetAt);
17947
18653
  const rateLimitResetAt2 = exports.useChatStore((s) => s.rateLimitResetAt);
17948
- const [now, setNow] = React2.useState(() => Date.now());
18654
+ const [now, setNow] = React3.useState(() => Date.now());
17949
18655
  const conversationAppError = activeConversationId ? conversationErrors[activeConversationId] ?? null : null;
17950
- React2.useEffect(() => {
18656
+ React3.useEffect(() => {
17951
18657
  if (rateLimitResetAt2 <= Date.now()) return;
17952
18658
  const id = window.setInterval(() => setNow(Date.now()), 1e3);
17953
18659
  return () => window.clearInterval(id);
17954
18660
  }, [rateLimitResetAt2]);
17955
- const alerts = React2.useMemo(() => {
18661
+ const alerts = React3.useMemo(() => {
17956
18662
  const items = [];
17957
18663
  const seen = /* @__PURE__ */ new Set();
17958
18664
  const push = (item) => {
@@ -17980,7 +18686,7 @@ var ChatPanelAlerts = () => {
17980
18686
  onDismiss: () => clearAppError(activeConversationId)
17981
18687
  });
17982
18688
  }
17983
- if (globalAppError?.message) {
18689
+ if (!activeConversationId && globalAppError?.message) {
17984
18690
  push({
17985
18691
  id: `global-app-error-${globalAppError.type}`,
17986
18692
  type: "error",
@@ -17995,7 +18701,8 @@ var ChatPanelAlerts = () => {
17995
18701
  id: "rate-limit",
17996
18702
  type: "warning",
17997
18703
  message: `Too many requests. Please wait ${seconds}s before trying again.`,
17998
- icon: lucideReact.AlertCircle
18704
+ icon: lucideReact.AlertCircle,
18705
+ onDismiss: () => setRateLimitResetAt(0)
17999
18706
  });
18000
18707
  }
18001
18708
  if (info?.trim()) {
@@ -18018,7 +18725,8 @@ var ChatPanelAlerts = () => {
18018
18725
  onDismissError,
18019
18726
  onDismissInfo,
18020
18727
  info,
18021
- clearAppError
18728
+ clearAppError,
18729
+ setRateLimitResetAt
18022
18730
  ]);
18023
18731
  if (alerts.length === 0) return null;
18024
18732
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 space-y-2 border-b border-(--chat-border) bg-(--chat-surface) px-4 py-2.5", children: alerts.map((alert) => {
@@ -18034,16 +18742,20 @@ var ChatPanelAlerts = () => {
18034
18742
  children: [
18035
18743
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-3.5 shrink-0", "aria-hidden": true }),
18036
18744
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 leading-relaxed", children: alert.message }),
18037
- alert.onDismiss && /* @__PURE__ */ jsxRuntime.jsx(
18745
+ alert.onDismiss ? /* @__PURE__ */ jsxRuntime.jsx(
18038
18746
  "button",
18039
18747
  {
18040
18748
  type: "button",
18041
- onClick: alert.onDismiss,
18749
+ onClick: (event) => {
18750
+ event.preventDefault();
18751
+ event.stopPropagation();
18752
+ alert.onDismiss?.();
18753
+ },
18042
18754
  className: "shrink-0 rounded-md p-0.5 opacity-70 transition-opacity hover:opacity-100",
18043
18755
  "aria-label": "Dismiss",
18044
18756
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-3.5" })
18045
18757
  }
18046
- )
18758
+ ) : null
18047
18759
  ]
18048
18760
  },
18049
18761
  alert.id
@@ -18376,9 +19088,9 @@ var emitToast = (payload, onPush) => {
18376
19088
  };
18377
19089
  var ForegroundPushBridge = ({ onPush }) => {
18378
19090
  const lastDM = exports.useChatStore((s) => s.lastDM);
18379
- const onPushRef = React2.useRef(onPush);
19091
+ const onPushRef = React3.useRef(onPush);
18380
19092
  onPushRef.current = onPush;
18381
- React2.useEffect(() => {
19093
+ React3.useEffect(() => {
18382
19094
  if (!lastDM) return;
18383
19095
  if (typeof document !== "undefined" && document.visibilityState !== "visible") {
18384
19096
  return;
@@ -18418,7 +19130,7 @@ var ForegroundPushBridge = ({ onPush }) => {
18418
19130
  onPushRef.current
18419
19131
  );
18420
19132
  }, [lastDM]);
18421
- React2.useEffect(() => {
19133
+ React3.useEffect(() => {
18422
19134
  let unsubscribe = null;
18423
19135
  let retryTimer = null;
18424
19136
  let cancelled = false;
@@ -18494,7 +19206,7 @@ var ForegroundPushBridge = ({ onPush }) => {
18494
19206
  window.removeEventListener(PUSH_CHANGED_EVENT, onPushChanged);
18495
19207
  };
18496
19208
  }, []);
18497
- React2.useEffect(() => {
19209
+ React3.useEffect(() => {
18498
19210
  const onOpen = (event) => {
18499
19211
  const id = String(
18500
19212
  event.detail?.conversationId || ""
@@ -18571,8 +19283,8 @@ var ChatMain = ({
18571
19283
  viewportHeight = "full",
18572
19284
  viewportClassName
18573
19285
  }) => {
18574
- const clientObj = React2__namespace.default.useMemo(() => ({ id: clientId }), [clientId]);
18575
- const uiConfigObj = React2__namespace.default.useMemo(
19286
+ const clientObj = React3__namespace.default.useMemo(() => ({ id: clientId }), [clientId]);
19287
+ const uiConfigObj = React3__namespace.default.useMemo(
18576
19288
  () => ({
18577
19289
  ...uiConfig || {},
18578
19290
  components: { ...uiConfig?.components, ...components },
@@ -18943,6 +19655,8 @@ var packageDefaultComponents = {
18943
19655
  };
18944
19656
 
18945
19657
  // src/index.ts
19658
+ init_settings_types();
19659
+ init_api_service();
18946
19660
  var index_default = ChatMain_default;
18947
19661
 
18948
19662
  exports.Calendar = Calendar;
@@ -18956,6 +19670,7 @@ exports.ChatAlertsProvider = ChatAlertsProvider;
18956
19670
  exports.ChatAvatar = ChatAvatar_default;
18957
19671
  exports.ChatCustomizationProvider = ChatCustomizationProvider;
18958
19672
  exports.ChatDateJumpMenu = ChatDateJumpMenu;
19673
+ exports.ChatEffectiveSettingsProvider = ChatEffectiveSettingsProvider;
18959
19674
  exports.ChatLayout = ChatLayout_default;
18960
19675
  exports.ChatLocaleProvider = ChatLocaleProvider;
18961
19676
  exports.ChatMain = ChatMain_default;
@@ -19035,6 +19750,8 @@ exports.useChatFeatures = useChatFeatures;
19035
19750
  exports.useChatFeaturesOptional = useChatFeaturesOptional;
19036
19751
  exports.useChatLocale = useChatLocale;
19037
19752
  exports.useChatTheme = useChatTheme;
19753
+ exports.useEffectiveSettings = useEffectiveSettings;
19754
+ exports.useEffectiveSettingsOptional = useEffectiveSettingsOptional;
19038
19755
  exports.usePushNotifications = usePushNotifications;
19039
19756
  //# sourceMappingURL=index.cjs.map
19040
19757
  //# sourceMappingURL=index.cjs.map