@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.mjs CHANGED
@@ -2,8 +2,8 @@ import { io } from 'socket.io-client';
2
2
  import axios from 'axios';
3
3
  import { create } from 'zustand';
4
4
  import { persist } from 'zustand/middleware';
5
- import * as React2 from 'react';
6
- import React2__default, { createContext, useMemo, useContext, useState, useCallback, useRef, useEffect, useLayoutEffect, useSyncExternalStore } from 'react';
5
+ import * as React3 from 'react';
6
+ import React3__default, { createContext, useMemo, useContext, useState, useCallback, useEffect, useRef, useLayoutEffect, Fragment as Fragment$1, useSyncExternalStore } from 'react';
7
7
  import { Tooltip as Tooltip$1, Slot, Switch as Switch$1, Dialog as Dialog$1, Avatar as Avatar$1, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, AlertDialog as AlertDialog$1 } from 'radix-ui';
8
8
  import { clsx } from 'clsx';
9
9
  import { twMerge } from 'tailwind-merge';
@@ -1162,6 +1162,7 @@ function resolveChatApiKey(url) {
1162
1162
  }
1163
1163
  if (path.includes("/message/search-messages")) return "searchMessages";
1164
1164
  if (path.includes("/message/searched-message/context")) return "searchMessagesContext";
1165
+ if (path.includes("/settings/effective")) return "effectiveSettings";
1165
1166
  if (path.includes("/conversation/group/create")) return "createGroup";
1166
1167
  if (path.includes("/conversation/group/permission/update")) return "updateGroupPermissions";
1167
1168
  if (path.includes("/conversation/group/information/update")) return "updateGroupInfo";
