@realtimexsco/live-chat 1.5.4 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -9,8 +9,8 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
9
  import { cva } from 'class-variance-authority';
10
10
  import * as React11 from 'react';
11
11
  import React11__default, { createContext, useState, useEffect, useMemo, useContext, useCallback, useRef, useLayoutEffect, Fragment as Fragment$1, useSyncExternalStore } from 'react';
12
- import { Loader2, X, Users, Video, Phone, PhoneOff, Sun, Moon, Settings, Search, UserPlus, ChevronDown, ArrowLeft, WifiOff, Wifi, Sparkles, MessageSquareText, ChevronRight, Pin, Star, MoreVertical, Info, UserKey, ShieldUser, Paperclip, Clock, MessageSquareX, Trash2, LogOut, Ban, CheckCheck, Check, Camera, Save, Pencil, Mail, CornerUpLeft, CheckSquare, Smile, ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon, MessageCircleMore, History, Shield, Send, AlertCircle, ShieldCheck, UserMinus, LayoutGrid, Rows3, MessageCircle, ExternalLink, Download, ChevronLeft, ZoomOut, ZoomIn, RotateCcw, Music, Hand, MicOff, MonitorUp, XIcon, ArrowRight, MoreHorizontal, PinOff, StarOff, ImageIcon, Film, Play, Pause, FileText, FileSpreadsheet, FileIcon, MessageSquare, Eye, Captions, Mic, VideoOff } from 'lucide-react';
13
- import { createPortal, flushSync } from 'react-dom';
12
+ import { Loader2, X, Users, Video, Phone, PhoneOff, Sun, Moon, Settings, Search, UserPlus, ChevronDown, ArrowLeft, WifiOff, Wifi, Sparkles, MessageSquareText, ChevronRight, Pin, Star, MoreVertical, Info, UserKey, ShieldUser, Paperclip, Clock, MessageSquareX, Trash2, LogOut, Ban, CheckCheck, Check, Camera, Save, Pencil, Mail, CornerUpLeft, CheckSquare, Smile, ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon, MessageCircleMore, History, Shield, Send, AlertCircle, ShieldCheck, UserMinus, LayoutGrid, Rows3, MessageCircle, ExternalLink, Download, ChevronLeft, ZoomOut, ZoomIn, RotateCcw, Music, Hand, MicOff, MonitorUp, XIcon, ArrowRight, MoreHorizontal, PinOff, StarOff, ImageIcon, Film, Play, Pause, FileText, FileSpreadsheet, FileIcon, MessageSquare, Eye, Captions, Mic, VideoOff, Circle } from 'lucide-react';
13
+ import { flushSync } from 'react-dom';
14
14
  import EmojiPicker, { Theme } from 'emoji-picker-react';
15
15
  import { getDefaultClassNames, DayPicker } from 'react-day-picker';
16
16
  import toast$1, { Toaster, toast } from 'react-hot-toast';
@@ -884,12 +884,13 @@ var init_store_helpers = __esm({
884
884
  });
885
885
 
886
886
  // src/notifications/open-conversation.ts
887
- var OPEN_CONVERSATION_EVENT, NOTIFICATION_CLICK_SW_TYPE, PUSH_BROADCAST_CHANNEL, PUSH_DATA_CACHE_NAME, PENDING_STORAGE_KEY, PENDING_OPEN_CACHE_PATH, resolveConversationIdFromPushData, normalizePushMeta, parsePendingRaw, peekPendingOpenConversation, peekPendingOpenRequest, consumePendingOpenConversation, consumePendingOpenRequest, requestOpenConversation, buildChannelFromPushRequest, mapConversationToChannel, stripConversationIdFromUrl, cacheRequestForPath, consumePendingOpenFromPushCache, readConversationIdFromLocation, consumedUrlConversationIds, lastHandledOpenId, lastHandledOpenAt, shouldHandleOpen, handleNotificationOpen, installNotificationClickBridge;
887
+ var OPEN_CONVERSATION_EVENT, NOTIFICATION_CLICK_SW_TYPE, CHAT_HEARTBEAT_SW_TYPE, PUSH_BROADCAST_CHANNEL, PUSH_DATA_CACHE_NAME, PENDING_STORAGE_KEY, PENDING_OPEN_CACHE_PATH, resolveConversationIdFromPushData, normalizePushMeta, parsePendingRaw, peekPendingOpenConversation, peekPendingOpenRequest, consumePendingOpenConversation, consumePendingOpenRequest, requestOpenConversation, buildChannelFromPushRequest, mapConversationToChannel, stripConversationIdFromUrl, cacheRequestForPath, consumePendingOpenFromPushCache, readConversationIdFromLocation, consumedUrlConversationIds, lastHandledOpenId, lastHandledOpenAt, shouldHandleOpen, handleNotificationOpen, installNotificationClickBridge;
888
888
  var init_open_conversation = __esm({
889
889
  "src/notifications/open-conversation.ts"() {
890
890
  init_store_helpers();
891
891
  OPEN_CONVERSATION_EVENT = "realtimex:open-conversation";
892
892
  NOTIFICATION_CLICK_SW_TYPE = "realtimex:notification-click";
893
+ CHAT_HEARTBEAT_SW_TYPE = "realtimex:chat-heartbeat";
893
894
  PUSH_BROADCAST_CHANNEL = "realtimex-push";
894
895
  PUSH_DATA_CACHE_NAME = "realtimex-push-data-v1";
895
896
  PENDING_STORAGE_KEY = "realtimex:pending-open-conversation";
@@ -4611,6 +4612,67 @@ var init_utils2 = __esm({
4611
4612
  "src/lib/utils.ts"() {
4612
4613
  }
4613
4614
  });
4615
+ function TooltipProvider({
4616
+ delayDuration = 0,
4617
+ ...props
4618
+ }) {
4619
+ return /* @__PURE__ */ jsx(
4620
+ Tooltip$1.Provider,
4621
+ {
4622
+ "data-slot": "tooltip-provider",
4623
+ delayDuration,
4624
+ ...props
4625
+ }
4626
+ );
4627
+ }
4628
+ function Tooltip({
4629
+ ...props
4630
+ }) {
4631
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
4632
+ }
4633
+ function TooltipTrigger({
4634
+ ...props
4635
+ }) {
4636
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
4637
+ }
4638
+ function TooltipContent({
4639
+ className,
4640
+ sideOffset = 0,
4641
+ children,
4642
+ showArrow = true,
4643
+ arrowClassName,
4644
+ ...props
4645
+ }) {
4646
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
4647
+ Tooltip$1.Content,
4648
+ {
4649
+ "data-slot": "tooltip-content",
4650
+ sideOffset,
4651
+ className: cn(
4652
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
4653
+ className
4654
+ ),
4655
+ ...props,
4656
+ children: [
4657
+ children,
4658
+ showArrow ? /* @__PURE__ */ jsx(
4659
+ Tooltip$1.Arrow,
4660
+ {
4661
+ className: cn(
4662
+ "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]",
4663
+ arrowClassName
4664
+ )
4665
+ }
4666
+ ) : null
4667
+ ]
4668
+ }
4669
+ ) });
4670
+ }
4671
+ var init_tooltip = __esm({
4672
+ "src/ui/tooltip.tsx"() {
4673
+ init_utils2();
4674
+ }
4675
+ });
4614
4676
  function Avatar({
4615
4677
  className,
4616
4678
  size = "default",
@@ -4750,9 +4812,8 @@ var init_ParticipantView = __esm({
4750
4812
  isHandRaised = false,
4751
4813
  className
4752
4814
  }) => {
4753
- const { webcamStream, micStream, webcamOn, micOn, displayName, isActiveSpeaker } = sdk.useParticipant(participantId);
4815
+ const { webcamStream, webcamOn, micOn, displayName, isActiveSpeaker } = sdk.useParticipant(participantId);
4754
4816
  const videoRef = useRef(null);
4755
- const audioRef = useRef(null);
4756
4817
  const [hasFrame, setHasFrame] = useState(false);
4757
4818
  useEffect(() => {
4758
4819
  const el = videoRef.current;
@@ -4765,17 +4826,6 @@ var init_ParticipantView = __esm({
4765
4826
  setHasFrame(false);
4766
4827
  }
4767
4828
  }, [webcamOn, webcamStream]);
4768
- useEffect(() => {
4769
- if (isLocal) return;
4770
- const el = audioRef.current;
4771
- if (!el) return;
4772
- if (micOn && micStream?.track) {
4773
- el.srcObject = new MediaStream([micStream.track]);
4774
- el.play().catch(() => void 0);
4775
- } else {
4776
- el.srcObject = null;
4777
- }
4778
- }, [micOn, micStream, isLocal]);
4779
4829
  const name = displayName || fallbackName || "Participant";
4780
4830
  const showVideo = webcamOn && hasFrame;
4781
4831
  return /* @__PURE__ */ jsxs(
@@ -4808,8 +4858,7 @@ var init_ParticipantView = __esm({
4808
4858
  )
4809
4859
  }
4810
4860
  ),
4811
- !isLocal && /* @__PURE__ */ jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true }),
4812
- isHandRaised && /* @__PURE__ */ jsx("span", { className: "absolute top-2 left-2 flex size-7 items-center justify-center rounded-full bg-amber-400 text-neutral-900 shadow", children: /* @__PURE__ */ jsx(Hand, { size: 14 }) }),
4861
+ isHandRaised && /* @__PURE__ */ jsx("span", { className: "absolute top-2 left-2 flex size-7 items-center justify-center rounded-full bg-amber-400 text-neutral-900 shadow-lg shadow-amber-400/30 animate-[bounce_1s_ease-in-out_2]", children: /* @__PURE__ */ jsx(Hand, { size: 14 }) }),
4813
4862
  !showVideo && /* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "size-20", children: [
4814
4863
  /* @__PURE__ */ jsx(AvatarImage, { src: fallbackImage, alt: name }),
4815
4864
  /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-xl font-medium text-white", children: name.charAt(0).toUpperCase() })
@@ -4866,7 +4915,7 @@ var init_AudioCallView = __esm({
4866
4915
  }
4867
4916
  ),
4868
4917
  isMuted && /* @__PURE__ */ jsx("span", { className: "absolute -right-1 -bottom-1 flex size-9 items-center justify-center rounded-full bg-neutral-700 ring-4 ring-neutral-900", children: /* @__PURE__ */ jsx(MicOff, { size: 16, className: "text-white/80" }) }),
4869
- isRemoteHandRaised && /* @__PURE__ */ jsx("span", { className: "absolute -left-1 -top-1 flex size-9 items-center justify-center rounded-full bg-amber-400 text-neutral-900 ring-4 ring-neutral-900", children: /* @__PURE__ */ jsx(Hand, { size: 16 }) })
4918
+ isRemoteHandRaised && /* @__PURE__ */ jsx("span", { className: "absolute -left-1 -top-1 flex size-9 items-center justify-center rounded-full bg-amber-400 text-neutral-900 shadow-lg shadow-amber-400/30 ring-4 ring-neutral-900 animate-[bounce_1s_ease-in-out_2]", children: /* @__PURE__ */ jsx(Hand, { size: 16 }) })
4870
4919
  ] }),
4871
4920
  /* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tracking-tight", children: name })
4872
4921
  ] });
@@ -4890,13 +4939,13 @@ var init_ScreenShareView = __esm({
4890
4939
  el.srcObject = null;
4891
4940
  }
4892
4941
  }, [screenShareOn, screenShareStream]);
4893
- return /* @__PURE__ */ jsxs("div", { className, children: [
4894
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-1 pb-2 text-xs font-medium text-white/70", children: [
4895
- /* @__PURE__ */ jsx(MonitorUp, { size: 14 }),
4942
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 items-center justify-center overflow-hidden rounded-2xl bg-black ring-1 ring-white/10 shadow-2xl", children: [
4943
+ /* @__PURE__ */ jsx("video", { ref: videoRef, playsInline: true, muted: isLocal, className: "max-h-full max-w-full object-contain" }),
4944
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-3 left-3 flex items-center gap-1.5 rounded-full bg-black/70 px-3 py-1.5 text-xs font-medium text-white shadow backdrop-blur-sm", children: [
4945
+ /* @__PURE__ */ jsx(MonitorUp, { size: 13, className: "text-indigo-300" }),
4896
4946
  isLocal ? "You are presenting" : `${displayName || "Someone"} is presenting`
4897
- ] }),
4898
- /* @__PURE__ */ jsx("div", { className: "relative flex flex-1 items-center justify-center overflow-hidden rounded-xl bg-black", children: /* @__PURE__ */ jsx("video", { ref: videoRef, playsInline: true, muted: isLocal, className: "max-h-full max-w-full object-contain" }) })
4899
- ] });
4947
+ ] })
4948
+ ] }) });
4900
4949
  };
