@realtimexsco/live-chat 1.4.9 → 1.4.11

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
@@ -16,7 +16,7 @@ var lucideReact = require('lucide-react');
16
16
  var classVarianceAuthority = require('class-variance-authority');
17
17
  var EmojiPicker = require('emoji-picker-react');
18
18
  var reactDayPicker = require('react-day-picker');
19
- var toast2 = require('react-hot-toast');
19
+ var toast = require('react-hot-toast');
20
20
 
21
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
22
 
@@ -41,7 +41,7 @@ function _interopNamespace(e) {
41
41
  var axios__default = /*#__PURE__*/_interopDefault(axios);
42
42
  var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
43
43
  var EmojiPicker__default = /*#__PURE__*/_interopDefault(EmojiPicker);
44
- var toast2__default = /*#__PURE__*/_interopDefault(toast2);
44
+ var toast__default = /*#__PURE__*/_interopDefault(toast);
45
45
 
46
46
  var __defProp = Object.defineProperty;
47
47
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1251,6 +1251,64 @@ var init_query_params = __esm({
1251
1251
  ];
1252
1252
  }
1253
1253
  });
1254
+
1255
+ // src/services/api/api-version.ts
1256
+ var DEFAULT_VERSIONS, apiVersions, normalizeApiVersion; exports.resolveApiRoot = void 0; exports.buildVersionedApiUrl = void 0; exports.setChatApiVersions = void 0; exports.getChatApiVersions = void 0; exports.setChatPushApiVersion = void 0; var syncDefaultApiVersionFromBaseUrl;
1257
+ var init_api_version = __esm({
1258
+ "src/services/api/api-version.ts"() {
1259
+ init_client();
1260
+ DEFAULT_VERSIONS = {
1261
+ default: "v1",
1262
+ push: "v2"
1263
+ };
1264
+ apiVersions = { ...DEFAULT_VERSIONS };
1265
+ normalizeApiVersion = (version) => {
1266
+ const raw = String(version || "v1").trim().replace(/^\/+|\/+$/g, "");
1267
+ if (!raw) return "v1";
1268
+ return /^v\d+$/i.test(raw) ? raw.toLowerCase() : `v${raw}`;
1269
+ };
1270
+ exports.resolveApiRoot = (baseUrl) => {
1271
+ const raw = String(
1272
+ baseUrl || exports.getChatBaseUrl() || apiClient.defaults.baseURL || ""
1273
+ ).trim().replace(/\/$/, "");
1274
+ if (!raw) return "";
1275
+ const withoutVersion = raw.replace(/\/v\d+$/i, "");
1276
+ return withoutVersion || raw;
1277
+ };
1278
+ exports.buildVersionedApiUrl = (path, options) => {
1279
+ const root = exports.resolveApiRoot(options?.baseUrl);
1280
+ const version = normalizeApiVersion(
1281
+ options?.version ?? (options?.service ? apiVersions[options.service] : apiVersions.default)
1282
+ );
1283
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1284
+ if (!root) {
1285
+ return `/${version}${normalizedPath}`;
1286
+ }
1287
+ return `${root}/${version}${normalizedPath}`;
1288
+ };
1289
+ exports.setChatApiVersions = (config) => {
1290
+ if (!config) return;
1291
+ if (config.default) {
1292
+ apiVersions.default = normalizeApiVersion(config.default);
1293
+ }
1294
+ if (config.push) {
1295
+ apiVersions.push = normalizeApiVersion(config.push);
1296
+ }
1297
+ };
1298
+ exports.getChatApiVersions = () => ({
1299
+ ...apiVersions
1300
+ });
1301
+ exports.setChatPushApiVersion = (version) => {
1302
+ apiVersions.push = normalizeApiVersion(version);
1303
+ };
1304
+ syncDefaultApiVersionFromBaseUrl = (baseUrl) => {
1305
+ const match = String(baseUrl || "").trim().match(/\/(v\d+)\/?$/i);
1306
+ if (match?.[1]) {
1307
+ apiVersions.default = normalizeApiVersion(match[1]);
1308
+ }
1309
+ };
1310
+ }
1311
+ });
1254
1312
  function resolveParamsForRequest(url, method) {
1255
1313
  const apiKey = resolveChatApiKey(url);
1256
1314
  const httpMethod = (method || "get").toLowerCase();
@@ -1267,7 +1325,9 @@ var chatClientId, chatAccessToken, chatBaseUrl, chatSocketUrl, chatQueryParams,
1267
1325
  var init_client = __esm({
1268
1326
  "src/services/api/client.ts"() {
1269
1327
  init_query_params();
1328
+ init_api_version();
1270
1329
  init_query_params();
1330
+ init_api_version();
1271
1331
  chatClientId = "";
1272
1332
  chatAccessToken = "";
1273
1333
  chatBaseUrl = "";
@@ -1285,6 +1345,7 @@ var init_client = __esm({
1285
1345
  exports.setChatBaseURL = (url) => {
1286
1346
  chatBaseUrl = url.trim();
1287
1347
  apiClient.defaults.baseURL = chatBaseUrl;
1348
+ syncDefaultApiVersionFromBaseUrl(chatBaseUrl);
1288
1349
  };
1289
1350
  exports.getChatBaseUrl = () => chatBaseUrl;
1290
1351
  exports.setChatSocketUrl = (url) => {
@@ -3750,7 +3811,7 @@ function resolveApiUrl(apiUrl) {
3750
3811
  function resolveSocketUrl(socketUrl, apiUrl) {
3751
3812
  const explicit = (socketUrl ?? "").trim();
3752
3813
  if (explicit) return explicit;
3753
- return resolveApiUrl(apiUrl).replace(/\/api\/v1\/?$/, "");
3814
+ return resolveApiUrl(apiUrl).replace(/\/api\/v\d+\/?$/i, "");
3754
3815
  }
3755
3816
 
3756
3817
  // src/hooks/chat-sync/useChatSync.ts
@@ -4497,6 +4558,7 @@ var Chat = ({
4497
4558
  queryParams,
4498
4559
  queryParamApis,
4499
4560
  queryParamsByApi,
4561
+ apiVersions: apiVersions2,
4500
4562
  channelsData,
4501
4563
  messagesData,
4502
4564
  onMessageReceived,
@@ -4522,6 +4584,7 @@ var Chat = ({
4522
4584
  const resolvedApiUrl = resolveApiUrl(apiUrl);
4523
4585
  const resolvedSocketUrl = resolveSocketUrl(socketUrl, resolvedApiUrl);
4524
4586
  if (resolvedApiUrl) exports.setChatBaseURL(resolvedApiUrl);
4587
+ exports.setChatApiVersions(apiVersions2);
4525
4588
  if (resolvedSocketUrl) exports.setChatSocketUrl(resolvedSocketUrl);
4526
4589
  let currentToken = accessToken || "";
4527
4590
  let sessionUser = resolveUserFromAccessToken(currentToken, loggedUserDetails);
@@ -4660,7 +4723,9 @@ init_store_helpers();
4660
4723
  var OPEN_CONVERSATION_EVENT = "realtimex:open-conversation";
4661
4724
  var NOTIFICATION_CLICK_SW_TYPE = "realtimex:notification-click";
4662
4725
  var PUSH_BROADCAST_CHANNEL = "realtimex-push";
4726
+ var PUSH_DATA_CACHE_NAME = "realtimex-push-data-v1";
4663
4727
  var PENDING_STORAGE_KEY = "realtimex:pending-open-conversation";
4728
+ var PENDING_OPEN_CACHE_PATH = "pending-open";
4664
4729
  var resolveConversationIdFromPushData = (data) => {
4665
4730
  if (!data || typeof data !== "object") return "";
4666
4731
  let fcmMsg = data.FCM_MSG;
@@ -4801,6 +4866,37 @@ var mapConversationToChannel = (conversation, loggedUserId, allUsers) => {
4801
4866
  starredCount: conversation.starredCount
4802
4867
  };
4803
4868
  };
4869
+ var stripConversationIdFromUrl = () => {
4870
+ if (typeof window === "undefined") return;
4871
+ try {
4872
+ const url = new URL(window.location.href);
4873
+ if (!url.searchParams.has("conversationId")) return;
4874
+ url.searchParams.delete("conversationId");
4875
+ const next = `${url.pathname}${url.search}${url.hash}`;
4876
+ window.history.replaceState(window.history.state, "", next);
4877
+ } catch {
4878
+ }
4879
+ };
4880
+ var cacheRequestForPath = (path) => new Request(new URL(path, window.location.origin).toString());
4881
+ var consumePendingOpenFromPushCache = async () => {
4882
+ if (typeof caches === "undefined") return null;
4883
+ try {
4884
+ const cache = await caches.open(PUSH_DATA_CACHE_NAME);
4885
+ const req = cacheRequestForPath(PENDING_OPEN_CACHE_PATH);
4886
+ const res = await cache.match(req);
4887
+ if (!res) return null;
4888
+ await cache.delete(req);
4889
+ const data = await res.json();
4890
+ const conversationId = resolveConversationIdFromPushData(data);
4891
+ if (!conversationId) return null;
4892
+ return {
4893
+ conversationId,
4894
+ ...normalizePushMeta(data)
4895
+ };
4896
+ } catch {
4897
+ return null;
4898
+ }
4899
+ };
4804
4900
  var readConversationIdFromLocation = () => {
4805
4901
  try {
4806
4902
  return new URLSearchParams(window.location.search).get("conversationId") || "";
@@ -4809,16 +4905,33 @@ var readConversationIdFromLocation = () => {
4809
4905
  }
4810
4906
  };
4811
4907
  var consumedUrlConversationIds = /* @__PURE__ */ new Set();
4908
+ var lastHandledOpenId = "";
4909
+ var lastHandledOpenAt = 0;
4910
+ var shouldHandleOpen = (conversationId) => {
4911
+ const id = String(conversationId || "").trim();
4912
+ if (!id) return false;
4913
+ const now = Date.now();
4914
+ if (lastHandledOpenId === id && now - lastHandledOpenAt < 2500) {
4915
+ return false;
4916
+ }
4917
+ lastHandledOpenId = id;
4918
+ lastHandledOpenAt = now;
4919
+ return true;
4920
+ };
4812
4921
  var handleNotificationOpen = (conversationId, pushData, options) => {
4813
4922
  const id = String(conversationId || "").trim();
4814
4923
  if (!id) {
4815
4924
  console.warn("[realtimex-push] notification open ignored \u2014 no conversationId", pushData);
4816
4925
  return;
4817
4926
  }
4927
+ if (!shouldHandleOpen(id)) {
4928
+ stripConversationIdFromUrl();
4929
+ return;
4930
+ }
4818
4931
  requestOpenConversation(id, pushData);
4819
4932
  const chatPath = options.chatPath || "/chat";
4820
- const pathWithQuery = `${chatPath}?conversationId=${encodeURIComponent(id)}`;
4821
- options.onNavigate?.(id, pathWithQuery);
4933
+ stripConversationIdFromUrl();
4934
+ options.onNavigate?.(id, chatPath);
4822
4935
  };
4823
4936
  var installNotificationClickBridge = (options = {}) => {
4824
4937
  if (typeof window === "undefined") return () => void 0;
@@ -4845,7 +4958,17 @@ var installNotificationClickBridge = (options = {}) => {
4845
4958
  if (fromUrl && !consumedUrlConversationIds.has(fromUrl)) {
4846
4959
  consumedUrlConversationIds.add(fromUrl);
4847
4960
  handleNotificationOpen(fromUrl, void 0, options);
4961
+ } else {
4962
+ stripConversationIdFromUrl();
4848
4963
  }
4964
+ void consumePendingOpenFromPushCache().then((request) => {
4965
+ if (!request?.conversationId) return;
4966
+ handleNotificationOpen(
4967
+ request.conversationId,
4968
+ request,
4969
+ options
4970
+ );
4971
+ });
4849
4972
  return () => {
4850
4973
  navigator.serviceWorker?.removeEventListener("message", onSwMessage);
4851
4974
  broadcast?.close();
@@ -5579,6 +5702,18 @@ function formatMemberName(name) {
5579
5702
  function formatLoggedUserName(name) {
5580
5703
  return name ? name.charAt(0).toUpperCase() + name.slice(1) : "User";
5581
5704
  }
5705
+
5706
+ // src/chat/sidebar/build-logged-user-profile.ts
5707
+ var buildLoggedUserProfile = (loggedUserDetails) => ({
5708
+ _id: loggedUserDetails?._id || "1",
5709
+ name: formatLoggedUserName(loggedUserDetails?.name),
5710
+ email: loggedUserDetails?.email || "user@example.com",
5711
+ role: loggedUserDetails?.role || "user",
5712
+ isActive: loggedUserDetails?.isActive ?? true,
5713
+ createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
5714
+ updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
5715
+ image: loggedUserDetails?.image || void 0
5716
+ });
5582
5717
  function Dialog({
5583
5718
  ...props
5584
5719
  }) {
@@ -6483,194 +6618,688 @@ var CreateChatDialog = (props) => {
6483
6618
  );
6484
6619
  };
6485
6620
  var CreateChatDialog_default = CreateChatDialog;
6486
- var LoggedUserDetails = ({
6487
- loggedUserDetails,
6488
- onUserSelect,
6489
- isCreateModalOpen = false,
6490
- setIsCreateModalOpen,
6491
- searchQuery = "",
6492
- setSearchQuery
6493
- }) => {
6494
- const { showColorModeToggle } = useChatTheme();
6495
- const user = {
6496
- _id: loggedUserDetails?._id || "1",
6497
- name: formatLoggedUserName(loggedUserDetails?.name),
6498
- email: loggedUserDetails?.email || "user@example.com",
6499
- role: loggedUserDetails?.role || "user",
6500
- isActive: loggedUserDetails?.isActive ?? true,
6501
- createdAt: loggedUserDetails?.createdAt ? new Date(loggedUserDetails.createdAt) : /* @__PURE__ */ new Date(),
6502
- updatedAt: loggedUserDetails?.updatedAt ? new Date(loggedUserDetails.updatedAt) : /* @__PURE__ */ new Date(),
6503
- image: loggedUserDetails?.image || void 0
6504
- };
6505
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-(--chat-border) bg-(--chat-surface) px-3 pb-3 pt-3", children: [
6506
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [
6507
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
6508
- /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "size-8 ring-1 ring-(--chat-surface)", children: [
6509
- user.image ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
6510
- /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-[10px] font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
6511
- ] }),
6512
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
6513
- ] }),
6514
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6515
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "truncate text-xs font-medium leading-tight text-(--chat-text)", children: user.name }),
6516
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[10px] text-(--chat-muted)", children: user.email })
6517
- ] }),
6518
- showColorModeToggle ? /* @__PURE__ */ jsxRuntime.jsx(ChatThemeToggle_default, {}) : null
6519
- ] }),
6520
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-1.5", children: [
6521
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chatSidebarSearchShellClass, children: [
6522
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none size-3.5 shrink-0 text-(--chat-muted)" }),
6523
- /* @__PURE__ */ jsxRuntime.jsx(
6524
- "input",
6525
- {
6526
- type: "text",
6527
- inputMode: "search",
6528
- enterKeyHint: "search",
6529
- placeholder: "Search conversations...",
6530
- value: searchQuery,
6531
- onChange: (e) => setSearchQuery?.(e.target.value),
6532
- className: chatSidebarSearchInputClass
6533
- }
6534
- ),
6535
- searchQuery ? /* @__PURE__ */ jsxRuntime.jsx(
6536
- "button",
6537
- {
6538
- type: "button",
6539
- onClick: () => setSearchQuery?.(""),
6540
- className: "flex size-6 shrink-0 items-center justify-center rounded-md text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text)",
6541
- "aria-label": "Clear search",
6542
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
6543
- }
6544
- ) : null
6545
- ] }),
6546
- /* @__PURE__ */ jsxRuntime.jsx(
6547
- CreateChatDialog_default,
6548
- {
6549
- loggedUserDetails,
6550
- onUserSelect,
6551
- isCreateModalOpen,
6552
- setIsCreateModalOpen
6553
- }
6554
- )
6555
- ] })
6556
- ] });
6557
- };
6558
- var LoggedUserDetails_default = LoggedUserDetails;
6559
- function ScrollArea({
6560
- className,
6561
- children,
6562
- ...props
6563
- }) {
6564
- return /* @__PURE__ */ jsxRuntime.jsxs(
6565
- radixUi.ScrollArea.Root,
6566
- {
6567
- "data-slot": "scroll-area",
6568
- className: cn("relative", className),
6569
- ...props,
6570
- children: [
6571
- /* @__PURE__ */ jsxRuntime.jsx(
6572
- radixUi.ScrollArea.Viewport,
6573
- {
6574
- "data-slot": "scroll-area-viewport",
6575
- className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
6576
- children
6577
- }
6578
- ),
6579
- /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, {}),
6580
- /* @__PURE__ */ jsxRuntime.jsx(radixUi.ScrollArea.Corner, {})
6581
- ]
6582
- }
6583
- );
6584
- }
6585
- function ScrollBar({
6621
+ function Switch({
6586
6622
  className,
6587
- orientation = "vertical",
6623
+ size = "default",
6588
6624
  ...props
6589
6625
  }) {
6590
6626
  return /* @__PURE__ */ jsxRuntime.jsx(
6591
- radixUi.ScrollArea.ScrollAreaScrollbar,
6627
+ radixUi.Switch.Root,
6592
6628
  {
6593
- "data-slot": "scroll-area-scrollbar",
6594
- orientation,
6629
+ "data-slot": "switch",
6630
+ "data-size": size,
6595
6631
  className: cn(
6596
- "flex touch-none p-px transition-colors select-none",
6597
- orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
6598
- orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
6632
+ "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
6599
6633
  className
6600
6634
  ),
6601
6635
  ...props,
6602
6636
  children: /* @__PURE__ */ jsxRuntime.jsx(
6603
- radixUi.ScrollArea.ScrollAreaThumb,
6637
+ radixUi.Switch.Thumb,
6604
6638
  {
6605
- "data-slot": "scroll-area-thumb",
6606
- className: "bg-border relative flex-1 rounded-full"
6639
+ "data-slot": "switch-thumb",
6640
+ className: cn(
6641
+ "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
6642
+ )
6607
6643
  }
6608
6644
  )
6609
6645
  }
6610
6646
  );
6611
6647
  }
6612
6648
 
6613
- // src/chat/channel-list/useChannelList.ts
6614
- init_useStore();
6615
- init_store_helpers();
6616
- var conversationDrafts = /* @__PURE__ */ new Map();
6617
- var DRAFT_PREVIEW_MAX_LENGTH = 50;
6618
- var emptyDraft = () => ({
6619
- messageInput: "",
6620
- attachments: []
6621
- });
6622
- var draftRevision = 0;
6623
- var draftListeners = /* @__PURE__ */ new Set();
6624
- function notifyDraftChange() {
6625
- draftRevision += 1;
6626
- draftListeners.forEach((listener) => listener());
6627
- }
6628
- function subscribeToDrafts(listener) {
6629
- draftListeners.add(listener);
6630
- return () => {
6631
- draftListeners.delete(listener);
6632
- };
6633
- }
6634
- function getDraftRevision() {
6635
- return draftRevision;
6636
- }
6637
- function useDraftRevision() {
6638
- return React2.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
6639
- }
6640
- function getConversationDraft(conversationId) {
6641
- if (!conversationId) return emptyDraft();
6642
- return conversationDrafts.get(conversationId) ?? emptyDraft();
6643
- }
6644
- function updateConversationDraft(conversationId, draft) {
6645
- if (!conversationId) return;
6646
- if (draft.messageInput.trim() || draft.attachments.length > 0) {
6647
- conversationDrafts.set(conversationId, {
6648
- messageInput: draft.messageInput,
6649
- attachments: draft.attachments
6650
- });
6651
- } else {
6652
- conversationDrafts.delete(conversationId);
6653
- }
6654
- notifyDraftChange();
6655
- }
6656
- function clearConversationDraft(conversationId) {
6657
- if (!conversationId) return;
6658
- if (!conversationDrafts.has(conversationId)) return;
6659
- conversationDrafts.delete(conversationId);
6660
- notifyDraftChange();
6661
- }
6662
- function getConversationDraftPreview(conversationId) {
6663
- if (!conversationId) return null;
6664
- const draft = conversationDrafts.get(conversationId);
6665
- if (!draft) return null;
6666
- const text = draft.messageInput.trim();
6667
- if (text) {
6668
- return text.length > DRAFT_PREVIEW_MAX_LENGTH ? `${text.slice(0, DRAFT_PREVIEW_MAX_LENGTH)}\u2026` : text;
6649
+ // src/notifications/push.service.ts
6650
+ init_client();
6651
+ init_api_version();
6652
+ var PUSH_TOKEN_STORAGE_KEY = "realtimex-push-token";
6653
+ var SERVICE_WORKER_PATH = "/firebase-messaging-sw.js";
6654
+ var PUSH_CHANGED_EVENT = "realtimex:push-changed";
6655
+ var emitPushChanged = (enabled) => {
6656
+ try {
6657
+ window.dispatchEvent(
6658
+ new CustomEvent(PUSH_CHANGED_EVENT, { detail: { enabled } })
6659
+ );
6660
+ } catch {
6669
6661
  }
6670
- if (draft.attachments.length > 0) {
6671
- return draft.attachments.length === 1 ? "Attachment" : `${draft.attachments.length} attachments`;
6662
+ };
6663
+ var pushApiUrl = (path) => exports.buildVersionedApiUrl(path, { service: "push" });
6664
+ var apiGetPushConfig = async () => {
6665
+ const { data } = await apiClient.get(pushApiUrl("/notifications/push/config"));
6666
+ return data?.data;
6667
+ };
6668
+ var apiRegisterPushToken = async (token, deviceLabel) => {
6669
+ const { data } = await apiClient.post(pushApiUrl("/notifications/push/token"), {
6670
+ token,
6671
+ deviceLabel
6672
+ });
6673
+ return data?.data;
6674
+ };
6675
+ var apiRemovePushToken = async (token) => {
6676
+ const { data } = await apiClient.delete(pushApiUrl("/notifications/push/token"), {
6677
+ data: { token }
6678
+ });
6679
+ return data;
6680
+ };
6681
+ var apiGetPushPreference = async () => {
6682
+ const { data } = await apiClient.get(
6683
+ pushApiUrl("/notifications/push/preference")
6684
+ );
6685
+ return data?.data;
6686
+ };
6687
+ var apiUpdatePushPreference = async (enabled) => {
6688
+ const { data } = await apiClient.patch(
6689
+ pushApiUrl("/notifications/push/preference"),
6690
+ { enabled }
6691
+ );
6692
+ return data?.data;
6693
+ };
6694
+ var isPushSupported = () => typeof window !== "undefined" && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
6695
+ var getStoredPushToken = () => {
6696
+ try {
6697
+ return localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
6698
+ } catch {
6699
+ return null;
6672
6700
  }
6673
- return null;
6701
+ };
6702
+ var storePushToken = (token) => {
6703
+ try {
6704
+ if (token) localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
6705
+ else localStorage.removeItem(PUSH_TOKEN_STORAGE_KEY);
6706
+ } catch {
6707
+ }
6708
+ };
6709
+ var defaultDeviceLabel = () => {
6710
+ const ua = navigator.userAgent;
6711
+ const browser = /edg\//i.test(ua) ? "Edge" : /chrome/i.test(ua) ? "Chrome" : /safari/i.test(ua) ? "Safari" : /firefox/i.test(ua) ? "Firefox" : "Browser";
6712
+ const os = /mac/i.test(ua) ? "macOS" : /windows/i.test(ua) ? "Windows" : /android/i.test(ua) ? "Android" : /iphone|ipad/i.test(ua) ? "iOS" : /linux/i.test(ua) ? "Linux" : "Unknown OS";
6713
+ return `${browser} on ${os}`;
6714
+ };
6715
+ var loadFirebaseMessaging = async () => {
6716
+ try {
6717
+ const [{ initializeApp, getApps }, messagingModule] = await Promise.all([
6718
+ import('firebase/app'),
6719
+ import('firebase/messaging')
6720
+ ]);
6721
+ return { initializeApp, getApps, ...messagingModule };
6722
+ } catch {
6723
+ throw new Error(
6724
+ 'Push notifications need the "firebase" package \u2014 run: npm install firebase'
6725
+ );
6726
+ }
6727
+ };
6728
+ var getFirebaseMessaging = async (firebaseConfig) => {
6729
+ const fb = await loadFirebaseMessaging();
6730
+ const app = fb.getApps().length ? fb.getApps()[0] : fb.initializeApp(firebaseConfig);
6731
+ return { fb, messaging: fb.getMessaging(app) };
6732
+ };
6733
+ var enablePushOnThisDevice = async () => {
6734
+ if (!isPushSupported()) {
6735
+ throw new Error("Push notifications are not supported in this browser");
6736
+ }
6737
+ const config = await apiGetPushConfig();
6738
+ if (!config?.enabled) {
6739
+ throw new Error("Push notifications are disabled for this workspace");
6740
+ }
6741
+ if (!config.configured || !config.firebaseConfig || !config.vapidKey) {
6742
+ throw new Error(
6743
+ "Push notifications are not configured on the server yet"
6744
+ );
6745
+ }
6746
+ const permission = await Notification.requestPermission();
6747
+ if (permission !== "granted") {
6748
+ throw new Error("Notification permission was not granted");
6749
+ }
6750
+ let registration;
6751
+ try {
6752
+ registration = await navigator.serviceWorker.register(
6753
+ `${SERVICE_WORKER_PATH}?config=${encodeURIComponent(
6754
+ JSON.stringify(config.firebaseConfig)
6755
+ )}`
6756
+ );
6757
+ } catch {
6758
+ throw new Error(
6759
+ `Could not register ${SERVICE_WORKER_PATH} \u2014 copy it from node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js into your app's public folder`
6760
+ );
6761
+ }
6762
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
6763
+ const token = await fb.getToken(messaging, {
6764
+ vapidKey: config.vapidKey,
6765
+ serviceWorkerRegistration: registration
6766
+ });
6767
+ if (!token) {
6768
+ throw new Error("Could not obtain a push token from Firebase");
6769
+ }
6770
+ await apiRegisterPushToken(token, defaultDeviceLabel());
6771
+ storePushToken(token);
6772
+ emitPushChanged(true);
6773
+ return token;
6774
+ };
6775
+ var disablePushOnThisDevice = async () => {
6776
+ const token = getStoredPushToken();
6777
+ if (!token) return;
6778
+ try {
6779
+ const config = await apiGetPushConfig();
6780
+ if (config?.firebaseConfig) {
6781
+ const { fb, messaging } = await getFirebaseMessaging(
6782
+ config.firebaseConfig
6783
+ );
6784
+ await fb.deleteToken(messaging).catch(() => void 0);
6785
+ }
6786
+ } catch {
6787
+ }
6788
+ await apiRemovePushToken(token).catch(() => void 0);
6789
+ storePushToken(null);
6790
+ emitPushChanged(false);
6791
+ };
6792
+ var onForegroundPush = async (callback) => {
6793
+ const config = await apiGetPushConfig();
6794
+ if (!config?.configured || !config.firebaseConfig) {
6795
+ throw new Error("Push config unavailable (not configured or API not ready)");
6796
+ }
6797
+ const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
6798
+ return fb.onMessage(messaging, (payload) => {
6799
+ console.log("[realtimex-push] FCM foreground payload (raw):", payload);
6800
+ console.log("[realtimex-push] FCM foreground data:", payload?.data);
6801
+ console.log("[realtimex-push] FCM foreground notification:", payload?.notification);
6802
+ callback({
6803
+ title: payload?.notification?.title,
6804
+ body: payload?.notification?.body,
6805
+ data: payload?.data
6806
+ });
6807
+ });
6808
+ };
6809
+
6810
+ // src/notifications/usePushNotifications.ts
6811
+ var usePushNotifications = () => {
6812
+ const [state, setState] = React2.useState({
6813
+ supported: false,
6814
+ allowedByWorkspace: false,
6815
+ configured: false,
6816
+ permission: "unsupported",
6817
+ userEnabled: true,
6818
+ deviceEnabled: false,
6819
+ loading: true,
6820
+ error: null
6821
+ });
6822
+ const refresh = React2.useCallback(async () => {
6823
+ if (!isPushSupported()) {
6824
+ setState((prev) => ({
6825
+ ...prev,
6826
+ supported: false,
6827
+ loading: false
6828
+ }));
6829
+ return;
6830
+ }
6831
+ try {
6832
+ const [config, preference] = await Promise.all([
6833
+ apiGetPushConfig(),
6834
+ apiGetPushPreference().catch(() => null)
6835
+ ]);
6836
+ setState({
6837
+ supported: true,
6838
+ allowedByWorkspace: Boolean(config?.enabled),
6839
+ configured: Boolean(config?.configured),
6840
+ permission: Notification.permission,
6841
+ userEnabled: preference?.enabled ?? true,
6842
+ deviceEnabled: Notification.permission === "granted" && Boolean(getStoredPushToken()),
6843
+ loading: false,
6844
+ error: null
6845
+ });
6846
+ } catch (error) {
6847
+ setState((prev) => ({
6848
+ ...prev,
6849
+ supported: true,
6850
+ loading: false,
6851
+ error: error instanceof Error ? error.message : "Could not load push settings"
6852
+ }));
6853
+ }
6854
+ }, []);
6855
+ React2.useEffect(() => {
6856
+ void refresh();
6857
+ }, [refresh]);
6858
+ const enable = React2.useCallback(async () => {
6859
+ setState((prev) => ({ ...prev, loading: true, error: null }));
6860
+ try {
6861
+ await enablePushOnThisDevice();
6862
+ await refresh();
6863
+ return true;
6864
+ } catch (error) {
6865
+ setState((prev) => ({
6866
+ ...prev,
6867
+ loading: false,
6868
+ permission: isPushSupported() ? Notification.permission : "unsupported",
6869
+ error: error instanceof Error ? error.message : "Could not enable push notifications"
6870
+ }));
6871
+ return false;
6872
+ }
6873
+ }, [refresh]);
6874
+ const disable = React2.useCallback(async () => {
6875
+ setState((prev) => ({ ...prev, loading: true, error: null }));
6876
+ try {
6877
+ await disablePushOnThisDevice();
6878
+ } finally {
6879
+ await refresh();
6880
+ }
6881
+ }, [refresh]);
6882
+ const setUserEnabled = React2.useCallback(
6883
+ async (enabled) => {
6884
+ setState((prev) => ({ ...prev, loading: true, error: null }));
6885
+ try {
6886
+ await apiUpdatePushPreference(enabled);
6887
+ setState((prev) => ({ ...prev, userEnabled: enabled, loading: false }));
6888
+ } catch (error) {
6889
+ setState((prev) => ({
6890
+ ...prev,
6891
+ userEnabled: !enabled,
6892
+ loading: false,
6893
+ error: error instanceof Error ? error.message : "Could not update push preference"
6894
+ }));
6895
+ }
6896
+ },
6897
+ []
6898
+ );
6899
+ return { ...state, enable, disable, setUserEnabled, refresh };
6900
+ };
6901
+ var PushNotificationToggle = ({
6902
+ className,
6903
+ label = "Push notifications",
6904
+ description = "Get notified about new messages on this device",
6905
+ children
6906
+ }) => {
6907
+ const push = usePushNotifications();
6908
+ const [isToggling, setIsToggling] = React2.useState(false);
6909
+ if (!push.supported || !push.allowedByWorkspace || !push.configured) {
6910
+ return null;
6911
+ }
6912
+ const isOn = push.deviceEnabled && push.userEnabled;
6913
+ const permissionBlocked = push.permission === "denied";
6914
+ const showLoader = isToggling;
6915
+ const handleChange = async (checked) => {
6916
+ setIsToggling(true);
6917
+ try {
6918
+ if (checked) {
6919
+ if (!push.userEnabled) await push.setUserEnabled(true);
6920
+ if (!push.deviceEnabled) await push.enable();
6921
+ } else {
6922
+ await push.disable();
6923
+ }
6924
+ } finally {
6925
+ setIsToggling(false);
6926
+ }
6927
+ };
6928
+ const renderProps = {
6929
+ label,
6930
+ description: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description,
6931
+ checked: isOn,
6932
+ disabled: showLoader || permissionBlocked || push.loading,
6933
+ loading: showLoader || push.loading,
6934
+ permissionBlocked,
6935
+ error: push.error,
6936
+ onCheckedChange: (checked) => void handleChange(checked)
6937
+ };
6938
+ if (children) {
6939
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(renderProps) });
6940
+ }
6941
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6942
+ "div",
6943
+ {
6944
+ className: cn(
6945
+ "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-surface) px-4 py-3",
6946
+ className
6947
+ ),
6948
+ children: [
6949
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
6950
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-(--chat-text)", children: label }),
6951
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-(--chat-muted)", children: renderProps.description })
6952
+ ] }),
6953
+ showLoader ? /* @__PURE__ */ jsxRuntime.jsx(
6954
+ lucideReact.Loader2,
6955
+ {
6956
+ className: "size-5 shrink-0 animate-spin text-(--chat-theme)",
6957
+ "aria-label": "Updating push notifications"
6958
+ }
6959
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
6960
+ Switch,
6961
+ {
6962
+ checked: isOn,
6963
+ disabled: permissionBlocked || push.loading,
6964
+ onCheckedChange: (checked) => void handleChange(checked),
6965
+ "aria-label": `Toggle ${label}`
6966
+ }
6967
+ )
6968
+ ]
6969
+ }
6970
+ );
6971
+ };
6972
+ var LoggedUserSettingsContent = ({
6973
+ user,
6974
+ title = "Account settings",
6975
+ showProfile = true,
6976
+ showPushToggle = true,
6977
+ className
6978
+ }) => {
6979
+ const { resolveComponent, slotClassName } = useChatCustomization();
6980
+ const PushToggleSlot = resolveComponent(
6981
+ "PushNotificationToggle",
6982
+ PushNotificationToggle
6983
+ );
6984
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6985
+ /* @__PURE__ */ jsxRuntime.jsx(
6986
+ "div",
6987
+ {
6988
+ className: cn(
6989
+ "border-b border-(--chat-border) px-5 py-4",
6990
+ slotClassName("loggedUserSettingsHeader")
6991
+ ),
6992
+ children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "text-left text-base font-semibold text-(--chat-text)", children: title })
6993
+ }
6994
+ ),
6995
+ /* @__PURE__ */ jsxRuntime.jsxs(
6996
+ "div",
6997
+ {
6998
+ className: cn(
6999
+ "space-y-5 px-5 py-5",
7000
+ slotClassName("loggedUserSettingsContent", className)
7001
+ ),
7002
+ children: [
7003
+ showProfile ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center text-center", children: [
7004
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
7005
+ /* @__PURE__ */ jsxRuntime.jsxs(
7006
+ Avatar,
7007
+ {
7008
+ className: cn(
7009
+ "size-16 ring-2 ring-(--chat-theme-20)",
7010
+ slotClassName("loggedUserSettingsAvatar")
7011
+ ),
7012
+ children: [
7013
+ user.image ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
7014
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-lg font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
7015
+ ]
7016
+ }
7017
+ ),
7018
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute bottom-0.5 right-0.5 size-3 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
7019
+ ] }),
7020
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mt-3 text-base font-semibold text-(--chat-text)", children: user.name }),
7021
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-sm text-(--chat-muted)", children: user.email }),
7022
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 inline-flex items-center gap-1.5 rounded-full bg-(--chat-theme-10) px-2.5 py-0.5 text-xs font-medium text-(--chat-theme)", children: [
7023
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 rounded-full bg-(--chat-online)" }),
7024
+ "Online"
7025
+ ] })
7026
+ ] }) : null,
7027
+ showPushToggle ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-(--chat-border) pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
7028
+ PushToggleSlot,
7029
+ {
7030
+ className: cn(
7031
+ "flex items-center justify-between gap-4 rounded-xl border border-(--chat-border) bg-(--chat-theme-10) px-4 py-3",
7032
+ slotClassName("pushNotificationToggle")
7033
+ )
7034
+ }
7035
+ ) }) : null
7036
+ ]
7037
+ }
7038
+ )
7039
+ ] });
7040
+ };
7041
+ var LoggedUserSettingsContent_default = LoggedUserSettingsContent;
7042
+ var LoggedUserSettingsTrigger = ({
7043
+ onClick,
7044
+ className,
7045
+ tooltip = "Settings",
7046
+ ariaLabel = "Account settings"
7047
+ }) => {
7048
+ const { slotClassName } = useChatCustomization();
7049
+ return /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
7050
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
7051
+ Button,
7052
+ {
7053
+ type: "button",
7054
+ variant: "ghost",
7055
+ size: "icon",
7056
+ className: cn(
7057
+ sidebarActionBtnClass,
7058
+ slotClassName("loggedUserSettingsTrigger", className)
7059
+ ),
7060
+ "aria-label": ariaLabel,
7061
+ onClick,
7062
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, { className: "size-3.5", strokeWidth: 2 })
7063
+ }
7064
+ ) }),
7065
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: tooltip })
7066
+ ] });
7067
+ };
7068
+ var LoggedUserSettingsTrigger_default = LoggedUserSettingsTrigger;
7069
+ var LoggedUserSettingsDialog = ({
7070
+ loggedUserDetails,
7071
+ open: controlledOpen,
7072
+ onOpenChange: controlledOnOpenChange,
7073
+ title,
7074
+ showProfile = true,
7075
+ showPushToggle = true,
7076
+ className
7077
+ }) => {
7078
+ const { resolveComponent, slotClassName } = useChatCustomization();
7079
+ const [internalOpen, setInternalOpen] = React2.useState(false);
7080
+ const open = controlledOpen ?? internalOpen;
7081
+ const onOpenChange = controlledOnOpenChange ?? setInternalOpen;
7082
+ const TriggerSlot = resolveComponent(
7083
+ "LoggedUserSettingsTrigger",
7084
+ LoggedUserSettingsTrigger_default
7085
+ );
7086
+ const ContentSlot = resolveComponent(
7087
+ "LoggedUserSettingsContent",
7088
+ LoggedUserSettingsContent_default
7089
+ );
7090
+ const user = buildLoggedUserProfile(loggedUserDetails);
7091
+ return /* @__PURE__ */ jsxRuntime.jsxs(Dialog, { open, onOpenChange, children: [
7092
+ /* @__PURE__ */ jsxRuntime.jsx(TriggerSlot, { onClick: () => onOpenChange(true) }),
7093
+ /* @__PURE__ */ jsxRuntime.jsx(
7094
+ ChatDialogContent,
7095
+ {
7096
+ className: cn(
7097
+ "gap-0 overflow-hidden p-0 sm:max-w-[380px]",
7098
+ slotClassName("loggedUserSettingsDialog", className)
7099
+ ),
7100
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7101
+ ContentSlot,
7102
+ {
7103
+ loggedUserDetails,
7104
+ user,
7105
+ onClose: () => onOpenChange(false),
7106
+ title,
7107
+ showProfile,
7108
+ showPushToggle
7109
+ }
7110
+ )
7111
+ }
7112
+ )
7113
+ ] });
7114
+ };
7115
+ var LoggedUserSettingsDialog_default = LoggedUserSettingsDialog;
7116
+ var LoggedUserDetails = ({
7117
+ loggedUserDetails,
7118
+ onUserSelect,
7119
+ isCreateModalOpen = false,
7120
+ setIsCreateModalOpen,
7121
+ searchQuery = "",
7122
+ setSearchQuery
7123
+ }) => {
7124
+ const { showColorModeToggle } = useChatTheme();
7125
+ const { resolveComponent } = useChatCustomization();
7126
+ const SettingsDialogSlot = resolveComponent(
7127
+ "LoggedUserSettingsDialog",
7128
+ LoggedUserSettingsDialog_default
7129
+ );
7130
+ const user = buildLoggedUserProfile(loggedUserDetails);
7131
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-(--chat-border) bg-(--chat-surface) px-3 pb-3 pt-3", children: [
7132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2.5", children: [
7133
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
7134
+ /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "size-8 ring-1 ring-(--chat-surface)", children: [
7135
+ user.image ? /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: user.image, alt: user.name }) : null,
7136
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-(--chat-theme-10) text-[10px] font-medium text-(--chat-theme)", children: (user.name || "?").charAt(0).toUpperCase() })
7137
+ ] }),
7138
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-(--chat-surface) bg-(--chat-online)" })
7139
+ ] }),
7140
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
7141
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "truncate text-xs font-medium leading-tight text-(--chat-text)", children: user.name }),
7142
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[10px] text-(--chat-muted)", children: user.email })
7143
+ ] }),
7144
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
7145
+ /* @__PURE__ */ jsxRuntime.jsx(SettingsDialogSlot, { loggedUserDetails }),
7146
+ showColorModeToggle ? /* @__PURE__ */ jsxRuntime.jsx(ChatThemeToggle_default, {}) : null
7147
+ ] })
7148
+ ] }),
7149
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-1.5", children: [
7150
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chatSidebarSearchShellClass, children: [
7151
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none size-3.5 shrink-0 text-(--chat-muted)" }),
7152
+ /* @__PURE__ */ jsxRuntime.jsx(
7153
+ "input",
7154
+ {
7155
+ type: "text",
7156
+ inputMode: "search",
7157
+ enterKeyHint: "search",
7158
+ placeholder: "Search conversations...",
7159
+ value: searchQuery,
7160
+ onChange: (e) => setSearchQuery?.(e.target.value),
7161
+ className: chatSidebarSearchInputClass
7162
+ }
7163
+ ),
7164
+ searchQuery ? /* @__PURE__ */ jsxRuntime.jsx(
7165
+ "button",
7166
+ {
7167
+ type: "button",
7168
+ onClick: () => setSearchQuery?.(""),
7169
+ className: "flex size-6 shrink-0 items-center justify-center rounded-md text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text)",
7170
+ "aria-label": "Clear search",
7171
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12 })
7172
+ }
7173
+ ) : null
7174
+ ] }),
7175
+ /* @__PURE__ */ jsxRuntime.jsx(
7176
+ CreateChatDialog_default,
7177
+ {
7178
+ loggedUserDetails,
7179
+ onUserSelect,
7180
+ isCreateModalOpen,
7181
+ setIsCreateModalOpen
7182
+ }
7183
+ )
7184
+ ] })
7185
+ ] });
7186
+ };
7187
+ var LoggedUserDetails_default = LoggedUserDetails;
7188
+ function ScrollArea({
7189
+ className,
7190
+ children,
7191
+ ...props
7192
+ }) {
7193
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7194
+ radixUi.ScrollArea.Root,
7195
+ {
7196
+ "data-slot": "scroll-area",
7197
+ className: cn("relative", className),
7198
+ ...props,
7199
+ children: [
7200
+ /* @__PURE__ */ jsxRuntime.jsx(
7201
+ radixUi.ScrollArea.Viewport,
7202
+ {
7203
+ "data-slot": "scroll-area-viewport",
7204
+ className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
7205
+ children
7206
+ }
7207
+ ),
7208
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollBar, {}),
7209
+ /* @__PURE__ */ jsxRuntime.jsx(radixUi.ScrollArea.Corner, {})
7210
+ ]
7211
+ }
7212
+ );
7213
+ }
7214
+ function ScrollBar({
7215
+ className,
7216
+ orientation = "vertical",
7217
+ ...props
7218
+ }) {
7219
+ return /* @__PURE__ */ jsxRuntime.jsx(
7220
+ radixUi.ScrollArea.ScrollAreaScrollbar,
7221
+ {
7222
+ "data-slot": "scroll-area-scrollbar",
7223
+ orientation,
7224
+ className: cn(
7225
+ "flex touch-none p-px transition-colors select-none",
7226
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
7227
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
7228
+ className
7229
+ ),
7230
+ ...props,
7231
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7232
+ radixUi.ScrollArea.ScrollAreaThumb,
7233
+ {
7234
+ "data-slot": "scroll-area-thumb",
7235
+ className: "bg-border relative flex-1 rounded-full"
7236
+ }
7237
+ )
7238
+ }
7239
+ );
7240
+ }
7241
+
7242
+ // src/chat/channel-list/useChannelList.ts
7243
+ init_useStore();
7244
+ init_store_helpers();
7245
+ var conversationDrafts = /* @__PURE__ */ new Map();
7246
+ var DRAFT_PREVIEW_MAX_LENGTH = 50;
7247
+ var emptyDraft = () => ({
7248
+ messageInput: "",
7249
+ attachments: []
7250
+ });
7251
+ var draftRevision = 0;
7252
+ var draftListeners = /* @__PURE__ */ new Set();
7253
+ function notifyDraftChange() {
7254
+ draftRevision += 1;
7255
+ draftListeners.forEach((listener) => listener());
7256
+ }
7257
+ function subscribeToDrafts(listener) {
7258
+ draftListeners.add(listener);
7259
+ return () => {
7260
+ draftListeners.delete(listener);
7261
+ };
7262
+ }
7263
+ function getDraftRevision() {
7264
+ return draftRevision;
7265
+ }
7266
+ function useDraftRevision() {
7267
+ return React2.useSyncExternalStore(subscribeToDrafts, getDraftRevision, getDraftRevision);
7268
+ }
7269
+ function getConversationDraft(conversationId) {
7270
+ if (!conversationId) return emptyDraft();
7271
+ return conversationDrafts.get(conversationId) ?? emptyDraft();
7272
+ }
7273
+ function updateConversationDraft(conversationId, draft) {
7274
+ if (!conversationId) return;
7275
+ if (draft.messageInput.trim() || draft.attachments.length > 0) {
7276
+ conversationDrafts.set(conversationId, {
7277
+ messageInput: draft.messageInput,
7278
+ attachments: draft.attachments
7279
+ });
7280
+ } else {
7281
+ conversationDrafts.delete(conversationId);
7282
+ }
7283
+ notifyDraftChange();
7284
+ }
7285
+ function clearConversationDraft(conversationId) {
7286
+ if (!conversationId) return;
7287
+ if (!conversationDrafts.has(conversationId)) return;
7288
+ conversationDrafts.delete(conversationId);
7289
+ notifyDraftChange();
7290
+ }
7291
+ function getConversationDraftPreview(conversationId) {
7292
+ if (!conversationId) return null;
7293
+ const draft = conversationDrafts.get(conversationId);
7294
+ if (!draft) return null;
7295
+ const text = draft.messageInput.trim();
7296
+ if (text) {
7297
+ return text.length > DRAFT_PREVIEW_MAX_LENGTH ? `${text.slice(0, DRAFT_PREVIEW_MAX_LENGTH)}\u2026` : text;
7298
+ }
7299
+ if (draft.attachments.length > 0) {
7300
+ return draft.attachments.length === 1 ? "Attachment" : `${draft.attachments.length} attachments`;
7301
+ }
7302
+ return null;
6674
7303
  }