@@ -1205,7 +1206,8 @@ var init_query_params = __esm({
1205
1206
  "pinnedMessages",
1206
1207
  "starredMessages",
1207
1208
  "searchMessages",
1208
- "searchMessagesContext"
1209
+ "searchMessagesContext",
1210
+ "effectiveSettings"
1209
1211
  ];
1210
1212
  CHAT_API_KEYS = [
1211
1213
  "all",
@@ -1230,8 +1232,8 @@ var init_api_version = __esm({
1230
1232
  "src/services/api/api-version.ts"() {
1231
1233
  init_client();
1232
1234
  DEFAULT_VERSIONS = {
1233
- default: "v1",
1234
- push: "v2"
1235
+ v1: "v1",
1236
+ v2: "v2"
1235
1237
  };
1236
1238
  apiVersions = { ...DEFAULT_VERSIONS };
1237
1239
  normalizeApiVersion = (version) => {
@@ -1250,7 +1252,7 @@ var init_api_version = __esm({
1250
1252
  buildVersionedApiUrl = (path, options) => {
1251
1253
  const root = resolveApiRoot(options?.baseUrl);
1252
1254
  const version = normalizeApiVersion(
1253
- options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.default)
1255
+ options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.v1)
1254
1256
  );
1255
1257
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1256
1258
  if (!root) {
@@ -1260,23 +1262,23 @@ var init_api_version = __esm({
1260
1262
  };
1261
1263
  setChatApiVersions = (config) => {
1262
1264
  if (!config) return;
1263
- if (config.default) {
1264
- apiVersions.default = normalizeApiVersion(config.default);
1265
+ if (config.v1) {
1266
+ apiVersions.v1 = normalizeApiVersion(config.v1);
1265
1267
  }
1266
- if (config.push) {
1267
- apiVersions.push = normalizeApiVersion(config.push);
1268
+ if (config.v2) {
1269
+ apiVersions.v2 = normalizeApiVersion(config.v2);
1268
1270
  }
1269
1271
  };
1270
1272
  getChatApiVersions = () => ({
1271
1273
  ...apiVersions
1272
1274
  });
1273
1275
  setChatPushApiVersion = (version) => {
1274
- apiVersions.push = normalizeApiVersion(version);
1276
+ apiVersions.v2 = normalizeApiVersion(version);
1275
1277
  };
1276
1278
  syncDefaultApiVersionFromBaseUrl = (baseUrl) => {
1277
1279
  const match = String(baseUrl || "").trim().match(/\/(v\d+)\/?$/i);
1278
1280
  if (match?.[1]) {
1279
- apiVersions.default = normalizeApiVersion(match[1]);
1281
+ apiVersions.v1 = normalizeApiVersion(match[1]);
1280
1282
  }
1281
1283
  };
1282
1284
  }
@@ -1421,6 +1423,9 @@ var init_api_constants = __esm({
1421
1423
  SEARCH_MESSAGES: "/message/search-messages",
1422
1424
  SEARCH_MESSAGES_CONTEXT: (messageId2) => `/message/searched-message/context?messageId=${messageId2}`,
1423
1425
  BLOCK_UNBLOCK_USER: "/user/block-unblock"
1426
+ },
1427
+ SETTINGS: {
1428
+ PERMISSIONS: "/settings/effective"
1424
1429
  }
1425
1430
  };
1426
1431
  }
@@ -1457,13 +1462,11 @@ var init_auth_api = __esm({
1457
1462
  });
1458
1463
 
1459
1464
  // src/constants/chat.constants.ts
1460
- var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, DM_EDIT_WINDOW_MS, GROUP_EDIT_WINDOW_MS, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1465
+ var DEFAULT_MESSAGES_LIMIT, DEFAULT_CONVERSATIONS_LIMIT, MAX_ATTACHMENT_CAPTION_LENGTH, ATTACHMENT_FILENAME_TIMESTAMP_LENGTH;
1461
1466
  var init_chat_constants = __esm({
1462
1467
  "src/constants/chat.constants.ts"() {
1463
1468
  DEFAULT_MESSAGES_LIMIT = 20;
1464
1469
  DEFAULT_CONVERSATIONS_LIMIT = 20;
1465
- DM_EDIT_WINDOW_MS = 5 * 60 * 1e3;
1466
- GROUP_EDIT_WINDOW_MS = 10 * 60 * 1e3;
1467
1470
  MAX_ATTACHMENT_CAPTION_LENGTH = 100;
1468
1471
  ATTACHMENT_FILENAME_TIMESTAMP_LENGTH = 13;
1469
1472
  }
@@ -1666,6 +1669,206 @@ var init_uploads_api = __esm({
1666
1669
  }
1667
1670
  });
1668
1671
 
1672
+ // src/types/settings.types.ts
1673
+ var DEFAULT_EFFECTIVE_SETTINGS, ADMIN_PERMISSION_DENIED_MESSAGE, CALLS_NOT_CONFIGURED_MESSAGE;
1674
+ var init_settings_types = __esm({
1675
+ "src/types/settings.types.ts"() {
1676
+ DEFAULT_EFFECTIVE_SETTINGS = {
1677
+ pushNotificationEnabled: true,
1678
+ videoCallEnabled: true,
1679
+ audioCallEnabled: true,
1680
+ blockUsersEnabled: true,
1681
+ calls: {
1682
+ enabled: true,
1683
+ configured: true
1684
+ },
1685
+ groupPolicy: {
1686
+ defaults: {
1687
+ onlyAdminCanSendMessage: false,
1688
+ onlyAdminCanEditInfo: false,
1689
+ senderCanEditMessage: true,
1690
+ allowMemberAdd: true,
1691
+ allowMemberRemove: false,
1692
+ moderationEnabled: false
1693
+ },
1694
+ locked: []
1695
+ },
1696
+ messageTimers: {
1697
+ dmEditMinutes: 5,
1698
+ dmDeleteMinutes: 5,
1699
+ groupEditMinutes: 10,
1700
+ groupDeleteMinutes: 10
1701
+ },
1702
+ attachments: {
1703
+ enabled: true,
1704
+ maxFileSizeMB: 10,
1705
+ allowedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
1706
+ maxAttachmentsPerMessage: 10
1707
+ },
1708
+ maxAdminsPerGroup: 5,
1709
+ maxParticipantsPerGroup: 100,
1710
+ maxPinnedMessagesPerConversation: 10,
1711
+ groupCreationEnabled: true
1712
+ };
1713
+ ADMIN_PERMISSION_DENIED_MESSAGE = "This feature is disabled by your administrator.";
1714
+ CALLS_NOT_CONFIGURED_MESSAGE = "Calls are not configured. Please contact your administrator.";
1715
+ }
1716
+ });
1717
+
1718
+ // src/services/api/settings.api.ts
1719
+ function asBoolean(value, fallback) {
1720
+ return typeof value === "boolean" ? value : fallback;
1721
+ }
1722
+ function asPositiveNumber(value, fallback) {
1723
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
1724
+ }
1725
+ function asStringArray(value, fallback) {
1726
+ if (!Array.isArray(value)) return fallback;
1727
+ const next = value.filter((item) => typeof item === "string");
1728
+ return next.length > 0 ? next : fallback;
1729
+ }
1730
+ function normalizeGroupPermissions(value, fallback) {
1731
+ if (!value || typeof value !== "object") return { ...fallback };
1732
+ const raw = value;
1733
+ return {
1734
+ onlyAdminCanSendMessage: asBoolean(
1735
+ raw.onlyAdminCanSendMessage,
1736
+ fallback.onlyAdminCanSendMessage
1737
+ ),
1738
+ onlyAdminCanEditInfo: asBoolean(
1739
+ raw.onlyAdminCanEditInfo,
1740
+ fallback.onlyAdminCanEditInfo
1741
+ ),
1742
+ senderCanEditMessage: asBoolean(
1743
+ raw.senderCanEditMessage,
1744
+ fallback.senderCanEditMessage
1745
+ ),
1746
+ allowMemberAdd: asBoolean(raw.allowMemberAdd, fallback.allowMemberAdd),
1747
+ allowMemberRemove: asBoolean(
1748
+ raw.allowMemberRemove,
1749
+ fallback.allowMemberRemove
1750
+ ),
1751
+ moderationEnabled: asBoolean(
1752
+ raw.moderationEnabled,
1753
+ fallback.moderationEnabled
1754
+ )
1755
+ };
1756
+ }
1757
+ function normalizeCalls(value, fallback) {
1758
+ if (!value || typeof value !== "object") return { ...fallback };
1759
+ const raw = value;
1760
+ return {
1761
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1762
+ configured: asBoolean(raw.configured, fallback.configured)
1763
+ };
1764
+ }
1765
+ function normalizeGroupPolicy(value, fallback) {
1766
+ if (!value || typeof value !== "object") {
1767
+ return {
1768
+ defaults: { ...fallback.defaults },
1769
+ locked: [...fallback.locked]
1770
+ };
1771
+ }
1772
+ const raw = value;
1773
+ return {
1774
+ defaults: normalizeGroupPermissions(raw.defaults, fallback.defaults),
1775
+ locked: asStringArray(raw.locked, fallback.locked)
1776
+ };
1777
+ }
1778
+ function normalizeMessageTimers(value, fallback) {
1779
+ if (!value || typeof value !== "object") return { ...fallback };
1780
+ const raw = value;
1781
+ return {
1782
+ dmEditMinutes: asPositiveNumber(raw.dmEditMinutes, fallback.dmEditMinutes),
1783
+ dmDeleteMinutes: asPositiveNumber(
1784
+ raw.dmDeleteMinutes,
1785
+ fallback.dmDeleteMinutes
1786
+ ),
1787
+ groupEditMinutes: asPositiveNumber(
1788
+ raw.groupEditMinutes,
1789
+ fallback.groupEditMinutes
1790
+ ),
1791
+ groupDeleteMinutes: asPositiveNumber(
1792
+ raw.groupDeleteMinutes,
1793
+ fallback.groupDeleteMinutes
1794
+ )
1795
+ };
1796
+ }
1797
+ function normalizeAttachments(value, fallback) {
1798
+ if (!value || typeof value !== "object") return { ...fallback };
1799
+ const raw = value;
1800
+ return {
1801
+ enabled: asBoolean(raw.enabled, fallback.enabled),
1802
+ maxFileSizeMB: asPositiveNumber(raw.maxFileSizeMB, fallback.maxFileSizeMB),
1803
+ allowedMimeTypes: asStringArray(
1804
+ raw.allowedMimeTypes,
1805
+ fallback.allowedMimeTypes
1806
+ ),
1807
+ maxAttachmentsPerMessage: asPositiveNumber(
1808
+ raw.maxAttachmentsPerMessage,
1809
+ fallback.maxAttachmentsPerMessage
1810
+ )
1811
+ };
1812
+ }
1813
+ function normalizeEffectiveSettings(raw) {
1814
+ const payload = raw && typeof raw === "object" ? raw.data ?? raw : null;
1815
+ const source = payload && typeof payload === "object" ? payload : {};
1816
+ const defaults = DEFAULT_EFFECTIVE_SETTINGS;
1817
+ return {
1818
+ pushNotificationEnabled: asBoolean(
1819
+ source.pushNotificationEnabled,
1820
+ defaults.pushNotificationEnabled
1821
+ ),
1822
+ videoCallEnabled: asBoolean(
1823
+ source.videoCallEnabled,
1824
+ defaults.videoCallEnabled
1825
+ ),
1826
+ audioCallEnabled: asBoolean(
1827
+ source.audioCallEnabled,
1828
+ defaults.audioCallEnabled
1829
+ ),
1830
+ blockUsersEnabled: asBoolean(
1831
+ source.blockUsersEnabled,
1832
+ defaults.blockUsersEnabled
1833
+ ),
1834
+ calls: normalizeCalls(source.calls, defaults.calls),
1835
+ groupPolicy: normalizeGroupPolicy(source.groupPolicy, defaults.groupPolicy),
1836
+ messageTimers: normalizeMessageTimers(
1837
+ source.messageTimers,
1838
+ defaults.messageTimers
1839
+ ),
1840
+ attachments: normalizeAttachments(source.attachments, defaults.attachments),
1841
+ maxAdminsPerGroup: asPositiveNumber(
1842
+ source.maxAdminsPerGroup,
1843
+ defaults.maxAdminsPerGroup
1844
+ ),
1845
+ maxParticipantsPerGroup: asPositiveNumber(
1846
+ source.maxParticipantsPerGroup,
1847
+ defaults.maxParticipantsPerGroup
1848
+ ),
1849
+ maxPinnedMessagesPerConversation: asPositiveNumber(
1850
+ source.maxPinnedMessagesPerConversation,
1851
+ defaults.maxPinnedMessagesPerConversation
1852
+ ),
1853
+ groupCreationEnabled: asBoolean(
1854
+ source.groupCreationEnabled,
1855
+ defaults.groupCreationEnabled
1856
+ )
1857
+ };
1858
+ }
1859
+ var fetchEffectiveSettings;
1860
+ var init_settings_api = __esm({
1861
+ "src/services/api/settings.api.ts"() {
1862
+ init_api_constants();
1863
+ init_client();
1864
+ init_settings_types();
1865
+ fetchEffectiveSettings = async () => {
1866
+ const response = await apiClient.get(API_ENDPOINTS.SETTINGS.PERMISSIONS);
1867
+ return normalizeEffectiveSettings(response.data);
1868
+ };
1869
+ }
1870
+ });
1871
+
1669
1872
  // src/services/api/index.ts
1670
1873
  var init_api = __esm({
1671
1874
  "src/services/api/index.ts"() {
@@ -1674,6 +1877,7 @@ var init_api = __esm({
1674
1877
  init_messages_api();
1675
1878
  init_groups_api();
1676
1879
  init_uploads_api();
1880
+ init_settings_api();
1677
1881
  }
1678
1882
  });
1679
1883
 
@@ -4505,6 +4709,143 @@ var useChatCustomizationOptional = () => {
4505
4709
  };
4506
4710
  var useChatFeatures = () => useChatCustomization().features;
4507
4711
  var useChatFeaturesOptional = () => useChatCustomizationOptional().features;
4712
+
4713
+ // src/context/ChatEffectiveSettingsContext.tsx
4714
+ init_settings_api();
4715
+ init_settings_types();
4716
+ var ChatEffectiveSettingsContext = createContext(null);
4717
+ function buildCallGate(settings, featureEnabled) {
4718
+ if (!settings.calls.configured) {
4719
+ return {
4720
+ visible: true,
4721
+ enabled: false,
4722
+ disabledReason: CALLS_NOT_CONFIGURED_MESSAGE
4723
+ };
4724
+ }
4725
+ if (!settings.calls.enabled) {
4726
+ return {
4727
+ visible: true,
4728
+ enabled: false,
4729
+ disabledReason: ADMIN_PERMISSION_DENIED_MESSAGE
4730
+ };
4731
+ }
4732
+ if (!featureEnabled) {
4733
+ return {
4734
+ visible: true,
4735
+ enabled: false,
4736
+ disabledReason: ADMIN_PERMISSION_DENIED_MESSAGE
4737
+ };
4738
+ }
4739
+ return { visible: true, enabled: true, disabledReason: null };
4740
+ }
4741
+ function ChatEffectiveSettingsProvider({
4742
+ children,
4743
+ enabled = true
4744
+ }) {
4745
+ const [settings, setSettings] = useState(
4746
+ DEFAULT_EFFECTIVE_SETTINGS
4747
+ );
4748
+ const [loading, setLoading] = useState(true);
4749
+ const [error, setError] = useState(null);
4750
+ const refresh = useCallback(async () => {
4751
+ if (!enabled) {
4752
+ setLoading(false);
4753
+ return;
4754
+ }
4755
+ setLoading(true);
4756
+ try {
4757
+ const next = await fetchEffectiveSettings();
4758
+ setSettings(next);
4759
+ setError(null);
4760
+ } catch (err) {
4761
+ console.error("Failed to fetch effective settings:", err);
4762
+ setError(
4763
+ err instanceof Error ? err.message : "Could not load chat permissions"
4764
+ );
4765
+ setSettings(DEFAULT_EFFECTIVE_SETTINGS);
4766
+ } finally {
4767
+ setLoading(false);
4768
+ }
4769
+ }, [enabled]);
4770
+ useEffect(() => {
4771
+ void refresh();
4772
+ }, [refresh]);
4773
+ const value = useMemo(() => {
4774
+ const isGroupPermissionLocked = (key) => settings.groupPolicy.locked.includes(key);
4775
+ return {
4776
+ settings,
4777
+ loading,
4778
+ error,
4779
+ refresh,
4780
+ isGroupPermissionLocked,
4781
+ getAudioCallGate: () => buildCallGate(settings, settings.audioCallEnabled),
4782
+ getVideoCallGate: () => buildCallGate(settings, settings.videoCallEnabled),
4783
+ getPushNotificationGate: () => ({
4784
+ visible: true,
4785
+ enabled: settings.pushNotificationEnabled,
4786
+ disabledReason: settings.pushNotificationEnabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4787
+ }),
4788
+ getBlockUsersGate: () => ({
4789
+ visible: true,
4790
+ enabled: settings.blockUsersEnabled,
4791
+ disabledReason: settings.blockUsersEnabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4792
+ }),
4793
+ getAttachmentsGate: () => ({
4794
+ visible: true,
4795
+ enabled: settings.attachments.enabled,
4796
+ disabledReason: settings.attachments.enabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4797
+ }),
4798
+ getGroupCreationGate: () => ({
4799
+ visible: true,
4800
+ enabled: settings.groupCreationEnabled,
4801
+ disabledReason: settings.groupCreationEnabled ? null : ADMIN_PERMISSION_DENIED_MESSAGE
4802
+ })
4803
+ };
4804
+ }, [settings, loading, error, refresh]);
4805
+ return /* @__PURE__ */ jsx(ChatEffectiveSettingsContext.Provider, { value, children });
4806
+ }
4807
+ function useEffectiveSettings() {
4808
+ const ctx = useContext(ChatEffectiveSettingsContext);
4809
+ if (!ctx) {
4810
+ throw new Error(
4811
+ "useEffectiveSettings must be used within ChatEffectiveSettingsProvider"
4812
+ );
4813
+ }
4814
+ return ctx;
4815
+ }
4816
+ function useEffectiveSettingsOptional() {
4817
+ const ctx = useContext(ChatEffectiveSettingsContext);
4818
+ if (ctx) return ctx;
4819
+ return {
4820
+ settings: DEFAULT_EFFECTIVE_SETTINGS,
4821
+ loading: false,
4822
+ error: null,
4823
+ refresh: async () => void 0,
4824
+ isGroupPermissionLocked: () => false,
4825
+ getAudioCallGate: () => buildCallGate(DEFAULT_EFFECTIVE_SETTINGS, true),
4826
+ getVideoCallGate: () => buildCallGate(DEFAULT_EFFECTIVE_SETTINGS, true),
4827
+ getPushNotificationGate: () => ({
4828
+ visible: true,
4829
+ enabled: true,
4830
+ disabledReason: null
4831
+ }),
4832
+ getBlockUsersGate: () => ({
4833
+ visible: true,
4834
+ enabled: true,
4835
+ disabledReason: null
4836
+ }),
4837
+ getAttachmentsGate: () => ({
4838
+ visible: true,
4839
+ enabled: true,
4840
+ disabledReason: null
4841
+ }),
4842
+ getGroupCreationGate: () => ({
4843
+ visible: true,
4844
+ enabled: true,
4845
+ disabledReason: null
4846
+ })
4847
+ };
4848
+ }
4508
4849
  var ChatContext = createContext(void 0);
4509
4850
  var useChatContext = () => {
4510
4851
  const context = useContext(ChatContext);
@@ -4544,7 +4885,7 @@ var Chat = ({
4544
4885
  const status = useStore((s) => s.status);
4545
4886
  const error = useStore((s) => s.error);
4546
4887
  useChatSync();
4547
- React2__default.useEffect(() => {
4888
+ React3__default.useEffect(() => {
4548
4889
  const clientId = client?.id;
4549
4890
  const initializeChat = async () => {
4550
4891
  if (!clientId || !loggedUserDetails) return;
@@ -4634,28 +4975,28 @@ var Chat = ({
4634
4975
  queryParamApis: queryParamApis ?? ["get"],
4635
4976
  queryParamsByApi: queryParamsByApi ?? {}
4636
4977
  });
4637
- React2__default.useEffect(() => {
4978
+ React3__default.useEffect(() => {
4638
4979
  setChatQueryParams(queryParams);
4639
4980
  setChatQueryParamApis(queryParamApis);
4640
4981
  setChatQueryParamsByApi(queryParamsByApi);
4641
4982
  }, [queryConfigKey]);
4642
- React2__default.useEffect(() => {
4983
+ React3__default.useEffect(() => {
4643
4984
  if (lastDM && onMessageReceived) {
4644
4985
  onMessageReceived(lastDM);
4645
4986
  }
4646
4987
  }, [lastDM, onMessageReceived]);
4647
- React2__default.useEffect(() => {
4988
+ React3__default.useEffect(() => {
4648
4989
  if (status === "connected" && onSocketConnected) {
4649
4990
  const socketId = useStore.getState().socketId;
4650
4991
  if (socketId) onSocketConnected(socketId);
4651
4992
  }
4652
4993
  }, [status, onSocketConnected]);
4653
- React2__default.useEffect(() => {
4994
+ React3__default.useEffect(() => {
4654
4995
  if (status === "error" && error && onSocketError) {
4655
4996
  onSocketError(error);
4656
4997
  }
4657
4998
  }, [status, error, onSocketError]);
4658
- const contextValue = React2__default.useMemo(
4999
+ const contextValue = React3__default.useMemo(
4659
5000
  () => ({
4660
5001
  onSendMessage,
4661
5002
  client,
@@ -4675,7 +5016,7 @@ var Chat = ({
4675
5016
  messagesData
4676
5017
  ]
4677
5018
  );
4678
- const customization = React2__default.useMemo(
5019
+ const customization = React3__default.useMemo(
4679
5020
  () => ({
4680
5021
  components: uiConfig.components,
4681
5022
  classNames: uiConfig.classNames,
@@ -4683,7 +5024,7 @@ var Chat = ({
4683
5024
  }),
4684
5025
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
4685
5026
  );
4686
- return /* @__PURE__ */ jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? children : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
5027
+ return /* @__PURE__ */ jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsx(ChatEffectiveSettingsProvider, { enabled: true, children }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
4687
5028
  };
4688
5029
 
4689
5030
  // src/hooks/useChatController.ts
@@ -6086,6 +6427,8 @@ function useCreateChatDialog({
6086
6427
  const fetchAllUsers = useStore((storeState) => storeState.fetchAllUsers);
6087
6428
  const accessToken = useStore((storeState) => storeState.accessToken);
6088
6429
  const fetchConversations2 = useStore((storeState) => storeState.fetchConversations);
6430
+ const { settings } = useEffectiveSettingsOptional();
6431
+ const maxParticipants = settings.maxParticipantsPerGroup;
6089
6432
  const fileInputRef = useRef(null);
6090
6433
  useEffect(() => {
6091
6434
  if (accessToken) {
@@ -6099,9 +6442,18 @@ function useCreateChatDialog({
6099
6442
  setState((prev) => {
6100
6443
  const memberId = String(member._id);
6101
6444
  const exists = prev.selectedMembers.some((m) => String(m._id) === memberId);
6445
+ if (exists) {
6446
+ return {
6447
+ ...prev,
6448
+ selectedMembers: prev.selectedMembers.filter((m) => String(m._id) !== memberId)
6449
+ };
6450
+ }
6451
+ if (prev.selectedMembers.length + 1 >= maxParticipants) {
6452
+ return prev;
6453
+ }
6102
6454
  return {
6103
6455
  ...prev,
6104
- selectedMembers: exists ? prev.selectedMembers.filter((m) => String(m._id) !== memberId) : [...prev.selectedMembers, member]
6456
+ selectedMembers: [...prev.selectedMembers, member]
6105
6457
  };
6106
6458
  });
6107
6459
  };
@@ -6485,8 +6837,25 @@ function GroupCreateTab({
6485
6837
  ) })
6486
6838
  ] });
6487
6839
  }
6840
+ function AdminPermissionTooltip({
6841
+ disabled,
6842
+ message,
6843
+ side = "top",
6844
+ className,
6845
+ children
6846
+ }) {
6847
+ if (!disabled || !message) {
6848
+ return /* @__PURE__ */ jsx(Fragment, { children });
6849
+ }
6850
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
6851
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: cn("inline-flex", className), children }) }),
6852
+ /* @__PURE__ */ jsx(TooltipContent, { side, className: "max-w-[240px] text-xs", children: message })
6853
+ ] });
6854
+ }
6488
6855
  var CreateChatDialog = (props) => {
6489
6856
  const { isCreateModalOpen = false, setIsCreateModalOpen } = props;
6857
+ const { getGroupCreationGate } = useEffectiveSettingsOptional();
6858
+ const groupCreationGate = getGroupCreationGate();
6490
6859
  const {
6491
6860
  state,
6492
6861
  updateState,
@@ -6540,24 +6909,39 @@ var CreateChatDialog = (props) => {
6540
6909
  ) })
6541
6910
  ] }),
6542
6911
  /* @__PURE__ */ jsx("div", { className: "grid shrink-0 grid-cols-2 gap-2 px-5", children: [
6543
- { id: "dm", label: "Message", icon: MessageCircle },
6544
- { id: "group", label: "New group", icon: Users }
6545
- ].map(({ id, label, icon: Icon }) => /* @__PURE__ */ jsxs(
6546
- "button",
6912
+ { id: "dm", label: "Message", icon: MessageCircle, disabled: false, reason: null },
6547
6913
  {
6548
- type: "button",
6549
- onClick: () => updateState({ activeTab: id }),
6550
- className: cn(
6551
- "flex items-center justify-center gap-2 rounded-full border py-2 text-sm transition-colors",
6552
- 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)"
6553
- ),
6554
- children: [
6555
- /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
6556
- label
6557
- ]
6558
- },
6559
- id
6560
- )) }),
6914
+ id: "group",
6915
+ label: "New group",
6916
+ icon: Users,
6917
+ disabled: !groupCreationGate.enabled,
6918
+ reason: groupCreationGate.disabledReason
6919
+ }
6920
+ ].map(({ id, label, icon: Icon, disabled, reason }) => {
6921
+ const tabButton = /* @__PURE__ */ jsxs(
6922
+ "button",
6923
+ {
6924
+ type: "button",
6925
+ disabled,
6926
+ onClick: () => {
6927
+ if (disabled) return;
6928
+ updateState({ activeTab: id });
6929
+ },
6930
+ className: cn(
6931
+ "flex w-full items-center justify-center gap-2 rounded-full border py-2 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40",
6932
+ state.activeTab === id ? "border-(--chat-theme-20) bg-(--chat-theme-5) text-(--chat-theme)" : "border-(--chat-border) text-(--chat-muted) hover:border-(--chat-theme-20) hover:text-(--chat-text)"
6933
+ ),
6934
+ children: [
6935
+ /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
6936
+ label
6937
+ ]
6938
+ }
6939
+ );
6940
+ if (!disabled || !reason) {
6941
+ return /* @__PURE__ */ jsx(Fragment$1, { children: tabButton }, id);
6942
+ }
6943
+ return /* @__PURE__ */ jsx(AdminPermissionTooltip, { disabled: true, message: reason, children: tabButton }, id);
6944
+ }) }),
6561
6945
  state.activeTab === "dm" && /* @__PURE__ */ jsx(
6562
6946
  DmContactsTab,
6563
6947
  {
@@ -6568,7 +6952,7 @@ var CreateChatDialog = (props) => {
6568
6952
  onStartDM: handleStartDM
6569
6953
  }
6570
6954
  ),
6571
- state.activeTab === "group" && /* @__PURE__ */ jsx(
6955
+ state.activeTab === "group" && groupCreationGate.enabled && /* @__PURE__ */ jsx(
6572
6956
  GroupCreateTab,
6573
6957
  {
6574
6958
  state,
@@ -6632,7 +7016,7 @@ var emitPushChanged = (enabled) => {
6632
7016
  } catch {
6633
7017
  }
6634
7018
  };
6635
- var pushApiUrl = (path) => buildVersionedApiUrl(path, { service: "push" });
7019
+ var pushApiUrl = (path) => buildVersionedApiUrl(path, { service: "v2" });
6636
7020
  var apiGetPushConfig = async () => {
6637
7021
  const { data } = await apiClient.get(pushApiUrl("/notifications/push/config"));
6638
7022
  return data?.data;
@@ -6877,14 +7261,20 @@ var PushNotificationToggle = ({
6877
7261
  children
6878
7262
  }) => {
6879
7263
  const push = usePushNotifications();
7264
+ const { getPushNotificationGate } = useEffectiveSettingsOptional();
7265
+ const adminGate = getPushNotificationGate();
6880
7266
  const [isToggling, setIsToggling] = useState(false);
6881
- if (!push.supported || !push.allowedByWorkspace || !push.configured) {
7267
+ if (!push.supported) {
6882
7268
  return null;
6883
7269
  }
6884
7270
  const isOn = push.deviceEnabled && push.userEnabled;
6885
7271
  const permissionBlocked = push.permission === "denied";
7272
+ const adminBlocked = !adminGate.enabled;
7273
+ const notConfigured = !push.configured;
6886
7274
  const showLoader = isToggling;
7275
+ const isDisabled = showLoader || permissionBlocked || push.loading || adminBlocked || notConfigured;
6887
7276
  const handleChange = async (checked) => {
7277
+ if (adminBlocked || notConfigured) return;
6888
7278
  setIsToggling(true);
6889
7279
  try {
6890
7280
  if (checked) {
@@ -6897,11 +7287,12 @@ var PushNotificationToggle = ({
6897
7287
  setIsToggling(false);
6898
7288
  }
6899
7289
  };
7290
+ const descriptionText = adminBlocked ? adminGate.disabledReason || description : notConfigured ? "Push notifications are not configured. Please contact your administrator." : permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description;
6900
7291
  const renderProps = {
6901
7292
  label,
6902
- description: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description,
6903
- checked: isOn,
6904
- disabled: showLoader || permissionBlocked || push.loading,
7293
+ description: descriptionText,
7294
+ checked: adminBlocked || notConfigured ? false : isOn,
7295
+ disabled: isDisabled,
6905
7296
  loading: showLoader || push.loading,
6906
7297
  permissionBlocked,
6907
7298
  error: push.error,
@@ -6929,12 +7320,19 @@ var PushNotificationToggle = ({
6929
7320
  "aria-label": "Updating push notifications"
6930
7321
  }
6931
7322
  ) : /* @__PURE__ */ jsx(
6932
- Switch,
7323
+ AdminPermissionTooltip,
6933
7324
  {
6934
- checked: isOn,
6935
- disabled: permissionBlocked || push.loading,
6936
- onCheckedChange: (checked) => void handleChange(checked),
6937
- "aria-label": `Toggle ${label}`
7325
+ disabled: adminBlocked || notConfigured,
7326
+ message: adminBlocked ? adminGate.disabledReason : "Push notifications are not configured. Please contact your administrator.",
7327
+ children: /* @__PURE__ */ jsx(
7328
+ Switch,
7329
+ {
7330
+ checked: adminBlocked || notConfigured ? false : isOn,
7331
+ disabled: isDisabled,
7332
+ onCheckedChange: (checked) => void handleChange(checked),
7333
+ "aria-label": `Toggle ${label}`
7334
+ }
7335
+ )
6938
7336
  }
6939
7337
  )
6940
7338
  ]
@@ -8355,7 +8753,7 @@ function highlightSearchMatch(text, query) {
8355
8753
  children: part
8356
8754
  },
8357
8755
  `${part}-${index}`
8358
- ) : /* @__PURE__ */ jsx(React2__default.Fragment, { children: part }, `${part}-${index}`)
8756
+ ) : /* @__PURE__ */ jsx(React3__default.Fragment, { children: part }, `${part}-${index}`)
8359
8757
  );
8360
8758
  }
8361
8759
  function getMessagePreview(message) {
@@ -8535,7 +8933,7 @@ var HeaderSearchPopover = ({
8535
8933
  ] });
8536
8934
  };
8537
8935
  var HeaderSearchPopover_default = HeaderSearchPopover;
8538
- var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text)";
8936
+ var actionBtnClass = "size-8 rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-surface) hover:text-(--chat-text) disabled:pointer-events-none disabled:opacity-40";
8539
8937
  var DefaultHeaderCallActions = ({
8540
8938
  activeChannel,
8541
8939
  conversationId,
@@ -8546,48 +8944,91 @@ var DefaultHeaderCallActions = ({
8546
8944
  className
8547
8945
  }) => {
8548
8946
  const { components, slotClassName, features } = useChatCustomizationOptional();
8947
+ const { getAudioCallGate, getVideoCallGate } = useEffectiveSettingsOptional();
8549
8948
  const PhoneSlot = components.PhoneCallButton;
8550
8949
  const VideoSlot = components.VideoCallButton;
8551
8950
  const hideInGroups = features.hideCallActionsInGroupChats ?? false;
8951
+ const audioGate = getAudioCallGate();
8952
+ const videoGate = getVideoCallGate();
8552
8953
  if (hideInGroups && activeChannel.isGroup) return null;
8553
8954
  if (!showPhoneCall && !showVideoCall) return null;
8955
+ const phoneEnabled = showPhoneCall && audioGate.enabled;
8956
+ const videoEnabled = showVideoCall && videoGate.enabled;
8554
8957
  return /* @__PURE__ */ jsxs(Fragment, { children: [
8555
8958
  showPhoneCall && (PhoneSlot ? /* @__PURE__ */ jsx(
8556
- PhoneSlot,
8959
+ AdminPermissionTooltip,
8557
8960
  {
8558
- activeChannel,
8559
- conversationId,
8560
- onPhoneCall,
8561
- className: cn(className, slotClassName("phoneCallButton"))
8961
+ disabled: !audioGate.enabled,
8962
+ message: audioGate.disabledReason,
8963
+ children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
8964
+ PhoneSlot,
8965
+ {
8966
+ activeChannel,
8967
+ conversationId,
8968
+ onPhoneCall: phoneEnabled ? onPhoneCall : void 0,
8969
+ className: cn(
8970
+ className,
8971
+ slotClassName("phoneCallButton"),
8972
+ !audioGate.enabled && "pointer-events-none opacity-40"
8973
+ )
8974
+ }
8975
+ ) })
8562
8976
  }
8563
8977
  ) : /* @__PURE__ */ jsx(
8564
- Button,
8978
+ AdminPermissionTooltip,
8565
8979
  {
8566
- variant: "ghost",
8567
- size: "icon",
8568
- className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
8569
- "aria-label": "Voice call",
8570
- onClick: onPhoneCall,
8571
- children: /* @__PURE__ */ jsx(Phone, { size: 15 })
8980
+ disabled: !audioGate.enabled,
8981
+ message: audioGate.disabledReason,
8982
+ children: /* @__PURE__ */ jsx(
8983
+ Button,
8984
+ {
8985
+ variant: "ghost",
8986
+ size: "icon",
8987
+ className: cn(actionBtnClass, className, slotClassName("phoneCallButton")),
8988
+ "aria-label": "Voice call",
8989
+ disabled: !audioGate.enabled,
8990
+ onClick: phoneEnabled ? onPhoneCall : void 0,
8991
+ children: /* @__PURE__ */ jsx(Phone, { size: 15 })
8992
+ }
8993
+ )
8572
8994
  }
8573
8995
  )),
8574
8996
  showVideoCall && (VideoSlot ? /* @__PURE__ */ jsx(
8575
- VideoSlot,
8997
+ AdminPermissionTooltip,
8576
8998
  {
8577
- activeChannel,
8578
- conversationId,
8579
- onVideoCall,
8580
- className: cn(className, slotClassName("videoCallButton"))
8999
+ disabled: !videoGate.enabled,
9000
+ message: videoGate.disabledReason,
9001
+ children: /* @__PURE__ */ jsx("span", { className: "inline-flex", children: /* @__PURE__ */ jsx(
9002
+ VideoSlot,
9003
+ {
9004
+ activeChannel,
9005
+ conversationId,
9006
+ onVideoCall: videoEnabled ? onVideoCall : void 0,
9007
+ className: cn(
9008
+ className,
9009
+ slotClassName("videoCallButton"),
9010
+ !videoGate.enabled && "pointer-events-none opacity-40"
9011
+ )
9012
+ }
9013
+ ) })
8581
9014
  }
8582
9015
  ) : /* @__PURE__ */ jsx(
8583
- Button,
9016
+ AdminPermissionTooltip,
8584
9017
  {
8585
- variant: "ghost",
8586
- size: "icon",
8587
- className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
8588
- "aria-label": "Video call",
8589
- onClick: onVideoCall,
8590
- children: /* @__PURE__ */ jsx(Video, { size: 15 })
9018
+ disabled: !videoGate.enabled,
9019
+ message: videoGate.disabledReason,
9020
+ children: /* @__PURE__ */ jsx(
9021
+ Button,
9022
+ {
9023
+ variant: "ghost",
9024
+ size: "icon",
9025
+ className: cn(actionBtnClass, className, slotClassName("videoCallButton")),
9026
+ "aria-label": "Video call",
9027
+ disabled: !videoGate.enabled,
9028
+ onClick: videoEnabled ? onVideoCall : void 0,
9029
+ children: /* @__PURE__ */ jsx(Video, { size: 15 })
9030
+ }
9031
+ )
8591
9032
  }
8592
9033
  ))
8593
9034
  ] });
@@ -8609,19 +9050,38 @@ function OptionsMenuItem({
8609
9050
  icon: Icon,
8610
9051
  label,
8611
9052
  onSelect,
8612
- destructive = false
9053
+ destructive = false,
9054
+ disabled = false,
9055
+ disabledReason
8613
9056
  }) {
8614
- return /* @__PURE__ */ jsxs(
9057
+ const item = /* @__PURE__ */ jsxs(
8615
9058
  DropdownMenuItem,
8616
9059
  {
8617
- className: destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
8618
- onSelect,
9060
+ className: cn(
9061
+ destructive ? chatOptionsMenuItemDestructiveClass : chatOptionsMenuItemClass,
9062
+ disabled && "pointer-events-none opacity-40"
9063
+ ),
9064
+ disabled,
9065
+ onSelect: (event) => {
9066
+ if (disabled) {
9067
+ event.preventDefault();
9068
+ return;
9069
+ }
9070
+ onSelect?.();
9071
+ },
8619
9072
  children: [
8620
9073
  /* @__PURE__ */ jsx(Icon, { strokeWidth: 1.75 }),
8621
9074
  /* @__PURE__ */ jsx("span", { className: "flex-1", children: label })
8622
9075
  ]
8623
9076
  }
8624
9077
  );
9078
+ if (!disabled || !disabledReason) {
9079
+ return item;
9080
+ }
9081
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
9082
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "w-full", children: item }) }),
9083
+ /* @__PURE__ */ jsx(TooltipContent, { side: "left", className: "max-w-[220px] text-xs", children: disabledReason })
9084
+ ] });
8625
9085
  }
8626
9086
  var HeaderRightSide = ({
8627
9087
  activeChannel,
@@ -8656,6 +9116,8 @@ var HeaderRightSide = ({
8656
9116
  const { scopeClassName, themeStyles, showColorModeToggle } = useChatTheme();
8657
9117
  const { showDateTimeSettings } = useChatLocale();
8658
9118
  const features = useChatFeaturesOptional();
9119
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
9120
+ const blockUsersGate = getBlockUsersGate();
8659
9121
  const showPhoneCall = features.showPhoneCall ?? defaultChatFeatures.showPhoneCall;
8660
9122
  const showVideoCall = features.showVideoCall ?? defaultChatFeatures.showVideoCall;
8661
9123
  const handlePhoneCall = () => {
@@ -8804,7 +9266,9 @@ var HeaderRightSide = ({
8804
9266
  icon: Ban,
8805
9267
  label: isBlocked ? "Unblock User" : "Block User",
8806
9268
  onSelect: onToggleBlock,
8807
- destructive: true
9269
+ destructive: true,
9270
+ disabled: !blockUsersGate.enabled,
9271
+ disabledReason: blockUsersGate.disabledReason
8808
9272
  }
8809
9273
  )
8810
9274
  ]
@@ -12844,17 +13308,24 @@ var ChannelDetailsDrawer = (props) => {
12844
13308
  };
12845
13309
  var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12846
13310
  init_useStore();
13311
+ init_settings_types();
12847
13312
  var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
12848
13313
  const updateGroupPermissions = useStore((s) => s.updateGroupPermissions);
13314
+ const { settings, isGroupPermissionLocked } = useEffectiveSettingsOptional();
12849
13315
  const [permissions, setPermissions] = useState(null);
12850
13316
  const [isSaving, setIsSaving] = useState(false);
12851
13317
  useEffect(() => {
12852
13318
  if (conversationInfo?.groupPermissions) {
12853
13319
  setPermissions(conversationInfo.groupPermissions);
13320
+ return;
12854
13321
  }
12855
- }, [conversationInfo]);
13322
+ if (isOpen) {
13323
+ setPermissions({ ...settings.groupPolicy.defaults });
13324
+ }
13325
+ }, [conversationInfo, isOpen, settings.groupPolicy.defaults]);
12856
13326
  if (!conversationId || !permissions) return null;
12857
13327
  const handleToggle = (key) => {
13328
+ if (isGroupPermissionLocked(key)) return;
12858
13329
  setPermissions((prev) => prev ? { ...prev, [key]: !prev[key] } : null);
12859
13330
  };
12860
13331
  const handleSave = async () => {
@@ -12886,31 +13357,43 @@ var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo
12886
13357
  /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "flex flex-col px-2.5 py-2"), children: [
12887
13358
  /* @__PURE__ */ jsxs("section", { className: "mb-2.5 flex-1", children: [
12888
13359
  /* @__PURE__ */ jsx("h3", { className: "mb-1 px-0.5 text-[10px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "Permissions" }),
12889
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => /* @__PURE__ */ jsxs(
12890
- "div",
12891
- {
12892
- className: cn(
12893
- "flex items-start justify-between gap-3 py-2.5",
12894
- index < permissionItems.length - 1 && "border-b border-(--chat-border)"
12895
- ),
12896
- children: [
12897
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-0.5", children: [
12898
- /* @__PURE__ */ jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
12899
- /* @__PURE__ */ jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc })
12900
- ] }),
12901
- /* @__PURE__ */ jsx(
12902
- Switch,
12903
- {
12904
- size: "sm",
12905
- className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
12906
- checked: "inverted" in item && item.inverted ? !permissions[item.key] : permissions[item.key],
12907
- onCheckedChange: () => handleToggle(item.key)
12908
- }
12909
- )
12910
- ]
12911
- },
12912
- item.key
12913
- )) })
13360
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-(--chat-border) px-3", children: permissionItems.map((item, index) => {
13361
+ const locked = isGroupPermissionLocked(item.key);
13362
+ return /* @__PURE__ */ jsxs(
13363
+ "div",
13364
+ {
13365
+ className: cn(
13366
+ "flex items-start justify-between gap-3 py-2.5",
13367
+ index < permissionItems.length - 1 && "border-b border-(--chat-border)"
13368
+ ),
13369
+ children: [
13370
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-0.5", children: [
13371
+ /* @__PURE__ */ jsx("p", { className: "text-[12px] font-semibold leading-snug text-(--chat-text)", children: item.label }),
13372
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] leading-relaxed text-(--chat-muted)", children: item.desc }),
13373
+ locked ? /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-(--chat-theme)", children: "Locked by administrator" }) : null
13374
+ ] }),
13375
+ /* @__PURE__ */ jsx(
13376
+ AdminPermissionTooltip,
13377
+ {
13378
+ disabled: locked,
13379
+ message: ADMIN_PERMISSION_DENIED_MESSAGE,
13380
+ children: /* @__PURE__ */ jsx(
13381
+ Switch,
13382
+ {
13383
+ size: "sm",
13384
+ className: "mt-0.5 shrink-0 data-[state=checked]:bg-(--chat-theme) data-[state=unchecked]:bg-(--chat-border)",
13385
+ disabled: locked,
13386
+ checked: item.inverted ? !permissions[item.key] : permissions[item.key],
13387
+ onCheckedChange: () => handleToggle(item.key)
13388
+ }
13389
+ )
13390
+ }
13391
+ )
13392
+ ]
13393
+ },
13394
+ item.key
13395
+ );
13396
+ }) })
12914
13397
  ] }),
12915
13398
  /* @__PURE__ */ jsx("div", { className: "sticky bottom-0 border-t border-(--chat-border) bg-(--chat-surface) px-0.5 pt-2.5 pb-1", children: /* @__PURE__ */ jsx(
12916
13399
  Button,
@@ -12940,6 +13423,8 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12940
13423
  const [isManaging, setIsManaging] = useState(null);
12941
13424
  const manageGroupAdmins = useStore((s) => s.manageGroupAdmins);
12942
13425
  const { isOwner } = useGroupInfo(conversationId);
13426
+ const { settings } = useEffectiveSettingsOptional();
13427
+ const maxAdmins = settings.maxAdminsPerGroup;
12943
13428
  const participants = useMemo(
12944
13429
  () => conversationInfo?.participants ?? [],
12945
13430
  [conversationInfo?.participants]
@@ -12979,8 +13464,16 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12979
13464
  }),
12980
13465
  [filteredParticipants, adminIds, ownerId]
12981
13466
  );
13467
+ const adminCount = admins.length + (ownerId ? 1 : 0);
13468
+ const atAdminLimit = adminCount >= maxAdmins;
12982
13469
  const handleManageAdmin = async (userId, type) => {
12983
13470
  if (!conversationId) return;
13471
+ if (type === "add" && adminCount >= maxAdmins) {
13472
+ useStore.getState().handleAppError(
13473
+ `You can have up to ${maxAdmins} admins in this group.`
13474
+ );
13475
+ return;
13476
+ }
12984
13477
  setIsManaging(userId);
12985
13478
  try {
12986
13479
  await manageGroupAdmins({
@@ -12992,7 +13485,6 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
12992
13485
  setIsManaging(null);
12993
13486
  }
12994
13487
  };
12995
- const adminCount = admins.length + (ownerId ? 1 : 0);
12996
13488
  return {
12997
13489
  searchQuery,
12998
13490
  setSearchQuery,
@@ -13004,6 +13496,8 @@ function useAdminsDrawer({ conversationId, conversationInfo }) {
13004
13496
  promotableParticipants,
13005
13497
  handleManageAdmin,
13006
13498
  adminCount,
13499
+ maxAdmins,
13500
+ atAdminLimit,
13007
13501
  hasRequiredData: Boolean(conversationId && conversationInfo)
13008
13502
  };
13009
13503
  }
@@ -13016,6 +13510,8 @@ function AdminParticipantRow({
13016
13510
  isAdmin,
13017
13511
  showPromote,
13018
13512
  showDemote,
13513
+ promoteDisabled = false,
13514
+ promoteDisabledReason,
13019
13515
  isOwnerViewer,
13020
13516
  isManaging,
13021
13517
  onManageAdmin
@@ -13056,14 +13552,24 @@ function AdminParticipantRow({
13056
13552
  }
13057
13553
  ),
13058
13554
  showPromote && isOwnerViewer && /* @__PURE__ */ jsx(
13059
- Button,
13555
+ AdminPermissionTooltip,
13060
13556
  {
13061
- variant: "ghost",
13062
- size: "sm",
13063
- className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme)",
13064
- onClick: () => onManageAdmin(memberId, "add"),
13065
- disabled: isManaging === memberId,
13066
- children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13557
+ disabled: promoteDisabled,
13558
+ message: promoteDisabledReason,
13559
+ children: /* @__PURE__ */ jsx(
13560
+ Button,
13561
+ {
13562
+ variant: "ghost",
13563
+ size: "sm",
13564
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13565
+ onClick: () => {
13566
+ if (promoteDisabled) return;
13567
+ onManageAdmin(memberId, "add");
13568
+ },
13569
+ disabled: isManaging === memberId || promoteDisabled,
13570
+ children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13571
+ }
13572
+ )
13067
13573
  }
13068
13574
  )
13069
13575
  ] })
@@ -13087,7 +13593,9 @@ function AdminParticipantList({
13087
13593
  isAdmin,
13088
13594
  onManageAdmin,
13089
13595
  showPromote,
13090
- showDemoteForAdmins
13596
+ showDemoteForAdmins,
13597
+ promoteDisabled = false,
13598
+ promoteDisabledReason
13091
13599
  }) {
13092
13600
  return /* @__PURE__ */ jsx(Fragment, { children: members.map((member, index) => {
13093
13601
  const { memberId, memberName, memberEmail, memberImage } = getMemberFields(member);
@@ -13105,6 +13613,8 @@ function AdminParticipantList({
13105
13613
  isAdmin: userIsAdmin,
13106
13614
  showPromote: showPromote && !userIsAdmin && !userIsOwner,
13107
13615
  showDemote: showDemoteForAdmins ? userIsAdmin && !userIsOwner : false,
13616
+ promoteDisabled,
13617
+ promoteDisabledReason,
13108
13618
  isOwnerViewer,
13109
13619
  isManaging,
13110
13620
  onManageAdmin
@@ -13124,6 +13634,8 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13124
13634
  promotableParticipants,
13125
13635
  handleManageAdmin,
13126
13636
  adminCount,
13637
+ maxAdmins,
13638
+ atAdminLimit,
13127
13639
  hasRequiredData
13128
13640
  } = useAdminsDrawer({ conversationId, conversationInfo });
13129
13641
  if (!hasRequiredData) return null;
@@ -13143,7 +13655,10 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13143
13655
  /* @__PURE__ */ jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Administrators" }),
13144
13656
  /* @__PURE__ */ jsx("span", { className: "rounded-full bg-(--chat-theme-10) px-2 py-0.5 text-[11px] font-medium leading-none text-(--chat-theme)", children: adminCount })
13145
13657
  ] }),
13146
- /* @__PURE__ */ jsx("p", { className: chatSheetSubtitleClass, children: "Manage who can adjust group settings and members." })
13658
+ /* @__PURE__ */ jsxs("p", { className: chatSheetSubtitleClass, children: [
13659
+ "Manage who can adjust group settings and members",
13660
+ maxAdmins ? ` (max ${maxAdmins}).` : "."
13661
+ ] })
13147
13662
  ] }),
13148
13663
  /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13149
13664
  /* @__PURE__ */ jsx("div", { className: "mb-2.5 px-0.5", children: /* @__PURE__ */ jsx(
@@ -13186,7 +13701,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13186
13701
  ownerId,
13187
13702
  isAdmin,
13188
13703
  onManageAdmin: handleManageAdmin,
13189
- showPromote: true
13704
+ showPromote: true,
13705
+ promoteDisabled: atAdminLimit,
13706
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13190
13707
  }
13191
13708
  ) })
13192
13709
  ] }),
@@ -13203,7 +13720,9 @@ var AdminsDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13203
13720
  isAdmin,
13204
13721
  onManageAdmin: handleManageAdmin,
13205
13722
  showPromote: true,
13206
- showDemoteForAdmins: true
13723
+ showDemoteForAdmins: true,
13724
+ promoteDisabled: atAdminLimit,
13725
+ promoteDisabledReason: `Maximum ${maxAdmins} admins allowed in this group.`
13207
13726
  }
13208
13727
  ) })