4901
4950
  ScreenShareView_default = ScreenShareView;
4902
4951
  }
@@ -4936,22 +4985,47 @@ var init_popover = __esm({
4936
4985
  init_utils2();
4937
4986
  }
4938
4987
  });
4939
- var circleBtn, idleBtnClass, activeBtnClass, QUICK_REACTIONS, CallControls, CallControls_default;
4988
+ var circleBtn, idleBtnClass, activeBtnClass, ControlButton, QUICK_REACTIONS, CallControls, CallControls_default;
4940
4989
  var init_CallControls = __esm({
4941
4990
  "src/call/CallControls.tsx"() {
4942
4991
  init_button();
4943
4992
  init_popover();
4993
+ init_tooltip();
4944
4994
  init_utils2();
4945
4995
  init_call_store();
4946
4996
  circleBtn = "size-13 rounded-full text-white transition-all duration-150 disabled:opacity-40 hover:scale-105 active:scale-95";
4947
4997
  idleBtnClass = "bg-white/10 hover:bg-white/20";
4948
4998
  activeBtnClass = "bg-white text-neutral-900 hover:bg-white/90";
4999
+ ControlButton = ({
5000
+ icon,
5001
+ label,
5002
+ active,
5003
+ activeClassName = activeBtnClass,
5004
+ className,
5005
+ onClick
5006
+ }) => /* @__PURE__ */ jsxs(Tooltip, { children: [
5007
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5008
+ Button,
5009
+ {
5010
+ variant: "ghost",
5011
+ size: "icon",
5012
+ "aria-label": label,
5013
+ onClick,
5014
+ className: cn(circleBtn, active ? activeClassName : idleBtnClass, className),
5015
+ children: icon
5016
+ }
5017
+ ) }),
5018
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: label })
5019
+ ] });
4949
5020
  QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F44F}", "\u{1F389}"];
4950
5021
  CallControls = ({
4951
5022
  sdk,
4952
5023
  onReaction,
4953
5024
  isHandRaised,
4954
- onToggleRaiseHand
5025
+ onToggleRaiseHand,
5026
+ isChatOpen,
5027
+ onToggleChat,
5028
+ isVideoActive
4955
5029
  }) => {
4956
5030
  const {
4957
5031
  toggleMic,
@@ -4960,81 +5034,124 @@ var init_CallControls = __esm({
4960
5034
  localWebcamOn,
4961
5035
  localScreenShareOn,
4962
5036
  toggleScreenShare,
5037
+ recordingState,
5038
+ startRecording,
5039
+ stopRecording,
4963
5040
  leave
4964
5041
  } = sdk.useMeeting();
4965
5042
  const endCall = useCallStore((s) => s.endCall);
5043
+ const isRecording = recordingState === "RECORDING_STARTED" || recordingState === "RECORDING_STARTING";
5044
+ const isRecordingBusy = recordingState === "RECORDING_STARTING" || recordingState === "RECORDING_STOPPING";
5045
+ const handleToggleRecording = () => {
5046
+ if (isRecordingBusy) return;
5047
+ if (isRecording) {
5048
+ stopRecording();
5049
+ return;
5050
+ }
5051
+ startRecording("", "", {
5052
+ layout: { type: "GRID", priority: "SPEAKER", gridSize: 4 },
5053
+ orientation: "landscape",
5054
+ theme: "DEFAULT",
5055
+ mode: "video-and-audio",
5056
+ quality: "high"
5057
+ });
5058
+ };
4966
5059
  const handleLeave = () => {
4967
5060
  leave();
4968
5061
  endCall();
4969
5062
  };
4970
5063
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-5 py-6", children: [
4971
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-full bg-white/10 p-2 backdrop-blur-sm", children: [
5064
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 rounded-full bg-white/10 p-2 shadow-lg shadow-black/20 backdrop-blur-sm", children: [
4972
5065
  /* @__PURE__ */ jsx(
4973
- Button,
5066
+ ControlButton,
4974
5067
  {
4975
- variant: "ghost",
4976
- size: "icon",
4977
- className: cn(circleBtn, localMicOn ? idleBtnClass : activeBtnClass),
4978
- "aria-label": localMicOn ? "Mute microphone" : "Unmute microphone",
4979
- onClick: () => toggleMic(),
4980
- children: localMicOn ? /* @__PURE__ */ jsx(Mic, { size: 20 }) : /* @__PURE__ */ jsx(MicOff, { size: 20 })
5068
+ icon: localMicOn ? /* @__PURE__ */ jsx(Mic, { size: 20 }) : /* @__PURE__ */ jsx(MicOff, { size: 20 }),
5069
+ label: localMicOn ? "Mute microphone" : "Unmute microphone",
5070
+ active: !localMicOn,
5071
+ onClick: () => toggleMic()
5072
+ }
5073
+ ),
5074
+ isVideoActive ? /* @__PURE__ */ jsx(
5075
+ ControlButton,
5076
+ {
5077
+ icon: localWebcamOn ? /* @__PURE__ */ jsx(Video, { size: 20 }) : /* @__PURE__ */ jsx(VideoOff, { size: 20 }),
5078
+ label: localWebcamOn ? "Turn off camera" : "Turn on camera",
5079
+ active: !localWebcamOn,
5080
+ onClick: () => toggleWebcam()
5081
+ }
5082
+ ) : /* @__PURE__ */ jsx(
5083
+ ControlButton,
5084
+ {
5085
+ icon: /* @__PURE__ */ jsx(Video, { size: 20 }),
5086
+ label: "Switch to video call",
5087
+ onClick: () => toggleWebcam()
4981
5088
  }
4982
5089
  ),
5090
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-0.5 h-7 w-px shrink-0 bg-white/15" }),
4983
5091
  /* @__PURE__ */ jsx(
4984
- Button,
5092
+ ControlButton,
4985
5093
  {
4986
- variant: "ghost",
4987
- size: "icon",
4988
- className: cn(circleBtn, localWebcamOn ? idleBtnClass : activeBtnClass),
4989
- "aria-label": localWebcamOn ? "Turn camera off" : "Turn camera on",
4990
- onClick: () => toggleWebcam(),
4991
- children: localWebcamOn ? /* @__PURE__ */ jsx(Video, { size: 20 }) : /* @__PURE__ */ jsx(VideoOff, { size: 20 })
5094
+ icon: /* @__PURE__ */ jsx(MonitorUp, { size: 20 }),
5095
+ label: localScreenShareOn ? "Stop sharing your screen" : "Share your screen",
5096
+ active: localScreenShareOn,
5097
+ activeClassName: "bg-indigo-500 hover:bg-indigo-400",
5098
+ onClick: () => toggleScreenShare()
4992
5099
  }
4993
5100
  ),
4994
5101
  /* @__PURE__ */ jsx(
4995
- Button,
5102
+ ControlButton,
4996
5103
  {
4997
- variant: "ghost",
4998
- size: "icon",
4999
- className: cn(circleBtn, localScreenShareOn ? "bg-indigo-500 hover:bg-indigo-400" : idleBtnClass),
5000
- "aria-label": localScreenShareOn ? "Stop sharing your screen" : "Share your screen",
5001
- onClick: () => toggleScreenShare(),
5002
- children: /* @__PURE__ */ jsx(MonitorUp, { size: 20 })
5104
+ icon: /* @__PURE__ */ jsx(Circle, { size: 14, className: cn("fill-current", isRecording && "animate-pulse") }),
5105
+ label: isRecording ? "Stop recording" : "Start recording",
5106
+ active: isRecording,
5107
+ activeClassName: "bg-red-600 hover:bg-red-500",
5108
+ onClick: handleToggleRecording
5003
5109
  }
5004
5110
  ),
5005
5111
  /* @__PURE__ */ jsx(
5006
- Button,
5112
+ ControlButton,
5007
5113
  {
5008
- variant: "ghost",
5009
- size: "icon",
5010
- className: cn(circleBtn, isHandRaised ? "bg-amber-400 text-neutral-900 hover:bg-amber-300" : idleBtnClass),
5011
- "aria-label": isHandRaised ? "Lower hand" : "Raise hand",
5012
- onClick: onToggleRaiseHand,
5013
- children: /* @__PURE__ */ jsx(Hand, { size: 20 })
5114
+ icon: /* @__PURE__ */ jsx(Hand, { size: 20, className: isHandRaised ? "animate-[bounce_1s_ease-in-out_2]" : void 0 }),
5115
+ label: isHandRaised ? "Lower hand" : "Raise hand",
5116
+ active: isHandRaised,
5117
+ activeClassName: "bg-amber-400 text-neutral-900 hover:bg-amber-300",
5118
+ onClick: onToggleRaiseHand
5119
+ }
5120
+ ),
5121
+ /* @__PURE__ */ jsx(
5122
+ ControlButton,
5123
+ {
5124
+ icon: /* @__PURE__ */ jsx(MessageSquare, { size: 20 }),
5125
+ label: isChatOpen ? "Close in-call chat" : "Open in-call chat",
5126
+ active: isChatOpen,
5127
+ onClick: onToggleChat
5014
5128
  }
5015
5129
  ),
5016
5130
  /* @__PURE__ */ jsxs(Popover, { children: [
5017
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5018
- Button,
5019
- {
5020
- variant: "ghost",
5021
- size: "icon",
5022
- className: cn(circleBtn, idleBtnClass),
5023
- "aria-label": "Send a reaction",
5024
- children: /* @__PURE__ */ jsx(Smile, { size: 20 })
5025
- }
5026
- ) }),
5131
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
5132
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5133
+ Button,
5134
+ {
5135
+ variant: "ghost",
5136
+ size: "icon",
5137
+ className: cn(circleBtn, idleBtnClass),
5138
+ "aria-label": "Send a reaction",
5139
+ children: /* @__PURE__ */ jsx(Smile, { size: 20 })
5140
+ }
5141
+ ) }) }),
5142
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: "React" })
5143
+ ] }),
5027
5144
  /* @__PURE__ */ jsx(
5028
5145
  PopoverContent,
5029
5146
  {
5030
5147
  side: "top",
5031
5148
  align: "center",
5032
- className: "w-auto rounded-full border-white/10 bg-neutral-900 p-1.5 shadow-xl",
5149
+ className: "z-[1000000] w-auto rounded-full border-white/10 bg-neutral-900 p-1.5 shadow-xl",
5033
5150
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5", children: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
5034
5151
  "button",
5035
5152
  {
5036
5153
  type: "button",
5037
- className: "flex size-10 items-center justify-center rounded-full text-xl transition-transform hover:scale-125 hover:bg-white/10",
5154
+ className: "flex size-10 items-center justify-center rounded-full text-xl transition-transform hover:scale-125 hover:bg-white/10 active:scale-95",
5038
5155
  "aria-label": `React with ${emoji}`,
5039
5156
  onClick: () => onReaction(emoji),
5040
5157
  children: emoji
@@ -5045,17 +5162,20 @@ var init_CallControls = __esm({
5045
5162
  )
5046
5163
  ] })
5047
5164
  ] }),