6675
7304
  function useConversationDraft(conversationId) {
6676
7305
  const composerRef = React2.useRef(emptyDraft());
@@ -12226,51 +12855,22 @@ var ChannelDetailsDrawer = (props) => {
12226
12855
  const pId = typeof p === "string" ? p : p._id;
12227
12856
  const pName = typeof p === "string" ? "Unknown" : p.name;
12228
12857
  if (!pId) return null;
12229
- return /* @__PURE__ */ jsxRuntime.jsx(
12230
- "span",
12231
- {
12232
- className: "rounded-md border border-(--chat-border) bg-(--chat-input-bg)/50 px-2 py-0.5 text-[10px] font-medium text-(--chat-text-secondary)",
12233
- children: pName
12234
- },
12235
- `${pId}-${index}`
12236
- );
12237
- }) })
12238
- ] })
12239
- ] })
12240
- ] })
12241
- ] })
12242
- ] }) });
12243
- };
12244
- var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12245
- function Switch({
12246
- className,
12247
- size = "default",
12248
- ...props
12249
- }) {
12250
- return /* @__PURE__ */ jsxRuntime.jsx(
12251
- radixUi.Switch.Root,
12252
- {
12253
- "data-slot": "switch",
12254
- "data-size": size,
12255
- className: cn(
12256
- "peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
12257
- className
12258
- ),
12259
- ...props,
12260
- children: /* @__PURE__ */ jsxRuntime.jsx(
12261
- radixUi.Switch.Thumb,
12262
- {
12263
- "data-slot": "switch-thumb",
12264
- className: cn(
12265
- "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
12266
- )
12267
- }
12268
- )
12269
- }
12270
- );
12271
- }
12272
-
12273
- // src/chat/header/PermissionDrawer.tsx
12858
+ return /* @__PURE__ */ jsxRuntime.jsx(
12859
+ "span",
12860
+ {
12861
+ className: "rounded-md border border-(--chat-border) bg-(--chat-input-bg)/50 px-2 py-0.5 text-[10px] font-medium text-(--chat-text-secondary)",
12862
+ children: pName
12863
+ },
12864
+ `${pId}-${index}`
12865
+ );
12866
+ }) })
12867
+ ] })
12868
+ ] })
12869
+ ] })
12870
+ ] })
12871
+ ] }) });
12872
+ };
12873
+ var ChannelDetailsDrawer_default = ChannelDetailsDrawer;
12274
12874
  init_useStore();
