@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.cjs CHANGED
@@ -912,12 +912,13 @@ var init_store_helpers = __esm({
912
912
  });
913
913
 
914
914
  // src/notifications/open-conversation.ts
915
- exports.OPEN_CONVERSATION_EVENT = void 0; exports.NOTIFICATION_CLICK_SW_TYPE = void 0; exports.PUSH_BROADCAST_CHANNEL = void 0; exports.PUSH_DATA_CACHE_NAME = void 0; var PENDING_STORAGE_KEY, PENDING_OPEN_CACHE_PATH; exports.resolveConversationIdFromPushData = void 0; var normalizePushMeta, parsePendingRaw; exports.peekPendingOpenConversation = void 0; exports.peekPendingOpenRequest = void 0; exports.consumePendingOpenConversation = void 0; exports.consumePendingOpenRequest = void 0; exports.requestOpenConversation = void 0; exports.buildChannelFromPushRequest = void 0; var mapConversationToChannel, stripConversationIdFromUrl, cacheRequestForPath; exports.consumePendingOpenFromPushCache = void 0; var readConversationIdFromLocation, consumedUrlConversationIds, lastHandledOpenId, lastHandledOpenAt, shouldHandleOpen, handleNotificationOpen; exports.installNotificationClickBridge = void 0;
915
+ exports.OPEN_CONVERSATION_EVENT = void 0; exports.NOTIFICATION_CLICK_SW_TYPE = void 0; var CHAT_HEARTBEAT_SW_TYPE; exports.PUSH_BROADCAST_CHANNEL = void 0; exports.PUSH_DATA_CACHE_NAME = void 0; var PENDING_STORAGE_KEY, PENDING_OPEN_CACHE_PATH; exports.resolveConversationIdFromPushData = void 0; var normalizePushMeta, parsePendingRaw; exports.peekPendingOpenConversation = void 0; exports.peekPendingOpenRequest = void 0; exports.consumePendingOpenConversation = void 0; exports.consumePendingOpenRequest = void 0; exports.requestOpenConversation = void 0; exports.buildChannelFromPushRequest = void 0; var mapConversationToChannel, stripConversationIdFromUrl, cacheRequestForPath; exports.consumePendingOpenFromPushCache = void 0; var readConversationIdFromLocation, consumedUrlConversationIds, lastHandledOpenId, lastHandledOpenAt, shouldHandleOpen, handleNotificationOpen; exports.installNotificationClickBridge = void 0;
916
916
  var init_open_conversation = __esm({
917
917
  "src/notifications/open-conversation.ts"() {
918
918
  init_store_helpers();
919
919
  exports.OPEN_CONVERSATION_EVENT = "realtimex:open-conversation";
920
920
  exports.NOTIFICATION_CLICK_SW_TYPE = "realtimex:notification-click";
921
+ CHAT_HEARTBEAT_SW_TYPE = "realtimex:chat-heartbeat";
921
922
  exports.PUSH_BROADCAST_CHANNEL = "realtimex-push";
922
923
  exports.PUSH_DATA_CACHE_NAME = "realtimex-push-data-v1";
923
924
  PENDING_STORAGE_KEY = "realtimex:pending-open-conversation";
@@ -4639,6 +4640,67 @@ var init_utils2 = __esm({
4639
4640
  "src/lib/utils.ts"() {
4640
4641
  }
4641
4642
  });
4643
+ function TooltipProvider({
4644
+ delayDuration = 0,
4645
+ ...props
4646
+ }) {
4647
+ return /* @__PURE__ */ jsxRuntime.jsx(
4648
+ radixUi.Tooltip.Provider,
4649
+ {
4650
+ "data-slot": "tooltip-provider",
4651
+ delayDuration,
4652
+ ...props
4653
+ }
4654
+ );
4655
+ }
4656
+ function Tooltip({
4657
+ ...props
4658
+ }) {
4659
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Root, { "data-slot": "tooltip", ...props });
4660
+ }
4661
+ function TooltipTrigger({
4662
+ ...props
4663
+ }) {
4664
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
4665
+ }
4666
+ function TooltipContent({
4667
+ className,
4668
+ sideOffset = 0,
4669
+ children,
4670
+ showArrow = true,
4671
+ arrowClassName,
4672
+ ...props
4673
+ }) {
4674
+ return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
4675
+ radixUi.Tooltip.Content,
4676
+ {
4677
+ "data-slot": "tooltip-content",
4678
+ sideOffset,
4679
+ className: cn(
4680
+ "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",
4681
+ className
4682
+ ),
4683
+ ...props,
4684
+ children: [
4685
+ children,
4686
+ showArrow ? /* @__PURE__ */ jsxRuntime.jsx(
4687
+ radixUi.Tooltip.Arrow,
4688
+ {
4689
+ className: cn(
4690
+ "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]",
4691
+ arrowClassName
4692
+ )
4693
+ }
4694
+ ) : null
4695
+ ]
4696
+ }
4697
+ ) });
4698
+ }
4699
+ var init_tooltip = __esm({
4700
+ "src/ui/tooltip.tsx"() {
4701
+ init_utils2();
4702
+ }
4703
+ });
4642
4704
  function Avatar({
4643
4705
  className,
4644
4706
  size = "default",
@@ -4778,9 +4840,8 @@ var init_ParticipantView = __esm({
4778
4840
  isHandRaised = false,
4779
4841
  className
4780
4842
  }) => {
4781
- const { webcamStream, micStream, webcamOn, micOn, displayName, isActiveSpeaker } = sdk.useParticipant(participantId);
4843
+ const { webcamStream, webcamOn, micOn, displayName, isActiveSpeaker } = sdk.useParticipant(participantId);
4782
4844
  const videoRef = React11.useRef(null);
4783
- const audioRef = React11.useRef(null);
4784
4845
  const [hasFrame, setHasFrame] = React11.useState(false);
4785
4846
  React11.useEffect(() => {
4786
4847
  const el = videoRef.current;
@@ -4793,17 +4854,6 @@ var init_ParticipantView = __esm({
4793
4854
  setHasFrame(false);
4794
4855
  }
4795
4856
  }, [webcamOn, webcamStream]);
4796
- React11.useEffect(() => {
4797
- if (isLocal) return;
4798
- const el = audioRef.current;
4799
- if (!el) return;
4800
- if (micOn && micStream?.track) {
4801
- el.srcObject = new MediaStream([micStream.track]);
4802
- el.play().catch(() => void 0);
4803
- } else {
4804
- el.srcObject = null;
4805
- }
4806
- }, [micOn, micStream, isLocal]);
4807
4857
  const name = displayName || fallbackName || "Participant";
4808
4858
  const showVideo = webcamOn && hasFrame;
4809
4859
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -4836,8 +4886,7 @@ var init_ParticipantView = __esm({
4836
4886
  )
4837
4887
  }
4838
4888
  ),
4839
- !isLocal && /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true }),
4840
- isHandRaised && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 14 }) }),
4889
+ isHandRaised && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 14 }) }),
4841
4890
  !showVideo && /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { size: "lg", className: "size-20", children: [
4842
4891
  /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: fallbackImage, alt: name }),
4843
4892
  /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-neutral-700 text-xl font-medium text-white", children: name.charAt(0).toUpperCase() })
@@ -4894,7 +4943,7 @@ var init_AudioCallView = __esm({
4894
4943
  }
4895
4944
  ),
4896
4945
  isMuted && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.MicOff, { size: 16, className: "text-white/80" }) }),
4897
- isRemoteHandRaised && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 16 }) })
4946
+ isRemoteHandRaised && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 16 }) })
4898
4947
  ] }),
4899
4948
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xl font-semibold tracking-tight", children: name })
4900
4949
  ] });
@@ -4918,13 +4967,13 @@ var init_ScreenShareView = __esm({
4918
4967
  el.srcObject = null;
4919
4968
  }
4920
4969
  }, [screenShareOn, screenShareStream]);
4921
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
4922
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-1 pb-2 text-xs font-medium text-white/70", children: [
4923
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, { size: 14 }),
4970
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.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: [
4971
+ /* @__PURE__ */ jsxRuntime.jsx("video", { ref: videoRef, playsInline: true, muted: isLocal, className: "max-h-full max-w-full object-contain" }),
4972
+ /* @__PURE__ */ jsxRuntime.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: [
4973
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, { size: 13, className: "text-indigo-300" }),
4924
4974
  isLocal ? "You are presenting" : `${displayName || "Someone"} is presenting`
4925
- ] }),
4926
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex flex-1 items-center justify-center overflow-hidden rounded-xl bg-black", children: /* @__PURE__ */ jsxRuntime.jsx("video", { ref: videoRef, playsInline: true, muted: isLocal, className: "max-h-full max-w-full object-contain" }) })
4927
- ] });
4975
+ ] })
4976
+ ] }) });
4928
4977
  };
4929
4978
  ScreenShareView_default = ScreenShareView;
4930
4979
  }
@@ -4964,22 +5013,47 @@ var init_popover = __esm({
4964
5013
  init_utils2();
4965
5014
  }
4966
5015
  });
4967
- var circleBtn, idleBtnClass, activeBtnClass, QUICK_REACTIONS, CallControls, CallControls_default;
5016
+ var circleBtn, idleBtnClass, activeBtnClass, ControlButton, QUICK_REACTIONS, CallControls, CallControls_default;
4968
5017
  var init_CallControls = __esm({
4969
5018
  "src/call/CallControls.tsx"() {
4970
5019
  init_button();
4971
5020
  init_popover();
5021
+ init_tooltip();
4972
5022
  init_utils2();
4973
5023
  init_call_store();
4974
5024
  circleBtn = "size-13 rounded-full text-white transition-all duration-150 disabled:opacity-40 hover:scale-105 active:scale-95";
4975
5025
  idleBtnClass = "bg-white/10 hover:bg-white/20";
4976
5026
  activeBtnClass = "bg-white text-neutral-900 hover:bg-white/90";
5027
+ ControlButton = ({
5028
+ icon,
5029
+ label,
5030
+ active,
5031
+ activeClassName = activeBtnClass,
5032
+ className,
5033
+ onClick
5034
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
5035
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5036
+ Button,
5037
+ {
5038
+ variant: "ghost",
5039
+ size: "icon",
5040
+ "aria-label": label,
5041
+ onClick,
5042
+ className: cn(circleBtn, active ? activeClassName : idleBtnClass, className),
5043
+ children: icon
5044
+ }
5045
+ ) }),
5046
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: label })
5047
+ ] });
4977
5048
  QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F44F}", "\u{1F389}"];
