@natoe/colab 0.1.1 → 0.1.3

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.js CHANGED
@@ -1,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React4 = require('react');
4
4
  var phoenix = require('phoenix');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
10
+
7
11
  // src/components/CollabPanel.tsx
8
12
 
9
13
  // src/core/constants.ts
@@ -316,25 +320,25 @@ var CollabSocket = class {
316
320
  this.config = null;
317
321
  }
318
322
  };
319
- var CollabContext = react.createContext(null);
323
+ var CollabContext = React4.createContext(null);
320
324
  function useCollab() {
321
- const context = react.useContext(CollabContext);
325
+ const context = React4.useContext(CollabContext);
322
326
  if (!context) {
323
327
  throw new Error("useCollab must be used within a <CollabProvider>");
324
328
  }
325
329
  return context;
326
330
  }
327
331
  function CollabProvider({ config, apiBaseUrl, children }) {
328
- const [socket, setSocket] = react.useState(() => {
332
+ const [socket, setSocket] = React4.useState(() => {
329
333
  if (typeof window === "undefined") return null;
330
334
  return new CollabSocket();
331
335
  });
332
- const [unreadCounts, setUnreadCounts] = react.useState({});
333
- const pendingOrderIds = react.useRef(/* @__PURE__ */ new Set());
334
- const pendingResolvers = react.useRef(/* @__PURE__ */ new Map());
335
- const previewCache = react.useRef(/* @__PURE__ */ new Map());
336
- const batchScheduled = react.useRef(false);
337
- react.useEffect(() => {
336
+ const [unreadCounts, setUnreadCounts] = React4.useState({});
337
+ const pendingOrderIds = React4.useRef(/* @__PURE__ */ new Set());
338
+ const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
339
+ const previewCache = React4.useRef(/* @__PURE__ */ new Map());
340
+ const batchScheduled = React4.useRef(false);
341
+ React4.useEffect(() => {
338
342
  if (typeof window === "undefined") return;
339
343
  let s = socket;
340
344
  if (!s) {
@@ -349,14 +353,14 @@ function CollabProvider({ config, apiBaseUrl, children }) {
349
353
  s.connect(config);
350
354
  }
351
355
  }, [config.socketUrl, config.userId]);
352
- const authHeaders = react.useCallback(
356
+ const authHeaders = React4.useCallback(
353
357
  () => ({
354
358
  Authorization: `Bearer ${config.getToken()}`,
355
359
  "Content-Type": "application/json"
356
360
  }),
357
361
  [config]
358
362
  );
359
- const flushPreviewBatch = react.useCallback(async () => {
363
+ const flushPreviewBatch = React4.useCallback(async () => {
360
364
  const orderIds = Array.from(pendingOrderIds.current);
361
365
  const resolvers = new Map(pendingResolvers.current);
362
366
  pendingOrderIds.current.clear();
@@ -386,7 +390,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
386
390
  });
387
391
  }
388
392
  }, [apiBaseUrl, authHeaders, config]);
389
- const requestPreview = react.useCallback(
393
+ const requestPreview = React4.useCallback(
390
394
  (orderId) => {
391
395
  if (previewCache.current.has(orderId)) {
392
396
  return Promise.resolve(previewCache.current.get(orderId) ?? null);
@@ -404,10 +408,10 @@ function CollabProvider({ config, apiBaseUrl, children }) {
404
408
  },
405
409
  [flushPreviewBatch]
406
410
  );
407
- const invalidatePreview = react.useCallback((orderId) => {
411
+ const invalidatePreview = React4.useCallback((orderId) => {
408
412
  previewCache.current.delete(orderId);
409
413
  }, []);
410
- const fetchMessages = react.useCallback(
414
+ const fetchMessages = React4.useCallback(
411
415
  async (conversationId, options = {}) => {
412
416
  const limit = options.limit ?? MESSAGES_PAGE_SIZE;
413
417
  const params = new URLSearchParams({ limit: String(limit) });
@@ -423,7 +427,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
423
427
  },
424
428
  [apiBaseUrl, authHeaders]
425
429
  );
426
- const createConversationWithMessage = react.useCallback(
430
+ const createConversationWithMessage = React4.useCallback(
427
431
  async (orderId, message, options) => {
428
432
  if (!orderId || !orderId.trim()) {
429
433
  throw {
@@ -452,7 +456,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
452
456
  },
453
457
  [apiBaseUrl, authHeaders]
454
458
  );
455
- const addParticipant = react.useCallback(
459
+ const addParticipant = React4.useCallback(
456
460
  async (conversationId, userId) => {
457
461
  const response = await fetch(
458
462
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants`,
@@ -468,7 +472,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
468
472
  },
469
473
  [apiBaseUrl, authHeaders]
470
474
  );
471
- const removeParticipant = react.useCallback(
475
+ const removeParticipant = React4.useCallback(
472
476
  async (conversationId, userId) => {
473
477
  const response = await fetch(
474
478
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants/${userId}`,
@@ -483,7 +487,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
483
487
  },
484
488
  [apiBaseUrl, authHeaders]
485
489
  );
486
- const fetchConversationList = react.useCallback(async () => {
490
+ const fetchConversationList = React4.useCallback(async () => {
487
491
  const response = await fetch(`${apiBaseUrl}/api/natoe-colab/conversations`, {
488
492
  headers: authHeaders()
489
493
  });
@@ -492,7 +496,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
492
496
  }
493
497
  return snakeToCamel(await response.json());
494
498
  }, [apiBaseUrl, authHeaders]);
495
- const pinMessage = react.useCallback(
499
+ const pinMessage = React4.useCallback(
496
500
  async (conversationId, messageId) => {
497
501
  const response = await fetch(
498
502
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
@@ -508,7 +512,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
508
512
  },
509
513
  [apiBaseUrl, authHeaders]
510
514
  );
511
- const unpinMessage = react.useCallback(
515
+ const unpinMessage = React4.useCallback(
512
516
  async (conversationId, messageId) => {
513
517
  const response = await fetch(
514
518
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
@@ -523,7 +527,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
523
527
  },
524
528
  [apiBaseUrl, authHeaders]
525
529
  );
526
- const fetchPinnedMessages = react.useCallback(
530
+ const fetchPinnedMessages = React4.useCallback(
527
531
  async (conversationId) => {
528
532
  const response = await fetch(
529
533
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/pinned`,
@@ -587,25 +591,25 @@ function useConversation({
587
591
  pinMessage: pinMessageApi,
588
592
  unpinMessage: unpinMessageApi
589
593
  } = useCollab();
590
- const [conversation, setConversation] = react.useState(null);
591
- const [messages, setMessages] = react.useState([]);
592
- const [pinnedMessages, setPinnedMessages] = react.useState([]);
593
- const [participants, setParticipants] = react.useState([]);
594
- const [typingUsers, setTypingUsers] = react.useState([]);
595
- const [isLoading, setIsLoading] = react.useState(true);
596
- const [hasMore, setHasMore] = react.useState(true);
597
- const [error, setError] = react.useState(null);
598
- const [isConnected, setIsConnected] = react.useState(false);
599
- const [replyTo, setReplyTo] = react.useState(null);
600
- const joinedConversationId = react.useRef(null);
601
- const typingTimers = react.useRef(/* @__PURE__ */ new Map());
602
- const ensureConversationInFlight = react.useRef(null);
603
- const markedReadIdsRef = react.useRef(/* @__PURE__ */ new Set());
604
- const buildName = react.useCallback(
594
+ const [conversation, setConversation] = React4.useState(null);
595
+ const [messages, setMessages] = React4.useState([]);
596
+ const [pinnedMessages, setPinnedMessages] = React4.useState([]);
597
+ const [participants, setParticipants] = React4.useState([]);
598
+ const [typingUsers, setTypingUsers] = React4.useState([]);
599
+ const [isLoading, setIsLoading] = React4.useState(true);
600
+ const [hasMore, setHasMore] = React4.useState(true);
601
+ const [error, setError] = React4.useState(null);
602
+ const [isConnected, setIsConnected] = React4.useState(false);
603
+ const [replyTo, setReplyTo] = React4.useState(null);
604
+ const joinedConversationId = React4.useRef(null);
605
+ const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
606
+ const ensureConversationInFlight = React4.useRef(null);
607
+ const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
608
+ const buildName = React4.useCallback(
605
609
  () => buildChannelName(patientData),
606
610
  [patientData]
607
611
  );
608
- const joinChannel = react.useCallback(
612
+ const joinChannel = React4.useCallback(
609
613
  (conv) => {
610
614
  if (joinedConversationId.current === conv.id) return;
611
615
  socket.joinConversation(conv.id, {
@@ -688,7 +692,7 @@ function useConversation({
688
692
  },
689
693
  [socket, config.userId]
690
694
  );
691
- react.useEffect(() => {
695
+ React4.useEffect(() => {
692
696
  let cancelled = false;
693
697
  const init = async () => {
694
698
  setIsLoading(true);
@@ -757,7 +761,7 @@ function useConversation({
757
761
  setIsConnected(false);
758
762
  };
759
763
  }, [orderId]);
760
- const ensureConversation = react.useCallback(
764
+ const ensureConversation = React4.useCallback(
761
765
  async (payload) => {
762
766
  if (conversation) return { conv: conversation, persistedByCreate: false };
763
767
  if (ensureConversationInFlight.current) {
@@ -811,7 +815,7 @@ function useConversation({
811
815
  fetchMessages
812
816
  ]
813
817
  );
814
- const sendMessage = react.useCallback(
818
+ const sendMessage = React4.useCallback(
815
819
  async (body) => {
816
820
  const payload = {
817
821
  body,
@@ -855,7 +859,7 @@ function useConversation({
855
859
  },
856
860
  [ensureConversation, socket, replyTo, config]
857
861
  );
858
- const sendAudioMessage = react.useCallback(
862
+ const sendAudioMessage = React4.useCallback(
859
863
  async (audioBlob, duration) => {
860
864
  if (!config.onUploadFile) return;
861
865
  const url = await config.onUploadFile(audioBlob, "voice-message.webm");
@@ -871,7 +875,7 @@ function useConversation({
871
875
  },
872
876
  [config, ensureConversation, socket]
873
877
  );
874
- const sendFileMessage = react.useCallback(
878
+ const sendFileMessage = React4.useCallback(
875
879
  async (file) => {
876
880
  if (!config.onUploadFile) return;
877
881
  const url = await config.onUploadFile(file, file.name);
@@ -888,7 +892,7 @@ function useConversation({
888
892
  },
889
893
  [config, ensureConversation, socket]
890
894
  );
891
- const sendDeepLink = react.useCallback(
895
+ const sendDeepLink = React4.useCallback(
892
896
  async (path, label) => {
893
897
  const payload = {
894
898
  body: label,
@@ -901,14 +905,14 @@ function useConversation({
901
905
  },
902
906
  [ensureConversation, socket]
903
907
  );
904
- const sendTypingIndicator = react.useCallback(
908
+ const sendTypingIndicator = React4.useCallback(
905
909
  (isTyping) => {
906
910
  if (!conversation || !isConnected) return;
907
911
  socket.sendTyping(conversation.id, isTyping);
908
912
  },
909
913
  [conversation, isConnected, socket]
910
914
  );
911
- const markAsRead = react.useCallback(
915
+ const markAsRead = React4.useCallback(
912
916
  (messageId) => {
913
917
  if (!conversation || !isConnected) return;
914
918
  if (markedReadIdsRef.current.has(messageId)) return;
@@ -917,7 +921,7 @@ function useConversation({
917
921
  },
918
922
  [conversation, isConnected, socket]
919
923
  );
920
- const loadMoreMessages = react.useCallback(async () => {
924
+ const loadMoreMessages = React4.useCallback(async () => {
921
925
  if (!conversation || !hasMore || isLoading) return;
922
926
  const oldestMessage = messages[0];
923
927
  const before = oldestMessage?.id;
@@ -936,7 +940,7 @@ function useConversation({
936
940
  setIsLoading(false);
937
941
  }
938
942
  }, [conversation, hasMore, isLoading, messages, fetchMessages, config]);
939
- const pinMessage = react.useCallback(
943
+ const pinMessage = React4.useCallback(
940
944
  async (messageId) => {
941
945
  if (!conversation) return;
942
946
  try {
@@ -951,7 +955,7 @@ function useConversation({
951
955
  },
952
956
  [conversation, pinMessageApi, config]
953
957
  );
954
- const unpinMessage = react.useCallback(
958
+ const unpinMessage = React4.useCallback(
955
959
  async (messageId) => {
956
960
  if (!conversation) return;
957
961
  try {
@@ -991,10 +995,10 @@ function useConversation({
991
995
  }
992
996
  function useChannelSettings({ conversationId }) {
993
997
  const { socket, config } = useCollab();
994
- const [isUpdating, setIsUpdating] = react.useState(false);
995
- const [error, setError] = react.useState(null);
998
+ const [isUpdating, setIsUpdating] = React4.useState(false);
999
+ const [error, setError] = React4.useState(null);
996
1000
  const isAdmin = config.userRole === "admin";
997
- const withLoading = react.useCallback(
1001
+ const withLoading = React4.useCallback(
998
1002
  async (operation) => {
999
1003
  if (!conversationId) return;
1000
1004
  setIsUpdating(true);
@@ -1013,25 +1017,25 @@ function useChannelSettings({ conversationId }) {
1013
1017
  },
1014
1018
  [conversationId, config]
1015
1019
  );
1016
- const inviteUser = react.useCallback(
1020
+ const inviteUser = React4.useCallback(
1017
1021
  async (payload) => {
1018
1022
  await withLoading(() => socket.inviteUser(conversationId, payload));
1019
1023
  },
1020
1024
  [conversationId, socket, withLoading]
1021
1025
  );
1022
- const removeUser = react.useCallback(
1026
+ const removeUser = React4.useCallback(
1023
1027
  async (userId) => {
1024
1028
  await withLoading(() => socket.removeUser(conversationId, userId));
1025
1029
  },
1026
1030
  [conversationId, socket, withLoading]
1027
1031
  );
1028
- const leaveChannel = react.useCallback(async () => {
1032
+ const leaveChannel = React4.useCallback(async () => {
1029
1033
  await withLoading(async () => {
1030
1034
  await socket.removeUser(conversationId, config.userId);
1031
1035
  socket.leaveConversation(conversationId);
1032
1036
  });
1033
1037
  }, [conversationId, socket, config.userId, withLoading]);
1034
- const deleteChannel = react.useCallback(async () => {
1038
+ const deleteChannel = React4.useCallback(async () => {
1035
1039
  if (!isAdmin) {
1036
1040
  const err = {
1037
1041
  code: "FORBIDDEN",
@@ -1043,7 +1047,7 @@ function useChannelSettings({ conversationId }) {
1043
1047
  }
1044
1048
  await withLoading(() => socket.deleteChannel(conversationId));
1045
1049
  }, [conversationId, socket, isAdmin, config, withLoading]);
1046
- const updateChannel = react.useCallback(
1050
+ const updateChannel = React4.useCallback(
1047
1051
  async (payload) => {
1048
1052
  await withLoading(() => socket.updateChannel(conversationId, payload));
1049
1053
  },
@@ -1062,7 +1066,7 @@ function useChannelSettings({ conversationId }) {
1062
1066
  }
1063
1067
  function useDeepLinks() {
1064
1068
  const { config } = useCollab();
1065
- const parseDeepLink = react.useCallback((link) => {
1069
+ const parseDeepLink = React4.useCallback((link) => {
1066
1070
  if (!link.startsWith(DEEP_LINK_PREFIX)) return null;
1067
1071
  const path = link.slice(DEEP_LINK_PREFIX.length);
1068
1072
  const parts = path.split("/").filter(Boolean);
@@ -1074,7 +1078,7 @@ function useDeepLinks() {
1074
1078
  id: parts[1]
1075
1079
  };
1076
1080
  }, []);
1077
- const handleDeepLink = react.useCallback(
1081
+ const handleDeepLink = React4.useCallback(
1078
1082
  (link) => {
1079
1083
  const parsed = parseDeepLink(link);
1080
1084
  if (!parsed) return;
@@ -1088,10 +1092,10 @@ function useDeepLinks() {
1088
1092
  },
1089
1093
  [parseDeepLink, config]
1090
1094
  );
1091
- const containsDeepLink = react.useCallback((text) => {
1095
+ const containsDeepLink = React4.useCallback((text) => {
1092
1096
  return text.includes(DEEP_LINK_PREFIX);
1093
1097
  }, []);
1094
- const extractDeepLinks = react.useCallback(
1098
+ const extractDeepLinks = React4.useCallback(
1095
1099
  (text) => {
1096
1100
  const regex = new RegExp(`${escapeRegex(DEEP_LINK_PREFIX)}[\\w/.-]+`, "g");
1097
1101
  const matches = text.match(regex) || [];
@@ -1155,7 +1159,7 @@ var styles = {
1155
1159
  },
1156
1160
  patientName: {
1157
1161
  fontWeight: 600,
1158
- fontSize: "15px",
1162
+ fontSize: "17px",
1159
1163
  color: "#111827"
1160
1164
  },
1161
1165
  secondaryRow: {
@@ -1165,8 +1169,8 @@ var styles = {
1165
1169
  alignItems: "center"
1166
1170
  },
1167
1171
  secondary: {
1168
- fontSize: "12px",
1169
- color: "#6b7280"
1172
+ fontSize: "13px",
1173
+ color: "#4b5563"
1170
1174
  },
1171
1175
  actions: {
1172
1176
  display: "flex",
@@ -1174,8 +1178,9 @@ var styles = {
1174
1178
  marginTop: "8px"
1175
1179
  },
1176
1180
  dicomButton: {
1177
- padding: "6px 12px",
1178
- fontSize: "13px",
1181
+ minHeight: "40px",
1182
+ padding: "8px 16px",
1183
+ fontSize: "14px",
1179
1184
  fontWeight: 500,
1180
1185
  color: "#ffffff",
1181
1186
  backgroundColor: "#2563eb",
@@ -1184,8 +1189,9 @@ var styles = {
1184
1189
  cursor: "pointer"
1185
1190
  },
1186
1191
  settingsButton: {
1187
- padding: "6px 12px",
1188
- fontSize: "13px",
1192
+ minHeight: "40px",
1193
+ padding: "8px 16px",
1194
+ fontSize: "14px",
1189
1195
  fontWeight: 500,
1190
1196
  color: "#374151",
1191
1197
  backgroundColor: "#ffffff",
@@ -1281,7 +1287,7 @@ function SeenByIndicator({
1281
1287
  senderId,
1282
1288
  className
1283
1289
  }) {
1284
- const [expanded, setExpanded] = react.useState(false);
1290
+ const [expanded, setExpanded] = React4.useState(false);
1285
1291
  if (senderId !== currentUserId) return null;
1286
1292
  const seenByOthers = readBy.filter((id) => id !== currentUserId && id !== senderId).map((id) => participants.find((p) => p.userId === id)).filter((p) => !!p);
1287
1293
  if (seenByOthers.length === 0) return null;
@@ -1426,6 +1432,40 @@ var styles4 = {
1426
1432
  cursor: "pointer"
1427
1433
  }
1428
1434
  };
1435
+ function FileIcon({ size = 18, color = "currentColor" }) {
1436
+ return /* @__PURE__ */ jsxRuntime.jsx(
1437
+ "svg",
1438
+ {
1439
+ xmlns: "http://www.w3.org/2000/svg",
1440
+ width: size,
1441
+ height: size,
1442
+ viewBox: "0 0 24 24",
1443
+ fill: color,
1444
+ "aria-hidden": "true",
1445
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" })
1446
+ }
1447
+ );
1448
+ }
1449
+ function PinIcon({ size = 14, color = "currentColor" }) {
1450
+ return /* @__PURE__ */ jsxRuntime.jsx(
1451
+ "svg",
1452
+ {
1453
+ xmlns: "http://www.w3.org/2000/svg",
1454
+ width: size,
1455
+ height: size,
1456
+ viewBox: "0 0 24 24",
1457
+ fill: color,
1458
+ "aria-hidden": "true",
1459
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" })
1460
+ }
1461
+ );
1462
+ }
1463
+ var ROLE_LABELS = {
1464
+ radiologist: "Radiologist",
1465
+ lab: "Imaging Center",
1466
+ physician: "Physician",
1467
+ admin: "Admin"
1468
+ };
1429
1469
  function MessageBubble({
1430
1470
  message,
1431
1471
  isOwn,
@@ -1441,7 +1481,7 @@ function MessageBubble({
1441
1481
  pinDisabled = false,
1442
1482
  className
1443
1483
  }) {
1444
- const [isHovered, setIsHovered] = react.useState(false);
1484
+ const [isHovered, setIsHovered] = React4.useState(false);
1445
1485
  if (message.type === "system") {
1446
1486
  return /* @__PURE__ */ jsxRuntime.jsx(SystemBubble, { message });
1447
1487
  }
@@ -1465,7 +1505,10 @@ function MessageBubble({
1465
1505
  ...isOwn ? styles5.ownBubble : styles5.otherBubble
1466
1506
  },
1467
1507
  children: [
1468
- message.isPinned && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.pinnedBadge, children: "\u{1F4CC} Pinned" }),
1508
+ message.isPinned && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.pinnedBadge, children: [
1509
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 12 }),
1510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pinned" })
1511
+ ] }),
1469
1512
  message.replyToSnapshot && /* @__PURE__ */ jsxRuntime.jsx(
1470
1513
  ReplyQuoteBlock,
1471
1514
  {
@@ -1560,7 +1603,7 @@ function ImageContent({ mediaUrl, fileName }) {
1560
1603
  function FileContent({ mediaUrl, fileName }) {
1561
1604
  if (!mediaUrl) return null;
1562
1605
  return /* @__PURE__ */ jsxRuntime.jsxs("a", { href: mediaUrl, target: "_blank", rel: "noopener noreferrer", style: styles5.fileLink, children: [
1563
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.fileIcon, children: "\u{1F4C4}" }),
1606
+ /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { size: 20 }),
1564
1607
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.fileName, children: fileName || "Download file" })
1565
1608
  ] });
1566
1609
  }
@@ -1598,7 +1641,7 @@ function RoleBadge({ role }) {
1598
1641
  physician: "#059669",
1599
1642
  admin: "#dc2626"
1600
1643
  };
1601
- return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: role });
1644
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: ROLE_LABELS[role] ?? role });
1602
1645
  }
1603
1646
  function formatTime(iso) {
1604
1647
  try {
@@ -1627,7 +1670,7 @@ var styles5 = {
1627
1670
  maxWidth: "75%"
1628
1671
  },
1629
1672
  bubble: {
1630
- padding: "8px 12px",
1673
+ padding: "10px 14px",
1631
1674
  borderRadius: "12px",
1632
1675
  wordBreak: "break-word"
1633
1676
  },
@@ -1642,11 +1685,13 @@ var styles5 = {
1642
1685
  borderBottomLeftRadius: "4px"
1643
1686
  },
1644
1687
  pinnedBadge: {
1645
- display: "inline-block",
1646
- fontSize: "10px",
1688
+ display: "inline-flex",
1689
+ alignItems: "center",
1690
+ gap: "4px",
1691
+ fontSize: "12px",
1647
1692
  fontWeight: 600,
1648
1693
  marginBottom: "4px",
1649
- opacity: 0.7
1694
+ color: "#6b7280"
1650
1695
  },
1651
1696
  senderRow: {
1652
1697
  display: "flex",
@@ -1655,24 +1700,25 @@ var styles5 = {
1655
1700
  marginBottom: "4px"
1656
1701
  },
1657
1702
  senderName: {
1658
- fontSize: "12px",
1703
+ fontSize: "13px",
1659
1704
  fontWeight: 600,
1660
1705
  color: "#374151"
1661
1706
  },
1662
1707
  roleBadge: {
1663
- fontSize: "10px",
1664
- fontWeight: 500,
1665
- padding: "1px 6px",
1708
+ fontSize: "11px",
1709
+ fontWeight: 600,
1710
+ padding: "2px 8px",
1666
1711
  borderRadius: "4px"
1667
1712
  },
1668
1713
  textBody: {
1669
1714
  margin: 0,
1670
- fontSize: "14px",
1671
- lineHeight: "1.4"
1715
+ fontSize: "15px",
1716
+ lineHeight: "1.45"
1672
1717
  },
1673
1718
  timestamp: {
1674
- fontSize: "10px",
1675
- opacity: 0.7,
1719
+ fontSize: "12px",
1720
+ color: "inherit",
1721
+ opacity: 0.8,
1676
1722
  marginTop: "4px",
1677
1723
  textAlign: "right"
1678
1724
  },
@@ -1711,9 +1757,6 @@ var styles5 = {
1711
1757
  color: "inherit",
1712
1758
  padding: "6px 0"
1713
1759
  },
1714
- fileIcon: {
1715
- fontSize: "18px"
1716
- },
1717
1760
  fileName: {
1718
1761
  fontSize: "13px",
1719
1762
  textDecoration: "underline"
@@ -1748,16 +1791,92 @@ var styles5 = {
1748
1791
  justifyContent: "center",
1749
1792
  alignItems: "center",
1750
1793
  gap: "8px",
1751
- padding: "4px 16px",
1752
- fontSize: "12px",
1753
- color: "#9ca3af"
1794
+ padding: "6px 16px",
1795
+ fontSize: "13px",
1796
+ color: "#6b7280"
1754
1797
  },
1755
1798
  systemTime: {
1756
- fontSize: "10px",
1757
- color: "#d1d5db"
1799
+ fontSize: "12px",
1800
+ color: "#9ca3af"
1758
1801
  }
1759
1802
  };
1760
- var MessageList = react.forwardRef(function MessageList2({
1803
+
1804
+ // src/core/dateLabels.ts
1805
+ var SHORT_MONTHS = [
1806
+ "Jan",
1807
+ "Feb",
1808
+ "Mar",
1809
+ "Apr",
1810
+ "May",
1811
+ "Jun",
1812
+ "Jul",
1813
+ "Aug",
1814
+ "Sep",
1815
+ "Oct",
1816
+ "Nov",
1817
+ "Dec"
1818
+ ];
1819
+ var LONG_WEEKDAYS = [
1820
+ "Sunday",
1821
+ "Monday",
1822
+ "Tuesday",
1823
+ "Wednesday",
1824
+ "Thursday",
1825
+ "Friday",
1826
+ "Saturday"
1827
+ ];
1828
+ var ONE_DAY_MS = 864e5;
1829
+ function parseSafe(iso) {
1830
+ const d = new Date(iso);
1831
+ return Number.isNaN(d.getTime()) ? null : d;
1832
+ }
1833
+ function startOfDay(d) {
1834
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
1835
+ }
1836
+ function daysBetween(a, b) {
1837
+ return Math.floor((startOfDay(a).getTime() - startOfDay(b).getTime()) / ONE_DAY_MS);
1838
+ }
1839
+ function formatFullDate(d) {
1840
+ return `${d.getDate()} ${SHORT_MONTHS[d.getMonth()]} ${d.getFullYear()}`;
1841
+ }
1842
+ function formatLocaleTime(d) {
1843
+ try {
1844
+ return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
1845
+ } catch {
1846
+ const h = d.getHours();
1847
+ const m = d.getMinutes().toString().padStart(2, "0");
1848
+ const suffix = h >= 12 ? "PM" : "AM";
1849
+ const h12 = (h + 11) % 12 + 1;
1850
+ return `${h12}:${m} ${suffix}`;
1851
+ }
1852
+ }
1853
+ function formatInboxTimestamp(iso) {
1854
+ if (!iso) return "";
1855
+ const d = parseSafe(iso);
1856
+ if (!d) return "";
1857
+ const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
1858
+ if (diffDays === 0) return formatLocaleTime(d);
1859
+ if (diffDays === 1) return "Yesterday";
1860
+ if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
1861
+ return formatFullDate(d);
1862
+ }
1863
+ function formatDayDivider(iso) {
1864
+ if (!iso) return "";
1865
+ const d = parseSafe(iso);
1866
+ if (!d) return "";
1867
+ const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
1868
+ if (diffDays === 0) return "Today";
1869
+ if (diffDays === 1) return "Yesterday";
1870
+ if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
1871
+ return formatFullDate(d);
1872
+ }
1873
+ function dayBucketKey(iso) {
1874
+ if (!iso) return "";
1875
+ const d = parseSafe(iso);
1876
+ if (!d) return "";
1877
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
1878
+ }
1879
+ var MessageList = React4.forwardRef(function MessageList2({
1761
1880
  messages,
1762
1881
  currentUserId,
1763
1882
  participants = [],
@@ -1775,10 +1894,10 @@ var MessageList = react.forwardRef(function MessageList2({
1775
1894
  pinDisabled,
1776
1895
  className
1777
1896
  }, ref) {
1778
- const containerRef = react.useRef(null);
1779
- const bottomRef = react.useRef(null);
1780
- const prevMessageCount = react.useRef(messages.length);
1781
- react.useImperativeHandle(
1897
+ const containerRef = React4.useRef(null);
1898
+ const bottomRef = React4.useRef(null);
1899
+ const prevMessageCount = React4.useRef(messages.length);
1900
+ React4.useImperativeHandle(
1782
1901
  ref,
1783
1902
  () => ({
1784
1903
  scrollToMessage: (messageId) => {
@@ -1795,7 +1914,7 @@ var MessageList = react.forwardRef(function MessageList2({
1795
1914
  }),
1796
1915
  []
1797
1916
  );
1798
- react.useEffect(() => {
1917
+ React4.useEffect(() => {
1799
1918
  if (messages.length > prevMessageCount.current) {
1800
1919
  const lastMessage = messages[messages.length - 1];
1801
1920
  const isOwnMessage = lastMessage?.senderId === currentUserId;
@@ -1805,7 +1924,7 @@ var MessageList = react.forwardRef(function MessageList2({
1805
1924
  }
1806
1925
  prevMessageCount.current = messages.length;
1807
1926
  }, [messages, currentUserId]);
1808
- react.useEffect(() => {
1927
+ React4.useEffect(() => {
1809
1928
  if (!onMessageVisible || !containerRef.current) return;
1810
1929
  const observer = new IntersectionObserver(
1811
1930
  (entries) => {
@@ -1822,7 +1941,7 @@ var MessageList = react.forwardRef(function MessageList2({
1822
1941
  messageElements.forEach((el) => observer.observe(el));
1823
1942
  return () => observer.disconnect();
1824
1943
  }, [messages, onMessageVisible]);
1825
- const handleScroll = react.useCallback(() => {
1944
+ const handleScroll = React4.useCallback(() => {
1826
1945
  if (!containerRef.current || !hasMore || isLoading) return;
1827
1946
  if (containerRef.current.scrollTop < 50) {
1828
1947
  onLoadMore();
@@ -1842,22 +1961,30 @@ var MessageList = react.forwardRef(function MessageList2({
1842
1961
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptyTitle, children: "No messages yet" }),
1843
1962
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation about this study" })
1844
1963
  ] }),
1845
- messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
1846
- MessageBubble,
1847
- {
1848
- message,
1849
- isOwn: message.senderId === currentUserId,
1850
- participants,
1851
- currentUserId,
1852
- showSeenBy,
1853
- onDeepLinkClick,
1854
- onReply,
1855
- onPin,
1856
- onUnpin,
1857
- onCopy,
1858
- pinDisabled
1859
- }
1860
- ) }, message.id)),
1964
+ messages.map((message, index) => {
1965
+ const currentBucket = dayBucketKey(message.insertedAt);
1966
+ const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
1967
+ const showDivider = currentBucket && currentBucket !== prevBucket;
1968
+ return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
1969
+ showDivider && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
1970
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
1971
+ MessageBubble,
1972
+ {
1973
+ message,
1974
+ isOwn: message.senderId === currentUserId,
1975
+ participants,
1976
+ currentUserId,
1977
+ showSeenBy,
1978
+ onDeepLinkClick,
1979
+ onReply,
1980
+ onPin,
1981
+ onUnpin,
1982
+ onCopy,
1983
+ pinDisabled
1984
+ }
1985
+ ) })
1986
+ ] }, message.id);
1987
+ }),
1861
1988
  typingUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.typingIndicator, children: [
1862
1989
  typingUsers.map((t) => t.userName ?? "Someone").join(", "),
1863
1990
  typingUsers.length === 1 ? " is " : " are ",
@@ -1883,18 +2010,19 @@ var styles6 = {
1883
2010
  loadingMore: {
1884
2011
  textAlign: "center",
1885
2012
  padding: "12px",
1886
- fontSize: "13px",
1887
- color: "#9ca3af"
2013
+ fontSize: "14px",
2014
+ color: "#6b7280"
1888
2015
  },
1889
2016
  loadMoreButton: {
1890
2017
  display: "block",
1891
2018
  margin: "0 auto 12px",
1892
- padding: "6px 16px",
1893
- fontSize: "12px",
1894
- color: "#6b7280",
2019
+ minHeight: "36px",
2020
+ padding: "8px 18px",
2021
+ fontSize: "13px",
2022
+ color: "#374151",
1895
2023
  backgroundColor: "transparent",
1896
- border: "1px solid #e5e7eb",
1897
- borderRadius: "16px",
2024
+ border: "1px solid #d1d5db",
2025
+ borderRadius: "18px",
1898
2026
  cursor: "pointer"
1899
2027
  },
1900
2028
  empty: {
@@ -1903,22 +2031,38 @@ var styles6 = {
1903
2031
  alignItems: "center",
1904
2032
  justifyContent: "center",
1905
2033
  padding: "48px 16px",
1906
- color: "#9ca3af"
2034
+ color: "#6b7280"
1907
2035
  },
1908
2036
  emptyTitle: {
1909
- fontSize: "15px",
1910
- fontWeight: 500,
2037
+ fontSize: "16px",
2038
+ fontWeight: 600,
2039
+ color: "#374151",
1911
2040
  margin: "0 0 4px"
1912
2041
  },
1913
2042
  emptySubtitle: {
1914
- fontSize: "13px",
2043
+ fontSize: "14px",
1915
2044
  margin: 0
1916
2045
  },
1917
2046
  typingIndicator: {
1918
2047
  padding: "4px 16px 8px",
1919
- fontSize: "12px",
1920
- color: "#9ca3af",
2048
+ fontSize: "13px",
2049
+ color: "#6b7280",
1921
2050
  fontStyle: "italic"
2051
+ },
2052
+ dayDivider: {
2053
+ display: "flex",
2054
+ justifyContent: "center",
2055
+ margin: "12px 0 8px"
2056
+ },
2057
+ dayDividerPill: {
2058
+ display: "inline-block",
2059
+ padding: "5px 14px",
2060
+ fontSize: "13px",
2061
+ fontWeight: 500,
2062
+ color: "#334155",
2063
+ backgroundColor: "#e2e8f0",
2064
+ borderRadius: "12px",
2065
+ letterSpacing: "0.2px"
1922
2066
  }
1923
2067
  };
1924
2068
  function ReplyPreview({ message, onCancel, className }) {
@@ -2013,6 +2157,48 @@ var styles7 = {
2013
2157
  flexShrink: 0
2014
2158
  }
2015
2159
  };
2160
+ function AttachIcon({ size = 20, color = "currentColor" }) {
2161
+ return /* @__PURE__ */ jsxRuntime.jsx(
2162
+ "svg",
2163
+ {
2164
+ xmlns: "http://www.w3.org/2000/svg",
2165
+ width: size,
2166
+ height: size,
2167
+ viewBox: "0 0 24 24",
2168
+ fill: color,
2169
+ "aria-hidden": "true",
2170
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" })
2171
+ }
2172
+ );
2173
+ }
2174
+ function SendIcon({ size = 20, color = "currentColor" }) {
2175
+ return /* @__PURE__ */ jsxRuntime.jsx(
2176
+ "svg",
2177
+ {
2178
+ xmlns: "http://www.w3.org/2000/svg",
2179
+ width: size,
2180
+ height: size,
2181
+ viewBox: "0 0 24 24",
2182
+ fill: color,
2183
+ "aria-hidden": "true",
2184
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" })
2185
+ }
2186
+ );
2187
+ }
2188
+ function CloseIcon({ size = 18, color = "currentColor" }) {
2189
+ return /* @__PURE__ */ jsxRuntime.jsx(
2190
+ "svg",
2191
+ {
2192
+ xmlns: "http://www.w3.org/2000/svg",
2193
+ width: size,
2194
+ height: size,
2195
+ viewBox: "0 0 24 24",
2196
+ fill: color,
2197
+ "aria-hidden": "true",
2198
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })
2199
+ }
2200
+ );
2201
+ }
2016
2202
  function MessageInput({
2017
2203
  onSendText,
2018
2204
  // MIC DISABLED — onSendAudio still accepted but unused.
@@ -2025,14 +2211,14 @@ function MessageInput({
2025
2211
  placeholder = "Type a message...",
2026
2212
  className
2027
2213
  }) {
2028
- const [text, setText] = react.useState("");
2029
- const [isSending, setIsSending] = react.useState(false);
2030
- const [fileError, setFileError] = react.useState(null);
2031
- const fileInputRef = react.useRef(null);
2032
- const typingTimeoutRef = react.useRef(null);
2033
- const isTypingRef = react.useRef(false);
2034
- const sendingRef = react.useRef(false);
2035
- const handleTyping = react.useCallback(() => {
2214
+ const [text, setText] = React4.useState("");
2215
+ const [isSending, setIsSending] = React4.useState(false);
2216
+ const [fileError, setFileError] = React4.useState(null);
2217
+ const fileInputRef = React4.useRef(null);
2218
+ const typingTimeoutRef = React4.useRef(null);
2219
+ const isTypingRef = React4.useRef(false);
2220
+ const sendingRef = React4.useRef(false);
2221
+ const handleTyping = React4.useCallback(() => {
2036
2222
  if (!isTypingRef.current) {
2037
2223
  isTypingRef.current = true;
2038
2224
  onTyping(true);
@@ -2045,12 +2231,12 @@ function MessageInput({
2045
2231
  onTyping(false);
2046
2232
  }, 2e3);
2047
2233
  }, [onTyping]);
2048
- react.useEffect(() => {
2234
+ React4.useEffect(() => {
2049
2235
  return () => {
2050
2236
  if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
2051
2237
  };
2052
2238
  }, []);
2053
- const handleSendText = react.useCallback(async () => {
2239
+ const handleSendText = React4.useCallback(async () => {
2054
2240
  const trimmed = text.trim();
2055
2241
  if (!trimmed || sendingRef.current) return;
2056
2242
  sendingRef.current = true;
@@ -2071,7 +2257,7 @@ function MessageInput({
2071
2257
  setIsSending(false);
2072
2258
  }
2073
2259
  }, [text, onSendText, onTyping]);
2074
- const handleKeyDown = react.useCallback(
2260
+ const handleKeyDown = React4.useCallback(
2075
2261
  (e) => {
2076
2262
  if (e.key === "Enter" && !e.shiftKey) {
2077
2263
  e.preventDefault();
@@ -2080,7 +2266,7 @@ function MessageInput({
2080
2266
  },
2081
2267
  [handleSendText]
2082
2268
  );
2083
- const handleFileSelect = react.useCallback(
2269
+ const handleFileSelect = React4.useCallback(
2084
2270
  async (e) => {
2085
2271
  const file = e.target.files?.[0];
2086
2272
  if (!file) return;
@@ -2101,7 +2287,19 @@ function MessageInput({
2101
2287
  );
2102
2288
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles8.container, children: [
2103
2289
  replyTo && onCancelReply && /* @__PURE__ */ jsxRuntime.jsx(ReplyPreview, { message: replyTo, onCancel: onCancelReply }),
2104
- fileError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles8.error, children: fileError }),
2290
+ fileError && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.error, role: "alert", children: [
2291
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles8.errorText, children: fileError }),
2292
+ /* @__PURE__ */ jsxRuntime.jsx(
2293
+ "button",
2294
+ {
2295
+ type: "button",
2296
+ onClick: () => setFileError(null),
2297
+ "aria-label": "Dismiss error",
2298
+ style: styles8.errorDismiss,
2299
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16 })
2300
+ }
2301
+ )
2302
+ ] }),
2105
2303
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.inputBar, children: [
2106
2304
  /* @__PURE__ */ jsxRuntime.jsx(
2107
2305
  "button",
@@ -2109,9 +2307,10 @@ function MessageInput({
2109
2307
  onClick: () => fileInputRef.current?.click(),
2110
2308
  disabled: disabled || isSending,
2111
2309
  style: styles8.iconButton,
2310
+ "aria-label": "Attach file",
2112
2311
  title: "Attach file",
2113
2312
  type: "button",
2114
- children: "\u{1F4CE}"
2313
+ children: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, { size: 22, color: "#374151" })
2115
2314
  }
2116
2315
  ),
2117
2316
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2144,12 +2343,14 @@ function MessageInput({
2144
2343
  {
2145
2344
  onClick: handleSendText,
2146
2345
  disabled: disabled || isSending || !text.trim(),
2346
+ "aria-label": "Send message",
2347
+ title: "Send message",
2147
2348
  style: {
2148
2349
  ...styles8.sendButton,
2149
2350
  opacity: text.trim() ? 1 : 0.4
2150
2351
  },
2151
2352
  type: "button",
2152
- children: "\u27A4"
2353
+ children: /* @__PURE__ */ jsxRuntime.jsx(SendIcon, { size: 20, color: "#ffffff" })
2153
2354
  }
2154
2355
  )
2155
2356
  ] })
@@ -2161,24 +2362,45 @@ var styles8 = {
2161
2362
  backgroundColor: "#ffffff"
2162
2363
  },
2163
2364
  error: {
2164
- padding: "6px 16px",
2165
- fontSize: "12px",
2166
- color: "#dc2626",
2167
- backgroundColor: "#fef2f2"
2365
+ display: "flex",
2366
+ alignItems: "center",
2367
+ justifyContent: "space-between",
2368
+ gap: "8px",
2369
+ padding: "8px 12px 8px 16px",
2370
+ fontSize: "13px",
2371
+ color: "#b91c1c",
2372
+ backgroundColor: "#fef2f2",
2373
+ borderTop: "1px solid #fecaca"
2374
+ },
2375
+ errorText: {
2376
+ flex: 1,
2377
+ minWidth: 0
2378
+ },
2379
+ errorDismiss: {
2380
+ width: "28px",
2381
+ height: "28px",
2382
+ display: "flex",
2383
+ alignItems: "center",
2384
+ justifyContent: "center",
2385
+ backgroundColor: "transparent",
2386
+ border: "none",
2387
+ color: "#b91c1c",
2388
+ borderRadius: "4px",
2389
+ cursor: "pointer",
2390
+ flexShrink: 0
2168
2391
  },
2169
2392
  inputBar: {
2170
2393
  display: "flex",
2171
2394
  alignItems: "flex-end",
2172
- gap: "4px",
2173
- padding: "8px 12px"
2395
+ gap: "6px",
2396
+ padding: "10px 12px"
2174
2397
  },
2175
2398
  iconButton: {
2176
- width: "36px",
2177
- height: "36px",
2399
+ width: "44px",
2400
+ height: "44px",
2178
2401
  display: "flex",
2179
2402
  alignItems: "center",
2180
2403
  justifyContent: "center",
2181
- fontSize: "18px",
2182
2404
  backgroundColor: "transparent",
2183
2405
  border: "none",
2184
2406
  borderRadius: "50%",
@@ -2187,23 +2409,23 @@ var styles8 = {
2187
2409
  },
2188
2410
  textarea: {
2189
2411
  flex: 1,
2190
- padding: "8px 12px",
2191
- fontSize: "14px",
2192
- lineHeight: "1.4",
2193
- border: "1px solid #e5e7eb",
2194
- borderRadius: "20px",
2412
+ padding: "10px 14px",
2413
+ fontSize: "15px",
2414
+ lineHeight: "1.45",
2415
+ border: "1px solid #d1d5db",
2416
+ borderRadius: "22px",
2195
2417
  resize: "none",
2196
2418
  outline: "none",
2197
2419
  fontFamily: "inherit",
2198
- maxHeight: "120px"
2420
+ maxHeight: "120px",
2421
+ minHeight: "44px"
2199
2422
  },
2200
2423
  sendButton: {
2201
- width: "36px",
2202
- height: "36px",
2424
+ width: "44px",
2425
+ height: "44px",
2203
2426
  display: "flex",
2204
2427
  alignItems: "center",
2205
2428
  justifyContent: "center",
2206
- fontSize: "18px",
2207
2429
  backgroundColor: "#2563eb",
2208
2430
  color: "#ffffff",
2209
2431
  border: "none",
@@ -2376,13 +2598,13 @@ function ChannelSettings({
2376
2598
  onClose,
2377
2599
  className
2378
2600
  }) {
2379
- const [editingName, setEditingName] = react.useState(false);
2380
- const [nameValue, setNameValue] = react.useState(channelName);
2381
- const [showInvite, setShowInvite] = react.useState(false);
2382
- const [inviteId, setInviteId] = react.useState("");
2383
- const [inviteName, setInviteName] = react.useState("");
2384
- const [inviteRole, setInviteRole] = react.useState("radiologist");
2385
- const [confirmDelete, setConfirmDelete] = react.useState(false);
2601
+ const [editingName, setEditingName] = React4.useState(false);
2602
+ const [nameValue, setNameValue] = React4.useState(channelName);
2603
+ const [showInvite, setShowInvite] = React4.useState(false);
2604
+ const [inviteId, setInviteId] = React4.useState("");
2605
+ const [inviteName, setInviteName] = React4.useState("");
2606
+ const [inviteRole, setInviteRole] = React4.useState("radiologist");
2607
+ const [confirmDelete, setConfirmDelete] = React4.useState(false);
2386
2608
  const handleSaveName = async () => {
2387
2609
  if (nameValue.trim() && nameValue !== channelName) {
2388
2610
  await onUpdateChannel({ name: nameValue.trim() });
@@ -2673,7 +2895,7 @@ function PinnedMessagesBar({
2673
2895
  onUnpin,
2674
2896
  className
2675
2897
  }) {
2676
- const [index, setIndex] = react.useState(0);
2898
+ const [index, setIndex] = React4.useState(0);
2677
2899
  if (pinnedMessages.length === 0) return null;
2678
2900
  const safeIndex = Math.min(index, pinnedMessages.length - 1);
2679
2901
  const current = pinnedMessages[safeIndex];
@@ -2832,8 +3054,8 @@ function CollabPanel({
2832
3054
  style
2833
3055
  }) {
2834
3056
  const { config } = useCollab();
2835
- const [showSettings, setShowSettings] = react.useState(false);
2836
- const messageListRef = react.useRef(null);
3057
+ const [showSettings, setShowSettings] = React4.useState(false);
3058
+ const messageListRef = React4.useRef(null);
2837
3059
  const {
2838
3060
  conversation,
2839
3061
  messages,
@@ -2994,6 +3216,34 @@ var panelStyles = {
2994
3216
  margin: 0
2995
3217
  }
2996
3218
  };
3219
+ function MinimizeIcon({ size = 18, color = "currentColor" }) {
3220
+ return /* @__PURE__ */ jsxRuntime.jsx(
3221
+ "svg",
3222
+ {
3223
+ xmlns: "http://www.w3.org/2000/svg",
3224
+ width: size,
3225
+ height: size,
3226
+ viewBox: "0 0 24 24",
3227
+ fill: color,
3228
+ "aria-hidden": "true",
3229
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 19h12v2H6z" })
3230
+ }
3231
+ );
3232
+ }
3233
+ function ExpandIcon({ size = 18, color = "currentColor" }) {
3234
+ return /* @__PURE__ */ jsxRuntime.jsx(
3235
+ "svg",
3236
+ {
3237
+ xmlns: "http://www.w3.org/2000/svg",
3238
+ width: size,
3239
+ height: size,
3240
+ viewBox: "0 0 24 24",
3241
+ fill: color,
3242
+ "aria-hidden": "true",
3243
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4h16v16H4V4zm2 4v10h12V8H6z" })
3244
+ }
3245
+ );
3246
+ }
2997
3247
  function CollabPopup({
2998
3248
  orderId,
2999
3249
  patientData,
@@ -3005,17 +3255,17 @@ function CollabPopup({
3005
3255
  height = 520,
3006
3256
  className
3007
3257
  }) {
3008
- const [position, setPosition] = react.useState(
3258
+ const [position, setPosition] = React4.useState(
3009
3259
  initialPosition || {
3010
3260
  x: window.innerWidth - width - 24,
3011
3261
  y: Math.max(24, window.innerHeight - height - 104)
3012
3262
  }
3013
3263
  );
3014
- const [isDragging, setIsDragging] = react.useState(false);
3015
- const [isMinimized, setIsMinimized] = react.useState(false);
3016
- const dragOffset = react.useRef({ x: 0, y: 0 });
3017
- const containerRef = react.useRef(null);
3018
- const handleMouseDown = react.useCallback(
3264
+ const [isDragging, setIsDragging] = React4.useState(false);
3265
+ const [isMinimized, setIsMinimized] = React4.useState(false);
3266
+ const dragOffset = React4.useRef({ x: 0, y: 0 });
3267
+ const containerRef = React4.useRef(null);
3268
+ const handleMouseDown = React4.useCallback(
3019
3269
  (e) => {
3020
3270
  if (!e.target.closest("[data-drag-handle]")) return;
3021
3271
  setIsDragging(true);
@@ -3027,7 +3277,7 @@ function CollabPopup({
3027
3277
  },
3028
3278
  [position]
3029
3279
  );
3030
- react.useEffect(() => {
3280
+ React4.useEffect(() => {
3031
3281
  if (!isOpen) return;
3032
3282
  const handleKey = (e) => {
3033
3283
  if (e.key === "Escape") {
@@ -3037,12 +3287,12 @@ function CollabPopup({
3037
3287
  window.addEventListener("keydown", handleKey);
3038
3288
  return () => window.removeEventListener("keydown", handleKey);
3039
3289
  }, [isOpen, onClose]);
3040
- react.useEffect(() => {
3290
+ React4.useEffect(() => {
3041
3291
  if (!isDragging) return;
3042
3292
  const handleMouseMove = (e) => {
3043
3293
  setPosition({
3044
3294
  x: Math.max(0, Math.min(e.clientX - dragOffset.current.x, window.innerWidth - width)),
3045
- y: Math.max(0, Math.min(e.clientY - dragOffset.current.y, window.innerHeight - (isMinimized ? 48 : height)))
3295
+ y: Math.max(0, Math.min(e.clientY - dragOffset.current.y, window.innerHeight - (isMinimized ? 52 : height)))
3046
3296
  });
3047
3297
  };
3048
3298
  const handleMouseUp = () => {
@@ -3066,7 +3316,7 @@ function CollabPopup({
3066
3316
  left: position.x,
3067
3317
  top: position.y,
3068
3318
  width,
3069
- height: isMinimized ? 48 : height,
3319
+ height: isMinimized ? 52 : height,
3070
3320
  cursor: isDragging ? "grabbing" : "default"
3071
3321
  },
3072
3322
  onMouseDown: handleMouseDown,
@@ -3079,12 +3329,23 @@ function CollabPopup({
3079
3329
  {
3080
3330
  onClick: () => setIsMinimized(!isMinimized),
3081
3331
  style: styles12.titleButton,
3332
+ "aria-label": isMinimized ? "Expand chat" : "Minimize chat",
3082
3333
  title: isMinimized ? "Expand" : "Minimize",
3083
3334
  type: "button",
3084
- children: isMinimized ? "\u25A2" : "\u2014"
3335
+ children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, { size: 16, color: "#cbd5e1" }) : /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, { size: 16, color: "#cbd5e1" })
3085
3336
  }
3086
3337
  ),
3087
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: styles12.titleButton, title: "Close", type: "button", children: "\u2715" })
3338
+ /* @__PURE__ */ jsxRuntime.jsx(
3339
+ "button",
3340
+ {
3341
+ onClick: onClose,
3342
+ style: styles12.titleButton,
3343
+ "aria-label": "Close chat",
3344
+ title: "Close",
3345
+ type: "button",
3346
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16, color: "#cbd5e1" })
3347
+ }
3348
+ )
3088
3349
  ] })
3089
3350
  ] }),
3090
3351
  !isMinimized && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles12.panelWrapper, children: /* @__PURE__ */ jsxRuntime.jsx(CollabPanel, { orderId, patientData, participantIds }) })
@@ -3109,15 +3370,15 @@ var styles12 = {
3109
3370
  display: "flex",
3110
3371
  justifyContent: "space-between",
3111
3372
  alignItems: "center",
3112
- padding: "0 12px",
3113
- height: "48px",
3373
+ padding: "0 8px 0 14px",
3374
+ height: "52px",
3114
3375
  backgroundColor: "#1e293b",
3115
3376
  cursor: "grab",
3116
3377
  userSelect: "none",
3117
3378
  flexShrink: 0
3118
3379
  },
3119
3380
  titleText: {
3120
- fontSize: "13px",
3381
+ fontSize: "15px",
3121
3382
  fontWeight: 500,
3122
3383
  color: "#ffffff",
3123
3384
  overflow: "hidden",
@@ -3130,17 +3391,16 @@ var styles12 = {
3130
3391
  flexShrink: 0
3131
3392
  },
3132
3393
  titleButton: {
3133
- width: "28px",
3134
- height: "28px",
3394
+ width: "40px",
3395
+ height: "40px",
3135
3396
  display: "flex",
3136
3397
  alignItems: "center",
3137
3398
  justifyContent: "center",
3138
3399
  backgroundColor: "transparent",
3139
3400
  border: "none",
3140
- borderRadius: "4px",
3401
+ borderRadius: "6px",
3141
3402
  cursor: "pointer",
3142
- fontSize: "13px",
3143
- color: "#94a3b8"
3403
+ color: "#cbd5e1"
3144
3404
  },
3145
3405
  panelWrapper: {
3146
3406
  flex: 1,
@@ -3160,25 +3420,25 @@ function useInlineCollab({
3160
3420
  invalidatePreview,
3161
3421
  createConversationWithMessage
3162
3422
  } = useCollab();
3163
- const [preview, setPreview] = react.useState(null);
3164
- const [messages, setMessages] = react.useState([]);
3165
- const [participants, setParticipants] = react.useState([]);
3166
- const [unreadCount, setUnreadCount] = react.useState(0);
3167
- const [isLoading, setIsLoading] = react.useState(true);
3168
- const [isSubscribed, setIsSubscribed] = react.useState(false);
3169
- const [error, setError] = react.useState(null);
3170
- const elementRef = react.useRef(null);
3171
- const observerRef = react.useRef(null);
3172
- const subscribedConversationIdRef = react.useRef(null);
3173
- const trimToLimit = react.useCallback(
3423
+ const [preview, setPreview] = React4.useState(null);
3424
+ const [messages, setMessages] = React4.useState([]);
3425
+ const [participants, setParticipants] = React4.useState([]);
3426
+ const [unreadCount, setUnreadCount] = React4.useState(0);
3427
+ const [isLoading, setIsLoading] = React4.useState(true);
3428
+ const [isSubscribed, setIsSubscribed] = React4.useState(false);
3429
+ const [error, setError] = React4.useState(null);
3430
+ const elementRef = React4.useRef(null);
3431
+ const observerRef = React4.useRef(null);
3432
+ const subscribedConversationIdRef = React4.useRef(null);
3433
+ const trimToLimit = React4.useCallback(
3174
3434
  (msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
3175
3435
  [messageLimit]
3176
3436
  );
3177
- const buildName = react.useCallback(
3437
+ const buildName = React4.useCallback(
3178
3438
  () => buildChannelName(patientData),
3179
3439
  [patientData]
3180
3440
  );
3181
- react.useEffect(() => {
3441
+ React4.useEffect(() => {
3182
3442
  let cancelled = false;
3183
3443
  const load = async () => {
3184
3444
  setIsLoading(true);
@@ -3204,7 +3464,7 @@ function useInlineCollab({
3204
3464
  cancelled = true;
3205
3465
  };
3206
3466
  }, [orderId, requestPreview, trimToLimit]);
3207
- const subscribe = react.useCallback(
3467
+ const subscribe = React4.useCallback(
3208
3468
  (conversationId) => {
3209
3469
  if (subscribedConversationIdRef.current === conversationId) return;
3210
3470
  socket.joinConversation(conversationId, {
@@ -3228,7 +3488,7 @@ function useInlineCollab({
3228
3488
  },
3229
3489
  [socket, trimToLimit, config.userId]
3230
3490
  );
3231
- const unsubscribe = react.useCallback(() => {
3491
+ const unsubscribe = React4.useCallback(() => {
3232
3492
  const convId = subscribedConversationIdRef.current;
3233
3493
  if (convId) {
3234
3494
  socket.leaveConversation(convId);
@@ -3236,7 +3496,7 @@ function useInlineCollab({
3236
3496
  setIsSubscribed(false);
3237
3497
  }
3238
3498
  }, [socket]);
3239
- const containerRef = react.useCallback(
3499
+ const containerRef = React4.useCallback(
3240
3500
  (element) => {
3241
3501
  if (observerRef.current) {
3242
3502
  observerRef.current.disconnect();
@@ -3262,18 +3522,18 @@ function useInlineCollab({
3262
3522
  },
3263
3523
  [preview, subscribe, unsubscribe]
3264
3524
  );
3265
- react.useEffect(() => {
3525
+ React4.useEffect(() => {
3266
3526
  return () => {
3267
3527
  if (observerRef.current) observerRef.current.disconnect();
3268
3528
  unsubscribe();
3269
3529
  };
3270
3530
  }, [unsubscribe]);
3271
- react.useEffect(() => {
3531
+ React4.useEffect(() => {
3272
3532
  if (preview?.conversationId && elementRef.current && !observerRef.current) {
3273
3533
  containerRef(elementRef.current);
3274
3534
  }
3275
3535
  }, [preview, containerRef]);
3276
- const dispatchSend = react.useCallback(
3536
+ const dispatchSend = React4.useCallback(
3277
3537
  async (payload) => {
3278
3538
  if (!preview) {
3279
3539
  try {
@@ -3330,7 +3590,7 @@ function useInlineCollab({
3330
3590
  config
3331
3591
  ]
3332
3592
  );
3333
- const sendMessage = react.useCallback(
3593
+ const sendMessage = React4.useCallback(
3334
3594
  async (body) => {
3335
3595
  const trimmed = body.trim();
3336
3596
  if (!trimmed) return;
@@ -3338,7 +3598,7 @@ function useInlineCollab({
3338
3598
  },
3339
3599
  [dispatchSend]
3340
3600
  );
3341
- const sendAudioMessage = react.useCallback(
3601
+ const sendAudioMessage = React4.useCallback(
3342
3602
  async (audioBlob, duration) => {
3343
3603
  if (!config.onUploadFile) {
3344
3604
  setError("File upload is not configured");
@@ -3451,10 +3711,10 @@ function RoleDot({ role }) {
3451
3711
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.roleDot, backgroundColor: colors[role] } });
3452
3712
  }
3453
3713
  function InlineAudioPlayer({ url, duration }) {
3454
- const audioRef = react.useRef(null);
3455
- const [isPlaying, setIsPlaying] = react.useState(false);
3456
- const [currentTime, setCurrentTime] = react.useState(0);
3457
- const toggle = react.useCallback(
3714
+ const audioRef = React4.useRef(null);
3715
+ const [isPlaying, setIsPlaying] = React4.useState(false);
3716
+ const [currentTime, setCurrentTime] = React4.useState(0);
3717
+ const toggle = React4.useCallback(
3458
3718
  (e) => {
3459
3719
  e.stopPropagation();
3460
3720
  const audio = audioRef.current;
@@ -3468,7 +3728,7 @@ function InlineAudioPlayer({ url, duration }) {
3468
3728
  },
3469
3729
  [isPlaying]
3470
3730
  );
3471
- react.useEffect(() => {
3731
+ React4.useEffect(() => {
3472
3732
  const audio = audioRef.current;
3473
3733
  if (!audio) return;
3474
3734
  const onPlay = () => setIsPlaying(true);
@@ -3544,10 +3804,10 @@ function InlineInputBar({
3544
3804
  showExpand,
3545
3805
  onExpand
3546
3806
  }) {
3547
- const [text, setText] = react.useState("");
3548
- const [isSending, setIsSending] = react.useState(false);
3549
- const inputRef = react.useRef(null);
3550
- const handleSend = react.useCallback(async () => {
3807
+ const [text, setText] = React4.useState("");
3808
+ const [isSending, setIsSending] = React4.useState(false);
3809
+ const inputRef = React4.useRef(null);
3810
+ const handleSend = React4.useCallback(async () => {
3551
3811
  const trimmed = text.trim();
3552
3812
  if (!trimmed || isSending) return;
3553
3813
  setIsSending(true);
@@ -3559,7 +3819,7 @@ function InlineInputBar({
3559
3819
  setIsSending(false);
3560
3820
  }
3561
3821
  }, [text, isSending, onSend]);
3562
- const handleKeyDown = react.useCallback(
3822
+ const handleKeyDown = React4.useCallback(
3563
3823
  (e) => {
3564
3824
  if (e.key === "Enter" && !e.shiftKey) {
3565
3825
  e.preventDefault();
@@ -3792,10 +4052,10 @@ var styles13 = {
3792
4052
  function useConversationList(options) {
3793
4053
  const enabled = options?.enabled ?? true;
3794
4054
  const { fetchConversationList, config, totalUnread: serverTotalUnread } = useCollab();
3795
- const [conversations, setConversations] = react.useState([]);
3796
- const [isLoading, setIsLoading] = react.useState(enabled);
3797
- const [error, setError] = react.useState(null);
3798
- const silentRefresh = react.useCallback(async () => {
4055
+ const [conversations, setConversations] = React4.useState([]);
4056
+ const [isLoading, setIsLoading] = React4.useState(enabled);
4057
+ const [error, setError] = React4.useState(null);
4058
+ const silentRefresh = React4.useCallback(async () => {
3799
4059
  try {
3800
4060
  const list = await fetchConversationList();
3801
4061
  setConversations(list);
@@ -3807,7 +4067,7 @@ function useConversationList(options) {
3807
4067
  });
3808
4068
  }
3809
4069
  }, [fetchConversationList, config]);
3810
- const refresh = react.useCallback(async () => {
4070
+ const refresh = React4.useCallback(async () => {
3811
4071
  setIsLoading(true);
3812
4072
  setError(null);
3813
4073
  try {
@@ -3825,12 +4085,12 @@ function useConversationList(options) {
3825
4085
  setIsLoading(false);
3826
4086
  }
3827
4087
  }, [fetchConversationList, config]);
3828
- react.useEffect(() => {
4088
+ React4.useEffect(() => {
3829
4089
  if (!enabled) return;
3830
4090
  refresh();
3831
4091
  }, [enabled, refresh]);
3832
- const hasHandledInitialUnreadRef = react.useRef(false);
3833
- react.useEffect(() => {
4092
+ const hasHandledInitialUnreadRef = React4.useRef(false);
4093
+ React4.useEffect(() => {
3834
4094
  if (!enabled) return;
3835
4095
  if (!hasHandledInitialUnreadRef.current) {
3836
4096
  hasHandledInitialUnreadRef.current = true;
@@ -3838,7 +4098,7 @@ function useConversationList(options) {
3838
4098
  }
3839
4099
  silentRefresh();
3840
4100
  }, [serverTotalUnread, enabled]);
3841
- const filter = react.useCallback(
4101
+ const filter = React4.useCallback(
3842
4102
  (query) => {
3843
4103
  const q = query.trim().toLowerCase();
3844
4104
  if (!q) return conversations;
@@ -3903,7 +4163,7 @@ function ConversationListItem({
3903
4163
  ...styles14.time,
3904
4164
  ...hasUnread ? styles14.timeUnread : {}
3905
4165
  },
3906
- children: formatRelativeTime(item.lastActivityAt)
4166
+ children: formatInboxTimestamp(item.lastActivityAt)
3907
4167
  }
3908
4168
  )
3909
4169
  ] }),
@@ -3946,23 +4206,6 @@ function renderLastMessagePreview(message) {
3946
4206
  }
3947
4207
  }
3948
4208
  }
3949
- function formatRelativeTime(iso) {
3950
- try {
3951
- const date = new Date(iso);
3952
- const now = /* @__PURE__ */ new Date();
3953
- const diffMs = now.getTime() - date.getTime();
3954
- const diffMin = Math.floor(diffMs / 6e4);
3955
- const diffHr = Math.floor(diffMs / 36e5);
3956
- const diffDay = Math.floor(diffMs / 864e5);
3957
- if (diffMin < 1) return "now";
3958
- if (diffMin < 60) return `${diffMin}m`;
3959
- if (diffHr < 24) return `${diffHr}h`;
3960
- if (diffDay < 7) return `${diffDay}d`;
3961
- return date.toLocaleDateString([], { month: "short", day: "numeric" });
3962
- } catch {
3963
- return "";
3964
- }
3965
- }
3966
4209
  var styles14 = {
3967
4210
  container: {
3968
4211
  display: "flex",
@@ -4028,7 +4271,7 @@ var styles14 = {
4028
4271
  gap: "8px"
4029
4272
  },
4030
4273
  name: {
4031
- fontSize: "14px",
4274
+ fontSize: "15px",
4032
4275
  fontWeight: 500,
4033
4276
  color: "#111827",
4034
4277
  overflow: "hidden",
@@ -4041,8 +4284,8 @@ var styles14 = {
4041
4284
  fontWeight: 700
4042
4285
  },
4043
4286
  time: {
4044
- fontSize: "11px",
4045
- color: "#9ca3af",
4287
+ fontSize: "12px",
4288
+ color: "#6b7280",
4046
4289
  flexShrink: 0
4047
4290
  },
4048
4291
  timeUnread: {
@@ -4056,8 +4299,8 @@ var styles14 = {
4056
4299
  gap: "8px"
4057
4300
  },
4058
4301
  preview: {
4059
- fontSize: "13px",
4060
- color: "#6b7280",
4302
+ fontSize: "14px",
4303
+ color: "#4b5563",
4061
4304
  overflow: "hidden",
4062
4305
  textOverflow: "ellipsis",
4063
4306
  whiteSpace: "nowrap",
@@ -4092,9 +4335,9 @@ function ConversationList({
4092
4335
  title = "Conversations",
4093
4336
  className
4094
4337
  }) {
4095
- const [query, setQuery] = react.useState("");
4096
- const [showUnreadOnly, setShowUnreadOnly] = react.useState(false);
4097
- const filtered = react.useMemo(() => {
4338
+ const [query, setQuery] = React4.useState("");
4339
+ const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
4340
+ const filtered = React4.useMemo(() => {
4098
4341
  let result = conversations;
4099
4342
  if (showUnreadOnly) {
4100
4343
  result = result.filter((c) => c.unreadCount > 0);
@@ -4271,7 +4514,7 @@ function CollabInbox({
4271
4514
  style
4272
4515
  }) {
4273
4516
  const { conversations, isLoading, error } = useConversationList();
4274
- const [selectedId, setSelectedId] = react.useState(initialConversationId ?? null);
4517
+ const [selectedId, setSelectedId] = React4.useState(initialConversationId ?? null);
4275
4518
  const selected = conversations.find((c) => c.id === selectedId) || null;
4276
4519
  const handleSelect = (item) => {
4277
4520
  setSelectedId(item.id);
@@ -4373,12 +4616,12 @@ function useMessages({
4373
4616
  initialLimit = MESSAGES_PAGE_SIZE
4374
4617
  }) {
4375
4618
  const { fetchMessages, config } = useCollab();
4376
- const [messages, setMessages] = react.useState([]);
4377
- const [isLoading, setIsLoading] = react.useState(false);
4378
- const [hasMore, setHasMore] = react.useState(true);
4379
- const [error, setError] = react.useState(null);
4380
- const loadedRef = react.useRef(null);
4381
- react.useEffect(() => {
4619
+ const [messages, setMessages] = React4.useState([]);
4620
+ const [isLoading, setIsLoading] = React4.useState(false);
4621
+ const [hasMore, setHasMore] = React4.useState(true);
4622
+ const [error, setError] = React4.useState(null);
4623
+ const loadedRef = React4.useRef(null);
4624
+ React4.useEffect(() => {
4382
4625
  if (!conversationId) {
4383
4626
  setMessages([]);
4384
4627
  setHasMore(false);
@@ -4414,7 +4657,7 @@ function useMessages({
4414
4657
  cancelled = true;
4415
4658
  };
4416
4659
  }, [conversationId, initialLimit, fetchMessages, config]);
4417
- const loadMore = react.useCallback(async () => {
4660
+ const loadMore = React4.useCallback(async () => {
4418
4661
  if (!conversationId || !hasMore || isLoading) return;
4419
4662
  const oldestId = messages[0]?.id;
4420
4663
  setIsLoading(true);
@@ -4435,17 +4678,17 @@ function useMessages({
4435
4678
  setIsLoading(false);
4436
4679
  }
4437
4680
  }, [conversationId, hasMore, isLoading, messages, initialLimit, fetchMessages, config]);
4438
- const addMessage = react.useCallback((message) => {
4681
+ const addMessage = React4.useCallback((message) => {
4439
4682
  setMessages((prev) => [...prev, message]);
4440
4683
  }, []);
4441
- const updateMessageReadBy = react.useCallback((messageId, userId) => {
4684
+ const updateMessageReadBy = React4.useCallback((messageId, userId) => {
4442
4685
  setMessages(
4443
4686
  (prev) => prev.map(
4444
4687
  (msg) => msg.id === messageId && !(msg.readBy ?? []).includes(userId) ? { ...msg, readBy: [...msg.readBy ?? [], userId] } : msg
4445
4688
  )
4446
4689
  );
4447
4690
  }, []);
4448
- const reset = react.useCallback(() => {
4691
+ const reset = React4.useCallback(() => {
4449
4692
  setMessages([]);
4450
4693
  setHasMore(true);
4451
4694
  setError(null);
@@ -4463,17 +4706,17 @@ function useMessages({
4463
4706
  };
4464
4707
  }
4465
4708
  function useAudioRecorder() {
4466
- const [isRecording, setIsRecording] = react.useState(false);
4467
- const [duration, setDuration] = react.useState(0);
4468
- const [error, setError] = react.useState(null);
4469
- const mediaRecorderRef = react.useRef(null);
4470
- const chunksRef = react.useRef([]);
4471
- const streamRef = react.useRef(null);
4472
- const timerRef = react.useRef(null);
4473
- const startTimeRef = react.useRef(0);
4474
- const resolveRef = react.useRef(null);
4709
+ const [isRecording, setIsRecording] = React4.useState(false);
4710
+ const [duration, setDuration] = React4.useState(0);
4711
+ const [error, setError] = React4.useState(null);
4712
+ const mediaRecorderRef = React4.useRef(null);
4713
+ const chunksRef = React4.useRef([]);
4714
+ const streamRef = React4.useRef(null);
4715
+ const timerRef = React4.useRef(null);
4716
+ const startTimeRef = React4.useRef(0);
4717
+ const resolveRef = React4.useRef(null);
4475
4718
  const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
4476
- const cleanup = react.useCallback(() => {
4719
+ const cleanup = React4.useCallback(() => {
4477
4720
  if (timerRef.current) {
4478
4721
  clearInterval(timerRef.current);
4479
4722
  timerRef.current = null;
@@ -4487,7 +4730,7 @@ function useAudioRecorder() {
4487
4730
  setIsRecording(false);
4488
4731
  setDuration(0);
4489
4732
  }, []);
4490
- const start = react.useCallback(async () => {
4733
+ const start = React4.useCallback(async () => {
4491
4734
  if (!isSupported) {
4492
4735
  setError("Audio recording is not supported in this browser");
4493
4736
  return;
@@ -4534,7 +4777,7 @@ function useAudioRecorder() {
4534
4777
  cleanup();
4535
4778
  }
4536
4779
  }, [isSupported, cleanup]);
4537
- const stop = react.useCallback(async () => {
4780
+ const stop = React4.useCallback(async () => {
4538
4781
  return new Promise((resolve) => {
4539
4782
  if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
4540
4783
  resolve(null);
@@ -4544,7 +4787,7 @@ function useAudioRecorder() {
4544
4787
  mediaRecorderRef.current.stop();
4545
4788
  });
4546
4789
  }, []);
4547
- const cancel = react.useCallback(() => {
4790
+ const cancel = React4.useCallback(() => {
4548
4791
  if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
4549
4792
  mediaRecorderRef.current.onstop = null;
4550
4793
  mediaRecorderRef.current.stop();
@@ -4565,19 +4808,19 @@ function useAudioRecorder() {
4565
4808
  }
4566
4809
  function useUnreadCount() {
4567
4810
  const { socket, totalUnread } = useCollab();
4568
- const [counts, setCounts] = react.useState({});
4569
- react.useEffect(() => {
4811
+ const [counts, setCounts] = React4.useState({});
4812
+ React4.useEffect(() => {
4570
4813
  socket.onUnreadCountUpdate((serverCounts) => {
4571
4814
  setCounts(serverCounts);
4572
4815
  });
4573
4816
  }, [socket]);
4574
- const getCountForConversation = react.useCallback(
4817
+ const getCountForConversation = React4.useCallback(
4575
4818
  (conversationId) => {
4576
4819
  return counts[conversationId] ?? 0;
4577
4820
  },
4578
4821
  [counts]
4579
4822
  );
4580
- const clearForConversation = react.useCallback((conversationId) => {
4823
+ const clearForConversation = React4.useCallback((conversationId) => {
4581
4824
  setCounts((prev) => {
4582
4825
  const next = { ...prev };
4583
4826
  delete next[conversationId];
@@ -4595,10 +4838,10 @@ function usePinnedMessages({
4595
4838
  initial = []
4596
4839
  }) {
4597
4840
  const { pinMessage, unpinMessage, fetchPinnedMessages, config } = useCollab();
4598
- const [pinnedMessages, setPinnedMessages] = react.useState(initial);
4599
- const [isLoading, setIsLoading] = react.useState(false);
4600
- const [error, setError] = react.useState(null);
4601
- const refresh = react.useCallback(async () => {
4841
+ const [pinnedMessages, setPinnedMessages] = React4.useState(initial);
4842
+ const [isLoading, setIsLoading] = React4.useState(false);
4843
+ const [error, setError] = React4.useState(null);
4844
+ const refresh = React4.useCallback(async () => {
4602
4845
  if (!conversationId) return;
4603
4846
  setIsLoading(true);
4604
4847
  setError(null);
@@ -4612,12 +4855,12 @@ function usePinnedMessages({
4612
4855
  setIsLoading(false);
4613
4856
  }
4614
4857
  }, [conversationId, fetchPinnedMessages]);
4615
- react.useEffect(() => {
4858
+ React4.useEffect(() => {
4616
4859
  if (conversationId && initial.length === 0) {
4617
4860
  refresh();
4618
4861
  }
4619
4862
  }, [conversationId]);
4620
- const pin = react.useCallback(
4863
+ const pin = React4.useCallback(
4621
4864
  async (messageId) => {
4622
4865
  if (!conversationId) return;
4623
4866
  setError(null);
@@ -4635,7 +4878,7 @@ function usePinnedMessages({
4635
4878
  },
4636
4879
  [conversationId, pinMessage, config]
4637
4880
  );
4638
- const unpin = react.useCallback(
4881
+ const unpin = React4.useCallback(
4639
4882
  async (messageId) => {
4640
4883
  if (!conversationId) return;
4641
4884
  setError(null);
@@ -4653,13 +4896,13 @@ function usePinnedMessages({
4653
4896
  },
4654
4897
  [conversationId, unpinMessage, config]
4655
4898
  );
4656
- const handlePinned = react.useCallback((message) => {
4899
+ const handlePinned = React4.useCallback((message) => {
4657
4900
  setPinnedMessages((prev) => {
4658
4901
  if (prev.some((m) => m.id === message.id)) return prev;
4659
4902
  return [...prev, message];
4660
4903
  });
4661
4904
  }, []);
4662
- const handleUnpinned = react.useCallback((messageId) => {
4905
+ const handleUnpinned = React4.useCallback((messageId) => {
4663
4906
  setPinnedMessages((prev) => prev.filter((m) => m.id !== messageId));
4664
4907
  }, []);
4665
4908
  return {