12275
12875
  var PermissionDrawer = ({ isOpen, onOpenChange, conversationId, conversationInfo }) => {
12276
12876
  const updateGroupPermissions = exports.useChatStore((s) => s.updateGroupPermissions);
@@ -17655,258 +18255,22 @@ function ChatViewport({
17655
18255
  style
17656
18256
  }) {
17657
18257
  const resolved = resolveViewportHeight(height);
17658
- return /* @__PURE__ */ jsxRuntime.jsx(
17659
- "div",
17660
- {
17661
- className: cn(
17662
- "chat-package-viewport flex min-h-0 w-full flex-col overflow-hidden",
17663
- resolved.className,
17664
- className
17665
- ),
17666
- style: { ...resolved.style, ...style },
17667
- children
17668
- }
17669
- );
17670
- }
17671
- var ChatViewport_default = ChatViewport;
17672
- var showNotification = (message, type = "info", icon, duration) => {
17673
- const options = {
17674
- icon,
17675
- duration
17676
- };
17677
- switch (type) {
17678
- case "success":
17679
- toast2.toast.success(message, options);
17680
- break;
17681
- case "error":
17682
- toast2.toast.error(message, options);
17683
- break;
17684
- case "info":
17685
- toast2.toast(message, options);
17686
- break;
17687
- case "warning":
17688
- toast2.toast(message, { ...options, icon: "\u26A0\uFE0F" });
17689
- break;
17690
- default:
17691
- toast2.toast(message, options);
17692
- }
17693
- };
17694
- var downloadFile = async (src, name) => {
17695
- try {
17696
- const link = document.createElement("a");
17697
- link.href = src;
17698
- link.setAttribute("download", name);
17699
- link.setAttribute("rel", "noopener noreferrer");
17700
- link.click();
17701
- showNotification("File Downloaded Successfully", "success");
17702
- } catch (err) {
17703
- console.error("File download failed:", err);
17704
- showNotification("Failed to download file", "error");
17705
- }
17706
- };
17707
- var getResponsiveWidth = (width) => {
17708
- const screenWidth = window.innerWidth;
17709
- if (width) {
17710
- return width;
17711
- } else if (screenWidth < 640) {
17712
- return 350;
17713
- } else if (screenWidth >= 640 && screenWidth < 1024) {
17714
- return 600;
17715
- } else if (screenWidth >= 1024 && screenWidth < 1280) {
17716
- return 800;
17717
- } else {
17718
- return 1e3;
17719
- }
17720
- };
17721
- var isEmpty = (value) => {
17722
- if (value === null || value === void 0) return true;
17723
- if (typeof value === "string") return value.trim().length === 0;
17724
- if (Array.isArray(value)) {
17725
- return value.length === 0 || value.every((item) => isEmpty(item));
17726
- }
17727
- if (typeof value === "object") {
17728
- return Object.keys(value).length === 0;
17729
- }
17730
- return false;
17731
- };
17732
- function uniqueList(list, key) {
17733
- const seen = /* @__PURE__ */ new Set();
17734
- return list.filter((item) => {
17735
- const value = item[key];
17736
- if (seen.has(value)) return false;
17737
- seen.add(value);
17738
- return true;
17739
- });
17740
- }
17741
- var capitalizeFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
17742
- var getFirstAndLastNameOneCharacter = (name) => {
17743
- const names = name.split(" ");
17744
- return names.map((name2) => name2.charAt(0).toUpperCase()).join("");
17745
- };
17746
-
17747
- // src/notifications/ForegroundPushBridge.tsx
17748
- init_normalize_helpers();
17749
- init_useStore();
17750
-
17751
- // src/notifications/push.service.ts
17752
- init_client();
17753
- var PUSH_TOKEN_STORAGE_KEY = "realtimex-push-token";
17754
- var SERVICE_WORKER_PATH = "/firebase-messaging-sw.js";
17755
- var PUSH_CHANGED_EVENT = "realtimex:push-changed";
17756
- var emitPushChanged = (enabled) => {
17757
- try {
17758
- window.dispatchEvent(
17759
- new CustomEvent(PUSH_CHANGED_EVENT, { detail: { enabled } })
17760
- );
17761
- } catch {
17762
- }
17763
- };
17764
- var v2Url = "https://realtimex.softwareco.com/api/v2";
17765
- var apiGetPushConfig = async () => {
17766
- const { data } = await apiClient.get(`${v2Url}/notifications/push/config`);
17767
- return data?.data;
17768
- };
17769
- var apiRegisterPushToken = async (token, deviceLabel) => {
17770
- const { data } = await apiClient.post(`${v2Url}/notifications/push/token`, {
17771
- token,
17772
- deviceLabel
17773
- });
17774
- return data?.data;
17775
- };
17776
- var apiRemovePushToken = async (token) => {
17777
- const { data } = await apiClient.delete(`${v2Url}/notifications/push/token`, {
17778
- data: { token }
17779
- });
17780
- return data;
17781
- };
17782
- var apiGetPushPreference = async () => {
17783
- const { data } = await apiClient.get(
17784
- `${v2Url}/notifications/push/preference`
17785
- );
17786
- return data?.data;
17787
- };
17788
- var apiUpdatePushPreference = async (enabled) => {
17789
- const { data } = await apiClient.patch(
17790
- `${v2Url}/notifications/push/preference`,
17791
- { enabled }
17792
- );
17793
- return data?.data;
17794
- };
17795
- var isPushSupported = () => typeof window !== "undefined" && "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
17796
- var getStoredPushToken = () => {
17797
- try {
17798
- return localStorage.getItem(PUSH_TOKEN_STORAGE_KEY);
17799
- } catch {
17800
- return null;
17801
- }
17802
- };
17803
- var storePushToken = (token) => {
17804
- try {
17805
- if (token) localStorage.setItem(PUSH_TOKEN_STORAGE_KEY, token);
17806
- else localStorage.removeItem(PUSH_TOKEN_STORAGE_KEY);
17807
- } catch {
17808
- }
17809
- };
17810
- var defaultDeviceLabel = () => {
17811
- const ua = navigator.userAgent;
17812
- const browser = /edg\//i.test(ua) ? "Edge" : /chrome/i.test(ua) ? "Chrome" : /safari/i.test(ua) ? "Safari" : /firefox/i.test(ua) ? "Firefox" : "Browser";
17813
- const os = /mac/i.test(ua) ? "macOS" : /windows/i.test(ua) ? "Windows" : /android/i.test(ua) ? "Android" : /iphone|ipad/i.test(ua) ? "iOS" : /linux/i.test(ua) ? "Linux" : "Unknown OS";
17814
- return `${browser} on ${os}`;
17815
- };
17816
- var loadFirebaseMessaging = async () => {
17817
- try {
17818
- const [{ initializeApp, getApps }, messagingModule] = await Promise.all([
17819
- import('firebase/app'),
17820
- import('firebase/messaging')
17821
- ]);
17822
- return { initializeApp, getApps, ...messagingModule };
17823
- } catch {
17824
- throw new Error(
17825
- 'Push notifications need the "firebase" package \u2014 run: npm install firebase'
17826
- );
17827
- }
17828
- };
17829
- var getFirebaseMessaging = async (firebaseConfig) => {
17830
- const fb = await loadFirebaseMessaging();
17831
- const app = fb.getApps().length ? fb.getApps()[0] : fb.initializeApp(firebaseConfig);
17832
- return { fb, messaging: fb.getMessaging(app) };
17833
- };
17834
- var enablePushOnThisDevice = async () => {
17835
- if (!isPushSupported()) {
17836
- throw new Error("Push notifications are not supported in this browser");
17837
- }
17838
- const config = await apiGetPushConfig();
17839
- if (!config?.enabled) {
17840
- throw new Error("Push notifications are disabled for this workspace");
17841
- }
17842
- if (!config.configured || !config.firebaseConfig || !config.vapidKey) {
17843
- throw new Error(
17844
- "Push notifications are not configured on the server yet"
17845
- );
17846
- }
17847
- const permission = await Notification.requestPermission();
17848
- if (permission !== "granted") {
17849
- throw new Error("Notification permission was not granted");
17850
- }
17851
- let registration;
17852
- try {
17853
- registration = await navigator.serviceWorker.register(
17854
- `${SERVICE_WORKER_PATH}?config=${encodeURIComponent(
17855
- JSON.stringify(config.firebaseConfig)
17856
- )}`
17857
- );
17858
- } catch {
17859
- throw new Error(
17860
- `Could not register ${SERVICE_WORKER_PATH} \u2014 copy it from node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js into your app's public folder`
17861
- );
17862
- }
17863
- const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17864
- const token = await fb.getToken(messaging, {
17865
- vapidKey: config.vapidKey,
17866
- serviceWorkerRegistration: registration
17867
- });
17868
- if (!token) {
17869
- throw new Error("Could not obtain a push token from Firebase");
17870
- }
17871
- await apiRegisterPushToken(token, defaultDeviceLabel());
17872
- storePushToken(token);
17873
- emitPushChanged(true);
17874
- return token;
17875
- };
17876
- var disablePushOnThisDevice = async () => {
17877
- const token = getStoredPushToken();
17878
- if (!token) return;
17879
- try {
17880
- const config = await apiGetPushConfig();
17881
- if (config?.firebaseConfig) {
17882
- const { fb, messaging } = await getFirebaseMessaging(
17883
- config.firebaseConfig
17884
- );
17885
- await fb.deleteToken(messaging).catch(() => void 0);
17886
- }
17887
- } catch {
17888
- }
17889
- await apiRemovePushToken(token).catch(() => void 0);
17890
- storePushToken(null);
17891
- emitPushChanged(false);
17892
- };
17893
- var onForegroundPush = async (callback) => {
17894
- const config = await apiGetPushConfig();
17895
- if (!config?.configured || !config.firebaseConfig) {
17896
- throw new Error("Push config unavailable (not configured or API not ready)");
17897
- }
17898
- const { fb, messaging } = await getFirebaseMessaging(config.firebaseConfig);
17899
- return fb.onMessage(messaging, (payload) => {
17900
- console.log("[realtimex-push] FCM foreground payload (raw):", payload);
17901
- console.log("[realtimex-push] FCM foreground data:", payload?.data);
17902
- console.log("[realtimex-push] FCM foreground notification:", payload?.notification);
17903
- callback({
17904
- title: payload?.notification?.title,
17905
- body: payload?.notification?.body,
17906
- data: payload?.data
17907
- });
17908
- });
17909
- };
18258
+ return /* @__PURE__ */ jsxRuntime.jsx(
18259
+ "div",
18260
+ {
18261
+ className: cn(
18262
+ "chat-package-viewport flex min-h-0 w-full flex-col overflow-hidden",
18263
+ resolved.className,
18264
+ className
18265
+ ),
18266
+ style: { ...resolved.style, ...style },
18267
+ children
18268
+ }
18269
+ );
18270
+ }
18271
+ var ChatViewport_default = ChatViewport;
18272
+ init_normalize_helpers();
18273
+ init_useStore();
17910
18274
  var RETRY_DELAYS_MS = [1e3, 3e3, 8e3, 2e4];