4978
5049
  CallControls = ({
4979
5050
  sdk,
4980
5051
  onReaction,
4981
5052
  isHandRaised,
4982
- onToggleRaiseHand
5053
+ onToggleRaiseHand,
5054
+ isChatOpen,
5055
+ onToggleChat,
5056
+ isVideoActive
4983
5057
  }) => {
4984
5058
  const {
4985
5059
  toggleMic,
@@ -4988,81 +5062,124 @@ var init_CallControls = __esm({
4988
5062
  localWebcamOn,
4989
5063
  localScreenShareOn,
4990
5064
  toggleScreenShare,
5065
+ recordingState,
5066
+ startRecording,
5067
+ stopRecording,
4991
5068
  leave
4992
5069
  } = sdk.useMeeting();
4993
5070
  const endCall = exports.useCallStore((s) => s.endCall);
5071
+ const isRecording = recordingState === "RECORDING_STARTED" || recordingState === "RECORDING_STARTING";
5072
+ const isRecordingBusy = recordingState === "RECORDING_STARTING" || recordingState === "RECORDING_STOPPING";
5073
+ const handleToggleRecording = () => {
5074
+ if (isRecordingBusy) return;
5075
+ if (isRecording) {
5076
+ stopRecording();
5077
+ return;
5078
+ }
5079
+ startRecording("", "", {
5080
+ layout: { type: "GRID", priority: "SPEAKER", gridSize: 4 },
5081
+ orientation: "landscape",
5082
+ theme: "DEFAULT",
5083
+ mode: "video-and-audio",
5084
+ quality: "high"
5085
+ });
5086
+ };
4994
5087
  const handleLeave = () => {
4995
5088
  leave();
4996
5089
  endCall();
4997
5090
  };
4998
5091
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-5 py-6", children: [
4999
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 rounded-full bg-white/10 p-2 backdrop-blur-sm", children: [
5092
+ /* @__PURE__ */ jsxRuntime.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: [
5000
5093
  /* @__PURE__ */ jsxRuntime.jsx(
5001
- Button,
5094
+ ControlButton,
5002
5095
  {
5003
- variant: "ghost",
5004
- size: "icon",
5005
- className: cn(circleBtn, localMicOn ? idleBtnClass : activeBtnClass),
5006
- "aria-label": localMicOn ? "Mute microphone" : "Unmute microphone",
5007
- onClick: () => toggleMic(),
5008
- children: localMicOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { size: 20 })
5096
+ icon: localMicOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { size: 20 }),
5097
+ label: localMicOn ? "Mute microphone" : "Unmute microphone",
5098
+ active: !localMicOn,
5099
+ onClick: () => toggleMic()
5100
+ }
5101
+ ),
5102
+ isVideoActive ? /* @__PURE__ */ jsxRuntime.jsx(
5103
+ ControlButton,
5104
+ {
5105
+ icon: localWebcamOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { size: 20 }),
5106
+ label: localWebcamOn ? "Turn off camera" : "Turn on camera",
5107
+ active: !localWebcamOn,
5108
+ onClick: () => toggleWebcam()
5109
+ }
5110
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
5111
+ ControlButton,
5112
+ {
5113
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 20 }),
5114
+ label: "Switch to video call",
5115
+ onClick: () => toggleWebcam()
5009
5116
  }
5010
5117
  ),
5118
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "mx-0.5 h-7 w-px shrink-0 bg-white/15" }),
5011
5119
  /* @__PURE__ */ jsxRuntime.jsx(
5012
- Button,
5120
+ ControlButton,
5013
5121
  {
5014
- variant: "ghost",
5015
- size: "icon",
5016
- className: cn(circleBtn, localWebcamOn ? idleBtnClass : activeBtnClass),
5017
- "aria-label": localWebcamOn ? "Turn camera off" : "Turn camera on",
5018
- onClick: () => toggleWebcam(),
5019
- children: localWebcamOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { size: 20 })
5122
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, { size: 20 }),
5123
+ label: localScreenShareOn ? "Stop sharing your screen" : "Share your screen",
5124
+ active: localScreenShareOn,
5125
+ activeClassName: "bg-indigo-500 hover:bg-indigo-400",
5126
+ onClick: () => toggleScreenShare()
5020
5127
  }
5021
5128
  ),
5022
5129
  /* @__PURE__ */ jsxRuntime.jsx(
5023
- Button,
5130
+ ControlButton,
5024
5131
  {
5025
- variant: "ghost",
5026
- size: "icon",
5027
- className: cn(circleBtn, localScreenShareOn ? "bg-indigo-500 hover:bg-indigo-400" : idleBtnClass),
5028
- "aria-label": localScreenShareOn ? "Stop sharing your screen" : "Share your screen",
5029
- onClick: () => toggleScreenShare(),
5030
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, { size: 20 })
5132
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { size: 14, className: cn("fill-current", isRecording && "animate-pulse") }),
5133
+ label: isRecording ? "Stop recording" : "Start recording",
5134
+ active: isRecording,
5135
+ activeClassName: "bg-red-600 hover:bg-red-500",
5136
+ onClick: handleToggleRecording
5031
5137
  }
5032
5138
  ),
5033
5139
  /* @__PURE__ */ jsxRuntime.jsx(
5034
- Button,
5140
+ ControlButton,
5035
5141
  {
5036
- variant: "ghost",
5037
- size: "icon",
5038
- className: cn(circleBtn, isHandRaised ? "bg-amber-400 text-neutral-900 hover:bg-amber-300" : idleBtnClass),
5039
- "aria-label": isHandRaised ? "Lower hand" : "Raise hand",
5040
- onClick: onToggleRaiseHand,
5041
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 20 })
5142
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { size: 20, className: isHandRaised ? "animate-[bounce_1s_ease-in-out_2]" : void 0 }),
5143
+ label: isHandRaised ? "Lower hand" : "Raise hand",
5144
+ active: isHandRaised,
5145
+ activeClassName: "bg-amber-400 text-neutral-900 hover:bg-amber-300",
5146
+ onClick: onToggleRaiseHand
5147
+ }
5148
+ ),
5149
+ /* @__PURE__ */ jsxRuntime.jsx(
5150
+ ControlButton,
5151
+ {
5152
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { size: 20 }),
5153
+ label: isChatOpen ? "Close in-call chat" : "Open in-call chat",
5154
+ active: isChatOpen,
5155
+ onClick: onToggleChat
5042
5156
  }
5043
5157
  ),
5044
5158
  /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
5045
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5046
- Button,
5047
- {
5048
- variant: "ghost",
5049
- size: "icon",
5050
- className: cn(circleBtn, idleBtnClass),
5051
- "aria-label": "Send a reaction",
5052
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Smile, { size: 20 })
5053
- }
5054
- ) }),
5159
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
5160
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5161
+ Button,
5162
+ {
5163
+ variant: "ghost",
5164
+ size: "icon",
5165
+ className: cn(circleBtn, idleBtnClass),
5166
+ "aria-label": "Send a reaction",
5167
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Smile, { size: 20 })
5168
+ }
5169
+ ) }) }),
5170
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: "React" })
5171
+ ] }),
5055
5172
  /* @__PURE__ */ jsxRuntime.jsx(
5056
5173
  PopoverContent,
5057
5174
  {
5058
5175
  side: "top",
5059
5176
  align: "center",
5060
- className: "w-auto rounded-full border-white/10 bg-neutral-900 p-1.5 shadow-xl",
5177
+ className: "z-[1000000] w-auto rounded-full border-white/10 bg-neutral-900 p-1.5 shadow-xl",
5061
5178
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5", children: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
5062
5179
  "button",
5063
5180
  {
5064
5181
  type: "button",
5065
- className: "flex size-10 items-center justify-center rounded-full text-xl transition-transform hover:scale-125 hover:bg-white/10",
5182
+ className: "flex size-10 items-center justify-center rounded-full text-xl transition-transform hover:scale-125 hover:bg-white/10 active:scale-95",
5066
5183
  "aria-label": `React with ${emoji}`,
5067
5184
  onClick: () => onReaction(emoji),
5068
5185
  children: emoji
@@ -5073,17 +5190,20 @@ var init_CallControls = __esm({
5073
5190
  )
5074
5191
  ] })
5075
5192
  ] }),
5076
- /* @__PURE__ */ jsxRuntime.jsx(
5077
- Button,
5078
- {
5079
- variant: "ghost",
5080
- size: "icon",
5081
- className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
5082
- "aria-label": "End call",
5083
- onClick: handleLeave,
5084
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 22 })
5085
- }
5086
- )
5193
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
5194
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5195
+ Button,
5196
+ {
5197
+ variant: "ghost",
5198
+ size: "icon",
5199
+ className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
5200
+ "aria-label": "End call",
5201
+ onClick: handleLeave,
5202
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 22 })
5203
+ }
5204
+ ) }),
5205
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: "Leave call" })
5206
+ ] })
5087
5207
  ] });
5088
5208
  };
5089
5209
  CallControls_default = CallControls;
@@ -5140,15 +5260,18 @@ var init_useCallReactions = __esm({
5140
5260
  }, []);
5141
5261
  const { send, localParticipant } = sdk.useMeeting({
5142
5262
  onData: (data) => {
5263
+ console.log("[realtimex-call] data channel received:", data);
5143
5264
  try {
5144
5265
  const raw = typeof data.payload === "string" ? data.payload : new TextDecoder().decode(data.payload);
5145
5266
  const message = JSON.parse(raw);
5267
+ console.log("[realtimex-call] data channel parsed:", message, "from", data.from);
5146
5268
  if (message.type === "reaction") {
5147
5269
  addFloatingReaction(message.emoji, data.from);
5148
5270
  } else if (message.type === "raise-hand") {
5149
5271
  setRaisedHands((prev) => ({ ...prev, [data.from]: message.raised }));
5150
5272
  }
5151
- } catch {
5273
+ } catch (err) {
5274
+ console.warn("[realtimex-call] data channel payload not decodable:", err, data);
5152
5275
  }
5153
5276
  }
5154
5277
  });
@@ -5161,12 +5284,22 @@ var init_useCallReactions = __esm({
5161
5284
  return next;
5162
5285
  });
5163
5286
  }, [localParticipant?.id]);
