@realtimexsco/live-chat 1.4.11 → 1.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
 
@@ -1190,6 +1190,7 @@ function resolveChatApiKey(url) {
1190
1190
  }
1191
1191
  if (path.includes("/message/search-messages")) return "searchMessages";
1192
1192
  if (path.includes("/message/searched-message/context")) return "searchMessagesContext";
1193
+ if (path.includes("/settings/effective")) return "effectiveSettings";
1193
1194
  if (path.includes("/conversation/group/create")) return "createGroup";
1194
1195
  if (path.includes("/conversation/group/permission/update")) return "updateGroupPermissions";
1195
1196
  if (path.includes("/conversation/group/information/update")) return "updateGroupInfo";
@@ -1233,7 +1234,8 @@ var init_query_params = __esm({
1233
1234
  "pinnedMessages",
1234
1235
  "starredMessages",
1235
1236
  "searchMessages",
1236
- "searchMessagesContext"
1237
+ "searchMessagesContext",
1238
+ "effectiveSettings"
1237
1239
  ];
1238
1240
  exports.CHAT_API_KEYS = [
1239
1241
  "all",
@@ -1258,8 +1260,8 @@ var init_api_version = __esm({
1258
1260
  "src/services/api/api-version.ts"() {
1259
1261
  init_client();
1260
1262
  DEFAULT_VERSIONS = {
1261
- default: "v1",
1262
- push: "v2"
1263
+ v1: "v1",
1264
+ v2: "v2"
1263
1265
  };
1264
1266
  apiVersions = { ...DEFAULT_VERSIONS };
1265
1267
  normalizeApiVersion = (version) => {
@@ -1278,7 +1280,7 @@ var init_api_version = __esm({
1278
1280
  exports.buildVersionedApiUrl = (path, options) => {
1279
1281
  const root = exports.resolveApiRoot(options?.baseUrl);
1280
1282
  const version = normalizeApiVersion(
1281
- options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.default)
1283
+ options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.v1)
1282
1284
  );
1283
1285
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1284
1286
  if (!root) {
@@ -1288,23 +1290,23 @@ var init_api_version = __esm({
1288
1290
  };
1289
1291
  exports.setChatApiVersions = (config) => {
1290
1292
  if (!config) return;
1291
- if (config.default) {
1292
- apiVersions.default = normalizeApiVersion(config.default);
1293
+ if (config.v1) {
1294
+ apiVersions.v1 = normalizeApiVersion(config.v1);
1293
1295
  }
1294
- if (config.push) {
1295
- apiVersions.push = normalizeApiVersion(config.push);
1296
+ if (config.v2) {
1297
+ apiVersions.v2 = normalizeApiVersion(config.v2);
1296
1298
  }
1297
1299
  };
1298
1300
  exports.getChatApiVersions = () => ({
1299
1301
  ...apiVersions
1300
1302
  });
1301
1303
  exports.setChatPushApiVersion = (version) => {
1302
- apiVersions.push = normalizeApiVersion(version);
1304
+ apiVersions.v2 = normalizeApiVersion(version);
1303
1305
  };
1304
1306
  syncDefaultApiVersionFromBaseUrl = (baseUrl) => {
1305
1307
  const match = String(baseUrl || "").trim().match(/\/(v\d+)\/?$/i);
1306
1308
  if (match?.[1]) {
1307
- apiVersions.default = normalizeApiVersion(match[1]);
1309
+ apiVersions.v1 = normalizeApiVersion(match[1]);
1308
1310
  }
1309
1311
  };
1310
1312
  }
@@ -1449,6 +1451,9 @@ var init_api_constants = __esm({
1449
1451
  SEARCH_MESSAGES: "/message/search-messages",
1450
1452
  SEARCH_MESSAGES_CONTEXT: (messageId2) => `/message/searched-message/context?messageId=${messageId2}`,
1451
1453
  BLOCK_UNBLOCK_USER: "/user/block-unblock"
1454
+ },
1455
+ SETTINGS: {
1456
+ PERMISSIONS: "/settings/effective"
1452
1457
  }
1453
1458
  };
1454
1459
  }
@@ -1485,13 +1490,11 @@ var init_auth_api = __esm({
1485
1490
  });
1486
1491
 
1487
1492
  // 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;
1493
+ var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1489
1494
  var init_chat_constants = __esm({
1490
1495
  "src/constants/chat.constants.ts"() {
1491
1496
  DEFAULT_MESSAGES_LIMIT = 20;
1492
1497
  DEFAULT_CONVERSATIONS_LIMIT = 20;
1493
- DM_EDIT_WINDOW_MS = 5 * 60 * 1e3;
1494
- GROUP_EDIT_WINDOW_MS = 10 * 60 * 1e3;
1495
1498
  MAX_ATTACHMENT_CAPTION_LENGTH = 100;
1496
1499
  ATTACHMENT_FILENAME_TIMESTAMP_LENGTH = 13;
1497
1500
  }
@@ -1694,6 +1697,206 @@ var init_uploads_api = __esm({
1694
1697
  }
1695
1698
  });
1696
1699
 
1700
+ // src/types/settings.types.ts
1701
+ exports.DEFAULT_EFFECTIVE_SETTINGS = void 0; exports.ADMIN_PERMISSION_DENIED_MESSAGE = void 0; exports.CALLS_NOT_CONFIGURED_MESSAGE = void 0;
1702
+ var init_settings_types = __esm({
1703
+ "src/types/settings.types.ts"() {
1704
+ exports.DEFAULT_EFFECTIVE_SETTINGS = {
1705
+ pushNotificationEnabled: true,
1706
+ videoCallEnabled: true,
1707
+ audioCallEnabled: true,
1708
+ blockUsersEnabled: true,
1709
+ calls: {
1710
+ enabled: true,
1711
+ configured: true
1712
+ },
1713
+ groupPolicy: {
1714
+ defaults: {
1715
+ onlyAdminCanSendMessage: false,
1716
+ onlyAdminCanEditInfo: false,
1717
+ senderCanEditMessage: true,
1718
+ allowMemberAdd: true,
1719
+ allowMemberRemove: false,
1720
+ moderationEnabled: false
1721
+ },
1722
+ locked: []
1723
+ },
1724
+ messageTimers: {
1725
+ dmEditMinutes: 5,
1726
+ dmDeleteMinutes: 5,
1727
+ groupEditMinutes: 10,
1728
+ groupDeleteMinutes: 10
1729
+ },
1730
+ attachments: {
1731
+ enabled: true,
1732
+ maxFileSizeMB: 10,
1733
+ allowedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
1734
+ maxAttachmentsPerMessage: 10
1735
+ },
1736
+ maxAdminsPerGroup: 5,
1737
+ maxParticipantsPerGroup: 100,
1738
+ maxPinnedMessagesPerConversation: 10,
1739
+ groupCreationEnabled: true
1740
+ };
1741
+ exports.ADMIN_PERMISSION_DENIED_MESSAGE = "This feature is disabled by your administrator.";
1742
+ exports.CALLS_NOT_CONFIGURED_MESSAGE = "Calls are not configured. Please contact your administrator.";
1743
+ }
1744
+ });
1745
+
1746
+ // src/services/api/settings.api.ts
1747
+ function asBoolean(value, fallback) {
1748
+ return typeof value === "boolean" ? value : fallback;
1749
+ }
1750
+ function asPositiveNumber(value, fallback) {
1751
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
1752
+ }
1753
+ function asStringArray(value, fallback) {
1754
+ if (!Array.isArray(value)) return fallback;
1755
+ const next = value.filter((item) => typeof item === "string");
1756
+ return next.length > 0 ? next : fallback;
1757
+ }
1758
+ function normalizeGroupPermissions(value, fallback) {
1759
+ if (!value || typeof value !== "object") return { ...fallback };
1760
+ const raw = value;
1761
+ return {
1762
+ onlyAdminCanSendMessage: asBoolean(
1763
+ raw.onlyAdminCanSendMessage,
1764
+ fallback.onlyAdminCanSendMessage
1765
+ ),
1766
+ onlyAdminCanEditInfo: asBoolean(
1767
+ raw.onlyAdminCanEditInfo,
1768
+ fallback.onlyAdminCanEditInfo
1769
+ ),
1770
+ senderCanEditMessage: asBoolean(
1771
+ raw.senderCanEditMessage,
1772
+ fallback.senderCanEditMessage
1773
+ ),
1774
+ allowMemberAdd: asBoolean(raw.allowMemberAdd, fallback.allowMemberAdd),
1775
+ allowMemberRemove: asBoolean(
1776
+ raw.allowMemberRemove,
1777
+ fallback.allowMemberRemove
1778
+ ),
1779
+ moderationEnabled: asBoolean(
1780
+ raw.moderationEnabled,
1781
+ fallback.moderationEnabled
1782
+ )
1783
+ };
1784
+ }
1785
+ function normalizeCalls(value, fallback) {
1786
+ if (!value || typeof value !== "object") return { ...fallback };
1787
+ const raw = value;
1788
+ return {
1789
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1790
+ configured: asBoolean(raw.configured, fallback.configured)
1791
+ };
1792
+ }
1793
+ function normalizeGroupPolicy(value, fallback) {
1794
+ if (!value || typeof value !== "object") {
1795
+ return {
1796
+ defaults: { ...fallback.defaults },
1797
+ locked: [...fallback.locked]
1798
+ };
1799
+ }
1800
+ const raw = value;
1801
+ return {
1802
+ defaults: normalizeGroupPermissions(raw.defaults, fallback.defaults),
1803
+ locked: asStringArray(raw.locked, fallback.locked)
1804
+ };
1805
+ }
1806
+ function normalizeMessageTimers(value, fallback) {
1807
+ if (!value || typeof value !== "object") return { ...fallback };
1808
+ const raw = value;
1809
+ return {
1810
+ dmEditMinutes: asPositiveNumber(raw.dmEditMinutes, fallback.dmEditMinutes),
1811
+ dmDeleteMinutes: asPositiveNumber(
1812
+ raw.dmDeleteMinutes,
1813
+ fallback.dmDeleteMinutes
1814
+ ),
1815
+ groupEditMinutes: asPositiveNumber(
1816
+ raw.groupEditMinutes,
1817
+ fallback.groupEditMinutes
1818
+ ),
1819
+ groupDeleteMinutes: asPositiveNumber(
1820
+ raw.groupDeleteMinutes,
1821
+ fallback.groupDeleteMinutes
1822
+ )
1823
+ };
1824
+ }
1825
+ function normalizeAttachments(value, fallback) {
1826
+ if (!value || typeof value !== "object") return { ...fallback };
1827
+ const raw = value;
1828
+ return {
1829
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1830
+ maxFileSizeMB: asPositiveNumber(raw.maxFileSizeMB, fallback.maxFileSizeMB),
1831
+ allowedMimeTypes: asStringArray(
1832
+ raw.allowedMimeTypes,
1833
+ fallback.allowedMimeTypes
1834
+ ),
1835
+ maxAttachmentsPerMessage: asPositiveNumber(
1836
+ raw.maxAttachmentsPerMessage,
1837
+ fallback.maxAttachmentsPerMessage
1838
+ )
1839
+ };
1840
+ }
1841
+ function normalizeEffectiveSettings(raw) {
1842
+ const payload = raw && typeof raw === "object" ? raw.data ?? raw : null;
1843
+ const source = payload && typeof payload === "object" ? payload : {};
1844
+ const defaults = exports.DEFAULT_EFFECTIVE_SETTINGS;
1845
+ return {
1846
+ pushNotificationEnabled: asBoolean(
1847
+ source.pushNotificationEnabled,
1848
+ defaults.pushNotificationEnabled
1849
+ ),
1850
+ videoCallEnabled: asBoolean(
1851
+ source.videoCallEnabled,
1852
+ defaults.videoCallEnabled
1853
+ ),
1854
+ audioCallEnabled: asBoolean(
1855
+ source.audioCallEnabled,
1856
+ defaults.audioCallEnabled
1857
+ ),
1858
+ blockUsersEnabled: asBoolean(
1859
+ source.blockUsersEnabled,
1860
+ defaults.blockUsersEnabled
1861
+ ),
1862
+ calls: normalizeCalls(source.calls, defaults.calls),
1863
+ groupPolicy: normalizeGroupPolicy(source.groupPolicy, defaults.groupPolicy),
1864
+ messageTimers: normalizeMessageTimers(
1865
+ source.messageTimers,
1866
+ defaults.messageTimers
1867
+ ),
1868
+ attachments: normalizeAttachments(source.attachments, defaults.attachments),
1869
+ maxAdminsPerGroup: asPositiveNumber(
1870
+ source.maxAdminsPerGroup,
1871
+ defaults.maxAdminsPerGroup
1872
+ ),
1873
+ maxParticipantsPerGroup: asPositiveNumber(
1874
+ source.maxParticipantsPerGroup,
1875
+ defaults.maxParticipantsPerGroup
1876
+ ),
1877
+ maxPinnedMessagesPerConversation: asPositiveNumber(
1878
+ source.maxPinnedMessagesPerConversation,
1879
+ defaults.maxPinnedMessagesPerConversation
1880
+ ),
1881
+ groupCreationEnabled: asBoolean(
1882
+ source.groupCreationEnabled,
1883
+ defaults.groupCreationEnabled
1884
+ )
1885
+ };
1886
+ }
1887
+ exports.fetchEffectiveSettings = void 0;
1888
+ var init_settings_api = __esm({
1889
+ "src/services/api/settings.api.ts"() {
1890
+ init_api_constants();
1891
+ init_client();
1892
+ init_settings_types();
1893
+ exports.fetchEffectiveSettings = async () => {
1894
+ const response = await apiClient.get(API_ENDPOINTS.SETTINGS.PERMISSIONS);
1895
+ return normalizeEffectiveSettings(response.data);
1896
+ };
1897
+ }
1898
+ });
1899
+
1697
1900
  // src/services/api/index.ts
1698
1901
  var init_api = __esm({
1699
1902
  "src/services/api/index.ts"() {
@@ -1702,6 +1905,7 @@ var init_api = __esm({
1702
1905
  init_messages_api();
1703
1906
  init_groups_api();
1704
1907
  init_uploads_api();
1908
+ init_settings_api();
1705
1909
  }
1706
1910
  });
1707
1911
 
@@ -4385,7 +4589,7 @@ var syncLocalMessagesOnDmMarkRead = (prev, lastDMMarkRead, conversations, logged
4385
4589
 
4386
4590
  // src/hooks/chat-sync/useChatSync.ts
4387
4591
  var useChatSync = () => {
4388
- const [localMessages, setLocalMessages] = React2.useState({});
4592
+ const [localMessages, setLocalMessages] = React3.useState({});
4389
4593
  const lastDM = useSocketStore((s) => s.lastDM);
4390
4594
  const lastEditDM = useSocketStore((s) => s.lastEditDM);
4391
4595
  const lastDeleteDM = useSocketStore((s) => s.lastDeleteDM);
@@ -4398,17 +4602,17 @@ var useChatSync = () => {
4398
4602
  const messagesByConversation = useSocketStore((s) => s.messagesByConversation);
4399
4603
  const messagesMetadata = useSocketStore((s) => s.messagesMetadata);
4400
4604
  const loggedUserDetails = useAuthStore((s) => s.loggedUserDetails);
4401
- React2.useEffect(() => {
4605
+ React3.useEffect(() => {
4402
4606
  setLocalMessages(
4403
4607
  (prev) => syncLocalMessagesOnStoreClear(prev, messagesByConversation, messagesMetadata)
4404
4608
  );
4405
4609
  }, [messagesByConversation, messagesMetadata]);
4406
- React2.useEffect(() => {
4610
+ React3.useEffect(() => {
4407
4611
  if (lastPinUpdate) {
4408
4612
  setLocalMessages((prev) => syncLocalMessagesOnPinUpdate(prev, lastPinUpdate));
4409
4613
  }
4410
4614
  }, [lastPinUpdate]);
4411
- React2.useEffect(() => {
4615
+ React3.useEffect(() => {
4412
4616
  if (lastStarUpdate) {
4413
4617
  setLocalMessages((prev) => {
4414
4618
  const next = syncLocalMessagesOnStarUpdate(
@@ -4420,18 +4624,18 @@ var useChatSync = () => {
4420
4624
  });
4421
4625
  }
4422
4626
  }, [lastStarUpdate, loggedUserDetails?._id]);
4423
- React2.useEffect(() => {
4627
+ React3.useEffect(() => {
4424
4628
  if (!lastReactionUpdate) return;
4425
4629
  setLocalMessages(
4426
4630
  (prev) => syncLocalMessagesOnReactionUpdate(prev, lastReactionUpdate, messagesByConversation)
4427
4631
  );
4428
4632
  }, [lastReactionUpdate, messagesByConversation]);
4429
- React2.useEffect(() => {
4633
+ React3.useEffect(() => {
4430
4634
  if (lastDeleteDM) {
4431
4635
  setLocalMessages((prev) => syncLocalMessagesOnDelete(prev, lastDeleteDM));
4432
4636
  }
4433
4637
  }, [lastDeleteDM]);
4434
- React2.useEffect(() => {
4638
+ React3.useEffect(() => {
4435
4639
  if (lastEditDM) {
4436
4640
  setLocalMessages((prev) => {
4437
4641
  const next = syncLocalMessagesOnEdit(prev, lastEditDM);
@@ -4439,7 +4643,7 @@ var useChatSync = () => {
4439
4643
  });
4440
4644
  }
4441
4645
  }, [lastEditDM]);
4442
- React2.useEffect(() => {
4646
+ React3.useEffect(() => {
4443
4647
  if (!lastDM) return;
4444
4648
  const result = buildIncomingMessageFromDM(
4445
4649
  lastDM,
@@ -4451,14 +4655,14 @@ var useChatSync = () => {
4451
4655
  (prev) => applyMessageUpdate(prev, result.storageKey, result.incomingMsg, result.isMe)
4452
4656
  );
4453
4657
  }, [lastDM, loggedUserDetails, conversations]);
4454
- React2.useEffect(() => {
4658
+ React3.useEffect(() => {
4455
4659
  if (lastStatusUpdate) {
4456
4660
  setLocalMessages(
4457
4661
  (prev) => syncLocalMessagesOnStatusUpdate(prev, lastStatusUpdate, loggedUserDetails)
4458
4662
  );
4459
4663
  }
4460
4664
  }, [lastStatusUpdate, loggedUserDetails]);
4461
- React2.useEffect(() => {
4665
+ React3.useEffect(() => {
4462
4666
  if (lastDMMarkRead) {
4463
4667
  setLocalMessages((prev) => {
4464
4668
  const next = syncLocalMessagesOnDmMarkRead(
@@ -4488,16 +4692,16 @@ var mergeChatCustomization = (config) => ({
4488
4692
  classNames: { ...defaultChatClassNames, ...config?.classNames ?? {} },
4489
4693
  features: { ...defaultChatFeatures, ...config?.features ?? {} }
4490
4694
  });
4491
- var ChatCustomizationContext = React2.createContext(void 0);
4695
+ var ChatCustomizationContext = React3.createContext(void 0);
4492
4696
  var ChatCustomizationProvider = ({
4493
4697
  children,
4494
4698
  customization
4495
4699
  }) => {
4496
- const merged = React2.useMemo(
4700
+ const merged = React3.useMemo(
4497
4701
  () => mergeChatCustomization(customization),
4498
4702
  [customization]
4499
4703
  );
4500
- const value = React2.useMemo(
4704
+ const value = React3.useMemo(
4501
4705
  () => ({
4502
4706
  components: merged.components,
4503
4707
  classNames: merged.classNames,
@@ -4513,7 +4717,7 @@ var ChatCustomizationProvider = ({
4513
4717
  return /* @__PURE__ */ jsxRuntime.jsx(ChatCustomizationContext.Provider, { value, children });
4514
4718
  };
4515
4719
  var useChatCustomization = () => {
4516
- const context = React2.useContext(ChatCustomizationContext);
4720
+ const context = React3.useContext(ChatCustomizationContext);
4517
4721
  if (!context) {
4518
4722
  throw new Error(
4519
4723
  "useChatCustomization must be used within ChatCustomizationProvider"
@@ -4522,7 +4726,7 @@ var useChatCustomization = () => {
4522
4726
  return context;
4523
4727
  };
4524
4728
  var useChatCustomizationOptional = () => {
4525
- const context = React2.useContext(ChatCustomizationContext);
4729
+ const context = React3.useContext(ChatCustomizationContext);
4526
4730
  return context ?? {
4527
4731
  components: {},
4528
4732
  classNames: {},
@@ -4533,9 +4737,146 @@ var useChatCustomizationOptional = () => {
4533
4737
  };
4534
4738
  var useChatFeatures = () => useChatCustomization().features;
4535
4739
  var useChatFeaturesOptional = () => useChatCustomizationOptional().features;
4536
- var ChatContext = React2.createContext(void 0);
4740
+
4741
+ // src/context/ChatEffectiveSettingsContext.tsx
4742
+ init_settings_api();
4743
+ init_settings_types();
4744
+ var ChatEffectiveSettingsContext = React3.createContext(null);
4745
+ function buildCallGate(settings, featureEnabled) {
4746
+ if (!settings.calls.configured) {
4747
+ return {
4748
+ visible: true,
4749
+ enabled: false,
4750
+ disabledReason: exports.CALLS_NOT_CONFIGURED_MESSAGE
4751
+ };
4752
+ }
4753
+ if (!settings.calls.enabled) {
4754
+ return {
4755
+ visible: true,
4756
+ enabled: false,
4757
+ disabledReason: exports.ADMIN_PERMISSION_DENIED_MESSAGE
4758
+ };
4759
+ }
4760
+ if (!featureEnabled) {
4761
+ return {
4762
+ visible: true,
4763
+ enabled: false,
4764
+ disabledReason: exports.ADMIN_PERMISSION_DENIED_MESSAGE
4765
+ };
4766
+ }
4767
+ return { visible: true, enabled: true, disabledReason: null };
4768
+ }
4769
+ function ChatEffectiveSettingsProvider({
4770
+ children,
4771
+ enabled = true
4772
+ }) {
4773
+ const [settings, setSettings] = React3.useState(
4774
+ exports.DEFAULT_EFFECTIVE_SETTINGS
4775
+ );
4776
+ const [loading, setLoading] = React3.useState(true);
4777
+ const [error, setError] = React3.useState(null);
4778
+ const refresh = React3.useCallback(async () => {
4779
+ if (!enabled) {
4780
+ setLoading(false);
4781
+ return;
4782
+ }
4783
+ setLoading(true);
4784
+ try {
4785
+ const next = await exports.fetchEffectiveSettings();
4786
+ setSettings(next);
4787
+ setError(null);
4788
+ } catch (err) {
4789
+ console.error("Failed to fetch effective settings:", err);
4790
+ setError(
4791
+ err instanceof Error ? err.message : "Could not load chat permissions"
4792
+ );
4793
+ setSettings(exports.DEFAULT_EFFECTIVE_SETTINGS);
4794
+ } finally {
4795
+ setLoading(false);
4796
+ }
4797
+ }, [enabled]);
4798
+ React3.useEffect(() => {
4799
+ void refresh();
4800
+ }, [refresh]);
4801
+ const value = React3.useMemo(() => {
4802
+ const isGroupPermissionLocked = (key) => settings.groupPolicy.locked.includes(key);
4803
+ return {
4804
+ settings,
4805
+ loading,
4806
+ error,
4807
+ refresh,
4808
+ isGroupPermissionLocked,
4809
+ getAudioCallGate: () => buildCallGate(settings, settings.audioCallEnabled),
4810
+ getVideoCallGate: () => buildCallGate(settings, settings.videoCallEnabled),
4811
+ getPushNotificationGate: () => ({
4812
+ visible: true,
4813
+ enabled: settings.pushNotificationEnabled,
4814
+ disabledReason: settings.pushNotificationEnabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4815
+ }),
4816
+ getBlockUsersGate: () => ({
4817
+ visible: true,
4818
+ enabled: settings.blockUsersEnabled,
4819
+ disabledReason: settings.blockUsersEnabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4820
+ }),
4821
+ getAttachmentsGate: () => ({
4822
+ visible: true,
4823
+ enabled: settings.attachments.enabled,
4824
+ disabledReason: settings.attachments.enabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4825
+ }),
4826
+ getGroupCreationGate: () => ({
4827
+ visible: true,
4828
+ enabled: settings.groupCreationEnabled,
4829
+ disabledReason: settings.groupCreationEnabled ? null : exports.ADMIN_PERMISSION_DENIED_MESSAGE
4830
+ })
4831
+ };
4832
+ }, [settings, loading, error, refresh]);
4833
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatEffectiveSettingsContext.Provider, { value, children });
4834
+ }
4835
+ function useEffectiveSettings() {
4836
+ const ctx = React3.useContext(ChatEffectiveSettingsContext);
4837
+ if (!ctx) {
4838
+ throw new Error(
4839
+ "useEffectiveSettings must be used within ChatEffectiveSettingsProvider"
4840
+ );
4841
+ }
4842
+ return ctx;
4843
+ }
4844
+ function useEffectiveSettingsOptional() {
4845
+ const ctx = React3.useContext(ChatEffectiveSettingsContext);
4846
+ if (ctx) return ctx;
4847
+ return {
4848
+ settings: exports.DEFAULT_EFFECTIVE_SETTINGS,
4849
+ loading: false,
4850
+ error: null,
4851
+ refresh: async () => void 0,
4852
+ isGroupPermissionLocked: () => false,
4853
+ getAudioCallGate: () => buildCallGate(exports.DEFAULT_EFFECTIVE_SETTINGS, true),
4854
+ getVideoCallGate: () => buildCallGate(exports.DEFAULT_EFFECTIVE_SETTINGS, true),
4855
+ getPushNotificationGate: () => ({
4856
+ visible: true,
4857
+ enabled: true,
4858
+ disabledReason: null
4859
+ }),
4860
+ getBlockUsersGate: () => ({
4861
+ visible: true,
4862
+ enabled: true,
4863
+ disabledReason: null
4864
+ }),
4865
+ getAttachmentsGate: () => ({
4866
+ visible: true,
4867
+ enabled: true,
4868
+ disabledReason: null
4869
+ }),
4870
+ getGroupCreationGate: () => ({
4871
+ visible: true,
4872
+ enabled: true,
4873
+ disabledReason: null
4874
+ })
4875
+ };
4876
+ }
4877
+ var ChatContext = React3.createContext(void 0);
4537
4878
  var useChatContext = () => {
4538
- const context = React2.useContext(ChatContext);
4879
+ const context = React3.useContext(ChatContext);
4539
4880
  if (!context) {
4540
4881
  throw new Error("useChatContext must be used within a Chat provider");
4541
4882
  }
@@ -4572,7 +4913,7 @@ var Chat = ({
4572
4913
  const status = exports.useChatStore((s) => s.status);
4573
4914
  const error = exports.useChatStore((s) => s.error);
4574
4915
  useChatSync();
4575
- React2__namespace.default.useEffect(() => {
4916
+ React3__namespace.default.useEffect(() => {
4576
4917
  const clientId = client?.id;
4577
4918
  const initializeChat = async () => {
4578
4919
  if (!clientId || !loggedUserDetails) return;
@@ -4662,28 +5003,28 @@ var Chat = ({
4662
5003
  queryParamApis: queryParamApis ?? ["get"],
4663
5004
  queryParamsByApi: queryParamsByApi ?? {}
4664
5005
  });
4665
- React2__namespace.default.useEffect(() => {
5006
+ React3__namespace.default.useEffect(() => {
4666
5007
  exports.setChatQueryParams(queryParams);
4667
5008
  exports.setChatQueryParamApis(queryParamApis);
4668
5009
  exports.setChatQueryParamsByApi(queryParamsByApi);
4669
5010
  }, [queryConfigKey]);
4670
- React2__namespace.default.useEffect(() => {
5011
+ React3__namespace.default.useEffect(() => {
4671
5012
  if (lastDM && onMessageReceived) {
4672
5013
  onMessageReceived(lastDM);
4673
5014
  }
4674
5015
  }, [lastDM, onMessageReceived]);
4675
- React2__namespace.default.useEffect(() => {
5016
+ React3__namespace.default.useEffect(() => {
4676
5017
  if (status === "connected" && onSocketConnected) {
4677
5018
  const socketId = exports.useChatStore.getState().socketId;
4678
5019
  if (socketId) onSocketConnected(socketId);
4679
5020
  }
4680
5021
  }, [status, onSocketConnected]);
4681
- React2__namespace.default.useEffect(() => {
5022
+ React3__namespace.default.useEffect(() => {
4682
5023
  if (status === "error" && error && onSocketError) {
4683
5024
  onSocketError(error);
4684
5025
  }
4685
5026
  }, [status, error, onSocketError]);
4686
- const contextValue = React2__namespace.default.useMemo(
5027
+ const contextValue = React3__namespace.default.useMemo(
4687
5028
  () => ({
4688
5029
  onSendMessage,
4689
5030
  client,
@@ -4703,7 +5044,7 @@ var Chat = ({
4703
5044
  messagesData
4704
5045
  ]
4705
5046
  );
4706
- const customization = React2__namespace.default.useMemo(
5047
+ const customization = React3__namespace.default.useMemo(
4707
5048
  () => ({
4708
5049
  components: uiConfig.components,
4709
5050
  classNames: uiConfig.classNames,
@@ -4711,7 +5052,7 @@ var Chat = ({
4711
5052
  }),
4712
5053
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
4713
5054
  );
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..." }) }) }) }) });
5055
+ 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
5056
  };
4716
5057
 
4717
5058
  // src/hooks/useChatController.ts
@@ -5009,7 +5350,7 @@ function toDisplayAttachments(attachments) {
5009
5350
  }));
5010
5351
  }
5011
5352
  var useChatController = () => {
5012
- const [state, setState] = React2.useState({
5353
+ const [state, setState] = React3.useState({
5013
5354
  activeChannel: null,
5014
5355
  activeConversationId: null,
5015
5356
  replyToMessage: null,
@@ -5019,15 +5360,15 @@ var useChatController = () => {
5019
5360
  debouncedSearch: "",
5020
5361
  isCreateModalOpen: false
5021
5362
  });
5022
- const updateState = React2.useCallback((updates) => {
5363
+ const updateState = React3.useCallback((updates) => {
5023
5364
  setState((prev) => ({ ...prev, ...updates }));
5024
5365
  }, []);
5025
- const handleSelectionModeChange = React2.useCallback((active) => {
5366
+ const handleSelectionModeChange = React3.useCallback((active) => {
5026
5367
  setState(
5027
5368
  (prev) => prev.isMessageSelectionMode === active ? prev : { ...prev, isMessageSelectionMode: active }
5028
5369
  );
5029
5370
  }, []);
5030
- const handleActiveChannelPatch = React2.useCallback((patch) => {
5371
+ const handleActiveChannelPatch = React3.useCallback((patch) => {
5031
5372
  setState(
5032
5373
  (prev) => prev.activeChannel ? { ...prev, activeChannel: { ...prev.activeChannel, ...patch } } : prev
5033
5374
  );
@@ -5051,11 +5392,11 @@ var useChatController = () => {
5051
5392
  const allUsers = exports.useChatStore((s) => s.allUsers);
5052
5393
  const isFetchingConversations = exports.useChatStore((s) => s.isFetchingConversations);
5053
5394
  const conversations = exports.useChatStore((s) => s.conversations);
5054
- const [pendingOpenRequest, setPendingOpenRequest] = React2.useState(() => peekPendingOpenRequest());
5395
+ const [pendingOpenRequest, setPendingOpenRequest] = React3.useState(() => peekPendingOpenRequest());
5055
5396
  const conversationsLength = exports.useChatStore(
5056
5397
  (s) => state.activeChannel?.conversationId ? 0 : s.conversations.length
5057
5398
  );
5058
- const lastConversationActionsRef = React2.useRef(null);
5399
+ const lastConversationActionsRef = React3.useRef(null);
5059
5400
  const activeChannelId = state.activeChannel?.id;
5060
5401
  const activeChannelConvId = state.activeChannel?.conversationId;
5061
5402
  const activeChannelIsGroup = !!state.activeChannel?.isGroup;
@@ -5080,7 +5421,7 @@ var useChatController = () => {
5080
5421
  isGroupConversation(latestInfo)
5081
5422
  ].join("|");
5082
5423
  });
5083
- React2.useEffect(() => {
5424
+ React3.useEffect(() => {
5084
5425
  const onOpen = (event) => {
5085
5426
  const detail = event.detail;
5086
5427
  if (detail?.conversationId) {
@@ -5097,7 +5438,7 @@ var useChatController = () => {
5097
5438
  window.addEventListener(OPEN_CONVERSATION_EVENT, onOpen);
5098
5439
  return () => window.removeEventListener(OPEN_CONVERSATION_EVENT, onOpen);
5099
5440
  }, []);
5100
- React2.useEffect(() => {
5441
+ React3.useEffect(() => {
5101
5442
  if (!pendingOpenRequest?.conversationId || !loggedUserDetails) return;
5102
5443
  let cancelled = false;
5103
5444
  const openPending = async () => {
@@ -5163,13 +5504,13 @@ var useChatController = () => {
5163
5504
  allUsers,
5164
5505
  fetchConversationInfo2
5165
5506
  ]);
5166
- React2.useEffect(() => {
5507
+ React3.useEffect(() => {
5167
5508
  const handler = setTimeout(() => {
5168
5509
  updateState({ debouncedSearch: state.searchQuery });
5169
5510
  }, 400);
5170
5511
  return () => clearTimeout(handler);
5171
5512
  }, [state.searchQuery, updateState]);
5172
- React2.useEffect(() => {
5513
+ React3.useEffect(() => {
5173
5514
  if (!state.activeChannel || !loggedUserDetails) {
5174
5515
  setState(
5175
5516
  (prev) => prev.activeConversationId === null ? prev : { ...prev, activeConversationId: null }
@@ -5185,7 +5526,7 @@ var useChatController = () => {
5185
5526
  (prev) => prev.activeConversationId === nextId ? prev : { ...prev, activeConversationId: nextId }
5186
5527
  );
5187
5528
  }, [state.activeChannel, loggedUserDetails, conversationsLength]);
5188
- React2.useEffect(() => {
5529
+ React3.useEffect(() => {
5189
5530
  if (!lastDM?.conversationId || !loggedUserDetails) return;
5190
5531
  const convId = String(lastDM.conversationId);
5191
5532
  const senderId = String(lastDM.sender?._id || lastDM.sender || "");
@@ -5218,7 +5559,7 @@ var useChatController = () => {
5218
5559
  };
5219
5560
  });
5220
5561
  }, [lastDM, loggedUserDetails]);
5221
- const resolvedLocalMessages = React2.useMemo(
5562
+ const resolvedLocalMessages = React3.useMemo(
5222
5563
  () => resolveLocalMessagesForChannel(
5223
5564
  localMessages,
5224
5565
  state.activeConversationId,
@@ -5226,7 +5567,7 @@ var useChatController = () => {
5226
5567
  ),
5227
5568
  [localMessages, state.activeConversationId, state.activeChannel?.id]
5228
5569
  );
5229
- const resolvedConversationMessages = React2.useMemo(() => {
5570
+ const resolvedConversationMessages = React3.useMemo(() => {
5230
5571
  if (state.activeConversationId) {
5231
5572
  return messagesByConversation[state.activeConversationId] || [];
5232
5573
  }
@@ -5234,7 +5575,7 @@ var useChatController = () => {
5234
5575
  const groupConversationId = String(state.activeChannel.id);
5235
5576
  return messagesByConversation[groupConversationId] || [];
5236
5577
  }, [messagesByConversation, state.activeConversationId, state.activeChannel]);
5237
- React2.useEffect(() => {
5578
+ React3.useEffect(() => {
5238
5579
  if (!activeChannelSyncKey || !state.activeChannel) return;
5239
5580
  const [convId, pinned, starred, isLeft, isBlocked, isGroup] = activeChannelSyncKey.split("|");
5240
5581
  setState((prev) => {
@@ -5257,10 +5598,10 @@ var useChatController = () => {
5257
5598
  };
5258
5599
  });
5259
5600
  }, [activeChannelSyncKey, state.activeChannel]);
5260
- React2.useEffect(() => {
5601
+ React3.useEffect(() => {
5261
5602
  setActiveConversationId(state.activeConversationId);
5262
5603
  }, [state.activeConversationId, setActiveConversationId]);
5263
- React2.useEffect(() => {
5604
+ React3.useEffect(() => {
5264
5605
  if (!state.activeConversationId || !state.activeChannel || !loggedUserDetails) {
5265
5606
  lastConversationActionsRef.current = null;
5266
5607
  return;
@@ -5307,7 +5648,7 @@ var useChatController = () => {
5307
5648
  emitGroupMarkRead,
5308
5649
  emitMarkAsRead
5309
5650
  ]);
5310
- React2.useEffect(() => {
5651
+ React3.useEffect(() => {
5311
5652
  if (globalAppError?.type === "participant" && state.activeChannel) {
5312
5653
  updateState({ activeChannel: null, activeConversationId: null });
5313
5654
  }
@@ -5484,7 +5825,7 @@ function withThemeTransition(apply) {
5484
5825
  }
5485
5826
  reactDom.flushSync(apply);
5486
5827
  }
5487
- var ChatThemeContext = React2.createContext(null);
5828
+ var ChatThemeContext = React3.createContext(null);
5488
5829
  var ChatThemeProvider = ({
5489
5830
  children,
5490
5831
  themeColor,
@@ -5494,10 +5835,10 @@ var ChatThemeProvider = ({
5494
5835
  showColorModeToggle = true
5495
5836
  }) => {
5496
5837
  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(() => {
5838
+ const [internalMode, setInternalMode] = React3.useState(defaultColorMode);
5839
+ const [isThemeTransitionReady, setIsThemeTransitionReady] = React3.useState(false);
5840
+ const skipThemeTransitionRef = React3.useRef(true);
5841
+ React3.useLayoutEffect(() => {
5501
5842
  if (!isControlled) {
5502
5843
  const stored = loadChatColorMode();
5503
5844
  if (stored) {
@@ -5512,7 +5853,7 @@ var ChatThemeProvider = ({
5512
5853
  }, [isControlled]);
5513
5854
  const resolvedMode = isControlled ? colorMode : internalMode;
5514
5855
  const isDark = resolvedMode === "dark";
5515
- const setColorMode = React2.useCallback(
5856
+ const setColorMode = React3.useCallback(
5516
5857
  (mode) => {
5517
5858
  const apply = () => {
5518
5859
  if (!isControlled) {
@@ -5529,10 +5870,10 @@ var ChatThemeProvider = ({
5529
5870
  },
5530
5871
  [isControlled, onColorModeChange]
5531
5872
  );
5532
- const toggleColorMode = React2.useCallback(() => {
5873
+ const toggleColorMode = React3.useCallback(() => {
5533
5874
  setColorMode(resolvedMode === "dark" ? "light" : "dark");
5534
5875
  }, [resolvedMode, setColorMode]);
5535
- const value = React2.useMemo(
5876
+ const value = React3.useMemo(
5536
5877
  () => ({
5537
5878
  colorMode: resolvedMode,
5538
5879
  isDark,
@@ -5560,7 +5901,7 @@ var ChatThemeProvider = ({
5560
5901
  return /* @__PURE__ */ jsxRuntime.jsx(ChatThemeContext.Provider, { value, children });
5561
5902
  };
5562
5903
  var useChatTheme = () => {
5563
- const context = React2.useContext(ChatThemeContext);
5904
+ const context = React3.useContext(ChatThemeContext);
5564
5905
  if (!context) {
5565
5906
  throw new Error("useChatTheme must be used within ChatThemeProvider");
5566
5907
  }
@@ -6105,7 +6446,7 @@ function useCreateChatDialog({
6105
6446
  onUserSelect,
6106
6447
  setIsCreateModalOpen
6107
6448
  }) {
6108
- const [state, setState] = React2.useState(initialState);
6449
+ const [state, setState] = React3.useState(initialState);
6109
6450
  const updateState = (updates) => {
6110
6451
  setState((prev) => ({ ...prev, ...updates }));
6111
6452
  };
@@ -6114,8 +6455,10 @@ function useCreateChatDialog({
6114
6455
  const fetchAllUsers = exports.useChatStore((storeState) => storeState.fetchAllUsers);
6115
6456
  const accessToken = exports.useChatStore((storeState) => storeState.accessToken);
6116
6457
  const fetchConversations2 = exports.useChatStore((storeState) => storeState.fetchConversations);
6117
- const fileInputRef = React2.useRef(null);
6118
- React2.useEffect(() => {
6458
+ const { settings } = useEffectiveSettingsOptional();
6459
+ const maxParticipants = settings.maxParticipantsPerGroup;
6460
+ const fileInputRef = React3.useRef(null);
6461
+ React3.useEffect(() => {
6119
6462
  if (accessToken) {
6120
6463
  fetchAllUsers();
6121
6464
  }
@@ -6127,9 +6470,18 @@ function useCreateChatDialog({
6127
6470
  setState((prev) => {
6128
6471
  const memberId = String(member._id);
6129
6472
  const exists = prev.selectedMembers.some((m) => String(m._id) === memberId);
6473
+ if (exists) {
6474
+ return {
6475
+ ...prev,
6476
+ selectedMembers: prev.selectedMembers.filter((m) => String(m._id) !== memberId)
6477
+ };
6478
+ }
6479
+ if (prev.selectedMembers.length + 1 >= maxParticipants) {
6480
+ return prev;
6481
+ }
6130
6482
  return {
6131
6483
  ...prev,
6132
- selectedMembers: exists ? prev.selectedMembers.filter((m) => String(m._id) !== memberId) : [...prev.selectedMembers, member]
6484
+ selectedMembers: [...prev.selectedMembers, member]
6133
6485
  };
6134
6486
  });
6135
6487
  };
@@ -6513,8 +6865,25 @@ function GroupCreateTab({
6513
6865
  ) })
6514
6866
  ] });
6515
6867
  }
6868
+ function AdminPermissionTooltip({
6869
+ disabled,
6870
+ message,
6871
+ side = "top",
6872
+ className,
6873
+ children
6874
+ }) {
6875
+ if (!disabled || !message) {
6876
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
6877
+ }
6878
+ return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6879
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("inline-flex", className), children }) }),
6880
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side, className: "max-w-[240px] text-xs", children: message })
6881
+ ] });
6882
+ }
6516
6883
  var CreateChatDialog = (props) => {
6517
6884
  const { isCreateModalOpen = false, setIsCreateModalOpen } = props;
6885
+ const { getGroupCreationGate } = useEffectiveSettingsOptional();
6886
+ const groupCreationGate = getGroupCreationGate();
6518
6887
  const {
6519
6888
  state,
6520
6889
  updateState,
@@ -6568,24 +6937,39 @@ var CreateChatDialog = (props) => {
6568
6937
  ) })
6569
6938
  ] }),
6570
6939
  /* @__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",
6940
+ { id: "dm", label: "Message", icon: lucideReact.MessageCircle, disabled: false, reason: null },
6575
6941
  {
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
- )) }),
6942
+ id: "group",
6943
+ label: "New group",
6944
+ icon: lucideReact.Users,
6945
+ disabled: !groupCreationGate.enabled,
6946
+ reason: groupCreationGate.disabledReason
6947
+ }
6948
+ ].map(({ id, label, icon: Icon, disabled, reason }) => {
6949
+ const tabButton = /* @__PURE__ */ jsxRuntime.jsxs(
6950
+ "button",
6951
+ {
6952
+ type: "button",
6953
+ disabled,
6954
+ onClick: () => {
6955
+ if (disabled) return;
6956
+ updateState({ activeTab: id });
6957
+ },
6958
+ className: cn(
6959
+ "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",
6960
+ 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)"
6961
+ ),
6962
+ children: [
6963
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4" }),
6964
+ label
6965
+ ]
6966
+ }
6967
+ );
6968
+ if (!disabled || !reason) {
6969
+ return /* @__PURE__ */ jsxRuntime.jsx(React3.Fragment, { children: tabButton }, id);
6970
+ }
6971
+ return /* @__PURE__ */ jsxRuntime.jsx(AdminPermissionTooltip, { disabled: true, message: reason, children: tabButton }, id);
6972
+ }) }),
6589
6973
  state.activeTab === "dm" && /* @__PURE__ */ jsxRuntime.jsx(
6590
6974
  DmContactsTab,
6591
6975
  {
@@ -6596,7 +6980,7 @@ var CreateChatDialog = (props) => {
6596
6980
  onStartDM: handleStartDM
6597
6981
  }
6598
6982
  ),
6599
- state.activeTab === "group" && /* @__PURE__ */ jsxRuntime.jsx(
6983
+ state.activeTab === "group" && groupCreationGate.enabled && /* @__PURE__ */ jsxRuntime.jsx(
6600
6984
  GroupCreateTab,
6601
6985
  {
6602
6986
  state,
@@ -6660,7 +7044,7 @@ var emitPushChanged = (enabled) => {
6660
7044
  } catch {
6661
7045
  }
6662
7046
  };
6663
- var pushApiUrl = (path) => exports.buildVersionedApiUrl(path, { service: "push" });
7047
+ var pushApiUrl = (path) => exports.buildVersionedApiUrl(path, { service: "v2" });
6664
7048
  var apiGetPushConfig = async () => {
6665
7049
  const { data } = await apiClient.get(pushApiUrl("/notifications/push/config"));
6666
7050
  return data?.data;
@@ -6809,7 +7193,7 @@ var onForegroundPush = async (callback) => {
6809
7193
 
6810
7194
  // src/notifications/usePushNotifications.ts
6811
7195
  var usePushNotifications = () => {
6812
- const [state, setState] = React2.useState({
7196
+ const [state, setState] = React3.useState({
6813
7197
  supported: false,
6814
7198
  allowedByWorkspace: false,
6815
7199
  configured: false,
@@ -6819,7 +7203,7 @@ var usePushNotifications = () => {
6819
7203
  loading: true,
6820
7204
  error: null
6821
7205
  });
6822
- const refresh = React2.useCallback(async () => {
7206
+ const refresh = React3.useCallback(async () => {
6823
7207
  if (!isPushSupported()) {
6824
7208
  setState((prev) => ({
6825
7209
  ...prev,
@@ -6852,10 +7236,10 @@ var usePushNotifications = () => {
6852
7236
  }));
6853
7237
  }
6854
7238
  }, []);
6855
- React2.useEffect(() => {
7239
+ React3.useEffect(() => {
6856
7240
  void refresh();
6857
7241
  }, [refresh]);
6858
- const enable = React2.useCallback(async () => {
7242
+ const enable = React3.useCallback(async () => {
6859
7243
  setState((prev) => ({ ...prev, loading: true, error: null }));
6860
7244
  try {
6861
7245
  await enablePushOnThisDevice();
@@ -6871,7 +7255,7 @@ var usePushNotifications = () => {
6871
7255
  return false;
6872
7256
  }
6873
7257
  }, [refresh]);
6874
- const disable = React2.useCallback(async () => {
7258
+ const disable = React3.useCallback(async () => {
6875
7259
  setState((prev) => ({ ...prev, loading: true, error: null }));
6876
7260
  try {
6877
7261
  await disablePushOnThisDevice();
@@ -6879,7 +7263,7 @@ var usePushNotifications = () => {
6879
7263
  await refresh();
6880
7264
  }
6881
7265
  }, [refresh]);
6882
- const setUserEnabled = React2.useCallback(
7266
+ const setUserEnabled = React3.useCallback(
6883
7267
  async (enabled) => {
6884
7268
  setState((prev) => ({ ...prev, loading: true, error: null }));
6885
7269
  try {
@@ -6905,14 +7289,20 @@ var PushNotificationToggle = ({
6905
7289
  children
6906
7290
  }) => {
6907
7291
  const push = usePushNotifications();
6908
- const [isToggling, setIsToggling] = React2.useState(false);
6909
- if (!push.supported || !push.allowedByWorkspace || !push.configured) {
7292
+ const { getPushNotificationGate } = useEffectiveSettingsOptional();
7293
+ const adminGate = getPushNotificationGate();
7294
+ const [isToggling, setIsToggling] = React3.useState(false);
7295
+ if (!push.supported) {
6910
7296
  return null;
6911
7297
  }
6912
7298
  const isOn = push.deviceEnabled && push.userEnabled;
6913
7299
  const permissionBlocked = push.permission === "denied";
7300
+ const adminBlocked = !adminGate.enabled;
7301
+ const notConfigured = !push.configured;
6914
7302
  const showLoader = isToggling;
7303
+ const isDisabled = showLoader || permissionBlocked || push.loading || adminBlocked || notConfigured;
6915
7304
  const handleChange = async (checked) => {
7305
+ if (adminBlocked || notConfigured) return;
6916
7306
  setIsToggling(true);
6917
7307
  try {
6918
7308
  if (checked) {
@@ -6925,11 +7315,12 @@ var PushNotificationToggle = ({
6925
7315
  setIsToggling(false);
6926
7316
  }
6927
7317
  };
7318
+ 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
7319
  const renderProps = {
6929
7320
  label,
6930
- description: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description,
6931
- checked: isOn,
6932
- disabled: showLoader || permissionBlocked || push.loading,
7321
+ description: descriptionText,
7322
+ checked: adminBlocked || notConfigured ? false : isOn,
7323
+ disabled: isDisabled,
6933
7324
  loading: showLoader || push.loading,
6934
7325
  permissionBlocked,
6935
7326
  error: push.error,
@@ -6957,12 +7348,19 @@ var PushNotificationToggle = ({
6957
7348
  "aria-label": "Updating push notifications"
6958
7349
  }
6959
7350
  ) : /* @__PURE__ */ jsxRuntime.jsx(
6960
- Switch,
7351
+ AdminPermissionTooltip,
6961
7352
  {
6962
- checked: isOn,
6963
- disabled: permissionBlocked || push.loading,
6964
- onCheckedChange: (checked) => void handleChange(checked),
6965
- "aria-label": `Toggle ${label}`
7353
+ disabled: adminBlocked || notConfigured,
7354
+ message: adminBlocked ? adminGate.disabledReason : "Push notifications are not configured. Please contact your administrator.",
7355
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7356
+ Switch,
7357
+ {
7358
+ checked: adminBlocked || notConfigured ? false : isOn,
7359
+ disabled: isDisabled,
7360
+ onCheckedChange: (checked) => void handleChange(checked),
7361
+ "aria-label": `Toggle ${label}`
7362
+ }
7363
+ )
6966
7364
  }
6967
7365
  )
6968
7366
  ]
@@ -7076,7 +7474,7 @@ var LoggedUserSettingsDialog = ({
7076
7474
  className
7077
7475
  }) => {
7078
7476
  const { resolveComponent, slotClassName } = useChatCustomization();
7079
- const [internalOpen, setInternalOpen] = React2.useState(false);
7477
+ const [internalOpen, setInternalOpen] = React3.useState(false);
7080
7478
  const open = controlledOpen ?? internalOpen;
7081
7479
  const onOpenChange = controlledOnOpenChange ?? setInternalOpen;
7082
7480
  const TriggerSlot = resolveComponent(
@@ -7264,7 +7662,7 @@ function getDraftRevision() {
7264
7662
  return draftRevision;
7265
7663
  }
7266
7664
  function useDraftRevision() {
7267
- return React2.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
7665
+ return React3.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
7268
7666
  }
7269
7667
  function getConversationDraft(conversationId) {
7270
7668
  if (!conversationId) return emptyDraft();
@@ -7302,11 +7700,11 @@ function getConversationDraftPreview(conversationId) {
7302
7700
  return null;
7303
7701
  }
7304
7702
  function useConversationDraft(conversationId) {
7305
- const composerRef = React2.useRef(emptyDraft());
7306
- const syncComposerRef = React2.useCallback((draft) => {
7703
+ const composerRef = React3.useRef(emptyDraft());
7704
+ const syncComposerRef = React3.useCallback((draft) => {
7307
7705
  composerRef.current = draft;
7308
7706
  }, []);
7309
- React2.useEffect(() => {
7707
+ React3.useEffect(() => {
7310
7708
  const id = conversationId;
7311
7709
  return () => {
7312
7710
  if (!id) return;
@@ -7525,7 +7923,7 @@ var saveChatDateTimePreferences = (prefs) => {
7525
7923
  } catch {
7526
7924
  }
7527
7925
  };
7528
- var ChatLocaleContext = React2.createContext(null);
7926
+ var ChatLocaleContext = React3.createContext(null);
7529
7927
  var resolveInitialPrefs = (defaults) => {
7530
7928
  const stored = loadChatDateTimePreferences();
7531
7929
  const fallback = resolveStoredPrefs(defaults);
@@ -7552,7 +7950,7 @@ var ChatLocaleProvider = ({
7552
7950
  const localeControlled = controlledLocale !== void 0;
7553
7951
  const timeZoneControlled = controlledTimeZone !== void 0;
7554
7952
  const hour12Controlled = controlledHour12 !== void 0;
7555
- const [internalPrefs, setInternalPrefs] = React2.useState(
7953
+ const [internalPrefs, setInternalPrefs] = React3.useState(
7556
7954
  () => resolveInitialPrefs({
7557
7955
  locale: defaultLocale,
7558
7956
  timeZone: defaultTimeZone,
@@ -7562,7 +7960,7 @@ var ChatLocaleProvider = ({
7562
7960
  const resolvedLocale = localeControlled ? controlledLocale : internalPrefs.locale;
7563
7961
  const resolvedTimeZone = timeZoneControlled ? controlledTimeZone : internalPrefs.timeZone;
7564
7962
  const resolvedHour12 = hour12Controlled ? controlledHour12 ?? getDefaultHour12() : internalPrefs.hour12 ?? getDefaultHour12();
7565
- const updatePrefs = React2.useCallback(
7963
+ const updatePrefs = React3.useCallback(
7566
7964
  (updates) => {
7567
7965
  const next = {
7568
7966
  locale: updates.locale !== void 0 ? updates.locale : resolvedLocale,
@@ -7591,19 +7989,19 @@ var ChatLocaleProvider = ({
7591
7989
  onDateTimePreferencesChange
7592
7990
  ]
7593
7991
  );
7594
- const setLocale = React2.useCallback(
7992
+ const setLocale = React3.useCallback(
7595
7993
  (locale) => updatePrefs({ locale }),
7596
7994
  [updatePrefs]
7597
7995
  );
7598
- const setTimeZone = React2.useCallback(
7996
+ const setTimeZone = React3.useCallback(
7599
7997
  (timeZone) => updatePrefs({ timeZone }),
7600
7998
  [updatePrefs]
7601
7999
  );
7602
- const setHour12 = React2.useCallback(
8000
+ const setHour12 = React3.useCallback(
7603
8001
  (hour12) => updatePrefs({ hour12 }),
7604
8002
  [updatePrefs]
7605
8003
  );
7606
- const prefs = React2.useMemo(
8004
+ const prefs = React3.useMemo(
7607
8005
  () => ({
7608
8006
  locale: resolvedLocale,
7609
8007
  timeZone: resolvedTimeZone,
@@ -7611,8 +8009,8 @@ var ChatLocaleProvider = ({
7611
8009
  }),
7612
8010
  [resolvedLocale, resolvedTimeZone, resolvedHour12]
7613
8011
  );
7614
- const formatters = React2.useMemo(() => createChatDateTimeFormatters(prefs), [prefs]);
7615
- const value = React2.useMemo(
8012
+ const formatters = React3.useMemo(() => createChatDateTimeFormatters(prefs), [prefs]);
8013
+ const value = React3.useMemo(
7616
8014
  () => ({
7617
8015
  ...formatters,
7618
8016
  locale: resolvedLocale,
@@ -7637,7 +8035,7 @@ var ChatLocaleProvider = ({
7637
8035
  return /* @__PURE__ */ jsxRuntime.jsx(ChatLocaleContext.Provider, { value, children });
7638
8036
  };
7639
8037
  var useChatLocale = () => {
7640
- const context = React2.useContext(ChatLocaleContext);
8038
+ const context = React3.useContext(ChatLocaleContext);
7641
8039
  if (!context) {
7642
8040
  throw new Error("useChatLocale must be used within ChatLocaleProvider");
7643
8041
  }
@@ -7759,15 +8157,15 @@ function useChannelList({ debouncedSearch = "" }) {
7759
8157
  const conversationsPage = exports.useChatStore((s) => s.conversationsPage);
7760
8158
  const accessToken = exports.useChatStore((s) => s.accessToken);
7761
8159
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
7762
- const lastFetchKeyRef = React2.useRef(null);
8160
+ const lastFetchKeyRef = React3.useRef(null);
7763
8161
  useDraftRevision();
7764
8162
  const { formatSidebarTime } = useChatLocale();
7765
- React2.useEffect(() => {
8163
+ React3.useEffect(() => {
7766
8164
  if (accessToken) {
7767
8165
  exports.useChatStore.getState().fetchAllUsers();
7768
8166
  }
7769
8167
  }, [accessToken]);
7770
- React2.useEffect(() => {
8168
+ React3.useEffect(() => {
7771
8169
  if (!accessToken) return;
7772
8170
  const fetchKey = debouncedSearch;
7773
8171
  if (lastFetchKeyRef.current === fetchKey) return;
@@ -7779,7 +8177,7 @@ function useChannelList({ debouncedSearch = "" }) {
7779
8177
  }
7780
8178
  fetchConversations2(DEFAULT_CONVERSATIONS_LIMIT, 1, debouncedSearch, true);
7781
8179
  }, [accessToken, debouncedSearch]);
7782
- const mappedChannels = React2.useMemo(() => {
8180
+ const mappedChannels = React3.useMemo(() => {
7783
8181
  const safeConversations = Array.isArray(conversations) ? conversations : [];
7784
8182
  const seenConversationIds = /* @__PURE__ */ new Set();
7785
8183
  return safeConversations.map((c) => {
@@ -8383,7 +8781,7 @@ function highlightSearchMatch(text, query) {
8383
8781
  children: part
8384
8782
  },
8385
8783
  `${part}-${index}`
8386
- ) : /* @__PURE__ */ jsxRuntime.jsx(React2__namespace.default.Fragment, { children: part }, `${part}-${index}`)
8784
+ ) : /* @__PURE__ */ jsxRuntime.jsx(React3__namespace.default.Fragment, { children: part }, `${part}-${index}`)
8387
8785
  );
8388
8786
  }
8389
8787
  function getMessagePreview(message) {
@@ -8430,14 +8828,14 @@ var HeaderSearchPopover = ({
8430
8828
  }) => {
8431
8829
  const { scopeClassName, themeStyles } = useChatTheme();
8432
8830
  const { formatMessageTime, formatShortDate } = useChatLocale();
8433
- const inputRef = React2.useRef(null);
8434
- React2.useEffect(() => {
8831
+ const inputRef = React3.useRef(null);
8832
+ React3.useEffect(() => {
8435
8833
  if (!open) return;
8436
8834
  const timer = window.setTimeout(() => inputRef.current?.focus(), 0);
8437
8835
  return () => window.clearTimeout(timer);
8438
8836
  }, [open]);
8439
8837
  const showResults = searchQuery.trim().length > 0;
8440
- const resultCountLabel = React2.useMemo(() => {
8838
+ const resultCountLabel = React3.useMemo(() => {
8441
8839
  if (!searchResults.length) return "0 results";
8442
8840
  return `${searchResults.length}${hasMore ? "+" : ""} result${searchResults.length === 1 ? "" : "s"}`;
8443
8841
  }, [hasMore, searchResults.length]);
@@ -8563,7 +8961,7 @@ var HeaderSearchPopover = ({
8563
8961
  ] });
8564
8962
  };
8565
8963
  var HeaderSearchPopover_default = HeaderSearchPopover;
8566
- var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text)";
8964
+ var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text) disabled:pointer-events-none disabled:opacity-40";
8567
8965
  var DefaultHeaderCallActions = ({
8568
8966
  activeChannel,
8569
8967
  conversationId,
@@ -8574,48 +8972,91 @@ var DefaultHeaderCallActions = ({
8574
8972
  className
8575
8973
  }) => {
8576
8974
  const { components, slotClassName, features } = useChatCustomizationOptional();
8975
+ const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
8577
8976
  const PhoneSlot = components.PhoneCallButton;
8578
8977
  const VideoSlot = components.VideoCallButton;
8579
8978
  const hideInGroups = features.hideCallActionsInGroupChats ?? false;
8979
+ const audioGate = getAudioCallGate();
8980
+ const videoGate = getVideoCallGate();
8580
8981
  if (hideInGroups && activeChannel.isGroup) return null;
8581
8982
  if (!showPhoneCall && !showVideoCall) return null;
8983
+ const phoneEnabled = showPhoneCall && audioGate.enabled;
8984
+ const videoEnabled = showVideoCall && videoGate.enabled;
8582
8985
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8583
8986
  showPhoneCall && (PhoneSlot ? /* @__PURE__ */ jsxRuntime.jsx(
8584
- PhoneSlot,
8987
+ AdminPermissionTooltip,
8585
8988
  {
8586
- activeChannel,
8587
- conversationId,
8588
- onPhoneCall,
8589
- className: cn(className, slotClassName("phoneCallButton"))
8989
+ disabled: !audioGate.enabled,
8990
+ message: audioGate.disabledReason,
8991
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
8992
+ PhoneSlot,
8993
+ {
8994
+ activeChannel,
8995
+ conversationId,
8996
+ onPhoneCall: phoneEnabled ? onPhoneCall : void 0,
8997
+ className: cn(
8998
+ className,
8999
+ slotClassName("phoneCallButton"),
9000
+ !audioGate.enabled && "pointer-events-none opacity-40"
9001
+ )
9002
+ }
9003
+ ) })
8590
9004
  }
8591
9005
  ) : /* @__PURE__ */ jsxRuntime.jsx(
8592
- Button,
9006
+ AdminPermissionTooltip,
8593
9007
  {
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 })
9008
+ disabled: !audioGate.enabled,
9009
+ message: audioGate.disabledReason,
9010
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9011
+ Button,
9012
+ {
9013
+ variant: "ghost",
9014
+ size: "icon",
9015
+ className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
9016
+ "aria-label": "Voice call",
9017
+ disabled: !audioGate.enabled,
9018
+ onClick: phoneEnabled ? onPhoneCall : void 0,
9019
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 15 })
9020
+ }
9021
+ )
8600
9022
  }
8601
9023
  )),
8602
9024
  showVideoCall && (VideoSlot ? /* @__PURE__ */ jsxRuntime.jsx(
8603
- VideoSlot,
9025
+ AdminPermissionTooltip,
8604
9026
  {
8605
- activeChannel,
8606
- conversationId,
8607
- onVideoCall,
8608
- className: cn(className, slotClassName("videoCallButton"))
9027
+ disabled: !videoGate.enabled,
9028
+ message: videoGate.disabledReason,
9029
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(
9030
+ VideoSlot,
9031
+ {
9032
+ activeChannel,
9033
+ conversationId,
9034
+ onVideoCall: videoEnabled ? onVideoCall : void 0,
9035
+ className: cn(
9036
+ className,
9037
+ slotClassName("videoCallButton"),
9038
+ !videoGate.enabled && "pointer-events-none opacity-40"
9039
+ )
9040
+ }
9041
+ ) })
8609
9042
  }
8610
9043
  ) : /* @__PURE__ */ jsxRuntime.jsx(
8611
- Button,
9044
+ AdminPermissionTooltip,
8612
9045
  {
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 })
9046
+ disabled: !videoGate.enabled,
9047
+ message: videoGate.disabledReason,
9048
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9049
+ Button,
9050
+ {
9051
+ variant: "ghost",
9052
+ size: "icon",
9053
+ className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
9054
+ "aria-label": "Video call",
9055
+ disabled: !videoGate.enabled,
9056
+ onClick: videoEnabled ? onVideoCall : void 0,
9057
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 15 })
9058
+ }
9059
+ )
8619
9060
  }
8620
9061
  ))
8621
9062
  ] });
@@ -8637,19 +9078,38 @@ function OptionsMenuItem({
8637
9078
  icon: Icon,
8638
9079
  label,
8639
9080
  onSelect,
8640
- destructive = false
9081
+ destructive = false,
9082
+ disabled = false,
9083
+ disabledReason
8641
9084
  }) {
8642
- return /* @__PURE__ */ jsxRuntime.jsxs(
9085
+ const item = /* @__PURE__ */ jsxRuntime.jsxs(
8643
9086
  DropdownMenuItem,
8644
9087
  {
8645
- className: destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
8646
- onSelect,
9088
+ className: cn(
9089
+ destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
9090
+ disabled && "pointer-events-none opacity-40"
9091
+ ),
9092
+ disabled,
9093
+ onSelect: (event) => {
9094
+ if (disabled) {
9095
+ event.preventDefault();
9096
+ return;
9097
+ }
9098
+ onSelect?.();
9099
+ },
8647
9100
  children: [
8648
9101
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { strokeWidth: 1.75 }),
8649
9102
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: label })
8650
9103
  ]
8651
9104
  }
8652
9105
  );
9106
+ if (!disabled || !disabledReason) {
9107
+ return item;
9108
+ }
9109
+ return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
9110
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: item }) }),
9111
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "left", className: "max-w-[220px] text-xs", children: disabledReason })
9112
+ ] });
8653
9113
  }
8654
9114
  var HeaderRightSide = ({
8655
9115
  activeChannel,
@@ -8684,6 +9144,8 @@ var HeaderRightSide = ({
8684
9144
  const { scopeClassName, themeStyles, showColorModeToggle } = useChatTheme();
8685
9145
  const { showDateTimeSettings } = useChatLocale();
8686
9146
  const features = useChatFeaturesOptional();
9147
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
9148
+ const blockUsersGate = getBlockUsersGate();
8687
9149
  const showPhoneCall = features.showPhoneCall ?? defaultChatFeatures.showPhoneCall;
8688
9150
  const showVideoCall = features.showVideoCall ?? defaultChatFeatures.showVideoCall;
8689
9151
  const handlePhoneCall = () => {
@@ -8832,7 +9294,9 @@ var HeaderRightSide = ({
8832
9294
  icon: lucideReact.Ban,
8833
9295
  label: isBlocked ? "Unblock User" : "Block User",
8834
9296
  onSelect: onToggleBlock,
8835
- destructive: true
9297
+ destructive: true,
9298
+ disabled: !blockUsersGate.enabled,
9299
+ disabledReason: blockUsersGate.disabledReason
8836
9300
  }
8837
9301
  )
8838
9302
  ]
@@ -9203,13 +9667,13 @@ function AudioAttachmentPlayer({
9203
9667
  showDownload = true,
9204
9668
  downloadName
9205
9669
  }) {
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);
9670
+ const audioRef = React3.useRef(null);
9671
+ const [isPlaying, setIsPlaying] = React3.useState(false);
9672
+ const [currentTime, setCurrentTime] = React3.useState(0);
9673
+ const [duration, setDuration] = React3.useState(durationProp ?? 0);
9210
9674
  const resolvedDuration = duration > 0 ? duration : 0;
9211
9675
  const progressPercent = resolvedDuration > 0 ? Math.min(100, currentTime / resolvedDuration * 100) : 0;
9212
- const togglePlay = React2.useCallback(async () => {
9676
+ const togglePlay = React3.useCallback(async () => {
9213
9677
  const audio = audioRef.current;
9214
9678
  if (!audio || !url) return;
9215
9679
  if (audio.paused) {
@@ -9231,7 +9695,7 @@ function AudioAttachmentPlayer({
9231
9695
  audio.currentTime = next;
9232
9696
  setCurrentTime(next);
9233
9697
  };
9234
- React2.useEffect(() => {
9698
+ React3.useEffect(() => {
9235
9699
  const audio = audioRef.current;
9236
9700
  if (!audio) return;
9237
9701
  const onTimeUpdate = () => setCurrentTime(audio.currentTime);
@@ -9259,7 +9723,7 @@ function AudioAttachmentPlayer({
9259
9723
  audio.removeEventListener("play", onPlay);
9260
9724
  };
9261
9725
  }, [url]);
9262
- React2.useEffect(() => {
9726
+ React3.useEffect(() => {
9263
9727
  if (durationProp && durationProp > 0) {
9264
9728
  setDuration(durationProp);
9265
9729
  }
@@ -9381,12 +9845,12 @@ function VideoAttachmentPlayer({
9381
9845
  downloadName,
9382
9846
  onOpenPreview
9383
9847
  }) {
9384
- const videoRef = React2.useRef(null);
9385
- const [isPlaying, setIsPlaying] = React2.useState(false);
9386
- const [duration, setDuration] = React2.useState(durationProp ?? 0);
9848
+ const videoRef = React3.useRef(null);
9849
+ const [isPlaying, setIsPlaying] = React3.useState(false);
9850
+ const [duration, setDuration] = React3.useState(durationProp ?? 0);
9387
9851
  const onThemeBubble = inBubble && isSender;
9388
9852
  const resolvedDuration = duration > 0 ? duration : 0;
9389
- const togglePlay = React2.useCallback(async () => {
9853
+ const togglePlay = React3.useCallback(async () => {
9390
9854
  const video = videoRef.current;
9391
9855
  if (!video || !url) return;
9392
9856
  if (video.paused) {
@@ -9401,7 +9865,7 @@ function VideoAttachmentPlayer({
9401
9865
  setIsPlaying(false);
9402
9866
  }
9403
9867
  }, [url]);
9404
- React2.useEffect(() => {
9868
+ React3.useEffect(() => {
9405
9869
  if (durationProp && durationProp > 0) {
9406
9870
  setDuration(durationProp);
9407
9871
  }
@@ -9679,10 +10143,10 @@ async function fetchAuthenticatedBlob(url) {
9679
10143
  return response.blob();
9680
10144
  }
9681
10145
  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(() => {
10146
+ const [blobUrl, setBlobUrl] = React3.useState(null);
10147
+ const [fallbackUrl, setFallbackUrl] = React3.useState(null);
10148
+ const [loading, setLoading] = React3.useState(true);
10149
+ React3.useEffect(() => {
9686
10150
  let active = true;
9687
10151
  let objectUrl = null;
9688
10152
  const loadPdf = async () => {
@@ -10186,60 +10650,60 @@ function AttachmentImageLightbox({
10186
10650
  onSaveCaption,
10187
10651
  initialCaptionEditing = false
10188
10652
  }) {
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 });
10653
+ const [index, setIndex] = React3.useState(initialIndex);
10654
+ const [zoom, setZoom] = React3.useState(1);
10655
+ const [pan, setPan] = React3.useState({ x: 0, y: 0 });
10656
+ const [imageVersion, setImageVersion] = React3.useState(0);
10657
+ const [localCaptions, setLocalCaptions] = React3.useState({});
10658
+ const [captionDraft, setCaptionDraft] = React3.useState("");
10659
+ const [isCaptionEditing, setIsCaptionEditing] = React3.useState(false);
10660
+ const thumbRefs = React3.useRef([]);
10661
+ const captionInputRef = React3.useRef(null);
10662
+ const touchStartX = React3.useRef(null);
10663
+ const isPanning = React3.useRef(false);
10664
+ const panStart = React3.useRef({ x: 0, y: 0, originX: 0, originY: 0 });
10201
10665
  const canEdit = canEditCaption && !!onSaveCaption;
10202
- React2.useEffect(() => {
10666
+ React3.useEffect(() => {
10203
10667
  setIndex(initialIndex);
10204
10668
  }, [initialIndex]);
10205
- const resetView = React2.useCallback(() => {
10669
+ const resetView = React3.useCallback(() => {
10206
10670
  setZoom(1);
10207
10671
  setPan({ x: 0, y: 0 });
10208
10672
  }, []);
10209
- const reloadImage = React2.useCallback(() => {
10673
+ const reloadImage = React3.useCallback(() => {
10210
10674
  resetView();
10211
10675
  setImageVersion((version) => version + 1);
10212
10676
  }, [resetView]);
10213
- React2.useEffect(() => {
10677
+ React3.useEffect(() => {
10214
10678
  resetView();
10215
10679
  setIsCaptionEditing(false);
10216
10680
  }, [index, resetView]);
10217
- React2.useEffect(() => {
10681
+ React3.useEffect(() => {
10218
10682
  if (!initialCaptionEditing || !canEdit) return;
10219
10683
  setIsCaptionEditing(true);
10220
10684
  }, [initialCaptionEditing, canEdit]);
10221
- React2.useEffect(() => {
10685
+ React3.useEffect(() => {
10222
10686
  if (!isCaptionEditing) return;
10223
10687
  const id = window.setTimeout(() => captionInputRef.current?.focus(), 40);
10224
10688
  return () => window.clearTimeout(id);
10225
10689
  }, [isCaptionEditing]);
10226
- const goPrev = React2.useCallback(() => {
10690
+ const goPrev = React3.useCallback(() => {
10227
10691
  setIndex((current2) => current2 > 0 ? current2 - 1 : images.length - 1);
10228
10692
  }, [images.length]);
10229
- const goNext = React2.useCallback(() => {
10693
+ const goNext = React3.useCallback(() => {
10230
10694
  setIndex((current2) => current2 < images.length - 1 ? current2 + 1 : 0);
10231
10695
  }, [images.length]);
10232
- const zoomIn = React2.useCallback(() => {
10696
+ const zoomIn = React3.useCallback(() => {
10233
10697
  setZoom((current2) => clampZoom(Number((current2 + ZOOM_STEP).toFixed(2))));
10234
10698
  }, []);
10235
- const zoomOut = React2.useCallback(() => {
10699
+ const zoomOut = React3.useCallback(() => {
10236
10700
  setZoom((current2) => {
10237
10701
  const next = clampZoom(Number((current2 - ZOOM_STEP).toFixed(2)));
10238
10702
  if (next <= MIN_ZOOM) setPan({ x: 0, y: 0 });
10239
10703
  return next;
10240
10704
  });
10241
10705
  }, []);
10242
- React2.useEffect(() => {
10706
+ React3.useEffect(() => {
10243
10707
  const onKeyDown = (event) => {
10244
10708
  const tag = event.target?.tagName;
10245
10709
  const isTyping = tag === "INPUT" || tag === "TEXTAREA";
@@ -10253,14 +10717,14 @@ function AttachmentImageLightbox({
10253
10717
  window.addEventListener("keydown", onKeyDown);
10254
10718
  return () => window.removeEventListener("keydown", onKeyDown);
10255
10719
  }, [goPrev, goNext, zoomIn, zoomOut, resetView]);
10256
- React2.useEffect(() => {
10720
+ React3.useEffect(() => {
10257
10721
  thumbRefs.current[index]?.scrollIntoView({
10258
10722
  behavior: "smooth",
10259
10723
  block: "nearest",
10260
10724
  inline: "center"
10261
10725
  });
10262
10726
  }, [index]);
10263
- React2.useEffect(() => {
10727
+ React3.useEffect(() => {
10264
10728
  const attachment = images[index];
10265
10729
  if (!attachment) return;
10266
10730
  setCaptionDraft(getCaptionForAttachment(attachment, localCaptions));
@@ -10731,7 +11195,7 @@ function MessageAttachmentGrid({
10731
11195
  onToggleAttachmentSelect,
10732
11196
  onAttachmentHover
10733
11197
  }) {
10734
- const [lightboxIndex, setLightboxIndex] = React2.useState(null);
11198
+ const [lightboxIndex, setLightboxIndex] = React3.useState(null);
10735
11199
  if (!images.length) return null;
10736
11200
  const visible = images.slice(0, MAX_VISIBLE);
10737
11201
  const overflow = images.length - MAX_VISIBLE;
@@ -10951,7 +11415,7 @@ function MessageAttachmentsImageSection({
10951
11415
  hoveredAttachmentId,
10952
11416
  onAttachmentHover
10953
11417
  }) {
10954
- const [lightboxIndex, setLightboxIndex] = React2.useState(null);
11418
+ const [lightboxIndex, setLightboxIndex] = React3.useState(null);
10955
11419
  if (images.length === 0) return null;
10956
11420
  const renderMediaMetaOverlay = () => /* @__PURE__ */ jsxRuntime.jsx(
10957
11421
  MediaMetaOverlay,
@@ -11426,7 +11890,7 @@ function MessageAttachmentsView({
11426
11890
  senderName,
11427
11891
  senderId
11428
11892
  }) {
11429
- const [documentPreview, setDocumentPreview] = React2.useState(null);
11893
+ const [documentPreview, setDocumentPreview] = React3.useState(null);
11430
11894
  const visibleAttachments = getVisibleAttachments(attachments);
11431
11895
  if (isDeletedForEveryone || visibleAttachments.length === 0) return null;
11432
11896
  const messageCaption = caption?.trim() || "";
@@ -11811,8 +12275,8 @@ var SavedMessagesDrawer = ({
11811
12275
  const conversationMessages = exports.useChatStore(
11812
12276
  (s) => conversationId ? s.messagesByConversation[conversationId] ?? EMPTY_CONVERSATION_MESSAGES : EMPTY_CONVERSATION_MESSAGES
11813
12277
  );
11814
- const [searchQuery, setSearchQuery] = React2.useState("");
11815
- const parsedEntries = React2.useMemo(() => {
12278
+ const [searchQuery, setSearchQuery] = React3.useState("");
12279
+ const parsedEntries = React3.useMemo(() => {
11816
12280
  return messages.map((msg) => ({
11817
12281
  msg,
11818
12282
  item: parseSavedMessage(msg, {
@@ -11833,7 +12297,7 @@ var SavedMessagesDrawer = ({
11833
12297
  variant,
11834
12298
  prefs
11835
12299
  ]);
11836
- const groupedEntries = React2.useMemo(() => {
12300
+ const groupedEntries = React3.useMemo(() => {
11837
12301
  const query = searchQuery.trim().toLowerCase();
11838
12302
  const filtered = parsedEntries.filter(({ item, msg }) => {
11839
12303
  if (!query) return true;
@@ -12106,15 +12570,15 @@ function SectionScrollArea({
12106
12570
  onLoadMore,
12107
12571
  dependencyKey
12108
12572
  }) {
12109
- const scrollRef = React2.useRef(null);
12110
- const [showArrow, setShowArrow] = React2.useState(false);
12111
- const updateArrow = React2.useCallback(() => {
12573
+ const scrollRef = React3.useRef(null);
12574
+ const [showArrow, setShowArrow] = React3.useState(false);
12575
+ const updateArrow = React3.useCallback(() => {
12112
12576
  const el = scrollRef.current;
12113
12577
  if (!el) return;
12114
12578
  const canScrollDown = el.scrollHeight - el.scrollTop - el.clientHeight > 6;
12115
12579
  setShowArrow(canScrollDown || hasMoreToLoad);
12116
12580
  }, [hasMoreToLoad]);
12117
- React2.useEffect(() => {
12581
+ React3.useEffect(() => {
12118
12582
  updateArrow();
12119
12583
  const el = scrollRef.current;
12120
12584
  if (!el) return;
@@ -12290,31 +12754,31 @@ var AttachmentsDrawer = ({ isOpen, onOpenChange, conversationId }) => {
12290
12754
  const messagesByConversation = exports.useChatStore((s) => s.messagesByConversation);
12291
12755
  const messagesMetadata = exports.useChatStore((s) => s.messagesMetadata);
12292
12756
  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);
12757
+ const [sectionVisibleCount, setSectionVisibleCount] = React3.useState({});
12758
+ const [documentPreview, setDocumentPreview] = React3.useState(null);
12759
+ const [imageLightboxIndex, setImageLightboxIndex] = React3.useState(null);
12296
12760
  const hasLoadedMessages = conversationId ? Boolean(messagesMetadata[conversationId]?.isFirstPage) : false;
12297
12761
  const isLoading = isOpen && !!conversationId && !hasLoadedMessages;
12298
- React2.useEffect(() => {
12762
+ React3.useEffect(() => {
12299
12763
  if (!isOpen) {
12300
12764
  setSectionVisibleCount({});
12301
12765
  setDocumentPreview(null);
12302
12766
  setImageLightboxIndex(null);
12303
12767
  }
12304
12768
  }, [isOpen]);
12305
- React2.useEffect(() => {
12769
+ React3.useEffect(() => {
12306
12770
  setSectionVisibleCount({});
12307
12771
  }, [conversationId]);
12308
- React2.useEffect(() => {
12772
+ React3.useEffect(() => {
12309
12773
  if (!isOpen || !conversationId || hasLoadedMessages) return;
12310
12774
  fetchMessages2(conversationId);
12311
12775
  }, [isOpen, conversationId, hasLoadedMessages, fetchMessages2]);
12312
- const attachments = React2.useMemo(() => {
12776
+ const attachments = React3.useMemo(() => {
12313
12777
  if (!conversationId) return [];
12314
12778
  const messages = messagesByConversation[conversationId] || [];
12315
12779
  return collectConversationAttachments(messages);
12316
12780
  }, [conversationId, messagesByConversation]);
12317
- const imageLightboxItems = React2.useMemo(
12781
+ const imageLightboxItems = React3.useMemo(
12318
12782
  () => attachments.filter((item) => item.type === "image").map((item) => ({
12319
12783
  id: item.id,
12320
12784
  url: item.url,
@@ -12323,14 +12787,14 @@ var AttachmentsDrawer = ({ isOpen, onOpenChange, conversationId }) => {
12323
12787
  })),
12324
12788
  [attachments]
12325
12789
  );
12326
- const openImagePreview = React2.useCallback(
12790
+ const openImagePreview = React3.useCallback(
12327
12791
  (item) => {
12328
12792
  const index = imageLightboxItems.findIndex((entry) => entry.id === item.id);
12329
12793
  if (index >= 0) setImageLightboxIndex(index);
12330
12794
  },
12331
12795
  [imageLightboxItems]
12332
12796
  );
12333
- const grouped = React2.useMemo(() => {
12797
+ const grouped = React3.useMemo(() => {
12334
12798
  const map = /* @__PURE__ */ new Map();
12335
12799
  for (const item of attachments) {
12336
12800
  const bucket = map.get(item.type) ?? [];
@@ -12430,13 +12894,13 @@ var useGroupInfo = (conversationId) => {
12430
12894
  const conversationInfos = exports.useChatStore((s) => s.conversationInfos);
12431
12895
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
12432
12896
  const info = conversationId ? conversationInfos[conversationId] : null;
12433
- const isAdmin = React2.useMemo(() => {
12897
+ const isAdmin = React3.useMemo(() => {
12434
12898
  if (!info || !loggedUserDetails) return false;
12435
12899
  return info.groupAdmins?.some(
12436
12900
  (admin) => (typeof admin === "string" ? admin : admin._id) === String(loggedUserDetails._id)
12437
12901
  ) || false;
12438
12902
  }, [info, loggedUserDetails]);
12439
- const isOwner = React2.useMemo(() => {
12903
+ const isOwner = React3.useMemo(() => {
12440
12904
  if (!info || !loggedUserDetails) return false;
12441
12905
  let ownerId = null;
12442
12906
  if (info.owner) {
@@ -12445,7 +12909,7 @@ var useGroupInfo = (conversationId) => {
12445
12909
  return String(ownerId) === String(loggedUserDetails._id);
12446
12910
  }, [info, loggedUserDetails]);
12447
12911
  const permissions = info?.groupPermissions || null;
12448
- const userPermissions = React2.useMemo(() => {
12912
+ const userPermissions = React3.useMemo(() => {
12449
12913
  return {
12450
12914
  canSendMessage: isOwner || isAdmin || !permissions?.onlyAdminCanSendMessage,
12451
12915
  canEditInfo: isOwner || isAdmin || !permissions?.onlyAdminCanEditInfo,
@@ -12494,21 +12958,21 @@ function useChannelDetailsDrawer({
12494
12958
  const conversationId = conversationInfo?._id || activeChannel?.conversationId || storeActiveConversationId || null;
12495
12959
  const { userPermissions } = useGroupInfo(conversationId);
12496
12960
  const { formatLongDate } = useChatLocale();
12497
- const storeConversation = React2.useMemo(
12961
+ const storeConversation = React3.useMemo(
12498
12962
  () => conversations.find((c) => conversationId && String(c._id) === String(conversationId)),
12499
12963
  [conversations, conversationId]
12500
12964
  );
12501
- const isGroupChannel = React2.useMemo(() => {
12965
+ const isGroupChannel = React3.useMemo(() => {
12502
12966
  if (storeConversation) {
12503
12967
  return storeConversation.conversationType === "group" || !!storeConversation.isGroup;
12504
12968
  }
12505
12969
  return !!activeChannel?.isGroup;
12506
12970
  }, [storeConversation, activeChannel?.isGroup]);
12507
- const participantsSource = React2.useMemo(
12971
+ const participantsSource = React3.useMemo(
12508
12972
  () => conversationInfo?.participants || storeConversation?.participants || [],
12509
12973
  [conversationInfo?.participants, storeConversation?.participants]
12510
12974
  );
12511
- const resolvedOtherParticipant = React2.useMemo(() => {
12975
+ const resolvedOtherParticipant = React3.useMemo(() => {
12512
12976
  if (isGroupChannel) return { id: "", user: null };
12513
12977
  return resolveOtherParticipant(
12514
12978
  {
@@ -12527,7 +12991,7 @@ function useChannelDetailsDrawer({
12527
12991
  allUsers
12528
12992
  ]);
12529
12993
  const otherUser = resolvedOtherParticipant.user;
12530
- const otherUserEmail = React2.useMemo(() => {
12994
+ const otherUserEmail = React3.useMemo(() => {
12531
12995
  if (isGroupChannel) return null;
12532
12996
  const fromResolved = resolvedOtherParticipant.user?.email?.trim();
12533
12997
  if (fromResolved) return fromResolved;
@@ -12535,7 +12999,7 @@ function useChannelDetailsDrawer({
12535
12999
  if (!otherId) return null;
12536
13000
  return allUsers.find((user) => String(user._id) === String(otherId))?.email?.trim() || activeChannel?.email?.trim() || null;
12537
13001
  }, [isGroupChannel, resolvedOtherParticipant.id, resolvedOtherParticipant.user?.email, allUsers, activeChannel?.email]);
12538
- const resolvedCreatedAt = React2.useMemo(
13002
+ const resolvedCreatedAt = React3.useMemo(
12539
13003
  () => pickDate(
12540
13004
  conversationInfo?.createdAt,
12541
13005
  conversationInfo?.updatedAt,
@@ -12548,9 +13012,9 @@ function useChannelDetailsDrawer({
12548
13012
  ),
12549
13013
  [conversationInfo, storeConversation, otherUser, conversationId, messagesByConversation]
12550
13014
  );
12551
- const [detailsLoading, setDetailsLoading] = React2.useState(false);
12552
- const fetchStartedRef = React2.useRef(null);
12553
- React2.useEffect(() => {
13015
+ const [detailsLoading, setDetailsLoading] = React3.useState(false);
13016
+ const fetchStartedRef = React3.useRef(null);
13017
+ React3.useEffect(() => {
12554
13018
  if (!isOpen) {
12555
13019
  fetchStartedRef.current = null;
12556
13020
  setDetailsLoading(false);
@@ -12592,16 +13056,16 @@ function useChannelDetailsDrawer({
12592
13056
  };
12593
13057
  }, [isOpen, conversationId, isGroupChannel, fetchAllUsers]);
12594
13058
  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(() => {
13059
+ const [isEditing, setIsEditing] = React3.useState(false);
13060
+ const [name, setName] = React3.useState("");
13061
+ const [description, setDescription] = React3.useState("");
13062
+ const [image, setImage] = React3.useState(null);
13063
+ const [uploadedImageUrl, setUploadedImageUrl] = React3.useState(null);
13064
+ const [imageUploadStatus, setImageUploadStatus] = React3.useState("idle");
13065
+ const [imageUploadError, setImageUploadError] = React3.useState(null);
13066
+ const [isSaving, setIsSaving] = React3.useState(false);
13067
+ const fileInputRef = React3.useRef(null);
13068
+ React3.useEffect(() => {
12605
13069
  if (conversationInfo) {
12606
13070
  setName(conversationInfo.groupName || activeChannel?.name || "");
12607
13071
  setDescription(conversationInfo.groupDescription || "");
@@ -12872,17 +13336,24 @@ var ChannelDetailsDrawer = (props) => {
12872
13336
  };
12873
13337
  var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12874
13338
  init_useStore();
13339
+ init_settings_types();
12875
13340
  var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
12876
13341
  const updateGroupPermissions = exports.useChatStore((s) => s.updateGroupPermissions);
12877
- const [permissions, setPermissions] = React2.useState(null);
12878
- const [isSaving, setIsSaving] = React2.useState(false);
12879
- React2.useEffect(() => {
13342
+ const { settings, isGroupPermissionLocked } = useEffectiveSettingsOptional();
13343
+ const [permissions, setPermissions] = React3.useState(null);
13344
+ const [isSaving, setIsSaving] = React3.useState(false);
13345
+ React3.useEffect(() => {
12880
13346
  if (conversationInfo?.groupPermissions) {
12881
13347
  setPermissions(conversationInfo.groupPermissions);
13348
+ return;
13349
+ }
13350
+ if (isOpen) {
13351
+ setPermissions({ ...settings.groupPolicy.defaults });
12882
13352
  }
12883
- }, [conversationInfo]);
13353
+ }, [conversationInfo, isOpen, settings.groupPolicy.defaults]);
12884
13354
  if (!conversationId || !permissions) return null;
12885
13355
  const handleToggle = (key) => {
13356
+ if (isGroupPermissionLocked(key)) return;
12886
13357
  setPermissions((prev) => prev ? { ...prev, [key]: !prev[key] } : null);
12887
13358
  };
12888
13359
  const handleSave = async () => {
@@ -12914,31 +13385,43 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
12914
13385
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "flex flex-col px-2.5 py-2"), children: [
12915
13386
  /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mb-2.5 flex-1", children: [
12916
13387
  /* @__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
- )) })
13388
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13389
+ const locked = isGroupPermissionLocked(item.key);
13390
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13391
+ "div",
13392
+ {
13393
+ className: cn(
13394
+ "flex items-start justify-between gap-3 py-2.5",
13395
+ index < permissionItems.length - 1 && "border-b border-(--chat-border)"
13396
+ ),
13397
+ children: [
13398
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-0.5", children: [
13399
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
13400
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc }),
13401
+ locked ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-medium text-(--chat-theme)", children: "Locked by administrator" }) : null
13402
+ ] }),
13403
+ /* @__PURE__ */ jsxRuntime.jsx(
13404
+ AdminPermissionTooltip,
13405
+ {
13406
+ disabled: locked,
13407
+ message: exports.ADMIN_PERMISSION_DENIED_MESSAGE,
13408
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13409
+ Switch,
13410
+ {
13411
+ size: "sm",
13412
+ className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
13413
+ disabled: locked,
13414
+ checked: item.inverted ? !permissions[item.key] : permissions[item.key],
13415
+ onCheckedChange: () => handleToggle(item.key)
13416
+ }
13417
+ )
13418
+ }
13419
+ )
13420
+ ]
13421
+ },
13422
+ item.key
13423
+ );
13424
+ }) })
12942
13425
  ] }),
12943
13426
  /* @__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
13427
  Button,
@@ -12964,51 +13447,61 @@ var PermissionDrawer_default = PermissionDrawer;
12964
13447
  // src/chat/header/admins-drawer/useAdminsDrawer.ts
12965
13448
  init_useStore();
12966
13449
  function useAdminsDrawer({ conversationId, conversationInfo }) {
12967
- const [searchQuery, setSearchQuery] = React2.useState("");
12968
- const [isManaging, setIsManaging] = React2.useState(null);
13450
+ const [searchQuery, setSearchQuery] = React3.useState("");
13451
+ const [isManaging, setIsManaging] = React3.useState(null);
12969
13452
  const manageGroupAdmins = exports.useChatStore((s) => s.manageGroupAdmins);
12970
13453
  const { isOwner } = useGroupInfo(conversationId);
12971
- const participants = React2.useMemo(
13454
+ const { settings } = useEffectiveSettingsOptional();
13455
+ const maxAdmins = settings.maxAdminsPerGroup;
13456
+ const participants = React3.useMemo(
12972
13457
  () => conversationInfo?.participants ?? [],
12973
13458
  [conversationInfo?.participants]
12974
13459
  );
12975
- const admins = React2.useMemo(
13460
+ const admins = React3.useMemo(
12976
13461
  () => conversationInfo?.groupAdmins ?? [],
12977
13462
  [conversationInfo?.groupAdmins]
12978
13463
  );
12979
- const ownerId = React2.useMemo(() => {
13464
+ const ownerId = React3.useMemo(() => {
12980
13465
  if (!conversationInfo?.owner) return null;
12981
13466
  return typeof conversationInfo.owner === "object" && conversationInfo.owner !== null ? conversationInfo.owner._id || conversationInfo.owner.id : conversationInfo.owner;
12982
13467
  }, [conversationInfo?.owner]);
12983
- const adminIds = React2.useMemo(
13468
+ const adminIds = React3.useMemo(
12984
13469
  () => new Set(
12985
13470
  admins.map((a) => typeof a === "string" ? a : a._id).filter((id) => Boolean(id))
12986
13471
  ),
12987
13472
  [admins]
12988
13473
  );
12989
- const isAdmin = React2.useCallback((userId) => adminIds.has(userId), [adminIds]);
12990
- const filteredParticipants = React2.useMemo(() => {
13474
+ const isAdmin = React3.useCallback((userId) => adminIds.has(userId), [adminIds]);
13475
+ const filteredParticipants = React3.useMemo(() => {
12991
13476
  if (!searchQuery.trim()) return participants;
12992
13477
  return participants.filter(
12993
13478
  (p) => p.name?.toLowerCase().includes(searchQuery.toLowerCase()) || p.email?.toLowerCase().includes(searchQuery.toLowerCase())
12994
13479
  );
12995
13480
  }, [participants, searchQuery]);
12996
- const adminParticipants = React2.useMemo(
13481
+ const adminParticipants = React3.useMemo(
12997
13482
  () => filteredParticipants.filter((p) => {
12998
13483
  const id = typeof p === "string" ? p : p._id;
12999
13484
  return id && (adminIds.has(id) || id === ownerId);
13000
13485
  }),
13001
13486
  [filteredParticipants, adminIds, ownerId]
13002
13487
  );
13003
- const promotableParticipants = React2.useMemo(
13488
+ const promotableParticipants = React3.useMemo(
13004
13489
  () => filteredParticipants.filter((p) => {
13005
13490
  const id = typeof p === "string" ? p : p._id;
13006
13491
  return id && id !== ownerId && !adminIds.has(id);
13007
13492
  }),
13008
13493
  [filteredParticipants, adminIds, ownerId]
13009
13494
  );
13495
+ const adminCount = admins.length + (ownerId ? 1 : 0);
13496
+ const atAdminLimit = adminCount >= maxAdmins;
13010
13497
  const handleManageAdmin = async (userId, type) => {
13011
13498
  if (!conversationId) return;
13499
+ if (type === "add" && adminCount >= maxAdmins) {
13500
+ exports.useChatStore.getState().handleAppError(
13501
+ `You can have up to ${maxAdmins} admins in this group.`
13502
+ );
13503
+ return;
13504
+ }
13012
13505
  setIsManaging(userId);
13013
13506
  try {
13014
13507
  await manageGroupAdmins({
@@ -13020,7 +13513,6 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
13020
13513
  setIsManaging(null);
13021
13514
  }
13022
13515
  };
13023
- const adminCount = admins.length + (ownerId ? 1 : 0);
13024
13516
  return {
13025
13517
  searchQuery,
13026
13518
  setSearchQuery,
@@ -13032,6 +13524,8 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
13032
13524
  promotableParticipants,
13033
13525
  handleManageAdmin,
13034
13526
  adminCount,
13527
+ maxAdmins,
13528
+ atAdminLimit,
13035
13529
  hasRequiredData: Boolean(conversationId && conversationInfo)
13036
13530
  };
13037
13531
  }
@@ -13044,6 +13538,8 @@ function AdminParticipantRow({
13044
13538
  isAdmin,
13045
13539
  showPromote,
13046
13540
  showDemote,
13541
+ promoteDisabled = false,
13542
+ promoteDisabledReason,
13047
13543
  isOwnerViewer,
13048
13544
  isManaging,
13049
13545
  onManageAdmin
@@ -13084,14 +13580,24 @@ function AdminParticipantRow({
13084
13580
  }
13085
13581
  ),
13086
13582
  showPromote && isOwnerViewer && /* @__PURE__ */ jsxRuntime.jsx(
13087
- Button,
13583
+ AdminPermissionTooltip,
13088
13584
  {
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 })
13585
+ disabled: promoteDisabled,
13586
+ message: promoteDisabledReason,
13587
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13588
+ Button,
13589
+ {
13590
+ variant: "ghost",
13591
+ size: "sm",
13592
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13593
+ onClick: () => {
13594
+ if (promoteDisabled) return;
13595
+ onManageAdmin(memberId, "add");
13596
+ },
13597
+ disabled: isManaging === memberId || promoteDisabled,
13598
+ children: isManaging === memberId ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { size: 13 })
13599
+ }
13600
+ )
13095
13601
  }
13096
13602
  )
13097
13603
  ] })
@@ -13115,7 +13621,9 @@ function AdminParticipantList({
13115
13621
  isAdmin,
13116
13622
  onManageAdmin,
13117
13623
  showPromote,
13118
- showDemoteForAdmins
13624
+ showDemoteForAdmins,
13625
+ promoteDisabled = false,
13626
+ promoteDisabledReason
13119
13627
  }) {
13120
13628
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: members.map((member, index) => {
13121
13629
  const { memberId, memberName, memberEmail, memberImage } = getMemberFields(member);
@@ -13133,6 +13641,8 @@ function AdminParticipantList({
13133
13641
  isAdmin: userIsAdmin,
13134
13642
  showPromote: showPromote && !userIsAdmin && !userIsOwner,
13135
13643
  showDemote: showDemoteForAdmins ? userIsAdmin && !userIsOwner : false,
13644
+ promoteDisabled,
13645
+ promoteDisabledReason,
13136
13646
  isOwnerViewer,
13137
13647
  isManaging,
13138
13648
  onManageAdmin
@@ -13152,6 +13662,8 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13152
13662
  promotableParticipants,
13153
13663
  handleManageAdmin,
13154
13664
  adminCount,
13665
+ maxAdmins,
13666
+ atAdminLimit,
13155
13667
  hasRequiredData
13156
13668
  } = useAdminsDrawer({ conversationId, conversationInfo });
13157
13669
  if (!hasRequiredData) return null;
@@ -13171,7 +13683,10 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13171
13683
  /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Administrators" }),
13172
13684
  /* @__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
13685
  ] }),
13174
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: chatSheetSubtitleClass, children: "Manage who can adjust group settings and members." })
13686
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: chatSheetSubtitleClass, children: [
13687
+ "Manage who can adjust group settings and members",
13688
+ maxAdmins ? ` (max ${maxAdmins}).` : "."
13689
+ ] })
13175
13690
  ] }),
13176
13691
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13177
13692
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2.5 px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -13214,7 +13729,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13214
13729
  ownerId,
13215
13730
  isAdmin,
13216
13731
  onManageAdmin: handleManageAdmin,
13217
- showPromote: true
13732
+ showPromote: true,
13733
+ promoteDisabled: atAdminLimit,
13734
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13218
13735
  }
13219
13736
  ) })
13220
13737
  ] }),
@@ -13231,7 +13748,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13231
13748
  isAdmin,
13232
13749
  onManageAdmin: handleManageAdmin,
13233
13750
  showPromote: true,
13234
- showDemoteForAdmins: true
13751
+ showDemoteForAdmins: true,
13752
+ promoteDisabled: atAdminLimit,
13753
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13235
13754
  }
13236
13755
  ) })
13237
13756
  ] }),
@@ -13247,21 +13766,24 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13247
13766
  var AdminsDrawer_default = AdminsDrawer;
13248
13767
  init_useStore();
13249
13768
  var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
13250
- const [searchQuery, setSearchQuery] = React2.useState("");
13251
- const [isManaging, setIsManaging] = React2.useState(null);
13769
+ const [searchQuery, setSearchQuery] = React3.useState("");
13770
+ const [isManaging, setIsManaging] = React3.useState(null);
13252
13771
  const allUsers = exports.useChatStore((s) => s.allUsers);
13253
13772
  const manageGroupMembers = exports.useChatStore((s) => s.manageGroupMembers);
13254
13773
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
13255
- const filteredUsers = React2.useMemo(() => {
13774
+ const filteredUsers = React3.useMemo(() => {
13256
13775
  if (!searchQuery.trim()) return allUsers;
13257
13776
  return allUsers.filter(
13258
13777
  (u) => u.name?.toLowerCase().includes(searchQuery.toLowerCase()) || u.email?.toLowerCase().includes(searchQuery.toLowerCase())
13259
13778
  );
13260
13779
  }, [allUsers, searchQuery]);
13261
13780
  const { userPermissions } = useGroupInfo(conversationId);
13781
+ const { settings } = useEffectiveSettingsOptional();
13782
+ const maxParticipants = settings.maxParticipantsPerGroup;
13262
13783
  if (!conversationId || !conversationInfo) return null;
13263
13784
  const participants = conversationInfo.participants || [];
13264
13785
  const admins = conversationInfo.groupAdmins || [];
13786
+ const atParticipantLimit = participants.length >= maxParticipants;
13265
13787
  let ownerId = null;
13266
13788
  if (conversationInfo.owner) {
13267
13789
  ownerId = typeof conversationInfo.owner === "object" && conversationInfo.owner !== null ? conversationInfo.owner._id || conversationInfo.owner.id : conversationInfo.owner;
@@ -13315,14 +13837,24 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13315
13837
  }
13316
13838
  ),
13317
13839
  options.showAdd && /* @__PURE__ */ jsxRuntime.jsx(
13318
- Button,
13840
+ AdminPermissionTooltip,
13319
13841
  {
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 })
13842
+ disabled: atParticipantLimit,
13843
+ message: `Maximum ${maxParticipants} participants allowed in this group.`,
13844
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13845
+ Button,
13846
+ {
13847
+ variant: "ghost",
13848
+ size: "sm",
13849
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13850
+ onClick: () => {
13851
+ if (atParticipantLimit) return;
13852
+ handleManageMember(memberId, "add");
13853
+ },
13854
+ disabled: isManaging === memberId || atParticipantLimit,
13855
+ children: isManaging === memberId ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { size: 13 })
13856
+ }
13857
+ )
13326
13858
  }
13327
13859
  )
13328
13860
  ] })
@@ -13344,7 +13876,11 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13344
13876
  /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Management" }),
13345
13877
  /* @__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
13878
  ] }),
13347
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: chatSheetSubtitleClass, children: "Manage members and invite people." })
13879
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: chatSheetSubtitleClass, children: [
13880
+ "Manage members and invite people (max ",
13881
+ maxParticipants,
13882
+ ")."
13883
+ ] })
13348
13884
  ] }),
13349
13885
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13350
13886
  !searchQuery && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mb-2.5", children: [
@@ -13465,8 +14001,8 @@ var ChatActionModal_default = ChatActionModal;
13465
14001
  // src/chat/channel-header/useChannelHeader.ts
13466
14002
  init_useStore();
13467
14003
  function useDebounce(value, delay) {
13468
- const [debouncedValue, setDebouncedValue] = React2.useState(value);
13469
- React2.useEffect(() => {
14004
+ const [debouncedValue, setDebouncedValue] = React3.useState(value);
14005
+ React3.useEffect(() => {
13470
14006
  const timer = setTimeout(() => setDebouncedValue(value), delay);
13471
14007
  return () => {
13472
14008
  clearTimeout(timer);
@@ -13486,7 +14022,9 @@ init_store_helpers();
13486
14022
  function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13487
14023
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
13488
14024
  const composerDisabledByConversation = exports.useChatStore((s) => s.composerDisabledByConversation);
13489
- const [isBlocking, setIsBlocking] = React2.useState(false);
14025
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
14026
+ const blockUsersGate = getBlockUsersGate();
14027
+ const [isBlocking, setIsBlocking] = React3.useState(false);
13490
14028
  const resolvedConversationId = conversationId ?? activeChannel?.conversationId ?? null;
13491
14029
  const composerDisabledReason = resolvedConversationId ? composerDisabledByConversation[resolvedConversationId] : void 0;
13492
14030
  const isBlockedFromConversation = exports.useChatStore((s) => {
@@ -13504,9 +14042,15 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13504
14042
  return !!conv?.isBlocked || !!activeChannel.isBlocked;
13505
14043
  });
13506
14044
  const isBlocked = isBlockedFromConversation || !activeChannel?.isGroup && !!composerDisabledReason && /block/i.test(composerDisabledReason);
13507
- const toggleBlock = React2.useCallback(async () => {
14045
+ const toggleBlock = React3.useCallback(async () => {
13508
14046
  if (!activeChannel?.id || activeChannel.isGroup) return false;
13509
14047
  const nextBlocked = !isBlocked;
14048
+ if (nextBlocked && !blockUsersGate.enabled) {
14049
+ exports.useChatStore.getState().handleAppError(
14050
+ blockUsersGate.disabledReason || "Blocking users is disabled by your administrator."
14051
+ );
14052
+ return false;
14053
+ }
13510
14054
  const type = nextBlocked ? "block" : "unblock";
13511
14055
  try {
13512
14056
  setIsBlocking(true);
@@ -13527,8 +14071,8 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13527
14071
  } finally {
13528
14072
  setIsBlocking(false);
13529
14073
  }
13530
- }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId]);
13531
- return { isBlocked, isBlocking, toggleBlock };
14074
+ }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId, blockUsersGate]);
14075
+ return { isBlocked, isBlocking, toggleBlock, blockUsersGate };
13532
14076
  }
13533
14077
 
13534
14078
  // src/chat/channel-header/useChannelHeader.ts
@@ -13561,8 +14105,8 @@ function useChannelHeader({
13561
14105
  onBack,
13562
14106
  onActiveChannelPatch
13563
14107
  }) {
13564
- const [state, setState] = React2.useState(initialState2);
13565
- const updateState = React2.useCallback((updates) => {
14108
+ const [state, setState] = React3.useState(initialState2);
14109
+ const updateState = React3.useCallback((updates) => {
13566
14110
  setState((prev) => ({ ...prev, ...updates }));
13567
14111
  }, []);
13568
14112
  const { formatLongDate } = useChatLocale();
@@ -13602,7 +14146,7 @@ function useChannelHeader({
13602
14146
  const setSearchedMessageId = exports.useChatStore((s) => s.setSearchedMessageId);
13603
14147
  const isSearchActive = exports.useChatStore((s) => s.isSearchActive);
13604
14148
  const debouncedSearch = useDebounce(state.searchQuery, 800);
13605
- const handleSearch = React2.useCallback(async (query, cursor) => {
14149
+ const handleSearch = React3.useCallback(async (query, cursor) => {
13606
14150
  if (!conversationId) return;
13607
14151
  const isFirstPage = !cursor;
13608
14152
  updateState({ isSearching: isFirstPage, isLoadingMoreSearch: !isFirstPage });
@@ -13623,7 +14167,7 @@ function useChannelHeader({
13623
14167
  updateState({ isSearching: false, isLoadingMoreSearch: false });
13624
14168
  }
13625
14169
  }, [conversationId, updateState]);
13626
- React2.useEffect(() => {
14170
+ React3.useEffect(() => {
13627
14171
  if (debouncedSearch && conversationId) {
13628
14172
  updateState({ searchCursor: null, searchHasMore: false });
13629
14173
  handleSearch(debouncedSearch);
@@ -13657,32 +14201,32 @@ function useChannelHeader({
13657
14201
  handleCloseSearch();
13658
14202
  };
13659
14203
  const canViewMembers = isAdmin || userPermissions.canAddMember || userPermissions.canRemoveMember;
13660
- const prevIsClearingRef = React2.useRef(isClearingChat);
13661
- React2.useEffect(() => {
14204
+ const prevIsClearingRef = React3.useRef(isClearingChat);
14205
+ React3.useEffect(() => {
13662
14206
  if (prevIsClearingRef.current === true && !isClearingChat) {
13663
14207
  updateState({ isClearChatAlertOpen: false });
13664
14208
  }
13665
14209
  prevIsClearingRef.current = isClearingChat;
13666
14210
  }, [isClearingChat, updateState]);
13667
- const prevIsDeletingRef = React2.useRef(isDeletingConversation);
13668
- React2.useEffect(() => {
14211
+ const prevIsDeletingRef = React3.useRef(isDeletingConversation);
14212
+ React3.useEffect(() => {
13669
14213
  if (prevIsDeletingRef.current === true && !isDeletingConversation) {
13670
14214
  updateState({ isDeletingConversationAlertOpen: false });
13671
14215
  onBack?.();
13672
14216
  }
13673
14217
  prevIsDeletingRef.current = isDeletingConversation;
13674
14218
  }, [isDeletingConversation, onBack, updateState]);
13675
- React2.useEffect(() => {
14219
+ React3.useEffect(() => {
13676
14220
  if (state.isPinOpen && conversationId) {
13677
14221
  fetchPinnedMessages2(conversationId);
13678
14222
  }
13679
14223
  }, [state.isPinOpen, conversationId, fetchPinnedMessages2]);
13680
- React2.useEffect(() => {
14224
+ React3.useEffect(() => {
13681
14225
  if (state.isFavoriteOpen && conversationId) {
13682
14226
  fetchStarredMessages2(conversationId);
13683
14227
  }
13684
14228
  }, [state.isFavoriteOpen, conversationId, fetchStarredMessages2]);
13685
- React2.useEffect(() => {
14229
+ React3.useEffect(() => {
13686
14230
  if (state.isAttachmentsOpen && conversationId) {
13687
14231
  const meta = exports.useChatStore.getState().messagesMetadata[conversationId];
13688
14232
  if (!meta?.isFirstPage) {
@@ -14064,6 +14608,7 @@ var MessageReactions_default = MessageReactions;
14064
14608
  var MessageToolbar = ({
14065
14609
  isSender,
14066
14610
  isRecent,
14611
+ isWithinDeleteWindow = isRecent,
14067
14612
  isPinned,
14068
14613
  isStarred,
14069
14614
  isPinPending,
@@ -14087,7 +14632,7 @@ var MessageToolbar = ({
14087
14632
  }) => {
14088
14633
  const { isDark, scopeClassName, themeStyles } = useChatTheme();
14089
14634
  const showDeleteMenu = isSender || hasAttachments || attachmentDeleteMode;
14090
- const showDeleteForEveryone = isSender && isRecent;
14635
+ const showDeleteForEveryone = isSender && isWithinDeleteWindow;
14091
14636
  return /* @__PURE__ */ jsxRuntime.jsxs(
14092
14637
  "div",
14093
14638
  {
@@ -14242,8 +14787,8 @@ function SentAttachmentCaptionModal({
14242
14787
  canEdit = false,
14243
14788
  onSave
14244
14789
  }) {
14245
- const [draft, setDraft] = React2.useState("");
14246
- React2.useEffect(() => {
14790
+ const [draft, setDraft] = React3.useState("");
14791
+ React3.useEffect(() => {
14247
14792
  if (open && attachment) {
14248
14793
  setDraft(attachment.caption || "");
14249
14794
  }
@@ -14409,16 +14954,59 @@ function resolveForwardItems(selectedKeys, messages) {
14409
14954
  }
14410
14955
 
14411
14956
  // 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;
14957
+ init_settings_types();
14958
+
14959
+ // src/lib/effective-settings.helpers.ts
14960
+ function minutesToMs(minutes) {
14961
+ return Math.max(0, minutes) * 60 * 1e3;
14962
+ }
14963
+ function getEditWindowMs(timers, isGroup) {
14964
+ return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
14415
14965
  }
14416
- function isWithinMessageEditWindow(createdAt, isGroup) {
14966
+ function getDeleteWindowMs(timers, isGroup) {
14967
+ return minutesToMs(
14968
+ isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
14969
+ );
14970
+ }
14971
+ function isWithinWindow(createdAt, windowMs) {
14417
14972
  if (!createdAt) return false;
14418
14973
  const date = new Date(createdAt);
14419
14974
  if (isNaN(date.getTime())) return false;
14420
14975
  const diff = Date.now() - date.getTime();
14421
- return diff >= 0 && diff < getMessageEditWindowMs(isGroup);
14976
+ return diff >= 0 && diff < windowMs;
14977
+ }
14978
+ function isWithinMessageEditWindow(createdAt, timers, isGroup) {
14979
+ return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
14980
+ }
14981
+ function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
14982
+ return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
14983
+ }
14984
+ function matchesAllowedMimeType(fileType, allowedMimeTypes) {
14985
+ const mime = (fileType || "application/octet-stream").toLowerCase();
14986
+ return allowedMimeTypes.some((pattern) => {
14987
+ const normalized = pattern.trim().toLowerCase();
14988
+ if (!normalized) return false;
14989
+ if (normalized.endsWith("/*")) {
14990
+ const prefix = normalized.slice(0, -1);
14991
+ return mime.startsWith(prefix);
14992
+ }
14993
+ return mime === normalized;
14994
+ });
14995
+ }
14996
+ function buildAcceptFromMimeTypes(allowedMimeTypes) {
14997
+ return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
14998
+ }
14999
+ function formatFileSizeMB(sizeBytes) {
15000
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
15001
+ }
15002
+
15003
+ // src/chat/lib/chat-edit-window.ts
15004
+ var FALLBACK_TIMERS = exports.DEFAULT_EFFECTIVE_SETTINGS.messageTimers;
15005
+ function isWithinMessageEditWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
15006
+ return isWithinMessageEditWindow(createdAt, timers, isGroup);
15007
+ }
15008
+ function isWithinMessageDeleteWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
15009
+ return isWithinMessageDeleteWindow(createdAt, timers, isGroup);
14422
15010
  }
14423
15011
 
14424
15012
  // src/chat/message-item/useMessageItemActions.ts
@@ -14443,7 +15031,7 @@ function useMessageItemActions({
14443
15031
  selectionMode = false,
14444
15032
  selectedKeys
14445
15033
  }) {
14446
- const [state, setState] = React2.useState({
15034
+ const [state, setState] = React3.useState({
14447
15035
  isHoverMessage: false,
14448
15036
  isEditing: false,
14449
15037
  editValue: String(message || ""),
@@ -14456,7 +15044,7 @@ function useMessageItemActions({
14456
15044
  const updateState = (updates) => {
14457
15045
  setState((prev) => ({ ...prev, ...updates }));
14458
15046
  };
14459
- React2.useEffect(() => {
15047
+ React3.useEffect(() => {
14460
15048
  setState((prev) => ({ ...prev, editValue: String(message || "") }));
14461
15049
  }, [message]);
14462
15050
  const emitDmEdit = exports.useChatStore((s) => s.emitDmEdit);
@@ -14496,6 +15084,9 @@ function useMessageItemActions({
14496
15084
  const canUnpin = !isPinned || isPinnedByMe;
14497
15085
  const { userPermissions } = useGroupInfo(conversationId || null);
14498
15086
  const { formatMessageTime } = useChatLocale();
15087
+ const { settings } = useEffectiveSettingsOptional();
15088
+ const messageTimers = settings.messageTimers;
15089
+ const maxPinnedMessages = settings.maxPinnedMessagesPerConversation;
14499
15090
  const visibleAttachments = getVisibleAttachments(attachments);
14500
15091
  const hasAttachments = visibleAttachments.length > 0;
14501
15092
  const hasMultiAttachments = hasAttachments && messageUsesAttachmentSelection({ attachments: visibleAttachments });
@@ -14512,7 +15103,8 @@ function useMessageItemActions({
14512
15103
  const hasText = Boolean(message?.trim());
14513
15104
  const isSelected = !!mid && !!selectedKeys && isMessageWhollySelected(selectedKeys, mid) && !hasMultiAttachments;
14514
15105
  const canEdit = (!isGroup || userPermissions.canEditMessage) && hasText;
14515
- const isWithinEditWindow = () => isWithinMessageEditWindow(createdAt, isGroup);
15106
+ const isWithinEditWindow = () => isWithinMessageEditWindow2(createdAt, isGroup, messageTimers);
15107
+ const isWithinDeleteWindow = () => isWithinMessageDeleteWindow2(createdAt, isGroup, messageTimers);
14516
15108
  const canEditAttachmentCaption = isSender && isWithinEditWindow() && (!isGroup || userPermissions.canEditMessage) && !isDeletedForEveryone && !!mid;
14517
15109
  const handleAttachmentCaptionClick = (attachment) => {
14518
15110
  if (!canEditAttachmentCaption) return;
@@ -14681,6 +15273,15 @@ function useMessageItemActions({
14681
15273
  });
14682
15274
  }
14683
15275
  } else {
15276
+ if (conversationId) {
15277
+ const pinnedCount = (pinnedMessagesByConversation[conversationId] || []).length || exports.useChatStore.getState().liveConversationCounts[conversationId]?.pinnedCount || 0;
15278
+ if (pinnedCount >= maxPinnedMessages) {
15279
+ exports.useChatStore.getState().handleAppError(
15280
+ `You can pin up to ${maxPinnedMessages} messages in this conversation.`
15281
+ );
15282
+ return;
15283
+ }
15284
+ }
14684
15285
  if (isGroup) {
14685
15286
  if (!conversationId) return;
14686
15287
  emitGroupPin({
@@ -14739,6 +15340,7 @@ function useMessageItemActions({
14739
15340
  canUnpin,
14740
15341
  canEdit,
14741
15342
  isWithinEditWindow,
15343
+ isWithinDeleteWindow,
14742
15344
  canEditAttachmentCaption,
14743
15345
  visibleAttachments,
14744
15346
  hasAttachments,
@@ -14770,7 +15372,7 @@ function useMessageItemActions({
14770
15372
  }
14771
15373
 
14772
15374
  // src/chat/lib/reply-preview.ts
14773
- function normalizeAttachments(replyTo, loggedUserId) {
15375
+ function normalizeAttachments2(replyTo, loggedUserId) {
14774
15376
  const raw = replyTo?.attachments;
14775
15377
  if (!Array.isArray(raw) || raw.length === 0) return [];
14776
15378
  return raw.map((att, index) => {
@@ -14783,7 +15385,7 @@ function resolveReplyToMessage(replyTo, messagesById, loggedUserId) {
14783
15385
  if (typeof replyTo === "object" && (replyTo.content?.trim() || replyTo.attachments?.length)) {
14784
15386
  return {
14785
15387
  ...replyTo,
14786
- attachments: normalizeAttachments(replyTo, loggedUserId)
15388
+ attachments: normalizeAttachments2(replyTo, loggedUserId)
14787
15389
  };
14788
15390
  }
14789
15391
  const replyId = String(
@@ -15220,6 +15822,7 @@ function MessageItemView(props) {
15220
15822
  canUnpin,
15221
15823
  canEdit,
15222
15824
  isWithinEditWindow,
15825
+ isWithinDeleteWindow,
15223
15826
  canEditAttachmentCaption,
15224
15827
  visibleAttachments,
15225
15828
  hasAttachments,
@@ -15311,6 +15914,7 @@ function MessageItemView(props) {
15311
15914
  {
15312
15915
  isSender,
15313
15916
  isRecent: isWithinEditWindow(),
15917
+ isWithinDeleteWindow: isWithinDeleteWindow(),
15314
15918
  messageId: mid,
15315
15919
  isPinned,
15316
15920
  isStarred,
@@ -15500,7 +16104,7 @@ function MessageSelectionBar({
15500
16104
  className
15501
16105
  }) {
15502
16106
  const { scopeClassName, themeStyles } = useChatTheme();
15503
- const [isDeleteOpen, setIsDeleteOpen] = React2.useState(false);
16107
+ const [isDeleteOpen, setIsDeleteOpen] = React3.useState(false);
15504
16108
  if (count === 0) return null;
15505
16109
  const countLabel = selectionLabel === "files" ? count === 1 ? "file" : "files" : count === 1 ? "message" : "messages";
15506
16110
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -15605,6 +16209,7 @@ function MessageSelectionBar({
15605
16209
  }
15606
16210
 
15607
16211
  // src/chat/active-channel-messages/utils.ts
16212
+ init_settings_types();
15608
16213
  var getMessageSenderId2 = (msg) => msg ? String(msg.senderId || msg.sender?._id || "") : "";
15609
16214
  var isLikelyObjectId = (value) => /^[a-f\d]{24}$/i.test(value);
15610
16215
  function resolveGroupSenderDisplay(message, allUsers = [], participants = []) {
@@ -15636,7 +16241,7 @@ var isSameMessageSide = (a, b, isGroupChat) => {
15636
16241
  const bId = getMessageSenderId2(b);
15637
16242
  return aId !== "" && aId === bId;
15638
16243
  };
15639
- function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
16244
+ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup, timers = exports.DEFAULT_EFFECTIVE_SETTINGS.messageTimers) {
15640
16245
  if (selectedKeys.size === 0) return false;
15641
16246
  const parsed = [...selectedKeys].map((key) => parseSelectionKey(key));
15642
16247
  const messageIds = new Set(parsed.map((entry) => entry.messageId));
@@ -15644,7 +16249,7 @@ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
15644
16249
  const messageId2 = [...messageIds][0];
15645
16250
  const message = messages.find((entry) => String(entry._id || entry.id) === messageId2);
15646
16251
  if (!message?.isOwnMessage) return false;
15647
- if (!isWithinMessageEditWindow(message.timestamp, isGroup)) return false;
16252
+ if (!isWithinMessageDeleteWindow2(message.timestamp, isGroup, timers)) return false;
15648
16253
  const hasAttachmentKey = parsed.some((entry) => entry.attachmentId);
15649
16254
  if (!hasAttachmentKey) {
15650
16255
  return selectedKeys.size === 1;
@@ -15808,22 +16413,24 @@ function useActiveChannelMessages({
15808
16413
  onSelectionModeChange,
15809
16414
  conversationInfo
15810
16415
  }) {
15811
- const [selectionMode, setSelectionMode] = React2.useState(false);
15812
- const [selectedKeys, setSelectedKeys] = React2.useState(/* @__PURE__ */ new Set());
16416
+ const [selectionMode, setSelectionMode] = React3.useState(false);
16417
+ const [selectedKeys, setSelectedKeys] = React3.useState(/* @__PURE__ */ new Set());
15813
16418
  const emitDmDelete = exports.useChatStore((s) => s.emitDmDelete);
15814
16419
  const emitGroupDelete = exports.useChatStore((s) => s.emitGroupDelete);
15815
16420
  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);
16421
+ const { settings } = useEffectiveSettingsOptional();
16422
+ const messageTimers = settings.messageTimers;
16423
+ const scrollContainerRef = React3.useRef(null);
16424
+ const bottomRef = React3.useRef(null);
16425
+ const messageListInitializedRef = React3.useRef(false);
16426
+ const previousMessageCountRef = React3.useRef(0);
16427
+ const previousLastMessageIdRef = React3.useRef(null);
16428
+ const [enteringMessageIds, setEnteringMessageIds] = React3.useState(/* @__PURE__ */ new Set());
16429
+ const [showScrollToBottom, setShowScrollToBottom] = React3.useState(false);
16430
+ const stickToBottomRef = React3.useRef(true);
16431
+ const pendingJumpRef = React3.useRef(null);
16432
+ const jumpPagesRef = React3.useRef(0);
16433
+ const [jumpNonce, setJumpNonce] = React3.useState(0);
15827
16434
  const { fetchMessages: fetchMessages2, messagesMetadata, deletedForMeIds, searchedMessageId, isSearchActive, setSearchActive, setSearchedMessageId, fetchSearchMessageContext } = exports.useChatStore();
15828
16435
  const loggedUserImage = exports.useChatStore((s) => s.loggedUserDetails?.image);
15829
16436
  const conversations = exports.useChatStore((s) => s.conversations);
@@ -15832,29 +16439,29 @@ function useActiveChannelMessages({
15832
16439
  const mappedApiMessages = conversationMessages.map(
15833
16440
  (m) => mapApiMessage(m, activeChannel, conversationId, loggedUserId, activeConversation)
15834
16441
  );
15835
- const displayMessages = React2.useMemo(
16442
+ const displayMessages = React3.useMemo(
15836
16443
  () => 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
16444
  [mappedApiMessages, localMessages, loggedUserId, deletedForMeIds, conversationId]
15838
16445
  );
15839
- const messagesById = React2.useMemo(
16446
+ const messagesById = React3.useMemo(
15840
16447
  () => new Map(displayMessages.map((message) => [String(message._id || message.id), message])),
15841
16448
  [displayMessages]
15842
16449
  );
15843
- const messagesWithResolvedReplies = React2.useMemo(
16450
+ const messagesWithResolvedReplies = React3.useMemo(
15844
16451
  () => displayMessages.map((message) => ({
15845
16452
  ...message,
15846
16453
  replyTo: resolveReplyToMessage(message.replyTo, messagesById, loggedUserId)
15847
16454
  })),
15848
16455
  [displayMessages, messagesById, loggedUserId]
15849
16456
  );
15850
- const jumpDateBounds = React2.useMemo(
16457
+ const jumpDateBounds = React3.useMemo(
15851
16458
  () => resolveJumpDateBounds(
15852
16459
  conversationInfo?.createdAt,
15853
16460
  displayMessages[0]?.timestamp
15854
16461
  ),
15855
16462
  [conversationInfo?.createdAt, displayMessages]
15856
16463
  );
15857
- React2.useEffect(() => {
16464
+ React3.useEffect(() => {
15858
16465
  const messages = messagesWithResolvedReplies;
15859
16466
  const lastMessage = messages[messages.length - 1];
15860
16467
  const lastMessageId = lastMessage ? String(lastMessage._id || lastMessage.id) : null;
@@ -15881,11 +16488,11 @@ function useActiveChannelMessages({
15881
16488
  }, [messagesWithResolvedReplies]);
15882
16489
  const isInitialLoading = isLoading && currentMeta?.isFirstPage;
15883
16490
  const isLoadingMore = isLoading && !currentMeta?.isFirstPage;
15884
- const clearSelection = React2.useCallback(() => {
16491
+ const clearSelection = React3.useCallback(() => {
15885
16492
  setSelectionMode(false);
15886
16493
  setSelectedKeys(/* @__PURE__ */ new Set());
15887
16494
  }, []);
15888
- React2.useEffect(() => {
16495
+ React3.useEffect(() => {
15889
16496
  clearSelection();
15890
16497
  messageListInitializedRef.current = false;
15891
16498
  previousMessageCountRef.current = 0;
@@ -15896,14 +16503,14 @@ function useActiveChannelMessages({
15896
16503
  stickToBottomRef.current = true;
15897
16504
  setShowScrollToBottom(false);
15898
16505
  }, [conversationId, clearSelection]);
15899
- const onSelectionModeChangeRef = React2.useRef(onSelectionModeChange);
15900
- React2.useEffect(() => {
16506
+ const onSelectionModeChangeRef = React3.useRef(onSelectionModeChange);
16507
+ React3.useEffect(() => {
15901
16508
  onSelectionModeChangeRef.current = onSelectionModeChange;
15902
16509
  }, [onSelectionModeChange]);
15903
- React2.useEffect(() => {
16510
+ React3.useEffect(() => {
15904
16511
  onSelectionModeChangeRef.current?.(selectionMode);
15905
16512
  }, [selectionMode]);
15906
- const toggleSelection = React2.useCallback((message, attachmentId) => {
16513
+ const toggleSelection = React3.useCallback((message, attachmentId) => {
15907
16514
  const messageId2 = String(message._id || message.id);
15908
16515
  if (!messageId2) return;
15909
16516
  const useAttachmentSelection = messageUsesAttachmentSelection(message);
@@ -15916,7 +16523,7 @@ function useActiveChannelMessages({
15916
16523
  return next;
15917
16524
  });
15918
16525
  }, []);
15919
- const enterSelectionMode = React2.useCallback((message, attachmentId) => {
16526
+ const enterSelectionMode = React3.useCallback((message, attachmentId) => {
15920
16527
  const messageId2 = String(message._id || message.id);
15921
16528
  if (!messageId2) return;
15922
16529
  setSelectionMode(true);
@@ -15930,17 +16537,23 @@ function useActiveChannelMessages({
15930
16537
  setSelectedKeys((prev) => new Set(prev).add(messageId2));
15931
16538
  }, []);
15932
16539
  const selectedForwardItems = resolveForwardItems(selectedKeys, displayMessages);
15933
- const canDeleteForEveryone = React2.useMemo(
15934
- () => canDeleteSelectionForEveryone(selectedKeys, displayMessages, !!activeChannel?.isGroup),
15935
- [selectedKeys, displayMessages, activeChannel?.isGroup]
16540
+ const canDeleteForEveryone = React3.useMemo(
16541
+ () => canDeleteSelectionForEveryone(
16542
+ selectedKeys,
16543
+ displayMessages,
16544
+ !!activeChannel?.isGroup,
16545
+ messageTimers
16546
+ ),
16547
+ [selectedKeys, displayMessages, activeChannel?.isGroup, messageTimers]
15936
16548
  );
15937
- const emitSelectionDelete = React2.useCallback(
16549
+ const emitSelectionDelete = React3.useCallback(
15938
16550
  (deleteType) => {
15939
16551
  if (!loggedUserDetails || selectedKeys.size === 0) return;
15940
16552
  if (deleteType === "everyone" && !canDeleteSelectionForEveryone(
15941
16553
  selectedKeys,
15942
16554
  displayMessages,
15943
- !!activeChannel?.isGroup
16555
+ !!activeChannel?.isGroup,
16556
+ messageTimers
15944
16557
  )) {
15945
16558
  return;
15946
16559
  }
@@ -15981,13 +16594,14 @@ function useActiveChannelMessages({
15981
16594
  conversationId,
15982
16595
  emitGroupDelete,
15983
16596
  emitDmDelete,
15984
- clearSelection
16597
+ clearSelection,
16598
+ messageTimers
15985
16599
  ]
15986
16600
  );
15987
- const handleBatchDeleteForMe = React2.useCallback(() => {
16601
+ const handleBatchDeleteForMe = React3.useCallback(() => {
15988
16602
  emitSelectionDelete("me");
15989
16603
  }, [emitSelectionDelete]);
15990
- const handleBatchDeleteForEveryone = React2.useCallback(() => {
16604
+ const handleBatchDeleteForEveryone = React3.useCallback(() => {
15991
16605
  emitSelectionDelete("everyone");
15992
16606
  }, [emitSelectionDelete]);
15993
16607
  const handleBatchForward = () => {
@@ -16011,7 +16625,7 @@ function useActiveChannelMessages({
16011
16625
  }
16012
16626
  await fetchSearchMessageContext(messageId2, { searchMode: false });
16013
16627
  };
16014
- const handleJumpTo = React2.useCallback((preset, specificDate) => {
16628
+ const handleJumpTo = React3.useCallback((preset, specificDate) => {
16015
16629
  stickToBottomRef.current = false;
16016
16630
  if (preset === "beginning") {
16017
16631
  pendingJumpRef.current = { kind: "beginning" };
@@ -16032,7 +16646,7 @@ function useActiveChannelMessages({
16032
16646
  jumpPagesRef.current = 0;
16033
16647
  setJumpNonce((n) => n + 1);
16034
16648
  }, [jumpDateBounds.minDay, jumpDateBounds.maxDay]);
16035
- React2.useEffect(() => {
16649
+ React3.useEffect(() => {
16036
16650
  const pending = pendingJumpRef.current;
16037
16651
  if (!pending || isLoading) return;
16038
16652
  const step = resolveJumpStep(
@@ -16071,13 +16685,13 @@ function useActiveChannelMessages({
16071
16685
  fetchMessages2,
16072
16686
  setSearchedMessageId
16073
16687
  ]);
16074
- const updateScrollToBottomVisibility = React2.useCallback((el) => {
16688
+ const updateScrollToBottomVisibility = React3.useCallback((el) => {
16075
16689
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
16076
16690
  const nearBottom = distanceFromBottom <= 96;
16077
16691
  stickToBottomRef.current = nearBottom;
16078
16692
  setShowScrollToBottom(!nearBottom);
16079
16693
  }, []);
16080
- const handleScrollToBottom = React2.useCallback(() => {
16694
+ const handleScrollToBottom = React3.useCallback(() => {
16081
16695
  stickToBottomRef.current = true;
16082
16696
  if (isSearchActive) {
16083
16697
  setSearchActive(false);
@@ -16104,12 +16718,12 @@ function useActiveChannelMessages({
16104
16718
  });
16105
16719
  }
16106
16720
  };
16107
- React2.useLayoutEffect(() => {
16721
+ React3.useLayoutEffect(() => {
16108
16722
  if (!isInitialLoading && stickToBottomRef.current && !pendingJumpRef.current) {
16109
16723
  bottomRef.current?.scrollIntoView({ behavior: "smooth" });
16110
16724
  }
16111
16725
  }, [messagesWithResolvedReplies.length, isInitialLoading]);
16112
- React2.useEffect(() => {
16726
+ React3.useEffect(() => {
16113
16727
  if (searchedMessageId && !isLoading) {
16114
16728
  stickToBottomRef.current = false;
16115
16729
  const container = scrollContainerRef.current;
@@ -16319,8 +16933,8 @@ function CalendarDayButton({
16319
16933
  ...props
16320
16934
  }) {
16321
16935
  const defaultClassNames = reactDayPicker.getDefaultClassNames();
16322
- const ref = React2__namespace.useRef(null);
16323
- React2__namespace.useEffect(() => {
16936
+ const ref = React3__namespace.useRef(null);
16937
+ React3__namespace.useEffect(() => {
16324
16938
  if (modifiers.focused) ref.current?.focus();
16325
16939
  }, [modifiers.focused]);
16326
16940
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -16352,12 +16966,12 @@ var PRESETS = [
16352
16966
  ];
16353
16967
  function ChatDateJumpMenu({ label, minDay, maxDay, onJump }) {
16354
16968
  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));
16969
+ const [open, setOpen] = React3.useState(false);
16970
+ const [showCalendar, setShowCalendar] = React3.useState(false);
16971
+ const [calendarMonth, setCalendarMonth] = React3.useState(() => new Date(maxDay));
16358
16972
  const minDate = new Date(minDay);
16359
16973
  const maxDate = new Date(maxDay);
16360
- React2.useEffect(() => {
16974
+ React3.useEffect(() => {
16361
16975
  if (showCalendar) setCalendarMonth(new Date(maxDay));
16362
16976
  }, [showCalendar, maxDay]);
16363
16977
  const queueJump = (preset, specificDate) => {
@@ -16691,8 +17305,8 @@ function AttachmentCaptionModal({
16691
17305
  attachment,
16692
17306
  onSave
16693
17307
  }) {
16694
- const [draft, setDraft] = React2.useState("");
16695
- React2.useEffect(() => {
17308
+ const [draft, setDraft] = React3.useState("");
17309
+ React3.useEffect(() => {
16696
17310
  if (open && attachment) {
16697
17311
  setDraft(attachment.caption || "");
16698
17312
  }
@@ -17069,9 +17683,9 @@ function ComposerAttachmentPreview({
17069
17683
  embedded = false,
17070
17684
  maxAttachments = COMPOSER_MAX_ATTACHMENTS
17071
17685
  }) {
17072
- const [captionTargetId, setCaptionTargetId] = React2.useState(null);
17073
- const [lightboxState, setLightboxState] = React2.useState(null);
17074
- const { media, files, imageMedia, lightboxImages } = React2.useMemo(() => {
17686
+ const [captionTargetId, setCaptionTargetId] = React3.useState(null);
17687
+ const [lightboxState, setLightboxState] = React3.useState(null);
17688
+ const { media, files, imageMedia, lightboxImages } = React3.useMemo(() => {
17075
17689
  const mediaItems = attachments.filter(
17076
17690
  (item) => (item.type === "image" || item.type === "video") && item.previewUrl
17077
17691
  );
@@ -17224,7 +17838,6 @@ var getAttachmentType = (file) => {
17224
17838
  };
17225
17839
 
17226
17840
  // src/chat/channel-message-box/useChannelMessageBox.ts
17227
- var MAX_ATTACHMENTS = COMPOSER_MAX_ATTACHMENTS;
17228
17841
  function useChannelMessageBox({
17229
17842
  activeChannel,
17230
17843
  conversationId,
@@ -17238,7 +17851,13 @@ function useChannelMessageBox({
17238
17851
  conversationId,
17239
17852
  onActiveChannelPatch
17240
17853
  );
17241
- const [state, setState] = React2.useState({
17854
+ const { settings, getAttachmentsGate } = useEffectiveSettingsOptional();
17855
+ const attachmentsGate = getAttachmentsGate();
17856
+ const maxAttachments = settings.attachments.maxAttachmentsPerMessage;
17857
+ const maxFileSizeBytes = settings.attachments.maxFileSizeMB * 1024 * 1024;
17858
+ const allowedMimeTypes = settings.attachments.allowedMimeTypes;
17859
+ const acceptAttribute = buildAcceptFromMimeTypes(allowedMimeTypes);
17860
+ const [state, setState] = React3.useState({
17242
17861
  messageInput: "",
17243
17862
  attachments: [],
17244
17863
  isFocused: false,
@@ -17248,8 +17867,8 @@ function useChannelMessageBox({
17248
17867
  const updateState = (updates) => {
17249
17868
  setState((prev) => ({ ...prev, ...updates }));
17250
17869
  };
17251
- const typingTimeoutRef = React2.useRef(null);
17252
- const conversationIdRef = React2.useRef(conversationId);
17870
+ const typingTimeoutRef = React3.useRef(null);
17871
+ const conversationIdRef = React3.useRef(conversationId);
17253
17872
  conversationIdRef.current = conversationId;
17254
17873
  const { syncComposerRef } = useConversationDraft(conversationId);
17255
17874
  const emitTypingStart = exports.useChatStore((s) => s.emitTypingStart);
@@ -17260,12 +17879,12 @@ function useChannelMessageBox({
17260
17879
  const allUsers = exports.useChatStore((s) => s.allUsers);
17261
17880
  const composerDisabledByConversation = exports.useChatStore((s) => s.composerDisabledByConversation);
17262
17881
  const composerDisabledReason = conversationId ? composerDisabledByConversation[conversationId] : void 0;
17263
- const fileInputRef = React2.useRef(null);
17264
- const attachmentsCountRef = React2.useRef(0);
17265
- React2__namespace.default.useEffect(() => {
17882
+ const fileInputRef = React3.useRef(null);
17883
+ const attachmentsCountRef = React3.useRef(0);
17884
+ React3__namespace.default.useEffect(() => {
17266
17885
  attachmentsCountRef.current = state.attachments.length;
17267
17886
  }, [state.attachments.length]);
17268
- React2__namespace.default.useEffect(() => {
17887
+ React3__namespace.default.useEffect(() => {
17269
17888
  const draft = {
17270
17889
  messageInput: state.messageInput,
17271
17890
  attachments: state.attachments
@@ -17273,7 +17892,7 @@ function useChannelMessageBox({
17273
17892
  syncComposerRef(draft);
17274
17893
  updateConversationDraft(conversationIdRef.current, draft);
17275
17894
  }, [state.messageInput, state.attachments, syncComposerRef]);
17276
- React2__namespace.default.useEffect(() => {
17895
+ React3__namespace.default.useEffect(() => {
17277
17896
  if (typingTimeoutRef.current) {
17278
17897
  clearTimeout(typingTimeoutRef.current);
17279
17898
  typingTimeoutRef.current = null;
@@ -17333,21 +17952,52 @@ function useChannelMessageBox({
17333
17952
  const handleFileChange = (e) => {
17334
17953
  const files = e.target.files ? Array.from(e.target.files) : [];
17335
17954
  if (!files.length) return;
17955
+ if (!attachmentsGate.enabled) {
17956
+ updateState({
17957
+ attachmentError: attachmentsGate.disabledReason || "Attachments are disabled by your administrator."
17958
+ });
17959
+ if (fileInputRef.current) fileInputRef.current.value = "";
17960
+ return;
17961
+ }
17336
17962
  const currentCount = attachmentsCountRef.current;
17337
- const remaining = MAX_ATTACHMENTS - currentCount;
17963
+ const remaining = maxAttachments - currentCount;
17338
17964
  if (remaining <= 0) {
17339
- updateState({ attachmentError: `Maximum ${MAX_ATTACHMENTS} files allowed` });
17965
+ updateState({ attachmentError: `Maximum ${maxAttachments} files allowed` });
17966
+ if (fileInputRef.current) fileInputRef.current.value = "";
17967
+ return;
17968
+ }
17969
+ const validFiles = [];
17970
+ const rejectionMessages = [];
17971
+ for (const file of files) {
17972
+ if (!matchesAllowedMimeType(file.type, allowedMimeTypes)) {
17973
+ rejectionMessages.push(`"${file.name}" type is not allowed`);
17974
+ continue;
17975
+ }
17976
+ if (file.size > maxFileSizeBytes) {
17977
+ rejectionMessages.push(
17978
+ `"${file.name}" exceeds ${settings.attachments.maxFileSizeMB} MB (got ${formatFileSizeMB(file.size)})`
17979
+ );
17980
+ continue;
17981
+ }
17982
+ validFiles.push(file);
17983
+ }
17984
+ if (validFiles.length === 0) {
17985
+ updateState({
17986
+ attachmentError: rejectionMessages[0] || "Selected files are not allowed"
17987
+ });
17340
17988
  if (fileInputRef.current) fileInputRef.current.value = "";
17341
17989
  return;
17342
17990
  }
17343
- if (files.length > remaining) {
17991
+ if (validFiles.length > remaining) {
17344
17992
  updateState({
17345
17993
  attachmentError: `You can upload only ${remaining} more file${remaining > 1 ? "s" : ""}`
17346
17994
  });
17995
+ } else if (rejectionMessages.length > 0) {
17996
+ updateState({ attachmentError: rejectionMessages[0] });
17347
17997
  } else {
17348
17998
  updateState({ attachmentError: "" });
17349
17999
  }
17350
- const filesToAdd = files.slice(0, remaining);
18000
+ const filesToAdd = validFiles.slice(0, remaining);
17351
18001
  attachmentsCountRef.current = currentCount + filesToAdd.length;
17352
18002
  const newAttachments = filesToAdd.map((file) => ({
17353
18003
  id: `att-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
@@ -17404,7 +18054,7 @@ function useChannelMessageBox({
17404
18054
  return {
17405
18055
  ...prev,
17406
18056
  attachments: nextAttachments,
17407
- attachmentError: nextAttachments.length < MAX_ATTACHMENTS ? "" : prev.attachmentError
18057
+ attachmentError: nextAttachments.length < maxAttachments ? "" : prev.attachmentError
17408
18058
  };
17409
18059
  });
17410
18060
  };
@@ -17511,7 +18161,10 @@ function useChannelMessageBox({
17511
18161
  onlyAdminCanSendMessage,
17512
18162
  fileInputRef,
17513
18163
  hasUploadingAttachments,
17514
- maxAttachments: MAX_ATTACHMENTS,
18164
+ maxAttachments,
18165
+ acceptAttribute,
18166
+ attachmentsEnabled: attachmentsGate.enabled,
18167
+ attachmentsDisabledReason: attachmentsGate.disabledReason,
17515
18168
  handleInputChange,
17516
18169
  handleFileChange,
17517
18170
  handleRemoveAttachment,
@@ -17539,6 +18192,9 @@ var ChannelMessageBoxView = (props) => {
17539
18192
  fileInputRef,
17540
18193
  hasUploadingAttachments,
17541
18194
  maxAttachments,
18195
+ acceptAttribute,
18196
+ attachmentsEnabled,
18197
+ attachmentsDisabledReason,
17542
18198
  handleInputChange,
17543
18199
  handleFileChange,
17544
18200
  handleRemoveAttachment,
@@ -17636,16 +18292,23 @@ var ChannelMessageBoxView = (props) => {
17636
18292
  ),
17637
18293
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-1.5 p-1.5", children: [
17638
18294
  /* @__PURE__ */ jsxRuntime.jsx(
17639
- Button,
18295
+ AdminPermissionTooltip,
17640
18296
  {
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 })
18297
+ disabled: !attachmentsEnabled,
18298
+ message: attachmentsDisabledReason,
18299
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18300
+ Button,
18301
+ {
18302
+ type: "button",
18303
+ variant: "ghost",
18304
+ size: "icon",
18305
+ className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text) disabled:opacity-40",
18306
+ onClick: () => attachmentsEnabled && !hasUploadingAttachments && fileInputRef.current?.click(),
18307
+ disabled: !attachmentsEnabled || hasUploadingAttachments,
18308
+ "aria-label": "Attach files",
18309
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { size: 16 })
18310
+ }
18311
+ )
17649
18312
  }
17650
18313
  ),
17651
18314
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -17655,8 +18318,9 @@ var ChannelMessageBoxView = (props) => {
17655
18318
  ref: fileInputRef,
17656
18319
  className: "hidden",
17657
18320
  multiple: true,
17658
- accept: "image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx",
17659
- onChange: handleFileChange
18321
+ accept: acceptAttribute || void 0,
18322
+ onChange: handleFileChange,
18323
+ disabled: !attachmentsEnabled
17660
18324
  }
17661
18325
  ),
17662
18326
  /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
@@ -17735,11 +18399,11 @@ var ChannelMessageBoxView_default = ChannelMessageBoxView;
17735
18399
  // src/chat/forward-modal/useForwardModal.ts
17736
18400
  init_useStore();
17737
18401
  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);
18402
+ const [search, setSearch] = React3.useState("");
18403
+ const [debouncedSearch, setDebouncedSearch] = React3.useState("");
18404
+ const targetChannelRef = React3.useRef(null);
18405
+ const wasForwardingRef = React3.useRef(false);
18406
+ const completionHandledRef = React3.useRef(false);
17743
18407
  const emitDmForward = exports.useChatStore((s) => s.emitDmForward);
17744
18408
  const emitGroupForward = exports.useChatStore((s) => s.emitGroupForward);
17745
18409
  const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
@@ -17747,23 +18411,23 @@ function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17747
18411
  const storeConversations = exports.useChatStore((s) => s.conversations);
17748
18412
  const allUsers = exports.useChatStore((s) => s.allUsers);
17749
18413
  const sourceMessage = items[0]?.message ?? null;
17750
- const mids = React2.useMemo(
18414
+ const mids = React3.useMemo(
17751
18415
  () => items.map((item) => String(item.message._id || item.message.id || "")).filter(Boolean),
17752
18416
  [items]
17753
18417
  );
17754
18418
  const isForwarding = mids.some((id) => pendingActions[id]?.includes("forward"));
17755
- const onCloseRef = React2.useRef(onClose);
17756
- const onForwardCompleteRef = React2.useRef(onForwardComplete);
17757
- React2.useEffect(() => {
18419
+ const onCloseRef = React3.useRef(onClose);
18420
+ const onForwardCompleteRef = React3.useRef(onForwardComplete);
18421
+ React3.useEffect(() => {
17758
18422
  onCloseRef.current = onClose;
17759
18423
  }, [onClose]);
17760
- React2.useEffect(() => {
18424
+ React3.useEffect(() => {
17761
18425
  onForwardCompleteRef.current = onForwardComplete;
17762
18426
  }, [onForwardComplete]);
17763
- const handleOpenChange = React2.useCallback((open) => {
18427
+ const handleOpenChange = React3.useCallback((open) => {
17764
18428
  if (!open) onCloseRef.current();
17765
18429
  }, []);
17766
- React2.useEffect(() => {
18430
+ React3.useEffect(() => {
17767
18431
  if (isForwarding) {
17768
18432
  wasForwardingRef.current = true;
17769
18433
  const timeout = setTimeout(() => {
@@ -17781,7 +18445,7 @@ function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17781
18445
  queueMicrotask(() => onForwardCompleteRef.current(target));
17782
18446
  }
17783
18447
  }, [isForwarding, mids]);
17784
- React2.useEffect(() => {
18448
+ React3.useEffect(() => {
17785
18449
  if (!isOpen) {
17786
18450
  setSearch("");
17787
18451
  setDebouncedSearch("");
@@ -17790,11 +18454,11 @@ function useForwardModal({ isOpen, onClose, items, onForwardComplete }) {
17790
18454
  completionHandledRef.current = false;
17791
18455
  }
17792
18456
  }, [isOpen]);
17793
- React2.useEffect(() => {
18457
+ React3.useEffect(() => {
17794
18458
  const handler = setTimeout(() => setDebouncedSearch(search), 400);
17795
18459
  return () => clearTimeout(handler);
17796
18460
  }, [search]);
17797
- const mappedChannels = React2.useMemo(() => {
18461
+ const mappedChannels = React3.useMemo(() => {
17798
18462
  const sourceConversationId = sourceMessage?.conversationId ? String(sourceMessage.conversationId) : null;
17799
18463
  const query = debouncedSearch.trim().toLowerCase();
17800
18464
  return storeConversations.filter((c) => !sourceConversationId || String(c._id) !== sourceConversationId).map((c) => {
@@ -17922,7 +18586,7 @@ function ForwardModalView(props) {
17922
18586
 
17923
18587
  // src/chat/ChatPanelAlerts.tsx
17924
18588
  init_useStore();
17925
- var ChatAlertsContext = React2.createContext({});
18589
+ var ChatAlertsContext = React3.createContext({});
17926
18590
  function ChatAlertsProvider({
17927
18591
  children,
17928
18592
  value
@@ -17930,7 +18594,7 @@ function ChatAlertsProvider({
17930
18594
  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
18595
  }
17932
18596
  function useChatAlerts() {
17933
- return React2.useContext(ChatAlertsContext);
18597
+ return React3.useContext(ChatAlertsContext);
17934
18598
  }
17935
18599
  var normalizeMessage = (message) => message.trim().toLowerCase();
17936
18600
  var alertStyles = {
@@ -17945,14 +18609,14 @@ var ChatPanelAlerts = () => {
17945
18609
  const conversationErrors = exports.useChatStore((s) => s.conversationErrors);
17946
18610
  const clearAppError = exports.useChatStore((s) => s.clearAppError);
17947
18611
  const rateLimitResetAt2 = exports.useChatStore((s) => s.rateLimitResetAt);
17948
- const [now, setNow] = React2.useState(() => Date.now());
18612
+ const [now, setNow] = React3.useState(() => Date.now());
17949
18613
  const conversationAppError = activeConversationId ? conversationErrors[activeConversationId] ?? null : null;
17950
- React2.useEffect(() => {
18614
+ React3.useEffect(() => {
17951
18615
  if (rateLimitResetAt2 <= Date.now()) return;
17952
18616
  const id = window.setInterval(() => setNow(Date.now()), 1e3);
17953
18617
  return () => window.clearInterval(id);
17954
18618
  }, [rateLimitResetAt2]);
17955
- const alerts = React2.useMemo(() => {
18619
+ const alerts = React3.useMemo(() => {
17956
18620
  const items = [];
17957
18621
  const seen = /* @__PURE__ */ new Set();
17958
18622
  const push = (item) => {
@@ -18376,9 +19040,9 @@ var emitToast = (payload, onPush) => {
18376
19040
  };
18377
19041
  var ForegroundPushBridge = ({ onPush }) => {
18378
19042
  const lastDM = exports.useChatStore((s) => s.lastDM);
18379
- const onPushRef = React2.useRef(onPush);
19043
+ const onPushRef = React3.useRef(onPush);
18380
19044
  onPushRef.current = onPush;
18381
- React2.useEffect(() => {
19045
+ React3.useEffect(() => {
18382
19046
  if (!lastDM) return;
18383
19047
  if (typeof document !== "undefined" && document.visibilityState !== "visible") {
18384
19048
  return;
@@ -18418,7 +19082,7 @@ var ForegroundPushBridge = ({ onPush }) => {
18418
19082
  onPushRef.current
18419
19083
  );
18420
19084
  }, [lastDM]);
18421
- React2.useEffect(() => {
19085
+ React3.useEffect(() => {
18422
19086
  let unsubscribe = null;
18423
19087
  let retryTimer = null;
18424
19088
  let cancelled = false;
@@ -18494,7 +19158,7 @@ var ForegroundPushBridge = ({ onPush }) => {
18494
19158
  window.removeEventListener(PUSH_CHANGED_EVENT, onPushChanged);
18495
19159
  };
18496
19160
  }, []);
18497
- React2.useEffect(() => {
19161
+ React3.useEffect(() => {
18498
19162
  const onOpen = (event) => {
18499
19163
  const id = String(
18500
19164
  event.detail?.conversationId || ""
@@ -18571,8 +19235,8 @@ var ChatMain = ({
18571
19235
  viewportHeight = "full",
18572
19236
  viewportClassName
18573
19237
  }) => {
18574
- const clientObj = React2__namespace.default.useMemo(() => ({ id: clientId }), [clientId]);
18575
- const uiConfigObj = React2__namespace.default.useMemo(
19238
+ const clientObj = React3__namespace.default.useMemo(() => ({ id: clientId }), [clientId]);
19239
+ const uiConfigObj = React3__namespace.default.useMemo(
18576
19240
  () => ({
18577
19241
  ...uiConfig || {},
18578
19242
  components: { ...uiConfig?.components, ...components },
@@ -18943,6 +19607,8 @@ var packageDefaultComponents = {
18943
19607
  };
18944
19608
 
18945
19609
  // src/index.ts
19610
+ init_settings_types();
19611
+ init_api_service();
18946
19612
  var index_default = ChatMain_default;
18947
19613
 
18948
19614
  exports.Calendar = Calendar;
@@ -18956,6 +19622,7 @@ exports.ChatAlertsProvider = ChatAlertsProvider;
18956
19622
  exports.ChatAvatar = ChatAvatar_default;
18957
19623
  exports.ChatCustomizationProvider = ChatCustomizationProvider;
18958
19624
  exports.ChatDateJumpMenu = ChatDateJumpMenu;
19625
+ exports.ChatEffectiveSettingsProvider = ChatEffectiveSettingsProvider;
18959
19626
  exports.ChatLayout = ChatLayout_default;
18960
19627
  exports.ChatLocaleProvider = ChatLocaleProvider;
18961
19628
  exports.ChatMain = ChatMain_default;
@@ -19035,6 +19702,8 @@ exports.useChatFeatures = useChatFeatures;
19035
19702
  exports.useChatFeaturesOptional = useChatFeaturesOptional;
19036
19703
  exports.useChatLocale = useChatLocale;
19037
19704
  exports.useChatTheme = useChatTheme;
19705
+ exports.useEffectiveSettings = useEffectiveSettings;
19706
+ exports.useEffectiveSettingsOptional = useEffectiveSettingsOptional;
19038
19707
  exports.usePushNotifications = usePushNotifications;
19039
19708
  //# sourceMappingURL=index.cjs.map
19040
19709
  //# sourceMappingURL=index.cjs.map