17911
18275
  var recentToastIds = /* @__PURE__ */ new Set();
17912
18276
  var rememberToastId = (id) => {
@@ -17920,48 +18284,95 @@ var rememberToastId = (id) => {
17920
18284
  }
17921
18285
  return true;
17922
18286
  };
17923
- var resolveToastText = (title, body) => {
17924
- if (title && body) return `${title}: ${body}`;
17925
- return title || body || "";
18287
+ var truncate = (value, max) => value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
18288
+ var PushMessageToast = ({
18289
+ t,
18290
+ title,
18291
+ body,
18292
+ onOpen
18293
+ }) => {
18294
+ const dismiss = () => toast__default.default.dismiss(t.id);
18295
+ const handleDismiss = (event) => {
18296
+ event.stopPropagation();
18297
+ dismiss();
18298
+ };
18299
+ const handleOpen = () => {
18300
+ dismiss();
18301
+ onOpen?.();
18302
+ };
18303
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18304
+ "div",
18305
+ {
18306
+ role: "alert",
18307
+ "aria-live": "polite",
18308
+ onClick: onOpen ? handleOpen : void 0,
18309
+ onKeyDown: onOpen ? (event) => {
18310
+ if (event.key === "Enter" || event.key === " ") {
18311
+ event.preventDefault();
18312
+ handleOpen();
18313
+ }
18314
+ } : void 0,
18315
+ tabIndex: onOpen ? 0 : void 0,
18316
+ className: cn(
18317
+ "pointer-events-auto flex w-[min(320px,calc(100vw-24px))] items-start gap-3 rounded-xl border border-(--chat-border) bg-(--chat-surface) p-3 shadow-[0_8px_24px_rgba(15,23,42,0.12)] transition-[opacity,transform] duration-200 ease-out",
18318
+ onOpen && "cursor-pointer hover:border-(--chat-theme-20) hover:shadow-[0_10px_28px_rgba(15,23,42,0.16)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--chat-theme-30)",
18319
+ t.visible ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"
18320
+ ),
18321
+ children: [
18322
+ /* @__PURE__ */ jsxRuntime.jsx(
18323
+ "div",
18324
+ {
18325
+ "aria-hidden": true,
18326
+ className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-(--chat-theme-10) text-(--chat-theme)",
18327
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "size-4", strokeWidth: 2.25 })
18328
+ }
18329
+ ),
18330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 pt-0.5", children: [
18331
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-wide text-(--chat-muted)", children: "New message" }),
18332
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 truncate text-sm font-semibold leading-snug text-(--chat-text)", children: truncate(title, 40) }),
18333
+ body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 line-clamp-2 text-xs leading-relaxed text-(--chat-muted)", children: truncate(body, 72) }) : null
18334
+ ] }),
18335
+ /* @__PURE__ */ jsxRuntime.jsx(
18336
+ "button",
18337
+ {
18338
+ type: "button",
18339
+ "aria-label": "Dismiss notification",
18340
+ onClick: handleDismiss,
18341
+ className: "shrink-0 rounded-md p-1 text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--chat-theme-30)",
18342
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4", strokeWidth: 2 })
18343
+ }
18344
+ )
18345
+ ]
18346
+ }
18347
+ );
17926
18348
  };