5048
- /* @__PURE__ */ jsx(
5049
- Button,
5050
- {
5051
- variant: "ghost",
5052
- size: "icon",
5053
- className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
5054
- "aria-label": "End call",
5055
- onClick: handleLeave,
5056
- children: /* @__PURE__ */ jsx(PhoneOff, { size: 22 })
5057
- }
5058
- )
5165
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
5166
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5167
+ Button,
5168
+ {
5169
+ variant: "ghost",
5170
+ size: "icon",
5171
+ className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
5172
+ "aria-label": "End call",
5173
+ onClick: handleLeave,
5174
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 22 })
5175
+ }
5176
+ ) }),
5177
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: "Leave call" })
5178
+ ] })
5059
5179
  ] });
5060
5180
  };
5061
5181
  CallControls_default = CallControls;
@@ -5112,15 +5232,18 @@ var init_useCallReactions = __esm({
5112
5232
  }, []);
5113
5233
  const { send, localParticipant } = sdk.useMeeting({
5114
5234
  onData: (data) => {
5235
+ console.log("[realtimex-call] data channel received:", data);
5115
5236
  try {
5116
5237
  const raw = typeof data.payload === "string" ? data.payload : new TextDecoder().decode(data.payload);
5117
5238
  const message = JSON.parse(raw);
5239
+ console.log("[realtimex-call] data channel parsed:", message, "from", data.from);
5118
5240
  if (message.type === "reaction") {
5119
5241
  addFloatingReaction(message.emoji, data.from);
5120
5242
  } else if (message.type === "raise-hand") {
5121
5243
  setRaisedHands((prev) => ({ ...prev, [data.from]: message.raised }));
5122
5244
  }
5123
- } catch {
5245
+ } catch (err) {
5246
+ console.warn("[realtimex-call] data channel payload not decodable:", err, data);
5124
5247
  }
5125
5248
  }
5126
5249
  });
@@ -5133,12 +5256,22 @@ var init_useCallReactions = __esm({
5133
5256
  return next;
5134
5257
  });
5135
5258
  }, [localParticipant?.id]);
5259
+ const sendDataMessage = useCallback(
5260
+ (message) => {
5261
+ send(JSON.stringify(message)).then((ok) => {
5262
+ console.log("[realtimex-call] data channel send result:", ok, message);
5263
+ }).catch((err) => {
5264
+ console.warn("[realtimex-call] data channel send failed:", err, message);
5265
+ });
5266
+ },
5267
+ [send]
5268
+ );
5136
5269
  const sendReaction = useCallback(
5137
5270
  (emoji) => {
5138
5271
  if (localParticipant?.id) addFloatingReaction(emoji, localParticipant.id);
5139
- void send(JSON.stringify({ type: "reaction", emoji }));
5272
+ sendDataMessage({ type: "reaction", emoji });
5140
5273
  },
5141
- [send, localParticipant?.id, addFloatingReaction]
5274
+ [localParticipant?.id, addFloatingReaction, sendDataMessage]
5142
5275
  );
5143
5276
  const toggleRaiseHand = useCallback(() => {
5144
5277
  setIsHandRaised((prev) => {
@@ -5146,15 +5279,15 @@ var init_useCallReactions = __esm({
5146
5279
  if (localParticipant?.id) {
5147
5280
  setRaisedHands((hands) => ({ ...hands, [localParticipant.id]: next }));
5148
5281
  }
5149
- void send(JSON.stringify({ type: "raise-hand", raised: next }));
5282
+ sendDataMessage({ type: "raise-hand", raised: next });
5150
5283
  return next;
5151
5284
  });
5152
- }, [send, localParticipant?.id]);
5285
+ }, [localParticipant?.id, sendDataMessage]);
5153
5286
  return { floatingReactions, raisedHands, isHandRaised, sendReaction, toggleRaiseHand };
5154
5287
  };
5155
5288
  }
5156
5289
  });