5287
+ const sendDataMessage = React11.useCallback(
5288
+ (message) => {
5289
+ send(JSON.stringify(message)).then((ok) => {
5290
+ console.log("[realtimex-call] data channel send result:", ok, message);
5291
+ }).catch((err) => {
5292
+ console.warn("[realtimex-call] data channel send failed:", err, message);
5293
+ });
5294
+ },
5295
+ [send]
5296
+ );
5164
5297
  const sendReaction = React11.useCallback(
5165
5298
  (emoji) => {
5166
5299
  if (localParticipant?.id) addFloatingReaction(emoji, localParticipant.id);
5167
- void send(JSON.stringify({ type: "reaction", emoji }));
5300
+ sendDataMessage({ type: "reaction", emoji });
5168
5301
  },
5169
- [send, localParticipant?.id, addFloatingReaction]
5302
+ [localParticipant?.id, addFloatingReaction, sendDataMessage]
5170
5303
  );
5171
5304
  const toggleRaiseHand = React11.useCallback(() => {
5172
5305
  setIsHandRaised((prev) => {
@@ -5174,15 +5307,15 @@ var init_useCallReactions = __esm({
5174
5307
  if (localParticipant?.id) {
5175
5308
  setRaisedHands((hands) => ({ ...hands, [localParticipant.id]: next }));
5176
5309
  }
5177
- void send(JSON.stringify({ type: "raise-hand", raised: next }));
5310
+ sendDataMessage({ type: "raise-hand", raised: next });
5178
5311
  return next;
5179
5312
  });
5180
- }, [send, localParticipant?.id]);
5313
+ }, [localParticipant?.id, sendDataMessage]);
5181
5314
  return { floatingReactions, raisedHands, isHandRaised, sendReaction, toggleRaiseHand };
5182
5315
  };
5183
5316
  }
5184
5317
  });