17927
18349
  var emitToast = (payload, onPush) => {
17928
18350
  if (onPush && onPush(payload) !== false) return;
17929
- const title = payload.title || payload.data?.title;
17930
- const body = payload.body || payload.data?.body || payload.data?.message;
17931
- const text = resolveToastText(title, body);
18351
+ const title = payload.title || payload.data?.title || "New message";
18352
+ const body = payload.body || payload.data?.body || payload.data?.message || "";
17932
18353
  const conversationId = resolveConversationIdFromPushData(payload.data);
17933
- if (!text) return;
17934
- if (conversationId) {
17935
- toast2__default.default.custom(
17936
- (t) => /* @__PURE__ */ jsxRuntime.jsx(
17937
- "button",
17938
- {
17939
- type: "button",
17940
- onClick: () => {
17941
- toast2__default.default.dismiss(t.id);
17942
- requestOpenConversation(conversationId, payload.data);
17943
- },
17944
- style: {
17945
- display: "block",
17946
- width: "100%",
17947
- textAlign: "left",
17948
- cursor: "pointer",
17949
- background: "var(--background, #fff)",
17950
- color: "var(--foreground, #111)",
17951
- border: "1px solid rgba(0,0,0,0.08)",
17952
- borderRadius: 8,
17953
- padding: "10px 14px",
17954
- boxShadow: "0 4px 12px rgba(0,0,0,0.12)",
17955
- font: "inherit"
17956
- },
17957
- children: text
17958
- }
17959
- ),
17960
- { duration: 5e3 }
17961
- );
17962
- return;
17963
- }
17964
- showNotification(text, "info");
18354
+ const messageId2 = String(
18355
+ payload.data?.messageId || payload.data?.message_id || payload.data?.messageID || ""
18356
+ );
18357
+ if (!title && !body) return;
18358
+ toast__default.default.custom(
18359
+ (t) => /* @__PURE__ */ jsxRuntime.jsx(
18360
+ PushMessageToast,
18361
+ {
18362
+ t,
18363
+ title,
18364
+ body: body || void 0,
18365
+ onOpen: conversationId ? () => requestOpenConversation(
18366
+ conversationId,
18367
+ payload.data
18368
+ ) : void 0
18369
+ }
18370
+ ),
18371
+ {
18372
+ duration: 5e3,
18373
+ id: messageId2 || void 0
18374
+ }
18375
+ );
17965
18376
  };