5157
- var MeetingView, MeetingView_default;
5290
+ var RemoteParticipantWatcher, MeetingView, MeetingView_default;
5158
5291
  var init_MeetingView = __esm({
5159
5292
  "src/call/MeetingView.tsx"() {
5160
5293
  init_ParticipantView();
@@ -5165,11 +5298,48 @@ var init_MeetingView = __esm({
5165
5298
  init_useCallReactions();
5166
5299
  init_call_store();
5167
5300
  init_utils2();
5168
- MeetingView = ({ sdk, mode, peerName, peerAvatar }) => {
5301
+ RemoteParticipantWatcher = ({
5302
+ sdk,
5303
+ participantId,
5304
+ onWebcamChange
5305
+ }) => {
5306
+ const { webcamOn, micStream, micOn } = sdk.useParticipant(participantId);
5307
+ const audioRef = useRef(null);
5308
+ useEffect(() => {
5309
+ onWebcamChange(participantId, webcamOn);
5310
+ return () => onWebcamChange(participantId, false);
5311
+ }, [participantId, webcamOn]);
5312
+ useEffect(() => {
5313
+ const el = audioRef.current;
5314
+ if (!el) return;
5315
+ if (micOn && micStream?.track) {
5316
+ el.srcObject = new MediaStream([micStream.track]);
5317
+ el.play().catch(() => void 0);
5318
+ } else {
5319
+ el.srcObject = null;
5320
+ }
5321
+ }, [micOn, micStream]);
5322
+ return /* @__PURE__ */ jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true });
5323
+ };
5324
+ MeetingView = ({
5325
+ sdk,
5326
+ mode,
5327
+ peerName,
5328
+ peerAvatar,
5329
+ isChatOpen,
5330
+ onToggleChat
5331
+ }) => {
5169
5332
  const endCall = useCallStore((s) => s.endCall);
5170
5333
  const [hasJoined, setHasJoined] = useState(false);
5171
5334
  const hasJoinedRef = useRef(false);
5172
5335
  const [groupView, setGroupView] = useState("grid");
5336
+ const [meetingState, setMeetingState] = useState("CONNECTING");
5337
+ const [remoteWebcamOn, setRemoteWebcamOn] = useState({});
5338
+ const handleRemoteWebcamChange = useCallback((participantId, on) => {
5339
+ setRemoteWebcamOn(
5340
+ (prev) => prev[participantId] === on ? prev : { ...prev, [participantId]: on }
5341
+ );
5342
+ }, []);
5173
5343
  const {
5174
5344
  join,
5175
5345
  participants,
@@ -5185,7 +5355,8 @@ var init_MeetingView = __esm({
5185
5355
  onMeetingLeft: () => {
5186
5356
  if (!hasJoinedRef.current) return;
5187
5357
  endCall();
5188
- }
5358
+ },
5359
+ onMeetingStateChanged: ({ state }) => setMeetingState(state)
5189
5360
  });
5190
5361
  const { floatingReactions, raisedHands, isHandRaised, sendReaction, toggleRaiseHand } = useCallReactions(sdk);
5191
5362
  useEffect(() => {
@@ -5201,149 +5372,58 @@ var init_MeetingView = __esm({
5201
5372
  (id) => id !== localParticipant?.id
5202
5373
  );
5203
5374
  const isGroup = remoteIds.length > 1;
5375
+ const remoteParticipantWatchers = remoteIds.map((id) => /* @__PURE__ */ jsx(
5376
+ RemoteParticipantWatcher,
5377
+ {
5378
+ sdk,
5379
+ participantId: id,
5380
+ onWebcamChange: handleRemoteWebcamChange
5381
+ },
5382
+ id
5383
+ ));
5384
+ const anyRemoteWebcamOn = Object.values(remoteWebcamOn).some(Boolean);
5385
+ const showVideoGrid = mode === "video" || localWebcamOn || anyRemoteWebcamOn;
5204
5386
  const controls = /* @__PURE__ */ jsx(
5205
5387
  CallControls_default,
5206
5388
  {
5207
5389
  sdk,
5208
5390
  onReaction: sendReaction,
5209
5391
  isHandRaised,
5210
- onToggleRaiseHand: toggleRaiseHand
5392
+ onToggleRaiseHand: toggleRaiseHand,
5393
+ isChatOpen,
5394
+ onToggleChat,
5395
+ isVideoActive: showVideoGrid
5211
5396
  }
5212
5397
  );
5398
+ const connectionStatusText = meetingState === "CONNECTING" ? "Connecting\u2026" : meetingState === "DISCONNECTED" ? "Reconnecting\u2026" : meetingState === "FAILED" ? "Connection lost" : null;
5399
+ const statusBanner = connectionStatusText && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-center gap-1.5 bg-amber-500/15 px-4 py-1.5 text-xs font-medium text-amber-300", children: [
5400
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-amber-400" }),
5401
+ connectionStatusText
5402
+ ] });
5213
5403
  if (presenterId) {
5214
- return /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5215
- /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5216
- /* @__PURE__ */ jsx(
5217
- ScreenShareView_default,
5218
- {
5219
- sdk,
5220
- presenterId,
5221
- isLocal: presenterId === localParticipant?.id,
5222
- className: "flex flex-1 flex-col overflow-hidden p-4 pb-2"
5223
- }
5224
- ),
5225
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 overflow-x-auto px-4 pb-2", children: [
5226
- localParticipant?.id && /* @__PURE__ */ jsx(
5227
- ParticipantView_default,
5404
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5405
+ remoteParticipantWatchers,
5406
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5407
+ statusBanner,
5408
+ /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5409
+ /* @__PURE__ */ jsx(
5410
+ ScreenShareView_default,
5228
5411
  {
5229
5412
  sdk,
5230
- participantId: localParticipant.id,
5231
- isLocal: true,
5232
- isHandRaised: !!raisedHands[localParticipant.id],
5233
- className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5413
+ presenterId,
5414
+ isLocal: presenterId === localParticipant?.id,
5415
+ className: "flex flex-1 flex-col overflow-hidden p-4 pb-2"
5234
5416
  }
5235
5417
  ),
5236
- remoteIds.map((id) => /* @__PURE__ */ jsx(
5237
- ParticipantView_default,
5238
- {
5239
- sdk,
5240
- participantId: id,
5241
- fallbackName: peerName,
5242
- fallbackImage: peerAvatar,
5243
- isHandRaised: !!raisedHands[id],
5244
- className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5245
- },
5246
- id
5247
- ))
5248
- ] }),
5249
- controls
5250
- ] });
5251
- }
5252
- const showVideoGrid = mode === "video" || localWebcamOn;
5253
- if (!showVideoGrid) {
5254
- return /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5255
- /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5256
- /* @__PURE__ */ jsx(
5257
- AudioCallView_default,
5258
- {
5259
- sdk,
5260
- remoteParticipantId: remoteIds[0],
5261
- peerName,
5262
- peerAvatar,
5263
- isRemoteHandRaised: !!(remoteIds[0] && raisedHands[remoteIds[0]])
5264
- }
5265
- ),
5266
- controls
5267
- ] });
5268
- }
5269
- const focusedId = groupView === "speaker" ? activeSpeakerId && activeSpeakerId !== localParticipant?.id ? activeSpeakerId : remoteIds[0] || localParticipant?.id : null;
5270
- const stripIds = focusedId ? [localParticipant?.id, ...remoteIds].filter(
5271
- (id) => !!id && id !== focusedId
5272
- ) : [];
5273
- return /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5274
- /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5275
- isGroup && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 px-4 pt-2", children: [
5276
- /* @__PURE__ */ jsx(
5277
- "button",
5278
- {
5279
- type: "button",
5280
- "aria-label": "Grid view",
5281
- onClick: () => setGroupView("grid"),
5282
- className: cn(
5283
- "flex size-8 items-center justify-center rounded-lg transition-colors",
5284
- groupView === "grid" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5285
- ),
5286
- children: /* @__PURE__ */ jsx(LayoutGrid, { size: 16 })
5287
- }
5288
- ),
5289
- /* @__PURE__ */ jsx(
5290
- "button",
5291
- {
5292
- type: "button",
5293
- "aria-label": "Speaker view",
5294
- onClick: () => setGroupView("speaker"),
5295
- className: cn(
5296
- "flex size-8 items-center justify-center rounded-lg transition-colors",
5297
- groupView === "speaker" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5298
- ),
5299
- children: /* @__PURE__ */ jsx(Rows3, { size: 16 })
5300
- }
5301
- )
5302
- ] }),
5303
- focusedId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 overflow-y-auto p-4", children: [
5304
- /* @__PURE__ */ jsx(
5305
- ParticipantView_default,
5306
- {
5307
- sdk,
5308
- participantId: focusedId,
5309
- isLocal: focusedId === localParticipant?.id,
5310
- fallbackName: peerName,
5311
- fallbackImage: peerAvatar,
5312
- isHandRaised: !!raisedHands[focusedId],
5313
- className: "min-h-[240px] flex-1"
5314
- }
5315
- ),
5316
- stripIds.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-x-auto", children: stripIds.map((id) => /* @__PURE__ */ jsx(
5317
- ParticipantView_default,
5318
- {
5319
- sdk,
5320
- participantId: id,
5321
- isLocal: id === localParticipant?.id,
5322
- fallbackName: peerName,
5323
- fallbackImage: peerAvatar,
5324
- isHandRaised: !!raisedHands[id],
5325
- className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5326
- },
5327
- id
5328
- )) })
5329
- ] }) : /* @__PURE__ */ jsxs(
5330
- "div",
5331
- {
5332
- className: "grid flex-1 auto-rows-fr gap-3 overflow-y-auto p-4",
5333
- style: {
5334
- gridTemplateColumns: `repeat(${Math.min(
5335
- Math.max(remoteIds.length + 1, 1),
5336
- 3
5337
- )}, minmax(0, 1fr))`
5338
- },
5339
- children: [
5418
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 overflow-x-auto px-4 pb-2", children: [
5340
5419
  localParticipant?.id && /* @__PURE__ */ jsx(
5341
5420
  ParticipantView_default,
5342
5421
  {
5343
5422
  sdk,
5344
5423
  participantId: localParticipant.id,
5345
5424
  isLocal: true,
5346
- isHandRaised: !!raisedHands[localParticipant.id]
5425
+ isHandRaised: !!raisedHands[localParticipant.id],
5426
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5347
5427
  }
5348
5428
  ),
5349
5429
  remoteIds.map((id) => /* @__PURE__ */ jsx(
@@ -5353,37 +5433,160 @@ var init_MeetingView = __esm({
5353
5433
  participantId: id,
5354
5434
  fallbackName: peerName,
5355
5435
  fallbackImage: peerAvatar,
5356
- isHandRaised: !!raisedHands[id]
5436
+ isHandRaised: !!raisedHands[id],
5437
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5357
5438
  },
5358
5439
  id
5359
5440
  ))
5360
- ]
5361
- }
5362
- ),
5363
- controls
5364
- ] });
5365
- };
5366
- MeetingView_default = MeetingView;
5367
- }
5368
- });
5369
-
5370
- // src/call/CallSession.tsx
5371
- var CallSession_exports = {};
5372
- __export(CallSession_exports, {
5373
- default: () => CallSession_default
5374
- });
5375
- var CallSession, CallSession_default;
5376
- var init_CallSession = __esm({
5377
- "src/call/CallSession.tsx"() {
5378
- init_useStore();
5379
- init_videosdk_loader();
5380
- init_MeetingView();
5381
- CallSession = ({
5382
- meetingId,
5383
- token,
5384
- mode,
5385
- peerName,
5441
+ ] }),
5442
+ controls
5443
+ ] })
5444
+ ] });
5445
+ }
5446
+ if (!showVideoGrid) {
5447
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5448
+ remoteParticipantWatchers,
5449
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5450
+ statusBanner,
5451
+ /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5452
+ /* @__PURE__ */ jsx(
5453
+ AudioCallView_default,
5454
+ {
5455
+ sdk,
5456
+ remoteParticipantId: remoteIds[0],
5457
+ peerName,
5458
+ peerAvatar,
5459
+ isRemoteHandRaised: !!(remoteIds[0] && raisedHands[remoteIds[0]])
5460
+ }
5461
+ ),
5462
+ controls
5463
+ ] })
5464
+ ] });
5465
+ }
5466
+ const focusedId = groupView === "speaker" ? activeSpeakerId && activeSpeakerId !== localParticipant?.id ? activeSpeakerId : remoteIds[0] || localParticipant?.id : null;
5467
+ const stripIds = focusedId ? [localParticipant?.id, ...remoteIds].filter(
5468
+ (id) => !!id && id !== focusedId
5469
+ ) : [];
5470
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5471
+ remoteParticipantWatchers,
5472
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5473
+ statusBanner,
5474
+ /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5475
+ isGroup && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 px-4 pt-2", children: [
5476
+ /* @__PURE__ */ jsx(
5477
+ "button",
5478
+ {
5479
+ type: "button",
5480
+ "aria-label": "Grid view",
5481
+ onClick: () => setGroupView("grid"),
5482
+ className: cn(
5483
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
5484
+ groupView === "grid" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5485
+ ),
5486
+ children: /* @__PURE__ */ jsx(LayoutGrid, { size: 16 })
5487
+ }
5488
+ ),
5489
+ /* @__PURE__ */ jsx(
5490
+ "button",
5491
+ {
5492
+ type: "button",
5493
+ "aria-label": "Speaker view",
5494
+ onClick: () => setGroupView("speaker"),
5495
+ className: cn(
5496
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
5497
+ groupView === "speaker" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5498
+ ),
5499
+ children: /* @__PURE__ */ jsx(Rows3, { size: 16 })
5500
+ }
5501
+ )
5502
+ ] }),
5503
+ focusedId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 overflow-y-auto p-4", children: [
5504
+ /* @__PURE__ */ jsx(
5505
+ ParticipantView_default,
5506
+ {
5507
+ sdk,
5508
+ participantId: focusedId,
5509
+ isLocal: focusedId === localParticipant?.id,
5510
+ fallbackName: peerName,
5511
+ fallbackImage: peerAvatar,
5512
+ isHandRaised: !!raisedHands[focusedId],
5513
+ className: "min-h-[240px] flex-1"
5514
+ }
5515
+ ),
5516
+ stripIds.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-x-auto", children: stripIds.map((id) => /* @__PURE__ */ jsx(
5517
+ ParticipantView_default,
5518
+ {
5519
+ sdk,
5520
+ participantId: id,
5521
+ isLocal: id === localParticipant?.id,
5522
+ fallbackName: peerName,
5523
+ fallbackImage: peerAvatar,
5524
+ isHandRaised: !!raisedHands[id],
5525
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5526
+ },
5527
+ id
5528
+ )) })
5529
+ ] }) : /* @__PURE__ */ jsxs(
5530
+ "div",
5531
+ {
5532
+ className: "grid flex-1 auto-rows-fr gap-3 overflow-y-auto p-4",
5533
+ style: {
5534
+ gridTemplateColumns: `repeat(${Math.min(
5535
+ Math.max(remoteIds.length + 1, 1),
5536
+ 3
5537
+ )}, minmax(0, 1fr))`
5538
+ },
5539
+ children: [
5540
+ localParticipant?.id && /* @__PURE__ */ jsx(
5541
+ ParticipantView_default,
5542
+ {
5543
+ sdk,
5544
+ participantId: localParticipant.id,
5545
+ isLocal: true,
5546
+ isHandRaised: !!raisedHands[localParticipant.id]
5547
+ }
5548
+ ),
5549
+ remoteIds.map((id) => /* @__PURE__ */ jsx(
5550
+ ParticipantView_default,
5551
+ {
5552
+ sdk,
5553
+ participantId: id,
5554
+ fallbackName: peerName,
5555
+ fallbackImage: peerAvatar,
5556
+ isHandRaised: !!raisedHands[id]
5557
+ },
5558
+ id
5559
+ ))
5560
+ ]
5561
+ }
5562
+ ),
5563
+ controls
5564
+ ] })
5565
+ ] });
5566
+ };
5567
+ MeetingView_default = MeetingView;
5568
+ }
5569
+ });
5570
+
5571
+ // src/call/CallSession.tsx
5572
+ var CallSession_exports = {};
5573
+ __export(CallSession_exports, {
5574
+ default: () => CallSession_default
5575
+ });
5576
+ var CallSession, CallSession_default;
5577
+ var init_CallSession = __esm({
5578
+ "src/call/CallSession.tsx"() {
5579
+ init_useStore();
5580
+ init_videosdk_loader();
5581
+ init_MeetingView();
5582
+ CallSession = ({
5583
+ meetingId,
5584
+ token,
5585
+ mode,
5586
+ peerName,
5386
5587
  peerAvatar,
5588
+ isChatOpen,
5589
+ onToggleChat,
5387
5590
  onError
5388
5591
  }) => {
5389
5592
  const [sdk, setSdk] = useState(null);
@@ -5426,7 +5629,17 @@ var init_CallSession = __esm({
5426
5629
  token,
5427
5630
  reinitialiseMeetingOnConfigChange: false,
5428
5631
  joinWithoutUserInteraction: true,
5429
- children: /* @__PURE__ */ jsx(MeetingView_default, { sdk, mode, peerName, peerAvatar })
5632
+ children: /* @__PURE__ */ jsx(
5633
+ MeetingView_default,
5634
+ {
5635
+ sdk,
5636
+ mode,
5637
+ peerName,
5638
+ peerAvatar,
5639
+ isChatOpen,
5640
+ onToggleChat
5641
+ }
5642
+ )
5430
5643
  }
5431
5644
  );
5432
5645
  };
@@ -5438,67 +5651,7 @@ var init_CallSession = __esm({
5438
5651
  init_useStore();
5439
5652
  init_api_service();
5440
5653
  init_socket_service2();
5441
-
5442
- // src/ui/tooltip.tsx
5443
- init_utils2();
5444
- function TooltipProvider({
5445
- delayDuration = 0,
5446
- ...props
5447
- }) {
5448
- return /* @__PURE__ */ jsx(
5449
- Tooltip$1.Provider,
5450
- {
5451
- "data-slot": "tooltip-provider",
5452
- delayDuration,
5453
- ...props
5454
- }
5455
- );
5456
- }
5457
- function Tooltip({
5458
- ...props
5459
- }) {
5460
- return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
5461
- }
5462
- function TooltipTrigger({
5463
- ...props
5464
- }) {
5465
- return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
5466
- }
5467
- function TooltipContent({
5468
- className,
5469
- sideOffset = 0,
5470
- children,
5471
- showArrow = true,
5472
- arrowClassName,
5473
- ...props
5474
- }) {
5475
- return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
5476
- Tooltip$1.Content,
5477
- {
5478
- "data-slot": "tooltip-content",
5479
- sideOffset,
5480
- className: cn(
5481
- "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
5482
- className
5483
- ),
5484
- ...props,
5485
- children: [
5486
- children,
5487
- showArrow ? /* @__PURE__ */ jsx(
5488
- Tooltip$1.Arrow,
5489
- {
5490
- className: cn(
5491
- "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]",
5492
- arrowClassName
5493
- )
5494
- }
5495
- ) : null
5496
- ]
5497
- }
5498
- ) });
5499
- }
5500
-
5501
- // src/chat/ChatContext.tsx
5654
+ init_tooltip();
5502
5655
  init_utils2();
5503
5656
 
5504
5657
  // src/config/chat-endpoints.ts
@@ -6419,11 +6572,353 @@ var useCallDuration = (startedAt) => {
6419
6572
  const pad = (n) => String(n).padStart(2, "0");
6420
6573
  return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
6421
6574
  };
6575
+
6576
+ // src/call/CallChatPanel.tsx
6577
+ init_useStore();
6578
+ init_store_helpers();
6579
+ init_utils2();
6580
+ init_button();
6581
+
6582
+ // src/chat/lib/chat-theme.ts
6583
+ init_utils2();
6584
+ var getChatScopeClass = (isDark) => cn("chat-theme-scope", isDark && "dark");
6585
+ var chatSheetContentClass = "flex w-full flex-col gap-0 p-0 bg-(--chat-surface) text-(--chat-text)";
6586
+ var chatSheetHeaderClass = "border-b border-(--chat-border) px-4 py-3 pr-11 text-left";
6587
+ var chatSheetBodyClass = "chat-message-scroll flex-1 overflow-y-auto bg-(--chat-surface) p-3";
6588
+ var chatSheetTitleClass = "text-[13px] font-semibold tracking-tight text-(--chat-text)";
6589
+ var chatSheetSubtitleClass = "text-[11px] text-(--chat-muted)";
6590
+ var chatDetailRowClass = "flex items-start justify-between gap-3 border-b border-(--chat-border) py-2.5 last:border-b-0";
6591
+ var chatDetailLabelClass = "text-[11px] text-(--chat-muted)";
6592
+ var chatDetailValueClass = "text-[13px] font-medium text-(--chat-text) text-right";
6593
+ var chatDialogContentClass = "overflow-hidden rounded-2xl border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-[var(--chat-shadow)]";
6594
+ var chatDropdownContentClass = "rounded-xl border border-(--chat-border) bg-(--chat-surface) text-(--chat-text) shadow-lg";
6595
+ var chatOptionsMenuClass = "min-w-[220px] rounded-xl border border-(--chat-border) bg-(--chat-surface) p-1.5 text-(--chat-text) shadow-[0_10px_40px_rgba(0,0,0,0.12)]";
6596
+ var chatOptionsMenuLabelClass = "px-2.5 pb-0.5 pt-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-(--chat-muted)";
6597
+ var chatOptionsMenuSeparatorClass = "my-1.5 h-px bg-(--chat-border)";
6598
+ var chatOptionsMenuItemClass = "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 text-[13px] font-medium text-(--chat-text) focus:bg-(--chat-hover) focus:text-(--chat-text) data-[highlighted]:bg-(--chat-hover) [&_svg]:size-[15px] [&_svg]:shrink-0 [&_svg]:text-(--chat-muted)";
6599
+ var chatOptionsMenuItemDestructiveClass = "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 text-[13px] font-medium text-(--chat-danger-text) focus:bg-red-500/10 focus:text-(--chat-danger-text) data-[highlighted]:bg-red-500/10 data-[highlighted]:text-(--chat-danger-text) [&_svg]:size-[15px] [&_svg]:shrink-0 [&_svg]:!text-(--chat-danger-text)";
6600
+ var chatOptionsMenuSubTriggerClass = "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 pr-7 text-[13px] font-medium text-(--chat-text) focus:bg-(--chat-hover) focus:text-(--chat-text) data-[highlighted]:bg-(--chat-hover) data-[state=open]:bg-(--chat-hover) [&_svg]:size-[15px] [&_svg]:shrink-0 [&_svg]:text-(--chat-muted)";
6601
+ var chatPopoverContentClass = "w-auto border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-2xl";
6602
+ var chatInputClass = "border border-(--chat-border) bg-(--chat-input-bg) text-(--chat-text) shadow-none placeholder:text-(--chat-muted) focus-visible:border-(--chat-theme-20) focus-visible:ring-2 focus-visible:ring-(--chat-theme-10)";
6603
+ var chatSidebarSearchShellClass = "relative flex min-w-0 flex-1 items-center rounded-lg border border-(--chat-border) bg-(--chat-search-bg) px-2.5 transition-colors focus-within:border-(--chat-theme) focus-within:shadow-[0_0_0_2px_var(--chat-theme-10)]";
6604
+ var chatSidebarSearchInputClass = "h-8 w-full min-w-0 border-0 bg-transparent px-1.5 text-xs text-(--chat-text) shadow-none outline-none placeholder:text-(--chat-muted)/60 focus-visible:border-0 focus-visible:shadow-none focus-visible:ring-0";
6605
+ var chatSelectClass = "h-10 w-full cursor-pointer appearance-none rounded-xl border border-(--chat-border) bg-(--chat-input-bg) pl-3 pr-9 text-sm text-(--chat-text) shadow-none focus-visible:border-(--chat-theme-20) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--chat-theme-10)";
6606
+ var chatListItemClass = "transition-colors hover:bg-(--chat-hover)";
6607
+ var chatLoadMoreButtonClass = "h-7 gap-1 rounded-full border border-(--chat-border) bg-(--chat-elevated) px-4 text-[10px] font-medium text-(--chat-text) transition-colors hover:border-(--chat-theme-20) hover:bg-(--chat-theme-5) hover:text-(--chat-theme) disabled:opacity-50 [&_svg]:size-3 [&_svg]:text-current";
6608
+ var chatDrawerSectionHeadingClass = "mb-1 px-0.5 text-[10px] font-semibold tracking-wide text-(--chat-muted)";
6609
+ var chatHeaderToolbarIconProps = { size: 14, strokeWidth: 1.75 };
6610
+ var chatSavedMessageModalIconClass = "shrink-0 text-(--chat-theme)";
6611
+ var chatHeaderToolbarIconLgProps = { size: 22, strokeWidth: 1.75 };
6612
+ var chatHeaderToolbarIconSmProps = { size: 11, strokeWidth: 1.75 };
6613
+ var chatDrawerFileRowClass = "flex items-center gap-2 rounded-md border border-(--chat-border)/70 bg-(--chat-elevated) px-2 py-1.5 transition-colors hover:bg-(--chat-hover)/50";
6614
+ var chatDrawerFileIconWrapClass = "flex size-8 shrink-0 items-center justify-center rounded-md bg-(--chat-hover)";
6615
+ var chatDrawerMediaFrameClass = "overflow-hidden rounded-md border border-(--chat-border)/70 bg-(--chat-elevated)";
6616
+ var buildChatThemeStyles = (themeColor) => ({
6617
+ "--chat-theme": themeColor,
6618
+ "--chat-theme-5": `${themeColor}0D`,
6619
+ "--chat-theme-10": `${themeColor}1A`,
6620
+ "--chat-theme-20": `${themeColor}33`,
6621
+ "--chat-theme-40": `${themeColor}66`,
6622
+ "--chat-theme-60": `${themeColor}99`,
6623
+ "--read-tick-color": themeColor.toLowerCase() === "#0000ff" || themeColor.toLowerCase().includes("007aff") ? "#00e5ff" : "#40C4FF"
6624
+ });
6625
+
6626
+ // src/chat/lib/chat-theme-storage.ts
6627
+ var STORAGE_KEY = "chat-color-mode";
6628
+ var loadChatColorMode = () => {
6629
+ if (typeof window === "undefined") return null;
6630
+ try {
6631
+ const stored = localStorage.getItem(STORAGE_KEY);
6632
+ return stored === "dark" || stored === "light" ? stored : null;
6633
+ } catch {
6634
+ return null;
6635
+ }
6636
+ };
6637
+ var saveChatColorMode = (mode) => {
6638
+ if (typeof window === "undefined") return;
6639
+ try {
6640
+ localStorage.setItem(STORAGE_KEY, mode);
6641
+ } catch {
6642
+ }
6643
+ };
6644
+ var CHAT_THEME_SHELL_CLASS = "chat-theme-shell";
6645
+ function withThemeTransition(apply) {
6646
+ if (typeof document === "undefined") {
6647
+ apply();
6648
+ return;
6649
+ }
6650
+ flushSync(apply);
6651
+ }
6652
+
6653
+ // src/context/ChatThemeContext.tsx
6654
+ init_utils2();
6655
+ var ChatThemeContext = createContext(null);
6656
+ var ChatThemeProvider = ({
6657
+ children,
6658
+ themeColor,
6659
+ colorMode,
6660
+ defaultColorMode = "light",
6661
+ onColorModeChange,
6662
+ showColorModeToggle = true
6663
+ }) => {
6664
+ const isControlled = colorMode !== void 0;
6665
+ const [internalMode, setInternalMode] = useState(defaultColorMode);
6666
+ const [isThemeTransitionReady, setIsThemeTransitionReady] = useState(false);
6667
+ const skipThemeTransitionRef = useRef(true);
6668
+ useLayoutEffect(() => {
6669
+ if (!isControlled) {
6670
+ const stored = loadChatColorMode();
6671
+ if (stored) {
6672
+ setInternalMode(stored);
6673
+ }
6674
+ }
6675
+ const frame = requestAnimationFrame(() => {
6676
+ skipThemeTransitionRef.current = false;
6677
+ setIsThemeTransitionReady(true);
6678
+ });
6679
+ return () => cancelAnimationFrame(frame);
6680
+ }, [isControlled]);
6681
+ const resolvedMode = isControlled ? colorMode : internalMode;
6682
+ const isDark = resolvedMode === "dark";
6683
+ const setColorMode = useCallback(
6684
+ (mode) => {
6685
+ const apply = () => {
6686
+ if (!isControlled) {
6687
+ setInternalMode(mode);
6688
+ saveChatColorMode(mode);
6689
+ }
6690
+ onColorModeChange?.(mode);
6691
+ };
6692
+ if (skipThemeTransitionRef.current) {
6693
+ apply();
6694
+ return;
6695
+ }
6696
+ withThemeTransition(apply);
6697
+ },
6698
+ [isControlled, onColorModeChange]
6699
+ );
6700
+ const toggleColorMode = useCallback(() => {
6701
+ setColorMode(resolvedMode === "dark" ? "light" : "dark");
6702
+ }, [resolvedMode, setColorMode]);
6703
+ const value = useMemo(
6704
+ () => ({
6705
+ colorMode: resolvedMode,
6706
+ isDark,
6707
+ isThemeTransitionReady,
6708
+ themeColor,
6709
+ themeStyles: buildChatThemeStyles(themeColor),
6710
+ scopeClassName: cn(
6711
+ getChatScopeClass(isDark),
6712
+ !isThemeTransitionReady && "chat-theme-no-transition"
6713
+ ),
6714
+ setColorMode,
6715
+ toggleColorMode,
6716
+ showColorModeToggle
6717
+ }),
6718
+ [
6719
+ resolvedMode,
6720
+ isDark,
6721
+ isThemeTransitionReady,
6722
+ themeColor,
6723
+ setColorMode,
6724
+ toggleColorMode,
6725
+ showColorModeToggle
6726
+ ]
6727
+ );
6728
+ return /* @__PURE__ */ jsx(ChatThemeContext.Provider, { value, children });
6729
+ };
6730
+ var useChatTheme = () => {
6731
+ const context = useContext(ChatThemeContext);
6732
+ if (!context) {
6733
+ throw new Error("useChatTheme must be used within ChatThemeProvider");
6734
+ }
6735
+ return context;
6736
+ };
6737
+
6738
+ // src/chat/lib/chat-message-bubble.ts
6739
+ init_utils2();
6740
+ function getBubbleCornerClasses(isSender) {
6741
+ return isSender ? "rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-none" : "rounded-tl-[18px] rounded-tr-[18px] rounded-br-[18px] rounded-bl-none";
6742
+ }
6743
+ function getChatBubbleClasses({
6744
+ isSender = false,
6745
+ surface,
6746
+ className
6747
+ }) {
6748
+ const resolvedSurface = surface ?? (isSender ? "sent" : "received");
6749
+ const isNeutral = resolvedSurface === "neutral";
6750
+ return cn(
6751
+ "relative",
6752
+ getBubbleCornerClasses(isSender),
6753
+ resolvedSurface === "sent" && "chat-bubble-sent bg-(--chat-theme) text-white",
6754
+ resolvedSurface === "received" && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
6755
+ isNeutral && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
6756
+ className
6757
+ );
6758
+ }
6759
+ var CallChatPanel = ({ conversationId, isGroupCall, onClose, className }) => {
6760
+ const { scopeClassName, themeStyles } = useChatTheme();
6761
+ const rawMessages = useStore((s) => s.messagesByConversation[conversationId]);
6762
+ const loggedUserDetails = useStore((s) => s.loggedUserDetails);
6763
+ const conversations = useStore((s) => s.conversations);
6764
+ const emitDmSend = useStore((s) => s.emitDmSend);
6765
+ const emitGroupMessageSend2 = useStore((s) => s.emitGroupMessageSend);
6766
+ const [text, setText] = useState("");
6767
+ const [optimistic, setOptimistic] = useState([]);
6768
+ const listEndRef = useRef(null);
6769
+ const loggedUserId = String(loggedUserDetails?._id || "");
6770
+ const serverMessages = useMemo(() => {
6771
+ return (rawMessages || []).filter((m) => !m?.isSystemMessage && !m?.isDeletedForEveryone).map((m) => {
6772
+ const senderId = String(m.sender?._id || m.sender || "");
6773
+ return {
6774
+ id: String(m._id || m.id || ""),
6775
+ content: String(m.content || ""),
6776
+ senderName: senderId === loggedUserId ? "You" : m.sender?.name || "Unknown",
6777
+ isOwn: senderId === loggedUserId,
6778
+ timestamp: m.createdAt || m.timestamp || (/* @__PURE__ */ new Date()).toISOString()
6779
+ };
6780
+ });
6781
+ }, [rawMessages, loggedUserId]);
6782
+ useEffect(() => {
6783
+ if (!optimistic.length) return;
6784
+ setOptimistic(
6785
+ (prev) => prev.filter(
6786
+ (pending) => !serverMessages.some(
6787
+ (real) => real.isOwn && real.content === pending.content
6788
+ )
6789
+ )
6790
+ );
6791
+ }, [serverMessages]);
6792
+ const messages = useMemo(
6793
+ () => [...serverMessages, ...optimistic].sort(
6794
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
6795
+ ),
6796
+ [serverMessages, optimistic]
6797
+ );
6798
+ useEffect(() => {
6799
+ listEndRef.current?.scrollIntoView({ block: "end" });
6800
+ }, [messages.length]);
6801
+ const handleSend = () => {
6802
+ const content = text.trim();
6803
+ if (!content) return;
6804
+ setText("");
6805
+ setOptimistic((prev) => [
6806
+ ...prev,
6807
+ {
6808
+ id: `pending-${Date.now()}`,
6809
+ content,
6810
+ senderName: "You",
6811
+ isOwn: true,
6812
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6813
+ pending: true
6814
+ }
6815
+ ]);
6816
+ const payload = {
6817
+ tempId: `temp-${Date.now()}`,
6818
+ sender: loggedUserId,
6819
+ content,
6820
+ type: "text",
6821
+ conversationId
6822
+ };
6823
+ if (isGroupCall) {
6824
+ emitGroupMessageSend2(payload);
6825
+ } else {
6826
+ const conversation = findConversationById(conversations, conversationId);
6827
+ payload.receiver = getOtherParticipantId(conversation, loggedUserId);
6828
+ emitDmSend(payload);
6829
+ }
6830
+ };
6831
+ return /* @__PURE__ */ jsxs(
6832
+ "div",
6833
+ {
6834
+ className: cn(
6835
+ scopeClassName,
6836
+ "flex flex-col border-l border-(--chat-border) bg-(--chat-surface) text-(--chat-text)",
6837
+ className
6838
+ ),
6839
+ style: themeStyles,
6840
+ children: [
6841
+ /* @__PURE__ */ jsxs("div", { className: cn(chatSheetHeaderClass, "flex items-center justify-between"), children: [
6842
+ /* @__PURE__ */ jsx("p", { className: chatSheetTitleClass, children: "In-call chat" }),
6843
+ /* @__PURE__ */ jsx(
6844
+ "button",
6845
+ {
6846
+ type: "button",
6847
+ "aria-label": "Close chat panel",
6848
+ onClick: onClose,
6849
+ className: "flex size-7 items-center justify-center rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text)",
6850
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
6851
+ }
6852
+ )
6853
+ ] }),
6854
+ /* @__PURE__ */ jsxs("div", { className: cn(chatSheetBodyClass, "space-y-3"), children: [
6855
+ messages.length === 0 && /* @__PURE__ */ jsx("p", { className: "pt-6 text-center text-xs text-(--chat-muted)", children: "No messages yet \u2014 say hi without leaving the call." }),
6856
+ messages.map((message) => /* @__PURE__ */ jsxs(
6857
+ "div",
6858
+ {
6859
+ className: cn("flex flex-col", message.isOwn ? "items-end" : "items-start"),
6860
+ children: [
6861
+ /* @__PURE__ */ jsx("span", { className: "mb-0.5 text-[11px] font-medium text-(--chat-muted)", children: message.isOwn ? "You" : message.senderName }),
6862
+ /* @__PURE__ */ jsx(
6863
+ "div",
6864
+ {
6865
+ className: cn(
6866
+ getChatBubbleClasses({ isSender: message.isOwn }),
6867
+ "max-w-[85%] px-3 py-1.5 text-sm break-words",
6868
+ message.pending && "opacity-60"
6869
+ ),
6870
+ children: message.content
6871
+ }
6872
+ )
6873
+ ]
6874
+ },
6875
+ message.id
6876
+ )),
6877
+ /* @__PURE__ */ jsx("div", { ref: listEndRef })
6878
+ ] }),
6879
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-(--chat-border) p-3", children: [
6880
+ /* @__PURE__ */ jsx(
6881
+ "input",
6882
+ {
6883
+ value: text,
6884
+ onChange: (e) => setText(e.target.value),
6885
+ onKeyDown: (e) => {
6886
+ if (e.key === "Enter" && !e.shiftKey) {
6887
+ e.preventDefault();
6888
+ handleSend();
6889
+ }
6890
+ },
6891
+ placeholder: "Message\u2026",
6892
+ className: cn(chatInputClass, "min-w-0 flex-1 rounded-full px-3.5 py-2 text-sm")
6893
+ }
6894
+ ),
6895
+ /* @__PURE__ */ jsx(
6896
+ Button,
6897
+ {
6898
+ variant: "ghost",
6899
+ size: "icon",
6900
+ "aria-label": "Send message",
6901
+ disabled: !text.trim(),
6902
+ onClick: handleSend,
6903
+ className: cn(
6904
+ "size-9 shrink-0 rounded-full transition-colors",
6905
+ text.trim() ? "bg-(--chat-theme) text-white hover:opacity-90" : "bg-(--chat-hover) text-(--chat-muted)"
6906
+ ),
6907
+ children: /* @__PURE__ */ jsx(Send, { size: 16 })
6908
+ }
6909
+ )
6910
+ ] })
6911
+ ]
6912
+ }
6913
+ );
6914
+ };
6915
+ var CallChatPanel_default = CallChatPanel;
6422
6916
  var CallSession2 = React11__default.lazy(() => Promise.resolve().then(() => (init_CallSession(), CallSession_exports)));