5185
- var MeetingView, MeetingView_default;
5318
+ var RemoteParticipantWatcher, MeetingView, MeetingView_default;
5186
5319
  var init_MeetingView = __esm({
5187
5320
  "src/call/MeetingView.tsx"() {
5188
5321
  init_ParticipantView();
@@ -5193,11 +5326,48 @@ var init_MeetingView = __esm({
5193
5326
  init_useCallReactions();
5194
5327
  init_call_store();
5195
5328
  init_utils2();
5196
- MeetingView = ({ sdk, mode, peerName, peerAvatar }) => {
5329
+ RemoteParticipantWatcher = ({
5330
+ sdk,
5331
+ participantId,
5332
+ onWebcamChange
5333
+ }) => {
5334
+ const { webcamOn, micStream, micOn } = sdk.useParticipant(participantId);
5335
+ const audioRef = React11.useRef(null);
5336
+ React11.useEffect(() => {
5337
+ onWebcamChange(participantId, webcamOn);
5338
+ return () => onWebcamChange(participantId, false);
5339
+ }, [participantId, webcamOn]);
5340
+ React11.useEffect(() => {
5341
+ const el = audioRef.current;
5342
+ if (!el) return;
5343
+ if (micOn && micStream?.track) {
5344
+ el.srcObject = new MediaStream([micStream.track]);
5345
+ el.play().catch(() => void 0);
5346
+ } else {
5347
+ el.srcObject = null;
5348
+ }
5349
+ }, [micOn, micStream]);
5350
+ return /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true });
5351
+ };
5352
+ MeetingView = ({
5353
+ sdk,
5354
+ mode,
5355
+ peerName,
5356
+ peerAvatar,
5357
+ isChatOpen,
5358
+ onToggleChat
5359
+ }) => {
5197
5360
  const endCall = exports.useCallStore((s) => s.endCall);
5198
5361
  const [hasJoined, setHasJoined] = React11.useState(false);
5199
5362
  const hasJoinedRef = React11.useRef(false);
5200
5363
  const [groupView, setGroupView] = React11.useState("grid");
5364
+ const [meetingState, setMeetingState] = React11.useState("CONNECTING");
5365
+ const [remoteWebcamOn, setRemoteWebcamOn] = React11.useState({});
5366
+ const handleRemoteWebcamChange = React11.useCallback((participantId, on) => {
5367
+ setRemoteWebcamOn(
5368
+ (prev) => prev[participantId] === on ? prev : { ...prev, [participantId]: on }
5369
+ );
5370
+ }, []);
5201
5371
  const {
5202
5372
  join,
5203
5373
  participants,
@@ -5213,7 +5383,8 @@ var init_MeetingView = __esm({
5213
5383
  onMeetingLeft: () => {
5214
5384
  if (!hasJoinedRef.current) return;
5215
5385
  endCall();
5216
- }
5386
+ },
5387
+ onMeetingStateChanged: ({ state }) => setMeetingState(state)
5217
5388
  });
5218
5389
  const { floatingReactions, raisedHands, isHandRaised, sendReaction, toggleRaiseHand } = useCallReactions(sdk);
5219
5390
  React11.useEffect(() => {
@@ -5229,149 +5400,58 @@ var init_MeetingView = __esm({
5229
5400
  (id) => id !== localParticipant?.id
5230
5401
  );
5231
5402
  const isGroup = remoteIds.length > 1;
5403
+ const remoteParticipantWatchers = remoteIds.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
5404
+ RemoteParticipantWatcher,
5405
+ {
5406
+ sdk,
5407
+ participantId: id,
5408
+ onWebcamChange: handleRemoteWebcamChange
5409
+ },
5410
+ id
5411
+ ));
5412
+ const anyRemoteWebcamOn = Object.values(remoteWebcamOn).some(Boolean);
5413
+ const showVideoGrid = mode === "video" || localWebcamOn || anyRemoteWebcamOn;
5232
5414
  const controls = /* @__PURE__ */ jsxRuntime.jsx(
5233
5415
  CallControls_default,
5234
5416
  {
5235
5417
  sdk,
5236
5418
  onReaction: sendReaction,
5237
5419
  isHandRaised,
5238
- onToggleRaiseHand: toggleRaiseHand
5420
+ onToggleRaiseHand: toggleRaiseHand,
5421
+ isChatOpen,
5422
+ onToggleChat,
5423
+ isVideoActive: showVideoGrid
5239
5424
  }
5240
5425
  );
5426
+ const connectionStatusText = meetingState === "CONNECTING" ? "Connecting\u2026" : meetingState === "DISCONNECTED" ? "Reconnecting\u2026" : meetingState === "FAILED" ? "Connection lost" : null;
5427
+ const statusBanner = connectionStatusText && /* @__PURE__ */ jsxRuntime.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: [
5428
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-amber-400" }),
5429
+ connectionStatusText
5430
+ ] });
5241
5431
  if (presenterId) {
5242
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5243
- /* @__PURE__ */ jsxRuntime.jsx(FloatingReactions_default, { reactions: floatingReactions }),
5244
- /* @__PURE__ */ jsxRuntime.jsx(
5245
- ScreenShareView_default,
5246
- {
5247
- sdk,
5248
- presenterId,
5249
- isLocal: presenterId === localParticipant?.id,
5250
- className: "flex flex-1 flex-col overflow-hidden p-4 pb-2"
5251
- }
5252
- ),
5253
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 overflow-x-auto px-4 pb-2", children: [
5254
- localParticipant?.id && /* @__PURE__ */ jsxRuntime.jsx(
5255
- ParticipantView_default,
5432
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5433
+ remoteParticipantWatchers,
5434
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5435
+ statusBanner,
5436
+ /* @__PURE__ */ jsxRuntime.jsx(FloatingReactions_default, { reactions: floatingReactions }),
5437
+ /* @__PURE__ */ jsxRuntime.jsx(
5438
+ ScreenShareView_default,
5256
5439
  {
5257
5440
  sdk,
5258
- participantId: localParticipant.id,
5259
- isLocal: true,
5260
- isHandRaised: !!raisedHands[localParticipant.id],
5261
- className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5441
+ presenterId,
5442
+ isLocal: presenterId === localParticipant?.id,
5443
+ className: "flex flex-1 flex-col overflow-hidden p-4 pb-2"
5262
5444
  }
5263
5445
  ),
5264
- remoteIds.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
5265
- ParticipantView_default,
5266
- {
5267
- sdk,
5268
- participantId: id,
5269
- fallbackName: peerName,
5270
- fallbackImage: peerAvatar,
5271
- isHandRaised: !!raisedHands[id],
5272
- className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5273
- },
5274
- id
5275
- ))
5276
- ] }),
5277
- controls
5278
- ] });
5279
- }
5280
- const showVideoGrid = mode === "video" || localWebcamOn;
5281
- if (!showVideoGrid) {
5282
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5283
- /* @__PURE__ */ jsxRuntime.jsx(FloatingReactions_default, { reactions: floatingReactions }),
5284
- /* @__PURE__ */ jsxRuntime.jsx(
5285
- AudioCallView_default,
5286
- {
5287
- sdk,
5288
- remoteParticipantId: remoteIds[0],
5289
- peerName,
5290
- peerAvatar,
5291
- isRemoteHandRaised: !!(remoteIds[0] && raisedHands[remoteIds[0]])
5292
- }
5293
- ),
5294
- controls
5295
- ] });
5296
- }
5297
- const focusedId = groupView === "speaker" ? activeSpeakerId && activeSpeakerId !== localParticipant?.id ? activeSpeakerId : remoteIds[0] || localParticipant?.id : null;
5298
- const stripIds = focusedId ? [localParticipant?.id, ...remoteIds].filter(
5299
- (id) => !!id && id !== focusedId
5300
- ) : [];
5301
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5302
- /* @__PURE__ */ jsxRuntime.jsx(FloatingReactions_default, { reactions: floatingReactions }),
5303
- isGroup && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-1 px-4 pt-2", children: [
5304
- /* @__PURE__ */ jsxRuntime.jsx(
5305
- "button",
5306
- {
5307
- type: "button",
5308
- "aria-label": "Grid view",
5309
- onClick: () => setGroupView("grid"),
5310
- className: cn(
5311
- "flex size-8 items-center justify-center rounded-lg transition-colors",
5312
- groupView === "grid" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5313
- ),
5314
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutGrid, { size: 16 })
5315
- }
5316
- ),
5317
- /* @__PURE__ */ jsxRuntime.jsx(
5318
- "button",
5319
- {
5320
- type: "button",
5321
- "aria-label": "Speaker view",
5322
- onClick: () => setGroupView("speaker"),
5323
- className: cn(
5324
- "flex size-8 items-center justify-center rounded-lg transition-colors",
5325
- groupView === "speaker" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5326
- ),
5327
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rows3, { size: 16 })
5328
- }
5329
- )
5330
- ] }),
5331
- focusedId ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-3 overflow-y-auto p-4", children: [
5332
- /* @__PURE__ */ jsxRuntime.jsx(
5333
- ParticipantView_default,
5334
- {
5335
- sdk,
5336
- participantId: focusedId,
5337
- isLocal: focusedId === localParticipant?.id,
5338
- fallbackName: peerName,
5339
- fallbackImage: peerAvatar,
5340
- isHandRaised: !!raisedHands[focusedId],
5341
- className: "min-h-[240px] flex-1"
5342
- }
5343
- ),
5344
- stripIds.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 overflow-x-auto", children: stripIds.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
5345
- ParticipantView_default,
5346
- {
5347
- sdk,
5348
- participantId: id,
5349
- isLocal: id === localParticipant?.id,
5350
- fallbackName: peerName,
5351
- fallbackImage: peerAvatar,
5352
- isHandRaised: !!raisedHands[id],
5353
- className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5354
- },
5355
- id
5356
- )) })
5357
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
5358
- "div",
5359
- {
5360
- className: "grid flex-1 auto-rows-fr gap-3 overflow-y-auto p-4",
5361
- style: {
5362
- gridTemplateColumns: `repeat(${Math.min(
5363
- Math.max(remoteIds.length + 1, 1),
5364
- 3
5365
- )}, minmax(0, 1fr))`
5366
- },
5367
- children: [
5446
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 overflow-x-auto px-4 pb-2", children: [
5368
5447
  localParticipant?.id && /* @__PURE__ */ jsxRuntime.jsx(
5369
5448
  ParticipantView_default,
5370
5449
  {
5371
5450
  sdk,
5372
5451
  participantId: localParticipant.id,
5373
5452
  isLocal: true,
5374
- isHandRaised: !!raisedHands[localParticipant.id]
5453
+ isHandRaised: !!raisedHands[localParticipant.id],
5454
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5375
5455
  }
5376
5456
  ),
5377
5457
  remoteIds.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -5381,37 +5461,160 @@ var init_MeetingView = __esm({
5381
5461
  participantId: id,
5382
5462
  fallbackName: peerName,
5383
5463
  fallbackImage: peerAvatar,
5384
- isHandRaised: !!raisedHands[id]
5464
+ isHandRaised: !!raisedHands[id],
5465
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5385
5466
  },
5386
5467
  id
5387
5468
  ))
5388
- ]
5389
- }
5390
- ),
5391
- controls
5392
- ] });
5393
- };
5394
- MeetingView_default = MeetingView;
5395
- }
5396
- });
5397
-
5398
- // src/call/CallSession.tsx
5399
- var CallSession_exports = {};
5400
- __export(CallSession_exports, {
5401
- default: () => CallSession_default
5402
- });
5403
- var CallSession, CallSession_default;
5404
- var init_CallSession = __esm({
5405
- "src/call/CallSession.tsx"() {
5406
- init_useStore();
5407
- init_videosdk_loader();
5408
- init_MeetingView();
5409
- CallSession = ({
5410
- meetingId,
5411
- token,
5412
- mode,
5413
- peerName,
5469
+ ] }),
5470
+ controls
5471
+ ] })
5472
+ ] });
5473
+ }
5474
+ if (!showVideoGrid) {
5475
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5476
+ remoteParticipantWatchers,
5477
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5478
+ statusBanner,
5479
+ /* @__PURE__ */ jsxRuntime.jsx(FloatingReactions_default, { reactions: floatingReactions }),
5480
+ /* @__PURE__ */ jsxRuntime.jsx(
5481
+ AudioCallView_default,
5482
+ {
5483
+ sdk,
5484
+ remoteParticipantId: remoteIds[0],
5485
+ peerName,
5486
+ peerAvatar,
5487
+ isRemoteHandRaised: !!(remoteIds[0] && raisedHands[remoteIds[0]])
5488
+ }
5489
+ ),
5490
+ controls
5491
+ ] })
5492
+ ] });
5493
+ }
5494
+ const focusedId = groupView === "speaker" ? activeSpeakerId && activeSpeakerId !== localParticipant?.id ? activeSpeakerId : remoteIds[0] || localParticipant?.id : null;
5495
+ const stripIds = focusedId ? [localParticipant?.id, ...remoteIds].filter(
5496
+ (id) => !!id && id !== focusedId
5497
+ ) : [];
5498
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5499
+ remoteParticipantWatchers,
5500
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5501
+ statusBanner,
5502
+ /* @__PURE__ */ jsxRuntime.jsx(FloatingReactions_default, { reactions: floatingReactions }),
5503
+ isGroup && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-1 px-4 pt-2", children: [
5504
+ /* @__PURE__ */ jsxRuntime.jsx(
5505
+ "button",
5506
+ {
5507
+ type: "button",
5508
+ "aria-label": "Grid view",
5509
+ onClick: () => setGroupView("grid"),
5510
+ className: cn(
5511
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
5512
+ groupView === "grid" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5513
+ ),
5514
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutGrid, { size: 16 })
5515
+ }
5516
+ ),
5517
+ /* @__PURE__ */ jsxRuntime.jsx(
5518
+ "button",
5519
+ {
5520
+ type: "button",
5521
+ "aria-label": "Speaker view",
5522
+ onClick: () => setGroupView("speaker"),
5523
+ className: cn(
5524
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
5525
+ groupView === "speaker" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5526
+ ),
5527
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rows3, { size: 16 })
5528
+ }
5529
+ )
5530
+ ] }),
5531
+ focusedId ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-3 overflow-y-auto p-4", children: [
5532
+ /* @__PURE__ */ jsxRuntime.jsx(
5533
+ ParticipantView_default,
5534
+ {
5535
+ sdk,
5536
+ participantId: focusedId,
5537
+ isLocal: focusedId === localParticipant?.id,
5538
+ fallbackName: peerName,
5539
+ fallbackImage: peerAvatar,
5540
+ isHandRaised: !!raisedHands[focusedId],
5541
+ className: "min-h-[240px] flex-1"
5542
+ }
5543
+ ),
5544
+ stripIds.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 overflow-x-auto", children: stripIds.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
5545
+ ParticipantView_default,
5546
+ {
5547
+ sdk,
5548
+ participantId: id,
5549
+ isLocal: id === localParticipant?.id,
5550
+ fallbackName: peerName,
5551
+ fallbackImage: peerAvatar,
5552
+ isHandRaised: !!raisedHands[id],
5553
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5554
+ },
5555
+ id
5556
+ )) })
5557
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
5558
+ "div",
5559
+ {
5560
+ className: "grid flex-1 auto-rows-fr gap-3 overflow-y-auto p-4",
5561
+ style: {
5562
+ gridTemplateColumns: `repeat(${Math.min(
5563
+ Math.max(remoteIds.length + 1, 1),
5564
+ 3
5565
+ )}, minmax(0, 1fr))`
5566
+ },
5567
+ children: [
5568
+ localParticipant?.id && /* @__PURE__ */ jsxRuntime.jsx(
5569
+ ParticipantView_default,
5570
+ {
5571
+ sdk,
5572
+ participantId: localParticipant.id,
5573
+ isLocal: true,
5574
+ isHandRaised: !!raisedHands[localParticipant.id]
5575
+ }
5576
+ ),
5577
+ remoteIds.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
5578
+ ParticipantView_default,
5579
+ {
5580
+ sdk,
5581
+ participantId: id,
5582
+ fallbackName: peerName,
5583
+ fallbackImage: peerAvatar,
5584
+ isHandRaised: !!raisedHands[id]
5585
+ },
5586
+ id
5587
+ ))
5588
+ ]
5589
+ }
5590
+ ),
5591
+ controls
5592
+ ] })
5593
+ ] });
5594
+ };
5595
+ MeetingView_default = MeetingView;
5596
+ }
5597
+ });
5598
+
5599
+ // src/call/CallSession.tsx
5600
+ var CallSession_exports = {};
5601
+ __export(CallSession_exports, {
5602
+ default: () => CallSession_default
5603
+ });
5604
+ var CallSession, CallSession_default;
5605
+ var init_CallSession = __esm({
5606
+ "src/call/CallSession.tsx"() {
5607
+ init_useStore();
5608
+ init_videosdk_loader();
5609
+ init_MeetingView();
5610
+ CallSession = ({
5611
+ meetingId,
5612
+ token,
5613
+ mode,
5614
+ peerName,
5414
5615
  peerAvatar,
5616
+ isChatOpen,
5617
+ onToggleChat,
5415
5618
  onError
5416
5619
  }) => {
5417
5620
  const [sdk, setSdk] = React11.useState(null);
@@ -5454,7 +5657,17 @@ var init_CallSession = __esm({
5454
5657
  token,
5455
5658
  reinitialiseMeetingOnConfigChange: false,
5456
5659
  joinWithoutUserInteraction: true,
5457
- children: /* @__PURE__ */ jsxRuntime.jsx(MeetingView_default, { sdk, mode, peerName, peerAvatar })
5660
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5661
+ MeetingView_default,
5662
+ {
5663
+ sdk,
5664
+ mode,
5665
+ peerName,
5666
+ peerAvatar,
5667
+ isChatOpen,
5668
+ onToggleChat
5669
+ }
5670
+ )
5458
5671
  }
5459
5672
  );
5460
5673
  };
@@ -5466,67 +5679,7 @@ var init_CallSession = __esm({
5466
5679
  init_useStore();
5467
5680
  init_api_service();
5468
5681
  init_socket_service2();
5469
-
5470
- // src/ui/tooltip.tsx
5471
- init_utils2();
5472
- function TooltipProvider({
5473
- delayDuration = 0,
5474
- ...props
5475
- }) {
5476
- return /* @__PURE__ */ jsxRuntime.jsx(
5477
- radixUi.Tooltip.Provider,
5478
- {
5479
- "data-slot": "tooltip-provider",
5480
- delayDuration,
5481
- ...props
5482
- }
5483
- );
5484
- }
5485
- function Tooltip({
5486
- ...props
5487
- }) {
5488
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Root, { "data-slot": "tooltip", ...props });
5489
- }
5490
- function TooltipTrigger({
5491
- ...props
5492
- }) {
5493
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
5494
- }
5495
- function TooltipContent({
5496
- className,
5497
- sideOffset = 0,
5498
- children,
5499
- showArrow = true,
5500
- arrowClassName,
5501
- ...props
5502
- }) {
5503
- return /* @__PURE__ */ jsxRuntime.jsx(radixUi.Tooltip.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
5504
- radixUi.Tooltip.Content,
5505
- {
5506
- "data-slot": "tooltip-content",
5507
- sideOffset,
5508
- className: cn(
5509
- "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",
5510
- className
5511
- ),
5512
- ...props,
5513
- children: [
5514
- children,
5515
- showArrow ? /* @__PURE__ */ jsxRuntime.jsx(
5516
- radixUi.Tooltip.Arrow,
5517
- {
5518
- className: cn(
5519
- "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]",
5520
- arrowClassName
5521
- )
5522
- }
5523
- ) : null
5524
- ]
5525
- }
5526
- ) });
5527
- }
5528
-
5529
- // src/chat/ChatContext.tsx
5682
+ init_tooltip();
5530
5683
  init_utils2();
5531
5684
 
5532
5685
  // src/config/chat-endpoints.ts
@@ -6447,11 +6600,353 @@ var useCallDuration = (startedAt) => {
6447
6600
  const pad = (n) => String(n).padStart(2, "0");
6448
6601
  return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
6449
6602
  };
6603
+
6604
+ // src/call/CallChatPanel.tsx
6605
+ init_useStore();
6606
+ init_store_helpers();
6607
+ init_utils2();
6608
+ init_button();
6609
+
6610
+ // src/chat/lib/chat-theme.ts
6611
+ init_utils2();
6612
+ var getChatScopeClass = (isDark) => cn("chat-theme-scope", isDark && "dark");
6613
+ var chatSheetContentClass = "flex w-full flex-col gap-0 p-0 bg-(--chat-surface) text-(--chat-text)";
6614
+ var chatSheetHeaderClass = "border-b border-(--chat-border) px-4 py-3 pr-11 text-left";
6615
+ var chatSheetBodyClass = "chat-message-scroll flex-1 overflow-y-auto bg-(--chat-surface) p-3";
6616
+ var chatSheetTitleClass = "text-[13px] font-semibold tracking-tight text-(--chat-text)";
6617
+ var chatSheetSubtitleClass = "text-[11px] text-(--chat-muted)";
6618
+ var chatDetailRowClass = "flex items-start justify-between gap-3 border-b border-(--chat-border) py-2.5 last:border-b-0";
6619
+ var chatDetailLabelClass = "text-[11px] text-(--chat-muted)";
6620
+ var chatDetailValueClass = "text-[13px] font-medium text-(--chat-text) text-right";
6621
+ var chatDialogContentClass = "overflow-hidden rounded-2xl border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-[var(--chat-shadow)]";
6622
+ var chatDropdownContentClass = "rounded-xl border border-(--chat-border) bg-(--chat-surface) text-(--chat-text) shadow-lg";
6623
+ 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)]";
6624
+ var chatOptionsMenuLabelClass = "px-2.5 pb-0.5 pt-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-(--chat-muted)";
6625
+ var chatOptionsMenuSeparatorClass = "my-1.5 h-px bg-(--chat-border)";
6626
+ 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)";
6627
+ 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)";
6628
+ 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)";
6629
+ var chatPopoverContentClass = "w-auto border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-2xl";
6630
+ 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)";
6631
+ 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)]";
6632
+ 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";
6633
+ 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)";
6634
+ var chatListItemClass = "transition-colors hover:bg-(--chat-hover)";
6635
+ 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";
6636
+ var chatDrawerSectionHeadingClass = "mb-1 px-0.5 text-[10px] font-semibold tracking-wide text-(--chat-muted)";
6637
+ var chatHeaderToolbarIconProps = { size: 14, strokeWidth: 1.75 };
6638
+ var chatSavedMessageModalIconClass = "shrink-0 text-(--chat-theme)";
6639
+ var chatHeaderToolbarIconLgProps = { size: 22, strokeWidth: 1.75 };
6640
+ var chatHeaderToolbarIconSmProps = { size: 11, strokeWidth: 1.75 };
6641
+ 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";
6642
+ var chatDrawerFileIconWrapClass = "flex size-8 shrink-0 items-center justify-center rounded-md bg-(--chat-hover)";
6643
+ var chatDrawerMediaFrameClass = "overflow-hidden rounded-md border border-(--chat-border)/70 bg-(--chat-elevated)";
6644
+ var buildChatThemeStyles = (themeColor) => ({
6645
+ "--chat-theme": themeColor,
6646
+ "--chat-theme-5": `${themeColor}0D`,
6647
+ "--chat-theme-10": `${themeColor}1A`,
6648
+ "--chat-theme-20": `${themeColor}33`,
6649
+ "--chat-theme-40": `${themeColor}66`,
6650
+ "--chat-theme-60": `${themeColor}99`,
6651
+ "--read-tick-color": themeColor.toLowerCase() === "#0000ff" || themeColor.toLowerCase().includes("007aff") ? "#00e5ff" : "#40C4FF"
6652
+ });
6653
+
6654
+ // src/chat/lib/chat-theme-storage.ts
6655
+ var STORAGE_KEY = "chat-color-mode";
6656
+ var loadChatColorMode = () => {
6657
+ if (typeof window === "undefined") return null;
6658
+ try {
6659
+ const stored = localStorage.getItem(STORAGE_KEY);
6660
+ return stored === "dark" || stored === "light" ? stored : null;
6661
+ } catch {
6662
+ return null;
6663
+ }
6664
+ };
6665
+ var saveChatColorMode = (mode) => {
6666
+ if (typeof window === "undefined") return;
6667
+ try {
6668
+ localStorage.setItem(STORAGE_KEY, mode);
6669
+ } catch {
6670
+ }
6671
+ };
6672
+ var CHAT_THEME_SHELL_CLASS = "chat-theme-shell";
6673
+ function withThemeTransition(apply) {
6674
+ if (typeof document === "undefined") {
6675
+ apply();
6676
+ return;
6677
+ }
6678
+ reactDom.flushSync(apply);
6679
+ }
6680
+
6681
+ // src/context/ChatThemeContext.tsx
6682
+ init_utils2();
6683
+ var ChatThemeContext = React11.createContext(null);
6684
+ var ChatThemeProvider = ({
6685
+ children,
6686
+ themeColor,
6687
+ colorMode,
6688
+ defaultColorMode = "light",
6689
+ onColorModeChange,
6690
+ showColorModeToggle = true
6691
+ }) => {
6692
+ const isControlled = colorMode !== void 0;
6693
+ const [internalMode, setInternalMode] = React11.useState(defaultColorMode);
6694
+ const [isThemeTransitionReady, setIsThemeTransitionReady] = React11.useState(false);
6695
+ const skipThemeTransitionRef = React11.useRef(true);
6696
+ React11.useLayoutEffect(() => {
6697
+ if (!isControlled) {
6698
+ const stored = loadChatColorMode();
6699
+ if (stored) {
6700
+ setInternalMode(stored);
6701
+ }
6702
+ }
6703
+ const frame = requestAnimationFrame(() => {
6704
+ skipThemeTransitionRef.current = false;
6705
+ setIsThemeTransitionReady(true);
6706
+ });
6707
+ return () => cancelAnimationFrame(frame);
6708
+ }, [isControlled]);
6709
+ const resolvedMode = isControlled ? colorMode : internalMode;
6710
+ const isDark = resolvedMode === "dark";
6711
+ const setColorMode = React11.useCallback(
6712
+ (mode) => {
6713
+ const apply = () => {
6714
+ if (!isControlled) {
6715
+ setInternalMode(mode);
6716
+ saveChatColorMode(mode);
6717
+ }
6718
+ onColorModeChange?.(mode);
6719
+ };
6720
+ if (skipThemeTransitionRef.current) {
6721
+ apply();
6722
+ return;
6723
+ }
6724
+ withThemeTransition(apply);
6725
+ },
6726
+ [isControlled, onColorModeChange]
6727
+ );
6728
+ const toggleColorMode = React11.useCallback(() => {
6729
+ setColorMode(resolvedMode === "dark" ? "light" : "dark");
6730
+ }, [resolvedMode, setColorMode]);
6731
+ const value = React11.useMemo(
6732
+ () => ({
6733
+ colorMode: resolvedMode,
6734
+ isDark,
6735
+ isThemeTransitionReady,
6736
+ themeColor,
6737
+ themeStyles: buildChatThemeStyles(themeColor),
6738
+ scopeClassName: cn(
6739
+ getChatScopeClass(isDark),
6740
+ !isThemeTransitionReady && "chat-theme-no-transition"
6741
+ ),
6742
+ setColorMode,
6743
+ toggleColorMode,
6744
+ showColorModeToggle
6745
+ }),
6746
+ [
6747
+ resolvedMode,
6748
+ isDark,
6749
+ isThemeTransitionReady,
6750
+ themeColor,
6751
+ setColorMode,
6752
+ toggleColorMode,
6753
+ showColorModeToggle
6754
+ ]
6755
+ );
6756
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatThemeContext.Provider, { value, children });
6757
+ };
6758
+ var useChatTheme = () => {
6759
+ const context = React11.useContext(ChatThemeContext);
6760
+ if (!context) {
6761
+ throw new Error("useChatTheme must be used within ChatThemeProvider");
6762
+ }
6763
+ return context;
6764
+ };
6765
+
6766
+ // src/chat/lib/chat-message-bubble.ts
6767
+ init_utils2();
6768
+ function getBubbleCornerClasses(isSender) {
6769
+ 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";
6770
+ }
6771
+ function getChatBubbleClasses({
6772
+ isSender = false,
6773
+ surface,
6774
+ className
6775
+ }) {
6776
+ const resolvedSurface = surface ?? (isSender ? "sent" : "received");
6777
+ const isNeutral = resolvedSurface === "neutral";
6778
+ return cn(
6779
+ "relative",
6780
+ getBubbleCornerClasses(isSender),
6781
+ resolvedSurface === "sent" && "chat-bubble-sent bg-(--chat-theme) text-white",
6782
+ resolvedSurface === "received" && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
6783
+ isNeutral && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
6784
+ className
6785
+ );
6786
+ }
6787
+ var CallChatPanel = ({ conversationId, isGroupCall, onClose, className }) => {
6788
+ const { scopeClassName, themeStyles } = useChatTheme();
6789
+ const rawMessages = exports.useChatStore((s) => s.messagesByConversation[conversationId]);
6790
+ const loggedUserDetails = exports.useChatStore((s) => s.loggedUserDetails);
6791
+ const conversations = exports.useChatStore((s) => s.conversations);
6792
+ const emitDmSend = exports.useChatStore((s) => s.emitDmSend);
6793
+ const emitGroupMessageSend2 = exports.useChatStore((s) => s.emitGroupMessageSend);
6794
+ const [text, setText] = React11.useState("");
6795
+ const [optimistic, setOptimistic] = React11.useState([]);
6796
+ const listEndRef = React11.useRef(null);
6797
+ const loggedUserId = String(loggedUserDetails?._id || "");
6798
+ const serverMessages = React11.useMemo(() => {
6799
+ return (rawMessages || []).filter((m) => !m?.isSystemMessage && !m?.isDeletedForEveryone).map((m) => {
6800
+ const senderId = String(m.sender?._id || m.sender || "");
6801
+ return {
6802
+ id: String(m._id || m.id || ""),
6803
+ content: String(m.content || ""),
6804
+ senderName: senderId === loggedUserId ? "You" : m.sender?.name || "Unknown",
6805
+ isOwn: senderId === loggedUserId,
6806
+ timestamp: m.createdAt || m.timestamp || (/* @__PURE__ */ new Date()).toISOString()
6807
+ };
6808
+ });
6809
+ }, [rawMessages, loggedUserId]);
6810
+ React11.useEffect(() => {
6811
+ if (!optimistic.length) return;
6812
+ setOptimistic(
6813
+ (prev) => prev.filter(
6814
+ (pending) => !serverMessages.some(
6815
+ (real) => real.isOwn && real.content === pending.content
6816
+ )
6817
+ )
6818
+ );
6819
+ }, [serverMessages]);
6820
+ const messages = React11.useMemo(
6821
+ () => [...serverMessages, ...optimistic].sort(
6822
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
6823
+ ),
6824
+ [serverMessages, optimistic]
6825
+ );
6826
+ React11.useEffect(() => {
6827
+ listEndRef.current?.scrollIntoView({ block: "end" });
6828
+ }, [messages.length]);
6829
+ const handleSend = () => {
6830
+ const content = text.trim();
6831
+ if (!content) return;
6832
+ setText("");
6833
+ setOptimistic((prev) => [
6834
+ ...prev,
6835
+ {
6836
+ id: `pending-${Date.now()}`,
6837
+ content,
6838
+ senderName: "You",
6839
+ isOwn: true,
6840
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6841
+ pending: true
6842
+ }
6843
+ ]);
6844
+ const payload = {
6845
+ tempId: `temp-${Date.now()}`,
6846
+ sender: loggedUserId,
6847
+ content,
6848
+ type: "text",
6849
+ conversationId
6850
+ };
6851
+ if (isGroupCall) {
6852
+ emitGroupMessageSend2(payload);
6853
+ } else {
6854
+ const conversation = findConversationById(conversations, conversationId);
6855
+ payload.receiver = getOtherParticipantId(conversation, loggedUserId);
6856
+ emitDmSend(payload);
6857
+ }
6858
+ };
6859
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6860
+ "div",
6861
+ {
6862
+ className: cn(
6863
+ scopeClassName,
6864
+ "flex flex-col border-l border-(--chat-border) bg-(--chat-surface) text-(--chat-text)",
6865
+ className
6866
+ ),
6867
+ style: themeStyles,
6868
+ children: [
6869
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetHeaderClass, "flex items-center justify-between"), children: [
6870
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: chatSheetTitleClass, children: "In-call chat" }),
6871
+ /* @__PURE__ */ jsxRuntime.jsx(
6872
+ "button",
6873
+ {
6874
+ type: "button",
6875
+ "aria-label": "Close chat panel",
6876
+ onClick: onClose,
6877
+ className: "flex size-7 items-center justify-center rounded-lg text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text)",
6878
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16 })
6879
+ }
6880
+ )
6881
+ ] }),
6882
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(chatSheetBodyClass, "space-y-3"), children: [
6883
+ messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pt-6 text-center text-xs text-(--chat-muted)", children: "No messages yet \u2014 say hi without leaving the call." }),
6884
+ messages.map((message) => /* @__PURE__ */ jsxRuntime.jsxs(
6885
+ "div",
6886
+ {
6887
+ className: cn("flex flex-col", message.isOwn ? "items-end" : "items-start"),
6888
+ children: [
6889
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-0.5 text-[11px] font-medium text-(--chat-muted)", children: message.isOwn ? "You" : message.senderName }),
6890
+ /* @__PURE__ */ jsxRuntime.jsx(
6891
+ "div",
6892
+ {
6893
+ className: cn(
6894
+ getChatBubbleClasses({ isSender: message.isOwn }),
6895
+ "max-w-[85%] px-3 py-1.5 text-sm break-words",
6896
+ message.pending && "opacity-60"
6897
+ ),
6898
+ children: message.content
6899
+ }
6900
+ )
6901
+ ]
6902
+ },
6903
+ message.id
6904
+ )),
6905
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: listEndRef })
6906
+ ] }),
6907
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-t border-(--chat-border) p-3", children: [
6908
+ /* @__PURE__ */ jsxRuntime.jsx(
6909
+ "input",
6910
+ {
6911
+ value: text,
6912
+ onChange: (e) => setText(e.target.value),
6913
+ onKeyDown: (e) => {
6914
+ if (e.key === "Enter" && !e.shiftKey) {
6915
+ e.preventDefault();
6916
+ handleSend();
6917
+ }
6918
+ },
6919
+ placeholder: "Message\u2026",
6920
+ className: cn(chatInputClass, "min-w-0 flex-1 rounded-full px-3.5 py-2 text-sm")
6921
+ }
6922
+ ),
6923
+ /* @__PURE__ */ jsxRuntime.jsx(
6924
+ Button,
6925
+ {
6926
+ variant: "ghost",
6927
+ size: "icon",
6928
+ "aria-label": "Send message",
6929
+ disabled: !text.trim(),
6930
+ onClick: handleSend,
6931
+ className: cn(
6932
+ "size-9 shrink-0 rounded-full transition-colors",
6933
+ text.trim() ? "bg-(--chat-theme) text-white hover:opacity-90" : "bg-(--chat-hover) text-(--chat-muted)"
6934
+ ),
6935
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { size: 16 })
6936
+ }
6937
+ )
6938
+ ] })
6939
+ ]
6940
+ }
6941
+ );
6942
+ };
6943
+ var CallChatPanel_default = CallChatPanel;
6450
6944
  var CallSession2 = React11__namespace.default.lazy(() => Promise.resolve().then(() => (init_CallSession(), CallSession_exports)));