17966
18377
  var ForegroundPushBridge = ({ onPush }) => {
17967
18378
  const lastDM = exports.useChatStore((s) => s.lastDM);
@@ -18096,11 +18507,31 @@ var ForegroundPushBridge = ({ onPush }) => {
18096
18507
  return () => window.removeEventListener(OPEN_CONVERSATION_EVENT, onOpen);
18097
18508
  }, []);
18098
18509
  return /* @__PURE__ */ jsxRuntime.jsx(
18099
- toast2.Toaster,
18510
+ toast.Toaster,
18100
18511
  {
18101
18512
  position: "top-right",
18102
- toastOptions: { duration: 4e3 },
18103
- containerStyle: { zIndex: 99999 }
18513
+ gutter: 12,
18514
+ containerClassName: "realtimex-push-toaster",
18515
+ containerStyle: {
18516
+ zIndex: 99999,
18517
+ top: 16,
18518
+ right: 16,
18519
+ width: "auto",
18520
+ maxWidth: "none"
18521
+ },
18522
+ toastOptions: {
18523
+ duration: 5e3,
18524
+ className: "!w-auto !max-w-none",
18525
+ style: {
18526
+ padding: 0,
18527
+ margin: 0,
18528
+ background: "transparent",
18529
+ boxShadow: "none",
18530
+ border: "none",
18531
+ width: "auto",
18532
+ maxWidth: "none"
18533
+ }
18534
+ }
18104
18535
  }
18105
18536
  );