6423
6917
  var CallOverlay = () => {
6424
6918
  const uiStatus = useCallStore((s) => s.uiStatus);
6425
6919
  const mode = useCallStore((s) => s.mode);
6426
6920
  const callId = useCallStore((s) => s.callId);
6921
+ const conversationId = useCallStore((s) => s.conversationId);
6427
6922
  const meetingId = useCallStore((s) => s.meetingId);
6428
6923
  const token = useCallStore((s) => s.token);
6429
6924
  const error = useCallStore((s) => s.error);
@@ -6435,112 +6930,111 @@ var CallOverlay = () => {
6435
6930
  const reset = useCallStore((s) => s.reset);
6436
6931
  const notifyServerCallFailed = useCallStore((s) => s.notifyServerCallFailed);
6437
6932
  const [sessionError, setSessionError] = useState(null);
6933
+ const [isChatOpen, setIsChatOpen] = useState(false);
6438
6934
  const secondsLeft = useRingCountdown(uiStatus === "ringing-out", callId);
6439
6935
  const duration = useCallDuration(callStartedAt);
6440
6936
  useEffect(() => {
6441
6937
  setSessionError(null);
6938
+ setIsChatOpen(false);
6442
6939
  }, [callId]);
6443
6940
  useEffect(() => {
6444
6941
  if (sessionError) notifyServerCallFailed();
6445
6942
  }, [sessionError, notifyServerCallFailed]);
6446
6943
  const isOpen = uiStatus !== "idle" && uiStatus !== "incoming";
6447
- if (!isOpen || typeof document === "undefined") return null;
6944
+ if (!isOpen) return null;
6448
6945
  const displayError = error || sessionError;
6449
6946
  const peerName = peer?.name || caller?.name || "";
6450
6947
  const peerAvatar = peer?.avatar || caller?.profileImage || void 0;
6451
- const isVideo = mode === "video";
6452
- return createPortal(
6453
- /* @__PURE__ */ jsx(
6454
- "div",
6455
- {
6456
- style: {
6457
- position: "fixed",
6458
- inset: 0,
6459
- width: "100vw",
6460
- height: "100vh",
6461
- zIndex: 2147483647
6462
- },
6463
- className: "flex flex-col overflow-hidden bg-gradient-to-b from-neutral-900 via-neutral-950 to-black text-white",
6464
- children: displayError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6 text-center", children: [
6465
- /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
6466
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: reset, children: [
6467
- /* @__PURE__ */ jsx(X, { size: 16, className: "mr-1" }),
6468
- " Close"
6469
- ] })
6470
- ] }) : uiStatus === "in-call" && meetingId && token && mode ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col", children: [
6471
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6472
- isGroupCall && /* @__PURE__ */ jsx(Users, { size: 14, className: "text-white/50" }),
6473
- peerName && /* @__PURE__ */ jsx("span", { className: "font-medium text-white/90", children: peerName }),
6474
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6475
- /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: duration })
6476
- ] }),
6477
- /* @__PURE__ */ jsx(
6478
- React11__default.Suspense,
6479
- {
6480
- fallback: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6481
- /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin" }),
6482
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6483
- ] }),
6484
- children: /* @__PURE__ */ jsx(
6485
- CallSession2,
6486
- {
6487
- meetingId,
6488
- token,
6489
- mode,
6490
- peerName,
6491
- peerAvatar,
6492
- onError: setSessionError
6493
- }
6494
- )
6495
- }
6496
- )
6497
- ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6498
- /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
6499
- uiStatus === "ringing-out" && /* @__PURE__ */ jsxs(Fragment, { children: [
6500
- /* @__PURE__ */ jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
6501
- /* @__PURE__ */ jsx(
6502
- "span",
6503
- {
6504
- className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
6505
- style: { animationDelay: "0.6s" }
6506
- }
6507
- )
6508
- ] }),
6509
- peerName ? /* @__PURE__ */ jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
6510
- /* @__PURE__ */ jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
6511
- /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
6512
- ] }) : /* @__PURE__ */ jsx("div", { className: "relative flex size-28 items-center justify-center rounded-full bg-white/10", children: isVideo ? /* @__PURE__ */ jsx(Video, { size: 36 }) : /* @__PURE__ */ jsx(Phone, { size: 36 }) })
6513
- ] }),
6514
- /* @__PURE__ */ jsxs("div", { children: [
6515
- peerName && /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-1.5 text-xl font-semibold tracking-tight", children: [
6516
- isGroupCall && /* @__PURE__ */ jsx(Users, { size: 16, className: "text-white/50" }),
6517
- peerName
6518
- ] }),
6519
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
6520
- uiStatus === "creating" && "Starting call\u2026",
6521
- uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
6522
- uiStatus === "joining" && "Connecting\u2026"
6523
- ] })
6948
+ const isVideo = mode === "video";
6949
+ return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-40 flex flex-col overflow-hidden bg-gradient-to-b from-neutral-900 via-neutral-950 to-black text-white", children: displayError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6 text-center", children: [
6950
+ /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
6951
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: reset, children: [
6952
+ /* @__PURE__ */ jsx(X, { size: 16, className: "mr-1" }),
6953
+ " Close"
6954
+ ] })
6955
+ ] }) : uiStatus === "in-call" && meetingId && token && mode && conversationId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [
6956
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6957
+ isGroupCall && /* @__PURE__ */ jsx(Users, { size: 14, className: "text-white/50" }),
6958
+ peerName && /* @__PURE__ */ jsx("span", { className: "font-medium text-white/90", children: peerName }),
6959
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6960
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: duration })
6961
+ ] }),
6962
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
6963
+ /* @__PURE__ */ jsx(
6964
+ React11__default.Suspense,
6965
+ {
6966
+ fallback: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6967
+ /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin" }),
6968
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6524
6969
  ] }),