6451
6945
  var CallOverlay = () => {
6452
6946
  const uiStatus = exports.useCallStore((s) => s.uiStatus);
6453
6947
  const mode = exports.useCallStore((s) => s.mode);
6454
6948
  const callId = exports.useCallStore((s) => s.callId);
6949
+ const conversationId = exports.useCallStore((s) => s.conversationId);
6455
6950
  const meetingId = exports.useCallStore((s) => s.meetingId);
6456
6951
  const token = exports.useCallStore((s) => s.token);
6457
6952
  const error = exports.useCallStore((s) => s.error);
@@ -6463,112 +6958,111 @@ var CallOverlay = () => {
6463
6958
  const reset = exports.useCallStore((s) => s.reset);
6464
6959
  const notifyServerCallFailed = exports.useCallStore((s) => s.notifyServerCallFailed);
6465
6960
  const [sessionError, setSessionError] = React11.useState(null);
6961
+ const [isChatOpen, setIsChatOpen] = React11.useState(false);
6466
6962
  const secondsLeft = useRingCountdown(uiStatus === "ringing-out", callId);
6467
6963
  const duration = useCallDuration(callStartedAt);
6468
6964
  React11.useEffect(() => {
6469
6965
  setSessionError(null);
6966
+ setIsChatOpen(false);
6470
6967
  }, [callId]);
6471
6968
  React11.useEffect(() => {
6472
6969
  if (sessionError) notifyServerCallFailed();
6473
6970
  }, [sessionError, notifyServerCallFailed]);
6474
6971
  const isOpen = uiStatus !== "idle" && uiStatus !== "incoming";
6475
- if (!isOpen || typeof document === "undefined") return null;
6972
+ if (!isOpen) return null;
6476
6973
  const displayError = error || sessionError;
6477
6974
  const peerName = peer?.name || caller?.name || "";
6478
6975
  const peerAvatar = peer?.avatar || caller?.profileImage || void 0;
6479
- const isVideo = mode === "video";
6480
- return reactDom.createPortal(
6481
- /* @__PURE__ */ jsxRuntime.jsx(
6482
- "div",
6483
- {
6484
- style: {
6485
- position: "fixed",
6486
- inset: 0,
6487
- width: "100vw",
6488
- height: "100vh",
6489
- zIndex: 2147483647
6490
- },
6491
- className: "flex flex-col overflow-hidden bg-gradient-to-b from-neutral-900 via-neutral-950 to-black text-white",
6492
- children: displayError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6 text-center", children: [
6493
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
6494
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: reset, children: [
6495
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, className: "mr-1" }),
6496
- " Close"
6497
- ] })
6498
- ] }) : uiStatus === "in-call" && meetingId && token && mode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col", children: [
6499
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6500
- isGroupCall && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { size: 14, className: "text-white/50" }),
6501
- peerName && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-white/90", children: peerName }),
6502
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6503
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: duration })
6504
- ] }),
6505
- /* @__PURE__ */ jsxRuntime.jsx(
6506
- React11__namespace.default.Suspense,
6507
- {
6508
- fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6509
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-8 animate-spin" }),
6510
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6511
- ] }),
6512
- children: /* @__PURE__ */ jsxRuntime.jsx(
6513
- CallSession2,
6514
- {
6515
- meetingId,
6516
- token,
6517
- mode,
6518
- peerName,
6519
- peerAvatar,
6520
- onError: setSessionError
6521
- }
6522
- )
6523
- }
6524
- )
6525
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6526
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
6527
- uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6528
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
6529
- /* @__PURE__ */ jsxRuntime.jsx(
6530
- "span",
6531
- {
6532
- className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
6533
- style: { animationDelay: "0.6s" }
6534
- }
6535
- )
6536
- ] }),
6537
- peerName ? /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
6538
- /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
6539
- /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
6540
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex size-28 items-center justify-center rounded-full bg-white/10", children: isVideo ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 36 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 36 }) })
6541
- ] }),
6542
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6543
- peerName && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center justify-center gap-1.5 text-xl font-semibold tracking-tight", children: [
6544
- isGroupCall && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { size: 16, className: "text-white/50" }),
6545
- peerName
6546
- ] }),
6547
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
6548
- uiStatus === "creating" && "Starting call\u2026",
6549
- uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
6550
- uiStatus === "joining" && "Connecting\u2026"
6551
- ] })
6976
+ const isVideo = mode === "video";
6977
+ return /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6 text-center", children: [
6978
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
6979
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: reset, children: [
6980
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, className: "mr-1" }),
6981
+ " Close"
6982
+ ] })
6983
+ ] }) : uiStatus === "in-call" && meetingId && token && mode && conversationId ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [
6984
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6985
+ isGroupCall && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { size: 14, className: "text-white/50" }),
6986
+ peerName && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-white/90", children: peerName }),
6987
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6988
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: duration })
6989
+ ] }),
6990
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [
6991
+ /* @__PURE__ */ jsxRuntime.jsx(
6992
+ React11__namespace.default.Suspense,
6993
+ {
6994
+ fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6995
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-8 animate-spin" }),
6996
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6552
6997
  ] }),