18106
18537
  };
@@ -18113,6 +18544,7 @@ var ChatMain = ({
18113
18544
  queryParams,
18114
18545
  queryParamApis,
18115
18546
  queryParamsByApi,
18547
+ apiVersions: apiVersions2,
18116
18548
  themeColor = "#7494ec",
18117
18549
  theme,
18118
18550
  uiConfig,
@@ -18182,6 +18614,7 @@ var ChatMain = ({
18182
18614
  queryParams,
18183
18615
  queryParamApis,
18184
18616
  queryParamsByApi,
18617
+ apiVersions: apiVersions2,
18185
18618
  children: [
18186
18619
  /* @__PURE__ */ jsxRuntime.jsx(ForegroundPushBridge, {}),
18187
18620
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -18257,6 +18690,80 @@ var getSocketConfig = (accessToken, tenantId, options) => {
18257
18690
  }
18258
18691
  };
18259
18692
  };
18693
+ var showNotification = (message, type = "info", icon, duration) => {
18694
+ const options = {
18695
+ icon,
18696
+ duration
18697
+ };
18698
+ switch (type) {
18699
+ case "success":
18700
+ toast.toast.success(message, options);
18701
+ break;
18702
+ case "error":
18703
+ toast.toast.error(message, options);
18704
+ break;
18705
+ case "info":
18706
+ toast.toast(message, options);
18707
+ break;
18708
+ case "warning":
18709
+ toast.toast(message, { ...options, icon: "\u26A0\uFE0F" });
18710
+ break;
18711
+ default:
18712
+ toast.toast(message, options);
18713
+ }
18714
+ };
18715
+ var downloadFile = async (src, name) => {
18716
+ try {
18717
+ const link = document.createElement("a");
18718
+ link.href = src;
18719
+ link.setAttribute("download", name);
18720
+ link.setAttribute("rel", "noopener noreferrer");
18721
+ link.click();
18722
+ showNotification("File Downloaded Successfully", "success");
18723
+ } catch (err) {
18724
+ console.error("File download failed:", err);
18725
+ showNotification("Failed to download file", "error");
18726
+ }
18727
+ };
18728
+ var getResponsiveWidth = (width) => {
18729
+ const screenWidth = window.innerWidth;
18730
+ if (width) {
18731
+ return width;
18732
+ } else if (screenWidth < 640) {
18733
+ return 350;
18734
+ } else if (screenWidth >= 640 && screenWidth < 1024) {
18735
+ return 600;
18736
+ } else if (screenWidth >= 1024 && screenWidth < 1280) {
18737
+ return 800;
18738
+ } else {
18739
+ return 1e3;
18740
+ }
18741
+ };
18742
+ var isEmpty = (value) => {
18743
+ if (value === null || value === void 0) return true;
18744
+ if (typeof value === "string") return value.trim().length === 0;
18745
+ if (Array.isArray(value)) {
18746
+ return value.length === 0 || value.every((item) => isEmpty(item));
18747
+ }
18748
+ if (typeof value === "object") {
18749
+ return Object.keys(value).length === 0;
18750
+ }
18751
+ return false;
18752
+ };
18753
+ function uniqueList(list, key) {
18754
+ const seen = /* @__PURE__ */ new Set();
18755
+ return list.filter((item) => {
18756
+ const value = item[key];
18757
+ if (seen.has(value)) return false;
18758
+ seen.add(value);
18759
+ return true;
18760
+ });
18761
+ }
18762
+ var capitalizeFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
18763
+ var getFirstAndLastNameOneCharacter = (name) => {
18764
+ const names = name.split(" ");
18765
+ return names.map((name2) => name2.charAt(0).toUpperCase()).join("");
18766
+ };
18260
18767
  var SIZE_MAP = {
18261
18768
  xs: "size-6",
18262
18769
  sm: "size-8",
@@ -18400,6 +18907,10 @@ var ChannelListItem_default = ChannelListItem2;
18400
18907
  var packageDefaultComponents = {
18401
18908
  ChatLayout: DefaultChatLayout_default,
18402
18909
  LoggedUserDetails: LoggedUserDetails_default,
18910
+ LoggedUserSettingsDialog: LoggedUserSettingsDialog_default,
18911
+ LoggedUserSettingsTrigger: LoggedUserSettingsTrigger_default,
18912
+ LoggedUserSettingsContent: LoggedUserSettingsContent_default,
18913
+ PushNotificationToggle,
18403
18914
  ChannelList: ChannelListView_default,
18404
18915
  ChannelListItem: DefaultChannelListItem,
18405
18916
  ChannelHeader: ChannelHeaderView_default,
@@ -18430,135 +18941,6 @@ var packageDefaultComponents = {
18430
18941
  ChatDateTimeSettings: ChatDateTimeSettings_default,
18431
18942
  ChatSocketStatus: ChatSocketStatus_default
18432
18943
  };
18433
- var usePushNotifications = () => {
18434
- const [state, setState] = React2.useState({
18435
- supported: false,
18436
- allowedByWorkspace: false,
18437
- configured: false,
18438
- permission: "unsupported",
18439
- userEnabled: true,
18440
- deviceEnabled: false,
18441
- loading: true,
18442
- error: null
18443
- });
18444
- const refresh = React2.useCallback(async () => {
18445
- if (!isPushSupported()) {
18446
- setState((prev) => ({
18447
- ...prev,
18448
- supported: false,
18449
- loading: false
18450
- }));
18451
- return;
18452
- }
18453
- try {
18454
- const [config, preference] = await Promise.all([
18455
- apiGetPushConfig(),
18456
- apiGetPushPreference().catch(() => null)
18457
- ]);
18458
- setState({
18459
- supported: true,
18460
- allowedByWorkspace: Boolean(config?.enabled),
18461
- configured: Boolean(config?.configured),
18462
- permission: Notification.permission,
18463
- userEnabled: preference?.enabled ?? true,
18464
- deviceEnabled: Notification.permission === "granted" && Boolean(getStoredPushToken()),
18465
- loading: false,
18466
- error: null
18467
- });
18468
- } catch (error) {
18469
- setState((prev) => ({
18470
- ...prev,
18471
- supported: true,
18472
- loading: false,
18473
- error: error instanceof Error ? error.message : "Could not load push settings"
18474
- }));
18475
- }
18476
- }, []);
18477
- React2.useEffect(() => {
18478
- void refresh();
18479
- }, [refresh]);
18480
- const enable = React2.useCallback(async () => {
18481
- setState((prev) => ({ ...prev, loading: true, error: null }));
18482
- try {
18483
- await enablePushOnThisDevice();
18484
- await refresh();
18485
- return true;
18486
- } catch (error) {
18487
- setState((prev) => ({
18488
- ...prev,
18489
- loading: false,
18490
- permission: isPushSupported() ? Notification.permission : "unsupported",
18491
- error: error instanceof Error ? error.message : "Could not enable push notifications"
18492
- }));
18493
- return false;
18494
- }
18495
- }, [refresh]);
18496
- const disable = React2.useCallback(async () => {
18497
- setState((prev) => ({ ...prev, loading: true, error: null }));
18498
- try {
18499
- await disablePushOnThisDevice();
18500
- } finally {
18501
- await refresh();
18502
- }
18503
- }, [refresh]);
18504
- const setUserEnabled = React2.useCallback(
18505
- async (enabled) => {
18506
- setState((prev) => ({ ...prev, userEnabled: enabled }));
18507
- try {
18508
- await apiUpdatePushPreference(enabled);
18509
- } catch (error) {
18510
- setState((prev) => ({
18511
- ...prev,
18512
- userEnabled: !enabled,
18513
- error: error instanceof Error ? error.message : "Could not update push preference"
18514
- }));
18515
- }
18516
- },
18517
- []
18518
- );
18519
- return { ...state, enable, disable, setUserEnabled, refresh };
18520
- };
18521
- var PushNotificationToggle = ({
18522
- className,
18523
- label = "Push notifications",
18524
- description = "Get notified about new messages on this device"
18525
- }) => {
18526
- const push = usePushNotifications();
18527
- if (!push.supported || !push.allowedByWorkspace || !push.configured) {
18528
- return null;
18529
- }
18530
- const isOn = push.deviceEnabled && push.userEnabled;
18531
- const permissionBlocked = push.permission === "denied";
18532
- const handleChange = async (checked) => {
18533
- if (checked) {
18534
- if (!push.userEnabled) await push.setUserEnabled(true);
18535
- if (!push.deviceEnabled) await push.enable();
18536
- } else {
18537
- await push.disable();
18538
- }
18539
- };
18540
- return /* @__PURE__ */ jsxRuntime.jsxs(
18541
- "div",
18542
- {
18543
- className: className ?? "flex items-center justify-between gap-4 rounded-xl border border-border bg-background px-4 py-3",
18544
- children: [
18545
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
18546
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: label }),
18547
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: permissionBlocked ? "Notifications are blocked in your browser settings" : push.error ?? description })
18548
- ] }),
18549
- /* @__PURE__ */ jsxRuntime.jsx(
18550
- Switch,
18551
- {
18552
- checked: isOn,
18553
- disabled: push.loading || permissionBlocked,
18554
- onCheckedChange: (checked) => void handleChange(checked),
18555
- "aria-label": `Toggle ${label}`
18556
- }
18557
- )
18558
- ]
18559
- }
18560
- );
18561
- };
18562
18944
 