13209
13728
  ] }),
@@ -13231,9 +13750,12 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13231
13750
  );
13232
13751
  }, [allUsers, searchQuery]);
13233
13752
  const { userPermissions } = useGroupInfo(conversationId);
13753
+ const { settings } = useEffectiveSettingsOptional();
13754
+ const maxParticipants = settings.maxParticipantsPerGroup;
13234
13755
  if (!conversationId || !conversationInfo) return null;
13235
13756
  const participants = conversationInfo.participants || [];
13236
13757
  const admins = conversationInfo.groupAdmins || [];
13758
+ const atParticipantLimit = participants.length >= maxParticipants;
13237
13759
  let ownerId = null;
13238
13760
  if (conversationInfo.owner) {
13239
13761
  ownerId = typeof conversationInfo.owner === "object" && conversationInfo.owner !== null ? conversationInfo.owner._id || conversationInfo.owner.id : conversationInfo.owner;
@@ -13287,14 +13809,24 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13287
13809
  }
13288
13810
  ),
13289
13811
  options.showAdd && /* @__PURE__ */ jsx(
13290
- Button,
13812
+ AdminPermissionTooltip,
13291
13813
  {
13292
- variant: "ghost",
13293
- size: "sm",
13294
- className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme)",
13295
- onClick: () => handleManageMember(memberId, "add"),
13296
- disabled: isManaging === memberId,
13297
- children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13814
+ disabled: atParticipantLimit,
13815
+ message: `Maximum ${maxParticipants} participants allowed in this group.`,
13816
+ children: /* @__PURE__ */ jsx(
13817
+ Button,
13818
+ {
13819
+ variant: "ghost",
13820
+ size: "sm",
13821
+ className: "size-7 rounded-md p-0 text-(--chat-muted) hover:bg-(--chat-theme-10) hover:text-(--chat-theme) disabled:opacity-40",
13822
+ onClick: () => {
13823
+ if (atParticipantLimit) return;
13824
+ handleManageMember(memberId, "add");
13825
+ },
13826
+ disabled: isManaging === memberId || atParticipantLimit,
13827
+ children: isManaging === memberId ? /* @__PURE__ */ jsx(Loader2, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsx(UserPlus, { size: 13 })
13828
+ }
13829
+ )
13298
13830
  }
13299
13831
  )