6553
- uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
6554
- /* @__PURE__ */ jsxRuntime.jsx(
6555
- Button,
6556
- {
6557
- variant: "ghost",
6558
- size: "icon",
6559
- 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",
6560
- "aria-label": "Cancel call",
6561
- onClick: cancelCall,
6562
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 24 })
6563
- }
6564
- ),
6565
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
6566
- ] })
6567
- ] })
6568
- }
6569
- ),
6570
- document.body
6571
- );
6998
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6999
+ CallSession2,
7000
+ {
7001
+ meetingId,
7002
+ token,
7003
+ mode,
7004
+ peerName,
7005
+ peerAvatar,
7006
+ isChatOpen,
7007
+ onToggleChat: () => setIsChatOpen((v) => !v),
7008
+ onError: setSessionError
7009
+ }
7010
+ )
7011
+ }
7012
+ ),
7013
+ isChatOpen && /* @__PURE__ */ jsxRuntime.jsx(
7014
+ CallChatPanel_default,
7015
+ {
7016
+ conversationId,
7017
+ isGroupCall,
7018
+ onClose: () => setIsChatOpen(false),
7019
+ className: "hidden w-80 shrink-0 sm:flex"
7020
+ }
7021
+ )
7022
+ ] })
7023
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
7024
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
7025
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7026
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
7027
+ /* @__PURE__ */ jsxRuntime.jsx(
7028
+ "span",
7029
+ {
7030
+ className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
7031
+ style: { animationDelay: "0.6s" }
7032
+ }
7033
+ )
7034
+ ] }),
7035
+ peerName ? /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
7036
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
7037
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
7038
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex size-28 items-center justify-center rounded-full bg-white/10", children: isVideo ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 36 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 36 }) })
7039
+ ] }),
7040
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7041
+ peerName && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center justify-center gap-1.5 text-xl font-semibold tracking-tight", children: [
7042
+ isGroupCall && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { size: 16, className: "text-white/50" }),
7043
+ peerName
7044
+ ] }),
7045
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
7046
+ uiStatus === "creating" && "Starting call\u2026",
7047
+ uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
7048
+ uiStatus === "joining" && "Connecting\u2026"
7049
+ ] })
7050
+ ] }),
7051
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
7052
+ /* @__PURE__ */ jsxRuntime.jsx(
7053
+ Button,
7054
+ {
7055
+ variant: "ghost",
7056
+ size: "icon",
7057
+ 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",
7058
+ "aria-label": "Cancel call",
7059
+ onClick: cancelCall,
7060
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 24 })
7061
+ }
7062
+ ),
7063
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
7064
+ ] })
7065
+ ] }) });
6572
7066
  };