6525
- uiStatus === "ringing-out" && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
6526
- /* @__PURE__ */ jsx(
6527
- Button,
6528
- {
6529
- variant: "ghost",
6530
- size: "icon",
6531
- className: "size-16 rounded-full bg-red-600 text-white shadow-lg shadow-red-600/30 transition-transform hover:scale-105 hover:bg-red-500 active:scale-95",
6532
- "aria-label": "Cancel call",
6533
- onClick: cancelCall,
6534
- children: /* @__PURE__ */ jsx(PhoneOff, { size: 24 })
6535
- }
6536
- ),
6537
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
6538
- ] })
6539
- ] })
6540
- }
6541
- ),
6542
- document.body
6543
- );
6970
+ children: /* @__PURE__ */ jsx(
6971
+ CallSession2,
6972
+ {
6973
+ meetingId,
6974
+ token,
6975
+ mode,
6976
+ peerName,
6977
+ peerAvatar,
6978
+ isChatOpen,
6979
+ onToggleChat: () => setIsChatOpen((v) => !v),
6980
+ onError: setSessionError
6981
+ }
6982
+ )
6983
+ }
6984
+ ),
6985
+ isChatOpen && /* @__PURE__ */ jsx(
6986
+ CallChatPanel_default,
6987
+ {
6988
+ conversationId,
6989
+ isGroupCall,
6990
+ onClose: () => setIsChatOpen(false),
6991
+ className: "hidden w-80 shrink-0 sm:flex"
6992
+ }
6993
+ )
6994
+ ] })
6995
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6996
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
6997
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxs(Fragment, { children: [
6998
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
6999
+ /* @__PURE__ */ jsx(
7000
+ "span",
7001
+ {
7002
+ className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
7003
+ style: { animationDelay: "0.6s" }
7004
+ }
7005
+ )
7006
+ ] }),
7007
+ peerName ? /* @__PURE__ */ jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
7008
+ /* @__PURE__ */ jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
7009
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
7010
+ ] }) : /* @__PURE__ */ jsx("div", { className: "relative flex size-28 items-center justify-center rounded-full bg-white/10", children: isVideo ? /* @__PURE__ */ jsx(Video, { size: 36 }) : /* @__PURE__ */ jsx(Phone, { size: 36 }) })
7011
+ ] }),
7012
+ /* @__PURE__ */ jsxs("div", { children: [
7013
+ peerName && /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-1.5 text-xl font-semibold tracking-tight", children: [
7014
+ isGroupCall && /* @__PURE__ */ jsx(Users, { size: 16, className: "text-white/50" }),
7015
+ peerName
7016
+ ] }),
7017
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
7018
+ uiStatus === "creating" && "Starting call\u2026",
7019
+ uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
7020
+ uiStatus === "joining" && "Connecting\u2026"
7021
+ ] })
7022
+ ] }),
7023
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
7024
+ /* @__PURE__ */ jsx(
7025
+ Button,
7026
+ {
7027
+ variant: "ghost",
7028
+ size: "icon",
7029
+ className: "size-16 rounded-full bg-red-600 text-white shadow-lg shadow-red-600/30 transition-transform hover:scale-105 hover:bg-red-500 active:scale-95",
7030
+ "aria-label": "Cancel call",
7031
+ onClick: cancelCall,
7032
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 24 })
7033
+ }
7034
+ ),
7035
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
7036
+ ] })
7037
+ ] }) });
6544
7038
  };