18563
18945
  // src/index.ts
18564
18946
  var index_default = ChatMain_default;
@@ -18586,6 +18968,9 @@ exports.ForegroundPushBridge = ForegroundPushBridge;
18586
18968
  exports.ForwardModal = ForwardModalView;
18587
18969
  exports.HeaderCallActions = HeaderCallActions_default;
18588
18970
  exports.LoggedUserDetails = LoggedUserDetails_default;
18971
+ exports.LoggedUserSettingsContent = LoggedUserSettingsContent_default;
18972
+ exports.LoggedUserSettingsDialog = LoggedUserSettingsDialog_default;
18973
+ exports.LoggedUserSettingsTrigger = LoggedUserSettingsTrigger_default;
18589
18974
  exports.MessageInput = ChannelMessageBoxView_default;
18590
18975
  exports.MessageItem = MessageItemView;
18591
18976
  exports.MessageList = ActiveChannelMessagesView_default;
@@ -18593,6 +18978,7 @@ exports.NOTIFICATION_CLICK_SW_TYPE = NOTIFICATION_CLICK_SW_TYPE;
18593
18978
  exports.OPEN_CONVERSATION_EVENT = OPEN_CONVERSATION_EVENT;
18594
18979
  exports.PUSH_BROADCAST_CHANNEL = PUSH_BROADCAST_CHANNEL;
18595
18980
  exports.PUSH_CHANGED_EVENT = PUSH_CHANGED_EVENT;
18981
+ exports.PUSH_DATA_CACHE_NAME = PUSH_DATA_CACHE_NAME;
18596
18982
  exports.PushNotificationToggle = PushNotificationToggle;
18597
18983
  exports.apiGetPushConfig = apiGetPushConfig;
18598
18984
  exports.apiGetPushPreference = apiGetPushPreference;
@@ -18600,9 +18986,11 @@ exports.apiRegisterPushToken = apiRegisterPushToken;
18600
18986
  exports.apiRemovePushToken = apiRemovePushToken;
18601
18987
  exports.apiUpdatePushPreference = apiUpdatePushPreference;
18602
18988
  exports.buildChannelFromPushRequest = buildChannelFromPushRequest;
18989
+ exports.buildLoggedUserProfile = buildLoggedUserProfile;
18603
18990
  exports.capitalizeFirst = capitalizeFirst;
18604
18991
  exports.clampJumpDate = clampJumpDate;
18605
18992
  exports.consumePendingOpenConversation = consumePendingOpenConversation;
18993
+ exports.consumePendingOpenFromPushCache = consumePendingOpenFromPushCache;
18606
18994
  exports.consumePendingOpenRequest = consumePendingOpenRequest;
18607
18995
  exports.default = index_default;
18608
18996
  exports.defaultChatClassNames = defaultChatClassNames;