6573
7067
  var CallOverlay_default = CallOverlay;
6574
7068
 
@@ -6904,162 +7398,6 @@ function AlertDialogCancel({
6904
7398
 
6905
7399
  // src/chat/ChatOverlay.tsx
6906
7400
  init_utils2();
6907
-
6908
- // src/chat/lib/chat-theme.ts
6909
- init_utils2();
6910
- var getChatScopeClass = (isDark) => cn("chat-theme-scope", isDark && "dark");
6911
- var chatSheetContentClass = "flex w-full flex-col gap-0 p-0 bg-(--chat-surface) text-(--chat-text)";
6912
- var chatSheetHeaderClass = "border-b border-(--chat-border) px-4 py-3 pr-11 text-left";
6913
- var chatSheetBodyClass = "chat-message-scroll flex-1 overflow-y-auto bg-(--chat-surface) p-3";
6914
- var chatSheetTitleClass = "text-[13px] font-semibold tracking-tight text-(--chat-text)";
6915
- var chatSheetSubtitleClass = "text-[11px] text-(--chat-muted)";
6916
- var chatDetailRowClass = "flex items-start justify-between gap-3 border-b border-(--chat-border) py-2.5 last:border-b-0";
6917
- var chatDetailLabelClass = "text-[11px] text-(--chat-muted)";
6918
- var chatDetailValueClass = "text-[13px] font-medium text-(--chat-text) text-right";
6919
- var chatDialogContentClass = "overflow-hidden rounded-2xl border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-[var(--chat-shadow)]";
6920
- var chatDropdownContentClass = "rounded-xl border border-(--chat-border) bg-(--chat-surface) text-(--chat-text) shadow-lg";
6921
- 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)]";
6922
- var chatOptionsMenuLabelClass = "px-2.5 pb-0.5 pt-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-(--chat-muted)";
6923
- var chatOptionsMenuSeparatorClass = "my-1.5 h-px bg-(--chat-border)";
6924
- 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)";
6925
- 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)";
6926
- 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)";
6927
- var chatPopoverContentClass = "w-auto border border-(--chat-border) bg-(--chat-surface) p-0 text-(--chat-text) shadow-2xl";
6928
- 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)";
6929
- 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)]";
6930
- 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";
6931
- 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)";
6932
- var chatListItemClass = "transition-colors hover:bg-(--chat-hover)";
6933
- 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";
6934
- var chatDrawerSectionHeadingClass = "mb-1 px-0.5 text-[10px] font-semibold tracking-wide text-(--chat-muted)";
6935
- var chatHeaderToolbarIconProps = { size: 14, strokeWidth: 1.75 };
6936
- var chatSavedMessageModalIconClass = "shrink-0 text-(--chat-theme)";
6937
- var chatHeaderToolbarIconLgProps = { size: 22, strokeWidth: 1.75 };
6938
- var chatHeaderToolbarIconSmProps = { size: 11, strokeWidth: 1.75 };
6939
- 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";
6940
- var chatDrawerFileIconWrapClass = "flex size-8 shrink-0 items-center justify-center rounded-md bg-(--chat-hover)";
6941
- var chatDrawerMediaFrameClass = "overflow-hidden rounded-md border border-(--chat-border)/70 bg-(--chat-elevated)";
6942
- var buildChatThemeStyles = (themeColor) => ({
6943
- "--chat-theme": themeColor,
6944
- "--chat-theme-5": `${themeColor}0D`,
6945
- "--chat-theme-10": `${themeColor}1A`,
6946
- "--chat-theme-20": `${themeColor}33`,
6947
- "--chat-theme-40": `${themeColor}66`,
6948
- "--chat-theme-60": `${themeColor}99`,
6949
- "--read-tick-color": themeColor.toLowerCase() === "#0000ff" || themeColor.toLowerCase().includes("007aff") ? "#00e5ff" : "#40C4FF"
6950
- });
6951
-
6952
- // src/chat/lib/chat-theme-storage.ts
6953
- var STORAGE_KEY = "chat-color-mode";
6954
- var loadChatColorMode = () => {
6955
- if (typeof window === "undefined") return null;
6956
- try {
6957
- const stored = localStorage.getItem(STORAGE_KEY);
6958
- return stored === "dark" || stored === "light" ? stored : null;
6959
- } catch {
6960
- return null;
6961
- }
6962
- };
6963
- var saveChatColorMode = (mode) => {
6964
- if (typeof window === "undefined") return;
6965
- try {
6966
- localStorage.setItem(STORAGE_KEY, mode);
6967
- } catch {
6968
- }
6969
- };
6970
- var CHAT_THEME_SHELL_CLASS = "chat-theme-shell";
6971
- function withThemeTransition(apply) {
6972
- if (typeof document === "undefined") {
6973
- apply();
6974
- return;
6975
- }
6976
- reactDom.flushSync(apply);
6977
- }
6978
-
6979
- // src/context/ChatThemeContext.tsx
6980
- init_utils2();
6981
- var ChatThemeContext = React11.createContext(null);
6982
- var ChatThemeProvider = ({
6983
- children,
6984
- themeColor,
6985
- colorMode,
6986
- defaultColorMode = "light",
6987
- onColorModeChange,
6988
- showColorModeToggle = true
6989
- }) => {
6990
- const isControlled = colorMode !== void 0;
6991
- const [internalMode, setInternalMode] = React11.useState(defaultColorMode);
6992
- const [isThemeTransitionReady, setIsThemeTransitionReady] = React11.useState(false);
6993
- const skipThemeTransitionRef = React11.useRef(true);
6994
- React11.useLayoutEffect(() => {
6995
- if (!isControlled) {
6996
- const stored = loadChatColorMode();
6997
- if (stored) {
6998
- setInternalMode(stored);
6999
- }
7000
- }
7001
- const frame = requestAnimationFrame(() => {
7002
- skipThemeTransitionRef.current = false;
7003
- setIsThemeTransitionReady(true);
7004
- });
7005
- return () => cancelAnimationFrame(frame);
7006
- }, [isControlled]);
7007
- const resolvedMode = isControlled ? colorMode : internalMode;
7008
- const isDark = resolvedMode === "dark";
7009
- const setColorMode = React11.useCallback(
7010
- (mode) => {
7011
- const apply = () => {
7012
- if (!isControlled) {
7013
- setInternalMode(mode);
7014
- saveChatColorMode(mode);
7015
- }
7016
- onColorModeChange?.(mode);
7017
- };
7018
- if (skipThemeTransitionRef.current) {
7019
- apply();
7020
- return;
7021
- }
7022
- withThemeTransition(apply);
7023
- },
7024
- [isControlled, onColorModeChange]
7025
- );
7026
- const toggleColorMode = React11.useCallback(() => {
7027
- setColorMode(resolvedMode === "dark" ? "light" : "dark");
7028
- }, [resolvedMode, setColorMode]);
7029
- const value = React11.useMemo(
7030
- () => ({
7031
- colorMode: resolvedMode,
7032
- isDark,
7033
- isThemeTransitionReady,
7034
- themeColor,
7035
- themeStyles: buildChatThemeStyles(themeColor),
7036
- scopeClassName: cn(
7037
- getChatScopeClass(isDark),
7038
- !isThemeTransitionReady && "chat-theme-no-transition"
7039
- ),
7040
- setColorMode,
7041
- toggleColorMode,
7042
- showColorModeToggle
7043
- }),
7044
- [
7045
- resolvedMode,
7046
- isDark,
7047
- isThemeTransitionReady,
7048
- themeColor,
7049
- setColorMode,
7050
- toggleColorMode,
7051
- showColorModeToggle
7052
- ]
7053
- );
7054
- return /* @__PURE__ */ jsxRuntime.jsx(ChatThemeContext.Provider, { value, children });
7055
- };
7056
- var useChatTheme = () => {
7057
- const context = React11.useContext(ChatThemeContext);
7058
- if (!context) {
7059
- throw new Error("useChatTheme must be used within ChatThemeProvider");
7060
- }
7061
- return context;
7062
- };
7063
7401
  var ChatSheetContent = ({
7064
7402
  className,
7065
7403
  style,
@@ -7115,6 +7453,66 @@ init_avatar();
7115
7453
  init_button();
7116
7454
  init_utils2();
7117
7455
  init_call_store();
7456
+
7457
+ // src/call/ringtone.ts
7458
+ var audioCtx = null;
7459
+ var scheduledTimer = null;
7460
+ var activeOscillators = [];
7461
+ var RING_ON_MS = 1200;
7462
+ var RING_OFF_MS = 2400;
7463
+ var RING_FREQUENCIES = [440, 480];
7464
+ var getContext = () => {
7465
+ if (typeof window === "undefined") return null;
7466
+ const Ctor = window.AudioContext || window.webkitAudioContext;
7467
+ if (!Ctor) return null;
7468
+ if (!audioCtx || audioCtx.state === "closed") audioCtx = new Ctor();
7469
+ return audioCtx;
7470
+ };
7471
+ var stopOscillators = () => {
7472
+ activeOscillators.forEach((osc) => {
7473
+ try {
7474
+ osc.stop();
7475
+ } catch {
7476
+ }
7477
+ });
7478
+ activeOscillators = [];
7479
+ };
7480
+ var ringBurst = (ctx) => {
7481
+ const onSeconds = RING_ON_MS / 1e3;
7482
+ const gain = ctx.createGain();
7483
+ gain.gain.setValueAtTime(0, ctx.currentTime);
7484
+ gain.gain.linearRampToValueAtTime(0.15, ctx.currentTime + 0.05);
7485
+ gain.gain.setValueAtTime(0.15, ctx.currentTime + onSeconds - 0.05);
7486
+ gain.gain.linearRampToValueAtTime(0, ctx.currentTime + onSeconds);
7487
+ gain.connect(ctx.destination);
7488
+ RING_FREQUENCIES.forEach((freq) => {
7489
+ const osc = ctx.createOscillator();
7490
+ osc.type = "sine";
7491
+ osc.frequency.value = freq;
7492
+ osc.connect(gain);
7493
+ osc.start();
7494
+ osc.stop(ctx.currentTime + onSeconds);
7495
+ activeOscillators.push(osc);
7496
+ });
7497
+ };
7498
+ var playDefaultRingtone = () => {
7499
+ if (scheduledTimer) return;
7500
+ const ctx = getContext();
7501
+ if (!ctx) return;
7502
+ ctx.resume().catch(() => void 0);
7503
+ const cycle = () => {
7504
+ ringBurst(ctx);
7505
+ scheduledTimer = setTimeout(cycle, RING_ON_MS + RING_OFF_MS);
7506
+ };
7507
+ cycle();
7508
+ };
7509
+ var stopDefaultRingtone = () => {
7510
+ if (scheduledTimer) {
7511
+ clearTimeout(scheduledTimer);
7512
+ scheduledTimer = null;
7513
+ }
7514
+ stopOscillators();
7515
+ };
7118
7516
  var IncomingCallDialog = ({ ringtoneUrl }) => {
7119
7517
  const uiStatus = exports.useCallStore((s) => s.uiStatus);
7120
7518
  const callId = exports.useCallStore((s) => s.callId);
@@ -7137,6 +7535,12 @@ var IncomingCallDialog = ({ ringtoneUrl }) => {
7137
7535
  el.pause();
7138
7536
  }
7139
7537
  }, [isOpen, ringtoneUrl]);
7538
+ React11.useEffect(() => {
7539
+ if (ringtoneUrl) return;
7540
+ if (isOpen) playDefaultRingtone();
7541
+ else stopDefaultRingtone();
7542
+ return () => stopDefaultRingtone();
7543
+ }, [isOpen, ringtoneUrl]);
7140
7544
  if (!isOpen) return null;
7141
7545
  const callerName = caller?.name || "Unknown caller";
7142
7546
  const name = isGroupCall ? peer?.name || callerName : callerName;
@@ -7404,7 +7808,7 @@ var Chat = ({
7404
7808
  }),
7405
7809
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
7406
7810
  );