13300
13832
  ] })
@@ -13316,7 +13848,11 @@ var MembersDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo })
13316
13848
  /* @__PURE__ */ jsx(SheetTitle, { className: chatSheetTitleClass, children: "Group Management" }),
13317
13849
  /* @__PURE__ */ jsx("span", { className: "rounded-full bg-(--chat-theme-10) px-2 py-0.5 text-[11px] font-medium leading-none text-(--chat-theme)", children: participants.length })
13318
13850
  ] }),
13319
- /* @__PURE__ */ jsx("p", { className: chatSheetSubtitleClass, children: "Manage members and invite people." })
13851
+ /* @__PURE__ */ jsxs("p", { className: chatSheetSubtitleClass, children: [
13852
+ "Manage members and invite people (max ",
13853
+ maxParticipants,
13854
+ ")."
13855
+ ] })
13320
13856
  ] }),
13321
13857
  /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "px-2.5 py-2"), children: [
13322
13858
  !searchQuery && /* @__PURE__ */ jsxs("section", { className: "mb-2.5", children: [
@@ -13458,6 +13994,8 @@ init_store_helpers();
13458
13994
  function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13459
13995
  const loggedUserDetails = useStore((s) => s.loggedUserDetails);
13460
13996
  const composerDisabledByConversation = useStore((s) => s.composerDisabledByConversation);
13997
+ const { getBlockUsersGate } = useEffectiveSettingsOptional();
13998
+ const blockUsersGate = getBlockUsersGate();
13461
13999
  const [isBlocking, setIsBlocking] = useState(false);
13462
14000
  const resolvedConversationId = conversationId ?? activeChannel?.conversationId ?? null;
13463
14001
  const composerDisabledReason = resolvedConversationId ? composerDisabledByConversation[resolvedConversationId] : void 0;
@@ -13479,6 +14017,12 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13479
14017
  const toggleBlock = useCallback(async () => {
13480
14018
  if (!activeChannel?.id || activeChannel.isGroup) return false;
13481
14019
  const nextBlocked = !isBlocked;
14020
+ if (nextBlocked && !blockUsersGate.enabled) {
14021
+ useStore.getState().handleAppError(
14022
+ blockUsersGate.disabledReason || "Blocking users is disabled by your administrator."
14023
+ );
14024
+ return false;
14025
+ }
13482
14026
  const type = nextBlocked ? "block" : "unblock";
13483
14027
  try {
13484
14028
  setIsBlocking(true);
@@ -13499,8 +14043,8 @@ function useBlockUser(activeChannel, conversationId, onActiveChannelPatch) {
13499
14043
  } finally {
13500
14044
  setIsBlocking(false);
13501
14045
  }
13502
- }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId]);
13503
- return { isBlocked, isBlocking, toggleBlock };
14046
+ }, [activeChannel, isBlocked, onActiveChannelPatch, resolvedConversationId, blockUsersGate]);
14047
+ return { isBlocked, isBlocking, toggleBlock, blockUsersGate };
13504
14048
  }
13505
14049
 
13506
14050
  // src/chat/channel-header/useChannelHeader.ts
@@ -14036,6 +14580,7 @@ var MessageReactions_default = MessageReactions;
14036
14580
  var MessageToolbar = ({
14037
14581
  isSender,
14038
14582
  isRecent,
14583
+ isWithinDeleteWindow = isRecent,
14039
14584
  isPinned,
14040
14585
  isStarred,
14041
14586
  isPinPending,
@@ -14059,7 +14604,7 @@ var MessageToolbar = ({
14059
14604
  }) => {
14060
14605
  const { isDark, scopeClassName, themeStyles } = useChatTheme();
14061
14606
  const showDeleteMenu = isSender || hasAttachments || attachmentDeleteMode;
14062
- const showDeleteForEveryone = isSender && isRecent;
14607
+ const showDeleteForEveryone = isSender && isWithinDeleteWindow;
14063
14608
  return /* @__PURE__ */ jsxs(
14064
14609
  "div",
14065
14610
  {
@@ -14381,16 +14926,59 @@ function resolveForwardItems(selectedKeys, messages) {
14381
14926
  }
14382
14927
 
14383
14928
  // src/chat/lib/chat-edit-window.ts
14384
- init_chat_constants();
14385
- function getMessageEditWindowMs(isGroup) {
14386
- return isGroup ? GROUP_EDIT_WINDOW_MS : DM_EDIT_WINDOW_MS;
14929
+ init_settings_types();
14930
+
14931
+ // src/lib/effective-settings.helpers.ts
14932
+ function minutesToMs(minutes) {
14933
+ return Math.max(0, minutes) * 60 * 1e3;
14934
+ }
14935
+ function getEditWindowMs(timers, isGroup) {
14936
+ return minutesToMs(isGroup ? timers.groupEditMinutes : timers.dmEditMinutes);
14937
+ }
14938
+ function getDeleteWindowMs(timers, isGroup) {
14939
+ return minutesToMs(
14940
+ isGroup ? timers.groupDeleteMinutes : timers.dmDeleteMinutes
14941
+ );
14387
14942
  }
14388
- function isWithinMessageEditWindow(createdAt, isGroup) {
14943
+ function isWithinWindow(createdAt, windowMs) {
14389
14944
  if (!createdAt) return false;
14390
14945
  const date = new Date(createdAt);
14391
14946
  if (isNaN(date.getTime())) return false;
14392
14947
  const diff = Date.now() - date.getTime();
14393
- return diff >= 0 && diff < getMessageEditWindowMs(isGroup);
14948
+ return diff >= 0 && diff < windowMs;
14949
+ }
14950
+ function isWithinMessageEditWindow(createdAt, timers, isGroup) {
14951
+ return isWithinWindow(createdAt, getEditWindowMs(timers, isGroup));
14952
+ }
14953
+ function isWithinMessageDeleteWindow(createdAt, timers, isGroup) {
14954
+ return isWithinWindow(createdAt, getDeleteWindowMs(timers, isGroup));
14955
+ }
14956
+ function matchesAllowedMimeType(fileType, allowedMimeTypes) {
14957
+ const mime = (fileType || "application/octet-stream").toLowerCase();
14958
+ return allowedMimeTypes.some((pattern) => {
14959
+ const normalized = pattern.trim().toLowerCase();
14960
+ if (!normalized) return false;
14961
+ if (normalized.endsWith("/*")) {
14962
+ const prefix = normalized.slice(0, -1);
14963
+ return mime.startsWith(prefix);
14964
+ }
14965
+ return mime === normalized;
14966
+ });
14967
+ }
14968
+ function buildAcceptFromMimeTypes(allowedMimeTypes) {
14969
+ return allowedMimeTypes.map((type) => type.trim()).filter(Boolean).join(",");
14970
+ }
14971
+ function formatFileSizeMB(sizeBytes) {
14972
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
14973
+ }
14974
+
14975
+ // src/chat/lib/chat-edit-window.ts
14976
+ var FALLBACK_TIMERS = DEFAULT_EFFECTIVE_SETTINGS.messageTimers;
14977
+ function isWithinMessageEditWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
14978
+ return isWithinMessageEditWindow(createdAt, timers, isGroup);
14979
+ }
14980
+ function isWithinMessageDeleteWindow2(createdAt, isGroup, timers = FALLBACK_TIMERS) {
14981
+ return isWithinMessageDeleteWindow(createdAt, timers, isGroup);
14394
14982
  }
14395
14983
 
14396
14984
  // src/chat/message-item/useMessageItemActions.ts
@@ -14468,6 +15056,9 @@ function useMessageItemActions({
14468
15056
  const canUnpin = !isPinned || isPinnedByMe;
14469
15057
  const { userPermissions } = useGroupInfo(conversationId || null);
14470
15058
  const { formatMessageTime } = useChatLocale();
15059
+ const { settings } = useEffectiveSettingsOptional();
15060
+ const messageTimers = settings.messageTimers;
15061
+ const maxPinnedMessages = settings.maxPinnedMessagesPerConversation;
14471
15062
  const visibleAttachments = getVisibleAttachments(attachments);
14472
15063
  const hasAttachments = visibleAttachments.length > 0;
14473
15064
  const hasMultiAttachments = hasAttachments && messageUsesAttachmentSelection({ attachments: visibleAttachments });
@@ -14484,7 +15075,8 @@ function useMessageItemActions({
14484
15075
  const hasText = Boolean(message?.trim());
14485
15076
  const isSelected = !!mid && !!selectedKeys && isMessageWhollySelected(selectedKeys, mid) && !hasMultiAttachments;
14486
15077
  const canEdit = (!isGroup || userPermissions.canEditMessage) && hasText;
14487
- const isWithinEditWindow = () => isWithinMessageEditWindow(createdAt, isGroup);
15078
+ const isWithinEditWindow = () => isWithinMessageEditWindow2(createdAt, isGroup, messageTimers);
15079
+ const isWithinDeleteWindow = () => isWithinMessageDeleteWindow2(createdAt, isGroup, messageTimers);
14488
15080
  const canEditAttachmentCaption = isSender && isWithinEditWindow() && (!isGroup || userPermissions.canEditMessage) && !isDeletedForEveryone && !!mid;
14489
15081
  const handleAttachmentCaptionClick = (attachment) => {
14490
15082
  if (!canEditAttachmentCaption) return;
@@ -14653,6 +15245,15 @@ function useMessageItemActions({
14653
15245
  });
14654
15246
  }
14655
15247
  } else {
15248
+ if (conversationId) {
15249
+ const pinnedCount = (pinnedMessagesByConversation[conversationId] || []).length || useStore.getState().liveConversationCounts[conversationId]?.pinnedCount || 0;
15250
+ if (pinnedCount >= maxPinnedMessages) {
15251
+ useStore.getState().handleAppError(
15252
+ `You can pin up to ${maxPinnedMessages} messages in this conversation.`
15253
+ );
15254
+ return;
15255
+ }
15256
+ }
14656
15257
  if (isGroup) {
14657
15258
  if (!conversationId) return;
14658
15259
  emitGroupPin({
@@ -14711,6 +15312,7 @@ function useMessageItemActions({
14711
15312
  canUnpin,
14712
15313
  canEdit,
14713
15314
  isWithinEditWindow,
15315
+ isWithinDeleteWindow,
14714
15316
  canEditAttachmentCaption,
14715
15317
  visibleAttachments,
14716
15318
  hasAttachments,
@@ -14742,7 +15344,7 @@ function useMessageItemActions({
14742
15344
  }
14743
15345
 
14744
15346
  // src/chat/lib/reply-preview.ts
14745
- function normalizeAttachments(replyTo, loggedUserId) {
15347
+ function normalizeAttachments2(replyTo, loggedUserId) {
14746
15348
  const raw = replyTo?.attachments;
14747
15349
  if (!Array.isArray(raw) || raw.length === 0) return [];
14748
15350
  return raw.map((att, index) => {
@@ -14755,7 +15357,7 @@ function resolveReplyToMessage(replyTo, messagesById, loggedUserId) {
14755
15357
  if (typeof replyTo === "object" && (replyTo.content?.trim() || replyTo.attachments?.length)) {
14756
15358
  return {
14757
15359
  ...replyTo,
14758
- attachments: normalizeAttachments(replyTo, loggedUserId)
15360
+ attachments: normalizeAttachments2(replyTo, loggedUserId)
14759
15361
  };
14760
15362
  }
14761
15363
  const replyId = String(
@@ -15192,6 +15794,7 @@ function MessageItemView(props) {
15192
15794
  canUnpin,
15193
15795
  canEdit,
15194
15796
  isWithinEditWindow,
15797
+ isWithinDeleteWindow,
15195
15798
  canEditAttachmentCaption,
15196
15799
  visibleAttachments,
15197
15800
  hasAttachments,
@@ -15283,6 +15886,7 @@ function MessageItemView(props) {
15283
15886
  {
15284
15887
  isSender,
15285
15888
  isRecent: isWithinEditWindow(),
15889
+ isWithinDeleteWindow: isWithinDeleteWindow(),
15286
15890
  messageId: mid,
15287
15891
  isPinned,
15288
15892
  isStarred,
@@ -15577,6 +16181,7 @@ function MessageSelectionBar({
15577
16181
  }
15578
16182
 
15579
16183
  // src/chat/active-channel-messages/utils.ts
16184
+ init_settings_types();
15580
16185
  var getMessageSenderId2 = (msg) => msg ? String(msg.senderId || msg.sender?._id || "") : "";
15581
16186
  var isLikelyObjectId = (value) => /^[a-f\d]{24}$/i.test(value);
15582
16187
  function resolveGroupSenderDisplay(message, allUsers = [], participants = []) {
@@ -15608,7 +16213,7 @@ var isSameMessageSide = (a, b, isGroupChat) => {
15608
16213
  const bId = getMessageSenderId2(b);
15609
16214
  return aId !== "" && aId === bId;
15610
16215
  };
15611
- function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
16216
+ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup, timers = DEFAULT_EFFECTIVE_SETTINGS.messageTimers) {
15612
16217
  if (selectedKeys.size === 0) return false;
15613
16218
  const parsed = [...selectedKeys].map((key) => parseSelectionKey(key));
15614
16219
  const messageIds = new Set(parsed.map((entry) => entry.messageId));
@@ -15616,7 +16221,7 @@ function canDeleteSelectionForEveryone(selectedKeys, messages, isGroup) {
15616
16221
  const messageId2 = [...messageIds][0];
15617
16222
  const message = messages.find((entry) => String(entry._id || entry.id) === messageId2);
15618
16223
  if (!message?.isOwnMessage) return false;
15619
- if (!isWithinMessageEditWindow(message.timestamp, isGroup)) return false;
16224
+ if (!isWithinMessageDeleteWindow2(message.timestamp, isGroup, timers)) return false;
15620
16225
  const hasAttachmentKey = parsed.some((entry) => entry.attachmentId);
15621
16226
  if (!hasAttachmentKey) {
15622
16227
  return selectedKeys.size === 1;
@@ -15785,6 +16390,8 @@ function useActiveChannelMessages({
15785
16390
  const emitDmDelete = useStore((s) => s.emitDmDelete);
15786
16391
  const emitGroupDelete = useStore((s) => s.emitGroupDelete);
15787
16392
  const loggedUserDetails = useStore((s) => s.loggedUserDetails);
16393
+ const { settings } = useEffectiveSettingsOptional();
16394
+ const messageTimers = settings.messageTimers;
15788
16395
  const scrollContainerRef = useRef(null);
15789
16396
  const bottomRef = useRef(null);
15790
16397
  const messageListInitializedRef = useRef(false);
@@ -15903,8 +16510,13 @@ function useActiveChannelMessages({
15903
16510
  }, []);
15904
16511
  const selectedForwardItems = resolveForwardItems(selectedKeys, displayMessages);
15905
16512
  const canDeleteForEveryone = useMemo(
15906
- () => canDeleteSelectionForEveryone(selectedKeys, displayMessages, !!activeChannel?.isGroup),
15907
- [selectedKeys, displayMessages, activeChannel?.isGroup]
16513
+ () => canDeleteSelectionForEveryone(
16514
+ selectedKeys,
16515
+ displayMessages,
16516
+ !!activeChannel?.isGroup,
16517
+ messageTimers
16518
+ ),
16519
+ [selectedKeys, displayMessages, activeChannel?.isGroup, messageTimers]
15908
16520
  );
15909
16521
  const emitSelectionDelete = useCallback(
15910
16522
  (deleteType) => {
@@ -15912,7 +16524,8 @@ function useActiveChannelMessages({
15912
16524
  if (deleteType === "everyone" && !canDeleteSelectionForEveryone(
15913
16525
  selectedKeys,
15914
16526
  displayMessages,
15915
- !!activeChannel?.isGroup
16527
+ !!activeChannel?.isGroup,
16528
+ messageTimers
15916
16529
  )) {
15917
16530
  return;
15918
16531
  }
@@ -15953,7 +16566,8 @@ function useActiveChannelMessages({
15953
16566
  conversationId,
15954
16567
  emitGroupDelete,
15955
16568
  emitDmDelete,
15956
- clearSelection
16569
+ clearSelection,
16570
+ messageTimers
15957
16571
  ]
15958
16572
  );
15959
16573
  const handleBatchDeleteForMe = useCallback(() => {
@@ -16291,8 +16905,8 @@ function CalendarDayButton({
16291
16905
  ...props
16292
16906
  }) {
16293
16907
  const defaultClassNames = getDefaultClassNames();
16294
- const ref = React2.useRef(null);
16295
- React2.useEffect(() => {
16908
+ const ref = React3.useRef(null);
16909
+ React3.useEffect(() => {
16296
16910
  if (modifiers.focused) ref.current?.focus();
16297
16911
  }, [modifiers.focused]);
16298
16912
  return /* @__PURE__ */ jsx(
@@ -17196,7 +17810,6 @@ var getAttachmentType = (file) => {
17196
17810
  };
17197
17811
 
17198
17812
  // src/chat/channel-message-box/useChannelMessageBox.ts
17199
- var MAX_ATTACHMENTS = COMPOSER_MAX_ATTACHMENTS;
17200
17813
  function useChannelMessageBox({
17201
17814
  activeChannel,
17202
17815
  conversationId,
@@ -17210,6 +17823,12 @@ function useChannelMessageBox({
17210
17823
  conversationId,
17211
17824
  onActiveChannelPatch
17212
17825
  );
17826
+ const { settings, getAttachmentsGate } = useEffectiveSettingsOptional();
17827
+ const attachmentsGate = getAttachmentsGate();
17828
+ const maxAttachments = settings.attachments.maxAttachmentsPerMessage;
17829
+ const maxFileSizeBytes = settings.attachments.maxFileSizeMB * 1024 * 1024;
17830
+ const allowedMimeTypes = settings.attachments.allowedMimeTypes;
17831
+ const acceptAttribute = buildAcceptFromMimeTypes(allowedMimeTypes);
17213
17832
  const [state, setState] = useState({
17214
17833
  messageInput: "",
17215
17834
  attachments: [],
@@ -17234,10 +17853,10 @@ function useChannelMessageBox({
17234
17853
  const composerDisabledReason = conversationId ? composerDisabledByConversation[conversationId] : void 0;
17235
17854
  const fileInputRef = useRef(null);
17236
17855
  const attachmentsCountRef = useRef(0);
17237
- React2__default.useEffect(() => {
17856
+ React3__default.useEffect(() => {
17238
17857
  attachmentsCountRef.current = state.attachments.length;
17239
17858
  }, [state.attachments.length]);
17240
- React2__default.useEffect(() => {
17859
+ React3__default.useEffect(() => {
17241
17860
  const draft = {
17242
17861
  messageInput: state.messageInput,
17243
17862
  attachments: state.attachments
@@ -17245,7 +17864,7 @@ function useChannelMessageBox({
17245
17864
  syncComposerRef(draft);
17246
17865
  updateConversationDraft(conversationIdRef.current, draft);
17247
17866
  }, [state.messageInput, state.attachments, syncComposerRef]);
17248
- React2__default.useEffect(() => {
17867
+ React3__default.useEffect(() => {
17249
17868
  if (typingTimeoutRef.current) {
17250
17869
  clearTimeout(typingTimeoutRef.current);
17251
17870
  typingTimeoutRef.current = null;
@@ -17305,21 +17924,52 @@ function useChannelMessageBox({
17305
17924
  const handleFileChange = (e) => {
17306
17925
  const files = e.target.files ? Array.from(e.target.files) : [];
17307
17926
  if (!files.length) return;
17927
+ if (!attachmentsGate.enabled) {
17928
+ updateState({
17929
+ attachmentError: attachmentsGate.disabledReason || "Attachments are disabled by your administrator."
17930
+ });
17931
+ if (fileInputRef.current) fileInputRef.current.value = "";
17932
+ return;
17933
+ }
17308
17934
  const currentCount = attachmentsCountRef.current;
17309
- const remaining = MAX_ATTACHMENTS - currentCount;
17935
+ const remaining = maxAttachments - currentCount;
17310
17936
  if (remaining <= 0) {
17311
- updateState({ attachmentError: `Maximum ${MAX_ATTACHMENTS} files allowed` });
17937
+ updateState({ attachmentError: `Maximum ${maxAttachments} files allowed` });
17312
17938
  if (fileInputRef.current) fileInputRef.current.value = "";
17313
17939
  return;
17314
17940
  }
17315
- if (files.length > remaining) {
17941
+ const validFiles = [];
17942
+ const rejectionMessages = [];
17943
+ for (const file of files) {
17944
+ if (!matchesAllowedMimeType(file.type, allowedMimeTypes)) {
17945
+ rejectionMessages.push(`"${file.name}" type is not allowed`);
17946
+ continue;
17947
+ }
17948
+ if (file.size > maxFileSizeBytes) {
17949
+ rejectionMessages.push(
17950
+ `"${file.name}" exceeds ${settings.attachments.maxFileSizeMB} MB (got ${formatFileSizeMB(file.size)})`
17951
+ );
17952
+ continue;
17953
+ }
17954
+ validFiles.push(file);
17955
+ }
17956
+ if (validFiles.length === 0) {
17957
+ updateState({
17958
+ attachmentError: rejectionMessages[0] || "Selected files are not allowed"
17959
+ });
17960
+ if (fileInputRef.current) fileInputRef.current.value = "";
17961
+ return;
17962
+ }
17963
+ if (validFiles.length > remaining) {
17316
17964
  updateState({
17317
17965
  attachmentError: `You can upload only ${remaining} more file${remaining > 1 ? "s" : ""}`
17318
17966
  });
17967
+ } else if (rejectionMessages.length > 0) {
17968
+ updateState({ attachmentError: rejectionMessages[0] });
17319
17969
  } else {
17320
17970
  updateState({ attachmentError: "" });
17321
17971
  }
17322
- const filesToAdd = files.slice(0, remaining);
17972
+ const filesToAdd = validFiles.slice(0, remaining);
17323
17973
  attachmentsCountRef.current = currentCount + filesToAdd.length;
17324
17974
  const newAttachments = filesToAdd.map((file) => ({
17325
17975
  id: `att-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
@@ -17376,7 +18026,7 @@ function useChannelMessageBox({
17376
18026
  return {
17377
18027
  ...prev,
17378
18028
  attachments: nextAttachments,
17379
- attachmentError: nextAttachments.length < MAX_ATTACHMENTS ? "" : prev.attachmentError
18029
+ attachmentError: nextAttachments.length < maxAttachments ? "" : prev.attachmentError
17380
18030
  };
17381
18031
  });
17382
18032
  };
@@ -17483,7 +18133,10 @@ function useChannelMessageBox({
17483
18133
  onlyAdminCanSendMessage,
17484
18134
  fileInputRef,
17485
18135
  hasUploadingAttachments,
17486
- maxAttachments: MAX_ATTACHMENTS,
18136
+ maxAttachments,
18137
+ acceptAttribute,
18138
+ attachmentsEnabled: attachmentsGate.enabled,
18139
+ attachmentsDisabledReason: attachmentsGate.disabledReason,
17487
18140
  handleInputChange,
17488
18141
  handleFileChange,
17489
18142
  handleRemoveAttachment,
@@ -17511,6 +18164,9 @@ var ChannelMessageBoxView = (props) => {
17511
18164
  fileInputRef,
17512
18165
  hasUploadingAttachments,
17513
18166
  maxAttachments,
18167
+ acceptAttribute,
18168
+ attachmentsEnabled,
18169
+ attachmentsDisabledReason,
17514
18170
  handleInputChange,
17515
18171
  handleFileChange,
17516
18172
  handleRemoveAttachment,
@@ -17608,16 +18264,23 @@ var ChannelMessageBoxView = (props) => {
17608
18264
  ),
17609
18265
  /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5 p-1.5", children: [
17610
18266
  /* @__PURE__ */ jsx(
17611
- Button,
18267
+ AdminPermissionTooltip,
17612
18268
  {
17613
- type: "button",
17614
- variant: "ghost",
17615
- size: "icon",
17616
- className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text)",
17617
- onClick: () => !hasUploadingAttachments && fileInputRef.current?.click(),
17618
- disabled: hasUploadingAttachments,
17619
- "aria-label": "Attach files",
17620
- children: /* @__PURE__ */ jsx(Paperclip, { size: 16 })
18269
+ disabled: !attachmentsEnabled,
18270
+ message: attachmentsDisabledReason,
18271
+ children: /* @__PURE__ */ jsx(
18272
+ Button,
18273
+ {
18274
+ type: "button",
18275
+ variant: "ghost",
18276
+ size: "icon",
18277
+ className: "shrink-0 size-9 rounded-xl text-(--chat-muted) transition-all hover:bg-(--chat-hover) hover:text-(--chat-text) disabled:opacity-40",
18278
+ onClick: () => attachmentsEnabled && !hasUploadingAttachments && fileInputRef.current?.click(),
18279
+ disabled: !attachmentsEnabled || hasUploadingAttachments,
18280
+ "aria-label": "Attach files",
18281
+ children: /* @__PURE__ */ jsx(Paperclip, { size: 16 })
18282
+ }
18283
+ )
17621
18284
  }
17622
18285
  ),
17623
18286
  /* @__PURE__ */ jsx(
@@ -17627,8 +18290,9 @@ var ChannelMessageBoxView = (props) => {
17627
18290
  ref: fileInputRef,
17628
18291
  className: "hidden",
17629
18292
  multiple: true,
17630
- accept: "image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx",
17631
- onChange: handleFileChange
18293
+ accept: acceptAttribute || void 0,
18294
+ onChange: handleFileChange,
18295
+ disabled: !attachmentsEnabled
17632
18296
  }
17633
18297
  ),
17634
18298
  /* @__PURE__ */ jsxs(Popover, { children: [
@@ -18543,8 +19207,8 @@ var ChatMain = ({
18543
19207
  viewportHeight = "full",
18544
19208
  viewportClassName
18545
19209
  }) => {
18546
- const clientObj = React2__default.useMemo(() => ({ id: clientId }), [clientId]);
18547
- const uiConfigObj = React2__default.useMemo(
19210
+ const clientObj = React3__default.useMemo(() => ({ id: clientId }), [clientId]);
19211
+ const uiConfigObj = React3__default.useMemo(
18548
19212
  () => ({
18549
19213
  ...uiConfig || {},
18550
19214
  components: { ...uiConfig?.components, ...components },
@@ -18915,8 +19579,10 @@ var packageDefaultComponents = {
18915
19579
  };
18916
19580
 
18917
19581
  // src/index.ts
19582
+ init_settings_types();
19583
+ init_api_service();
18918
19584
  var index_default = ChatMain_default;
18919
19585
 
18920
- export { CHAT_API_KEYS, CHAT_GET_API_KEYS, Calendar, CalendarDayButton, Channel, ChannelHeaderView_default as ChannelHeader, ChannelListView_default as ChannelList, ChannelListItem_default as ChannelListItem, Chat, ChatAlertsProvider, ChatAvatar_default as ChatAvatar, ChatCustomizationProvider, ChatDateJumpMenu, ChatLayout_default as ChatLayout, ChatLocaleProvider, ChatMain_default as ChatMain, ChatThemeProvider, ChatViewport, DefaultChannelListItem, DefaultChatAvatar, DefaultChatLayout_default as DefaultChatLayout, ForegroundPushBridge, ForwardModalView as ForwardModal, HeaderCallActions_default as HeaderCallActions, LoggedUserDetails_default as LoggedUserDetails, LoggedUserSettingsContent_default as LoggedUserSettingsContent, LoggedUserSettingsDialog_default as LoggedUserSettingsDialog, LoggedUserSettingsTrigger_default as LoggedUserSettingsTrigger, ChannelMessageBoxView_default as MessageInput, MessageItemView as MessageItem, ActiveChannelMessagesView_default as MessageList, NOTIFICATION_CLICK_SW_TYPE, OPEN_CONVERSATION_EVENT, PUSH_BROADCAST_CHANNEL, PUSH_CHANGED_EVENT, PUSH_DATA_CACHE_NAME, PushNotificationToggle, apiGetPushConfig, apiGetPushPreference, apiRegisterPushToken, apiRemovePushToken, apiUpdatePushPreference, buildChannelFromPushRequest, buildLoggedUserProfile, buildVersionedApiUrl, capitalizeFirst, clampJumpDate, consumePendingOpenConversation, consumePendingOpenFromPushCache, consumePendingOpenRequest, index_default as default, defaultChatClassNames, defaultChatComponents, defaultChatFeatures, defaultChatLayoutConfig, disablePushOnThisDevice, downloadFile, enablePushOnThisDevice, getChatApiVersions, getChatBaseUrl, getChatQueryParamApis, getChatQueryParams, getChatQueryParamsByApi, getChatSocketUrl, getFirstAndLastNameOneCharacter, getResponsiveWidth, getSocketConfig, getStoredPushToken, installNotificationClickBridge, isEmpty, isPushSupported, mergeChatCustomization, mergeChatLayoutConfig, onForegroundPush, packageDefaultComponents, peekPendingOpenConversation, peekPendingOpenRequest, requestOpenConversation, resolveApiRoot, resolveApiUrl, resolveChatApiKey, resolveConversationIdFromPushData, resolveJumpDateBounds, resolveJumpPreset, resolveJumpStep, resolveSocketUrl, setChatAccessToken, setChatApiVersions, setChatBaseURL, setChatClientId, setChatPushApiVersion, setChatQueryParamApis, setChatQueryParams, setChatQueryParamsByApi, setChatSocketUrl, showNotification, smoothScrollToMessage, socket_service_default as socketService, startOfLocalDay, toDateInputValue, uniqueList, useChatAlerts, useChatContext, useChatController, useChatCustomization, useChatCustomizationOptional, useChatFeatures, useChatFeaturesOptional, useChatLocale, useStore as useChatStore, useChatTheme, usePushNotifications };
19586
+ export { ADMIN_PERMISSION_DENIED_MESSAGE, CALLS_NOT_CONFIGURED_MESSAGE, CHAT_API_KEYS, CHAT_GET_API_KEYS, Calendar, CalendarDayButton, Channel, ChannelHeaderView_default as ChannelHeader, ChannelListView_default as ChannelList, ChannelListItem_default as ChannelListItem, Chat, ChatAlertsProvider, ChatAvatar_default as ChatAvatar, ChatCustomizationProvider, ChatDateJumpMenu, ChatEffectiveSettingsProvider, ChatLayout_default as ChatLayout, ChatLocaleProvider, ChatMain_default as ChatMain, ChatThemeProvider, ChatViewport, DEFAULT_EFFECTIVE_SETTINGS, DefaultChannelListItem, DefaultChatAvatar, DefaultChatLayout_default as DefaultChatLayout, ForegroundPushBridge, ForwardModalView as ForwardModal, HeaderCallActions_default as HeaderCallActions, LoggedUserDetails_default as LoggedUserDetails, LoggedUserSettingsContent_default as LoggedUserSettingsContent, LoggedUserSettingsDialog_default as LoggedUserSettingsDialog, LoggedUserSettingsTrigger_default as LoggedUserSettingsTrigger, ChannelMessageBoxView_default as MessageInput, MessageItemView as MessageItem, ActiveChannelMessagesView_default as MessageList, NOTIFICATION_CLICK_SW_TYPE, OPEN_CONVERSATION_EVENT, PUSH_BROADCAST_CHANNEL, PUSH_CHANGED_EVENT, PUSH_DATA_CACHE_NAME, PushNotificationToggle, apiGetPushConfig, apiGetPushPreference, apiRegisterPushToken, apiRemovePushToken, apiUpdatePushPreference, buildChannelFromPushRequest, buildLoggedUserProfile, buildVersionedApiUrl, capitalizeFirst, clampJumpDate, consumePendingOpenConversation, consumePendingOpenFromPushCache, consumePendingOpenRequest, index_default as default, defaultChatClassNames, defaultChatComponents, defaultChatFeatures, defaultChatLayoutConfig, disablePushOnThisDevice, downloadFile, enablePushOnThisDevice, fetchEffectiveSettings, getChatApiVersions, getChatBaseUrl, getChatQueryParamApis, getChatQueryParams, getChatQueryParamsByApi, getChatSocketUrl, getFirstAndLastNameOneCharacter, getResponsiveWidth, getSocketConfig, getStoredPushToken, installNotificationClickBridge, isEmpty, isPushSupported, mergeChatCustomization, mergeChatLayoutConfig, onForegroundPush, packageDefaultComponents, peekPendingOpenConversation, peekPendingOpenRequest, requestOpenConversation, resolveApiRoot, resolveApiUrl, resolveChatApiKey, resolveConversationIdFromPushData, resolveJumpDateBounds, resolveJumpPreset, resolveJumpStep, resolveSocketUrl, setChatAccessToken, setChatApiVersions, setChatBaseURL, setChatClientId, setChatPushApiVersion, setChatQueryParamApis, setChatQueryParams, setChatQueryParamsByApi, setChatSocketUrl, showNotification, smoothScrollToMessage, socket_service_default as socketService, startOfLocalDay, toDateInputValue, uniqueList, useChatAlerts, useChatContext, useChatController, useChatCustomization, useChatCustomizationOptional, useChatFeatures, useChatFeaturesOptional, useChatLocale, useStore as useChatStore, useChatTheme, useEffectiveSettings, useEffectiveSettingsOptional, usePushNotifications };
18921
19587
  //# sourceMappingURL=index.mjs.map
18922
19588
  //# sourceMappingURL=index.mjs.map