6545
7039
  var CallOverlay_default = CallOverlay;
6546
7040
 
@@ -6876,162 +7370,6 @@ function AlertDialogCancel({
6876
7370
 
6877
7371
  // src/chat/ChatOverlay.tsx
6878
7372
  init_utils2();
6879
-
6880
- // src/chat/lib/chat-theme.ts
6881
- init_utils2();
6882
- var getChatScopeClass = (isDark) => cn("chat-theme-scope", isDark && "dark");
6883
- var chatSheetContentClass = "flex w-full flex-col gap-0 p-0 bg-(--chat-surface) text-(--chat-text)";
6884
- var chatSheetHeaderClass = "border-b border-(--chat-border) px-4 py-3 pr-11 text-left";
6885
- var chatSheetBodyClass = "chat-message-scroll flex-1 overflow-y-auto bg-(--chat-surface) p-3";
6886
- var chatSheetTitleClass = "text-[13px] font-semibold tracking-tight text-(--chat-text)";
6887
- var chatSheetSubtitleClass = "text-[11px] text-(--chat-muted)";
6888
- var chatDetailRowClass = "flex items-start justify-between gap-3 border-b border-(--chat-border) py-2.5 last:border-b-0";
6889
- var chatDetailLabelClass = "text-[11px] text-(--chat-muted)";
6890
- var chatDetailValueClass = "text-[13px] font-medium text-(--chat-text) text-right";
6891
- var chatDialogContentClass = "overflow-hidden rounded-2xl border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-[var(--chat-shadow)]";
6892
- var chatDropdownContentClass = "rounded-xl border border-(--chat-border) bg-(--chat-surface) text-(--chat-text) shadow-lg";
6893
- var chatOptionsMenuClass = "min-w-[220px] rounded-xl border border-(--chat-border) bg-(--chat-surface) p-1.5 text-(--chat-text) shadow-[0_10px_40px_rgba(0,0,0,0.12)]";
6894
- var chatOptionsMenuLabelClass = "px-2.5 pb-0.5 pt-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-(--chat-muted)";
6895
- var chatOptionsMenuSeparatorClass = "my-1.5 h-px bg-(--chat-border)";
6896
- var chatOptionsMenuItemClass = "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 text-[13px] font-medium text-(--chat-text) focus:bg-(--chat-hover) focus:text-(--chat-text) data-[highlighted]:bg-(--chat-hover) [&_svg]:size-[15px] [&_svg]:shrink-0 [&_svg]:text-(--chat-muted)";
6897
- var chatOptionsMenuItemDestructiveClass = "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 text-[13px] font-medium text-(--chat-danger-text) focus:bg-red-500/10 focus:text-(--chat-danger-text) data-[highlighted]:bg-red-500/10 data-[highlighted]:text-(--chat-danger-text) [&_svg]:size-[15px] [&_svg]:shrink-0 [&_svg]:!text-(--chat-danger-text)";
6898
- var chatOptionsMenuSubTriggerClass = "cursor-pointer gap-2.5 rounded-lg px-2.5 py-2 pr-7 text-[13px] font-medium text-(--chat-text) focus:bg-(--chat-hover) focus:text-(--chat-text) data-[highlighted]:bg-(--chat-hover) data-[state=open]:bg-(--chat-hover) [&_svg]:size-[15px] [&_svg]:shrink-0 [&_svg]:text-(--chat-muted)";
6899
- var chatPopoverContentClass = "w-auto border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-2xl";
6900
- var chatInputClass = "border border-(--chat-border) bg-(--chat-input-bg) text-(--chat-text) shadow-none placeholder:text-(--chat-muted) focus-visible:border-(--chat-theme-20) focus-visible:ring-2 focus-visible:ring-(--chat-theme-10)";
6901
- var chatSidebarSearchShellClass = "relative flex min-w-0 flex-1 items-center rounded-lg border border-(--chat-border) bg-(--chat-search-bg) px-2.5 transition-colors focus-within:border-(--chat-theme) focus-within:shadow-[0_0_0_2px_var(--chat-theme-10)]";
6902
- var chatSidebarSearchInputClass = "h-8 w-full min-w-0 border-0 bg-transparent px-1.5 text-xs text-(--chat-text) shadow-none outline-none placeholder:text-(--chat-muted)/60 focus-visible:border-0 focus-visible:shadow-none focus-visible:ring-0";
6903
- var chatSelectClass = "h-10 w-full cursor-pointer appearance-none rounded-xl border border-(--chat-border) bg-(--chat-input-bg) pl-3 pr-9 text-sm text-(--chat-text) shadow-none focus-visible:border-(--chat-theme-20) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--chat-theme-10)";
6904
- var chatListItemClass = "transition-colors hover:bg-(--chat-hover)";
6905
- var chatLoadMoreButtonClass = "h-7 gap-1 rounded-full border border-(--chat-border) bg-(--chat-elevated) px-4 text-[10px] font-medium text-(--chat-text) transition-colors hover:border-(--chat-theme-20) hover:bg-(--chat-theme-5) hover:text-(--chat-theme) disabled:opacity-50 [&_svg]:size-3 [&_svg]:text-current";
6906
- var chatDrawerSectionHeadingClass = "mb-1 px-0.5 text-[10px] font-semibold tracking-wide text-(--chat-muted)";
6907
- var chatHeaderToolbarIconProps = { size: 14, strokeWidth: 1.75 };
6908
- var chatSavedMessageModalIconClass = "shrink-0 text-(--chat-theme)";
6909
- var chatHeaderToolbarIconLgProps = { size: 22, strokeWidth: 1.75 };
6910
- var chatHeaderToolbarIconSmProps = { size: 11, strokeWidth: 1.75 };
6911
- var chatDrawerFileRowClass = "flex items-center gap-2 rounded-md border border-(--chat-border)/70 bg-(--chat-elevated) px-2 py-1.5 transition-colors hover:bg-(--chat-hover)/50";
6912
- var chatDrawerFileIconWrapClass = "flex size-8 shrink-0 items-center justify-center rounded-md bg-(--chat-hover)";
6913
- var chatDrawerMediaFrameClass = "overflow-hidden rounded-md border border-(--chat-border)/70 bg-(--chat-elevated)";
6914
- var buildChatThemeStyles = (themeColor) => ({
6915
- "--chat-theme": themeColor,
6916
- "--chat-theme-5": `${themeColor}0D`,
6917
- "--chat-theme-10": `${themeColor}1A`,
6918
- "--chat-theme-20": `${themeColor}33`,
6919
- "--chat-theme-40": `${themeColor}66`,
6920
- "--chat-theme-60": `${themeColor}99`,
6921
- "--read-tick-color": themeColor.toLowerCase() === "#0000ff" || themeColor.toLowerCase().includes("007aff") ? "#00e5ff" : "#40C4FF"
6922
- });
6923
-
6924
- // src/chat/lib/chat-theme-storage.ts
6925
- var STORAGE_KEY = "chat-color-mode";
6926
- var loadChatColorMode = () => {
6927
- if (typeof window === "undefined") return null;
6928
- try {
6929
- const stored = localStorage.getItem(STORAGE_KEY);
6930
- return stored === "dark" || stored === "light" ? stored : null;
6931
- } catch {
6932
- return null;
6933
- }
6934
- };
6935
- var saveChatColorMode = (mode) => {
6936
- if (typeof window === "undefined") return;
6937
- try {
6938
- localStorage.setItem(STORAGE_KEY, mode);
6939
- } catch {
6940
- }
6941
- };
6942
- var CHAT_THEME_SHELL_CLASS = "chat-theme-shell";
6943
- function withThemeTransition(apply) {
6944
- if (typeof document === "undefined") {
6945
- apply();
6946
- return;
6947
- }
6948
- flushSync(apply);
6949
- }
6950
-
6951
- // src/context/ChatThemeContext.tsx
6952
- init_utils2();
6953
- var ChatThemeContext = createContext(null);
6954
- var ChatThemeProvider = ({
6955
- children,
6956
- themeColor,
6957
- colorMode,
6958
- defaultColorMode = "light",
6959
- onColorModeChange,
6960
- showColorModeToggle = true
6961
- }) => {
6962
- const isControlled = colorMode !== void 0;
6963
- const [internalMode, setInternalMode] = useState(defaultColorMode);
6964
- const [isThemeTransitionReady, setIsThemeTransitionReady] = useState(false);
6965
- const skipThemeTransitionRef = useRef(true);
6966
- useLayoutEffect(() => {
6967
- if (!isControlled) {
6968
- const stored = loadChatColorMode();
6969
- if (stored) {
6970
- setInternalMode(stored);
6971
- }
6972
- }
6973
- const frame = requestAnimationFrame(() => {
6974
- skipThemeTransitionRef.current = false;
6975
- setIsThemeTransitionReady(true);
6976
- });
6977
- return () => cancelAnimationFrame(frame);
6978
- }, [isControlled]);
6979
- const resolvedMode = isControlled ? colorMode : internalMode;
6980
- const isDark = resolvedMode === "dark";
6981
- const setColorMode = useCallback(
6982
- (mode) => {
6983
- const apply = () => {
6984
- if (!isControlled) {
6985
- setInternalMode(mode);
6986
- saveChatColorMode(mode);
6987
- }
6988
- onColorModeChange?.(mode);
6989
- };
6990
- if (skipThemeTransitionRef.current) {
6991
- apply();
6992
- return;
6993
- }
6994
- withThemeTransition(apply);
6995
- },
6996
- [isControlled, onColorModeChange]
6997
- );
6998
- const toggleColorMode = useCallback(() => {
6999
- setColorMode(resolvedMode === "dark" ? "light" : "dark");
7000
- }, [resolvedMode, setColorMode]);
7001
- const value = useMemo(
7002
- () => ({
7003
- colorMode: resolvedMode,
7004
- isDark,
7005
- isThemeTransitionReady,
7006
- themeColor,
7007
- themeStyles: buildChatThemeStyles(themeColor),
7008
- scopeClassName: cn(
7009
- getChatScopeClass(isDark),
7010
- !isThemeTransitionReady && "chat-theme-no-transition"
7011
- ),
7012
- setColorMode,
7013
- toggleColorMode,
7014
- showColorModeToggle
7015
- }),
7016
- [
7017
- resolvedMode,
7018
- isDark,
7019
- isThemeTransitionReady,
7020
- themeColor,
7021
- setColorMode,
7022
- toggleColorMode,
7023
- showColorModeToggle
7024
- ]
7025
- );
7026
- return /* @__PURE__ */ jsx(ChatThemeContext.Provider, { value, children });
7027
- };
7028
- var useChatTheme = () => {
7029
- const context = useContext(ChatThemeContext);
7030
- if (!context) {
7031
- throw new Error("useChatTheme must be used within ChatThemeProvider");
7032
- }
7033
- return context;
7034
- };
7035
7373
  var ChatSheetContent = ({
7036
7374
  className,
7037
7375
  style,
@@ -7087,6 +7425,66 @@ init_avatar();
7087
7425
  init_button();
7088
7426
  init_utils2();
7089
7427
  init_call_store();
7428
+
7429
+ // src/call/ringtone.ts
7430
+ var audioCtx = null;
7431
+ var scheduledTimer = null;
7432
+ var activeOscillators = [];
7433
+ var RING_ON_MS = 1200;
7434
+ var RING_OFF_MS = 2400;
7435
+ var RING_FREQUENCIES = [440, 480];
7436
+ var getContext = () => {
7437
+ if (typeof window === "undefined") return null;
7438
+ const Ctor = window.AudioContext || window.webkitAudioContext;
7439
+ if (!Ctor) return null;
7440
+ if (!audioCtx || audioCtx.state === "closed") audioCtx = new Ctor();
7441
+ return audioCtx;
7442
+ };
7443
+ var stopOscillators = () => {
7444
+ activeOscillators.forEach((osc) => {
7445
+ try {
7446
+ osc.stop();
7447
+ } catch {
7448
+ }
7449
+ });
7450
+ activeOscillators = [];
7451
+ };
7452
+ var ringBurst = (ctx) => {
7453
+ const onSeconds = RING_ON_MS / 1e3;
7454
+ const gain = ctx.createGain();
7455
+ gain.gain.setValueAtTime(0, ctx.currentTime);
7456
+ gain.gain.linearRampToValueAtTime(0.15, ctx.currentTime + 0.05);
7457
+ gain.gain.setValueAtTime(0.15, ctx.currentTime + onSeconds - 0.05);
7458
+ gain.gain.linearRampToValueAtTime(0, ctx.currentTime + onSeconds);
7459
+ gain.connect(ctx.destination);
7460
+ RING_FREQUENCIES.forEach((freq) => {
7461
+ const osc = ctx.createOscillator();
7462
+ osc.type = "sine";
7463
+ osc.frequency.value = freq;
7464
+ osc.connect(gain);
7465
+ osc.start();
7466
+ osc.stop(ctx.currentTime + onSeconds);
7467
+ activeOscillators.push(osc);
7468
+ });
7469
+ };
7470
+ var playDefaultRingtone = () => {
7471
+ if (scheduledTimer) return;
7472
+ const ctx = getContext();
7473
+ if (!ctx) return;
7474
+ ctx.resume().catch(() => void 0);
7475
+ const cycle = () => {
7476
+ ringBurst(ctx);
7477
+ scheduledTimer = setTimeout(cycle, RING_ON_MS + RING_OFF_MS);
7478
+ };
7479
+ cycle();
7480
+ };
7481
+ var stopDefaultRingtone = () => {
7482
+ if (scheduledTimer) {
7483
+ clearTimeout(scheduledTimer);
7484
+ scheduledTimer = null;
7485
+ }
7486
+ stopOscillators();
7487
+ };
7090
7488
  var IncomingCallDialog = ({ ringtoneUrl }) => {
7091
7489
  const uiStatus = useCallStore((s) => s.uiStatus);
7092
7490
  const callId = useCallStore((s) => s.callId);
@@ -7109,6 +7507,12 @@ var IncomingCallDialog = ({ ringtoneUrl }) => {
7109
7507
  el.pause();
7110
7508
  }
7111
7509
  }, [isOpen, ringtoneUrl]);
7510
+ useEffect(() => {
7511
+ if (ringtoneUrl) return;
7512
+ if (isOpen) playDefaultRingtone();
7513
+ else stopDefaultRingtone();
7514
+ return () => stopDefaultRingtone();
7515
+ }, [isOpen, ringtoneUrl]);
7112
7516
  if (!isOpen) return null;
7113
7517
  const callerName = caller?.name || "Unknown caller";
7114
7518
  const name = isGroupCall ? peer?.name || callerName : callerName;
@@ -7376,7 +7780,7 @@ var Chat = ({
7376
7780
  }),
7377
7781
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
7378
7782
  );
7379
- return /* @__PURE__ */ jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsxs(ChatEffectiveSettingsProvider, { enabled: true, children: [
7783
+ return /* @__PURE__ */ jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", { className: "chat-container-root relative flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsxs(ChatEffectiveSettingsProvider, { enabled: true, children: [
7380
7784
  children,
7381
7785
  /* @__PURE__ */ jsx(CallLayer_default, {})
7382
7786
  ] }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
@@ -7833,6 +8237,7 @@ init_avatar();
7833
8237
 
7834
8238
  // src/chat/ChatThemeToggle.tsx
7835
8239
  init_button();
8240
+ init_tooltip();
7836
8241
  init_utils2();
7837
8242
  var ChatThemeToggle = ({ className }) => {
7838
8243
  const { isDark, toggleColorMode } = useChatTheme();
@@ -7890,6 +8295,7 @@ var buildLoggedUserProfile = (loggedUserDetails) => ({
7890
8295
 
7891
8296
  // src/chat/sidebar/create-chat-dialog/CreateChatDialog.tsx
7892
8297
  init_button();
8298
+ init_tooltip();
7893
8299
  init_utils2();
7894
8300
 
7895
8301
  // src/chat/sidebar/create-chat-dialog/useCreateChatDialog.ts
@@ -8340,6 +8746,9 @@ function GroupCreateTab({
8340
8746
  ) })
8341
8747
  ] });
8342
8748
  }
8749
+
8750
+ // src/chat/components/AdminPermissionTooltip.tsx
8751
+ init_tooltip();
8343
8752
  init_utils2();
8344
8753
  function AdminPermissionTooltip({
8345
8754
  disabled,
@@ -8966,6 +9375,7 @@ var LoggedUserSettingsContent_default = LoggedUserSettingsContent;
8966
9375
 
8967
9376
  // src/chat/sidebar/LoggedUserSettingsTrigger.tsx
8968
9377
  init_button();
9378
+ init_tooltip();
8969
9379
  init_utils2();
8970
9380
  var LoggedUserSettingsTrigger = ({
8971
9381
  onClick,
@@ -10632,6 +11042,7 @@ init_call_store();
10632
11042
  init_utils2();
10633
11043
 
10634
11044
  // src/chat/header/header-right/OptionsMenuItem.tsx
11045
+ init_tooltip();
10635
11046
  init_utils2();
10636
11047
  function OptionsMenuItem({
10637
11048
  icon: Icon,
@@ -11678,28 +12089,6 @@ function MessageForwardedLabel({ isSender, className, overlay }) {
11678
12089
  }
11679
12090
  );
11680
12091
  }
11681
-
11682
- // src/chat/lib/chat-message-bubble.ts
11683
- init_utils2();
11684
- function getBubbleCornerClasses(isSender) {
11685
- return isSender ? "rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-none" : "rounded-tl-[18px] rounded-tr-[18px] rounded-br-[18px] rounded-bl-none";
11686
- }
11687
- function getChatBubbleClasses({
11688
- isSender = false,
11689
- surface,
11690
- className
11691
- }) {
11692
- const resolvedSurface = surface ?? (isSender ? "sent" : "received");
11693
- const isNeutral = resolvedSurface === "neutral";
11694
- return cn(
11695
- "relative",
11696
- getBubbleCornerClasses(isSender),
11697
- resolvedSurface === "sent" && "chat-bubble-sent bg-(--chat-theme) text-white",
11698
- resolvedSurface === "received" && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
11699
- isNeutral && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
11700
- className
11701
- );
11702
- }
11703
12092
  init_utils2();
11704
12093
  init_client();
11705
12094
  function getPreviewKind(name) {
@@ -16314,6 +16703,7 @@ init_avatar();
16314
16703
 
16315
16704
  // src/chat/message/MessageReactions.tsx
16316
16705
  init_utils2();
16706
+ init_tooltip();
16317
16707
  var MessageReactions = ({
16318
16708
  groupedReactions,
16319
16709
  reactions,
@@ -20726,6 +21116,7 @@ init_normalize_helpers();
20726
21116
  init_useStore();
20727
21117
  init_open_conversation();
20728
21118
  var RETRY_DELAYS_MS = [1e3, 3e3, 8e3, 2e4];
21119
+ var CHAT_HEARTBEAT_INTERVAL_MS = 15e3;
20729
21120
  var recentToastIds = /* @__PURE__ */ new Set();
20730
21121
  var rememberToastId = (id) => {
20731
21122
  if (!id) return true;
@@ -20948,6 +21339,36 @@ var ForegroundPushBridge = ({ onPush }) => {
20948
21339
  window.removeEventListener(PUSH_CHANGED_EVENT, onPushChanged);
20949
21340
  };
20950
21341
  }, []);
21342
+ useEffect(() => {
21343
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
21344
+ return;
21345
+ }
21346
+ let interval = null;
21347
+ const sendHeartbeat = () => {
21348
+ navigator.serviceWorker.getRegistration().then(
21349
+ (registration) => registration?.active?.postMessage({ type: CHAT_HEARTBEAT_SW_TYPE })
21350
+ ).catch(() => void 0);
21351
+ };
21352
+ const startHeartbeat = () => {
21353
+ if (interval || !getStoredPushToken()) return;
21354
+ sendHeartbeat();
21355
+ interval = setInterval(sendHeartbeat, CHAT_HEARTBEAT_INTERVAL_MS);
21356
+ };
21357
+ const stopHeartbeat = () => {
21358
+ if (interval) clearInterval(interval);
21359
+ interval = null;
21360
+ };
21361
+ startHeartbeat();
21362
+ const onPushChanged = (event) => {
21363
+ if (event.detail?.enabled) startHeartbeat();
21364
+ else stopHeartbeat();
21365
+ };
21366
+ window.addEventListener(PUSH_CHANGED_EVENT, onPushChanged);
21367
+ return () => {
21368
+ stopHeartbeat();
21369
+ window.removeEventListener(PUSH_CHANGED_EVENT, onPushChanged);
21370
+ };
21371
+ }, []);
20951
21372
  useEffect(() => {
20952
21373
  const onOpen = (event) => {
20953
21374
  const id = String(