7407
- return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chat-container-root flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsxRuntime.jsxs(ChatEffectiveSettingsProvider, { enabled: true, children: [
7811
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(ChatCustomizationProvider, { customization, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chat-container-root relative flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden", children: isSessionReady ? /* @__PURE__ */ jsxRuntime.jsxs(ChatEffectiveSettingsProvider, { enabled: true, children: [
7408
7812
  children,
7409
7813
  /* @__PURE__ */ jsxRuntime.jsx(CallLayer_default, {})
7410
7814
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
@@ -7861,6 +8265,7 @@ init_avatar();
7861
8265
 
7862
8266
  // src/chat/ChatThemeToggle.tsx
7863
8267
  init_button();
8268
+ init_tooltip();
7864
8269
  init_utils2();
7865
8270
  var ChatThemeToggle = ({ className }) => {
7866
8271
  const { isDark, toggleColorMode } = useChatTheme();
@@ -7918,6 +8323,7 @@ var buildLoggedUserProfile = (loggedUserDetails) => ({
7918
8323
 
7919
8324
  // src/chat/sidebar/create-chat-dialog/CreateChatDialog.tsx
7920
8325
  init_button();
8326
+ init_tooltip();
7921
8327
  init_utils2();
7922
8328
 
7923
8329
  // src/chat/sidebar/create-chat-dialog/useCreateChatDialog.ts
@@ -8368,6 +8774,9 @@ function GroupCreateTab({
8368
8774
  ) })
8369
8775
  ] });
8370
8776
  }
8777
+
8778
+ // src/chat/components/AdminPermissionTooltip.tsx
8779
+ init_tooltip();
8371
8780
  init_utils2();
8372
8781
  function AdminPermissionTooltip({
8373
8782
  disabled,
@@ -8994,6 +9403,7 @@ var LoggedUserSettingsContent_default = LoggedUserSettingsContent;
8994
9403
 
8995
9404
  // src/chat/sidebar/LoggedUserSettingsTrigger.tsx
8996
9405
  init_button();
9406
+ init_tooltip();
8997
9407
  init_utils2();
8998
9408
  var LoggedUserSettingsTrigger = ({
8999
9409
  onClick,
@@ -10660,6 +11070,7 @@ init_call_store();
10660
11070
  init_utils2();
10661
11071
 
10662
11072
  // src/chat/header/header-right/OptionsMenuItem.tsx
11073
+ init_tooltip();
10663
11074
  init_utils2();
10664
11075
  function OptionsMenuItem({
10665
11076
  icon: Icon,
@@ -11706,28 +12117,6 @@ function MessageForwardedLabel({ isSender, className, overlay }) {
11706
12117
  }
11707
12118
  );
11708
12119
  }
11709
-
11710
- // src/chat/lib/chat-message-bubble.ts
11711
- init_utils2();
11712
- function getBubbleCornerClasses(isSender) {
11713
- 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";
11714
- }
11715
- function getChatBubbleClasses({
11716
- isSender = false,
11717
- surface,
11718
- className
11719
- }) {
11720
- const resolvedSurface = surface ?? (isSender ? "sent" : "received");
11721
- const isNeutral = resolvedSurface === "neutral";
11722
- return cn(
11723
- "relative",
11724
- getBubbleCornerClasses(isSender),
11725
- resolvedSurface === "sent" && "chat-bubble-sent bg-(--chat-theme) text-white",
11726
- resolvedSurface === "received" && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
11727
- isNeutral && "chat-bubble-received border border-(--chat-border)/60 bg-(--chat-incoming-bubble) text-(--chat-incoming-text)",
11728
- className
11729
- );
11730
- }
11731
12120
  init_utils2();
11732
12121
  init_client();
11733
12122
  function getPreviewKind(name) {
@@ -16342,6 +16731,7 @@ init_avatar();
16342
16731
 
16343
16732
  // src/chat/message/MessageReactions.tsx
16344
16733
  init_utils2();
16734
+ init_tooltip();
16345
16735
  var MessageReactions = ({
16346
16736
  groupedReactions,
16347
16737
  reactions,
@@ -20754,6 +21144,7 @@ init_normalize_helpers();
20754
21144
  init_useStore();
20755
21145
  init_open_conversation();
20756
21146
  var RETRY_DELAYS_MS = [1e3, 3e3, 8e3, 2e4];
21147
+ var CHAT_HEARTBEAT_INTERVAL_MS = 15e3;
20757
21148
  var recentToastIds = /* @__PURE__ */ new Set();
20758
21149
  var rememberToastId = (id) => {
20759
21150
  if (!id) return true;
@@ -20976,6 +21367,36 @@ var ForegroundPushBridge = ({ onPush }) => {
20976
21367
  window.removeEventListener(PUSH_CHANGED_EVENT, onPushChanged);
20977
21368
  };
20978
21369
  }, []);
21370
+ React11.useEffect(() => {
21371
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
21372
+ return;
21373
+ }
21374
+ let interval = null;
21375
+ const sendHeartbeat = () => {
21376
+ navigator.serviceWorker.getRegistration().then(
21377
+ (registration) => registration?.active?.postMessage({ type: CHAT_HEARTBEAT_SW_TYPE })
21378
+ ).catch(() => void 0);
21379
+ };
21380
+ const startHeartbeat = () => {
21381
+ if (interval || !getStoredPushToken()) return;
21382
+ sendHeartbeat();
21383
+ interval = setInterval(sendHeartbeat, CHAT_HEARTBEAT_INTERVAL_MS);
21384
+ };
21385
+ const stopHeartbeat = () => {
21386
+ if (interval) clearInterval(interval);
21387
+ interval = null;
21388
+ };
21389
+ startHeartbeat();
21390
+ const onPushChanged = (event) => {
21391
+ if (event.detail?.enabled) startHeartbeat();
21392
+ else stopHeartbeat();
21393
+ };
21394
+ window.addEventListener(PUSH_CHANGED_EVENT, onPushChanged);
21395
+ return () => {
21396
+ stopHeartbeat();
21397
+ window.removeEventListener(PUSH_CHANGED_EVENT, onPushChanged);
21398
+ };
21399
+ }, []);
20979
21400
  React11.useEffect(() => {
20980
21401
  const onOpen = (event) => {
20981
21402
  const id = String(