@realtimexsco/live-chat 1.5.4 → 1.5.5

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
@@ -10,7 +10,7 @@ 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
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';
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';
@@ -4611,6 +4611,67 @@ var init_utils2 = __esm({
4611
4611
  "src/lib/utils.ts"() {
4612
4612
  }
4613
4613
  });
4614
+ function TooltipProvider({
4615
+ delayDuration = 0,
4616
+ ...props
4617
+ }) {
4618
+ return /* @__PURE__ */ jsx(
4619
+ Tooltip$1.Provider,
4620
+ {
4621
+ "data-slot": "tooltip-provider",
4622
+ delayDuration,
4623
+ ...props
4624
+ }
4625
+ );
4626
+ }
4627
+ function Tooltip({
4628
+ ...props
4629
+ }) {
4630
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
4631
+ }
4632
+ function TooltipTrigger({
4633
+ ...props
4634
+ }) {
4635
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
4636
+ }
4637
+ function TooltipContent({
4638
+ className,
4639
+ sideOffset = 0,
4640
+ children,
4641
+ showArrow = true,
4642
+ arrowClassName,
4643
+ ...props
4644
+ }) {
4645
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsxs(
4646
+ Tooltip$1.Content,
4647
+ {
4648
+ "data-slot": "tooltip-content",
4649
+ sideOffset,
4650
+ className: cn(
4651
+ "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",
4652
+ className
4653
+ ),
4654
+ ...props,
4655
+ children: [
4656
+ children,
4657
+ showArrow ? /* @__PURE__ */ jsx(
4658
+ Tooltip$1.Arrow,
4659
+ {
4660
+ className: cn(
4661
+ "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]",
4662
+ arrowClassName
4663
+ )
4664
+ }
4665
+ ) : null
4666
+ ]
4667
+ }
4668
+ ) });
4669
+ }
4670
+ var init_tooltip = __esm({
4671
+ "src/ui/tooltip.tsx"() {
4672
+ init_utils2();
4673
+ }
4674
+ });
4614
4675
  function Avatar({
4615
4676
  className,
4616
4677
  size = "default",
@@ -4809,7 +4870,7 @@ var init_ParticipantView = __esm({
4809
4870
  }
4810
4871
  ),
4811
4872
  !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 }) }),
4873
+ 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
4874
  !showVideo && /* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "size-20", children: [
4814
4875
  /* @__PURE__ */ jsx(AvatarImage, { src: fallbackImage, alt: name }),
4815
4876
  /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-xl font-medium text-white", children: name.charAt(0).toUpperCase() })
@@ -4866,7 +4927,7 @@ var init_AudioCallView = __esm({
4866
4927
  }
4867
4928
  ),
4868
4929
  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 }) })
4930
+ 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
4931
  ] }),
4871
4932
  /* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tracking-tight", children: name })
4872
4933
  ] });
@@ -4890,13 +4951,13 @@ var init_ScreenShareView = __esm({
4890
4951
  el.srcObject = null;
4891
4952
  }
4892
4953
  }, [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 }),
4954
+ 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: [
4955
+ /* @__PURE__ */ jsx("video", { ref: videoRef, playsInline: true, muted: isLocal, className: "max-h-full max-w-full object-contain" }),
4956
+ /* @__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: [
4957
+ /* @__PURE__ */ jsx(MonitorUp, { size: 13, className: "text-indigo-300" }),
4896
4958
  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
- ] });
4959
+ ] })
4960
+ ] }) });
4900
4961
  };
4901
4962
  ScreenShareView_default = ScreenShareView;
4902
4963
  }
@@ -4936,22 +4997,46 @@ var init_popover = __esm({
4936
4997
  init_utils2();
4937
4998
  }
4938
4999
  });
4939
- var circleBtn, idleBtnClass, activeBtnClass, QUICK_REACTIONS, CallControls, CallControls_default;
5000
+ var circleBtn, idleBtnClass, activeBtnClass, ControlButton, QUICK_REACTIONS, CallControls, CallControls_default;
4940
5001
  var init_CallControls = __esm({
4941
5002
  "src/call/CallControls.tsx"() {
4942
5003
  init_button();
4943
5004
  init_popover();
5005
+ init_tooltip();
4944
5006
  init_utils2();
4945
5007
  init_call_store();
4946
5008
  circleBtn = "size-13 rounded-full text-white transition-all duration-150 disabled:opacity-40 hover:scale-105 active:scale-95";
4947
5009
  idleBtnClass = "bg-white/10 hover:bg-white/20";
4948
5010
  activeBtnClass = "bg-white text-neutral-900 hover:bg-white/90";
5011
+ ControlButton = ({
5012
+ icon,
5013
+ label,
5014
+ active,
5015
+ activeClassName = activeBtnClass,
5016
+ className,
5017
+ onClick
5018
+ }) => /* @__PURE__ */ jsxs(Tooltip, { children: [
5019
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5020
+ Button,
5021
+ {
5022
+ variant: "ghost",
5023
+ size: "icon",
5024
+ "aria-label": label,
5025
+ onClick,
5026
+ className: cn(circleBtn, active ? activeClassName : idleBtnClass, className),
5027
+ children: icon
5028
+ }
5029
+ ) }),
5030
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: label })
5031
+ ] });
4949
5032
  QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F44F}", "\u{1F389}"];
4950
5033
  CallControls = ({
4951
5034
  sdk,
4952
5035
  onReaction,
4953
5036
  isHandRaised,
4954
- onToggleRaiseHand
5037
+ onToggleRaiseHand,
5038
+ isChatOpen,
5039
+ onToggleChat
4955
5040
  }) => {
4956
5041
  const {
4957
5042
  toggleMic,
@@ -4968,73 +5053,80 @@ var init_CallControls = __esm({
4968
5053
  endCall();
4969
5054
  };
4970
5055
  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: [
5056
+ /* @__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
5057
  /* @__PURE__ */ jsx(
4973
- Button,
5058
+ ControlButton,
4974
5059
  {
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 })
5060
+ icon: localMicOn ? /* @__PURE__ */ jsx(Mic, { size: 20 }) : /* @__PURE__ */ jsx(MicOff, { size: 20 }),
5061
+ label: localMicOn ? "Mute microphone" : "Unmute microphone",
5062
+ active: !localMicOn,
5063
+ onClick: () => toggleMic()
4981
5064
  }
4982
5065
  ),
4983
5066
  /* @__PURE__ */ jsx(
4984
- Button,
5067
+ ControlButton,
4985
5068
  {
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 })
5069
+ icon: localWebcamOn ? /* @__PURE__ */ jsx(Video, { size: 20 }) : /* @__PURE__ */ jsx(VideoOff, { size: 20 }),
5070
+ label: localWebcamOn ? "Turn off camera" : "Turn on camera",
5071
+ active: !localWebcamOn,
5072
+ onClick: () => toggleWebcam()
4992
5073
  }
4993
5074
  ),
5075
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-0.5 h-7 w-px shrink-0 bg-white/15" }),
4994
5076
  /* @__PURE__ */ jsx(
4995
- Button,
5077
+ ControlButton,
4996
5078
  {
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 })
5079
+ icon: /* @__PURE__ */ jsx(MonitorUp, { size: 20 }),
5080
+ label: localScreenShareOn ? "Stop sharing your screen" : "Share your screen",
5081
+ active: localScreenShareOn,
5082
+ activeClassName: "bg-indigo-500 hover:bg-indigo-400",
5083
+ onClick: () => toggleScreenShare()
5003
5084
  }
5004
5085
  ),
5005
5086
  /* @__PURE__ */ jsx(
5006
- Button,
5087
+ ControlButton,
5007
5088
  {
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 })
5089
+ icon: /* @__PURE__ */ jsx(Hand, { size: 20, className: isHandRaised ? "animate-[bounce_1s_ease-in-out_2]" : void 0 }),
5090
+ label: isHandRaised ? "Lower hand" : "Raise hand",
5091
+ active: isHandRaised,
5092
+ activeClassName: "bg-amber-400 text-neutral-900 hover:bg-amber-300",
5093
+ onClick: onToggleRaiseHand
5094
+ }
5095
+ ),
5096
+ /* @__PURE__ */ jsx(
5097
+ ControlButton,
5098
+ {
5099
+ icon: /* @__PURE__ */ jsx(MessageSquare, { size: 20 }),
5100
+ label: isChatOpen ? "Close in-call chat" : "Open in-call chat",
5101
+ active: isChatOpen,
5102
+ onClick: onToggleChat
5014
5103
  }
5015
5104
  ),
5016
5105
  /* @__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
- ) }),
5106
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
5107
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5108
+ Button,
5109
+ {
5110
+ variant: "ghost",
5111
+ size: "icon",
5112
+ className: cn(circleBtn, idleBtnClass),
5113
+ "aria-label": "Send a reaction",
5114
+ children: /* @__PURE__ */ jsx(Smile, { size: 20 })
5115
+ }
5116
+ ) }) }),
5117
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: "React" })
5118
+ ] }),
5027
5119
  /* @__PURE__ */ jsx(
5028
5120
  PopoverContent,
5029
5121
  {
5030
5122
  side: "top",
5031
5123
  align: "center",
5032
- className: "w-auto rounded-full border-white/10 bg-neutral-900 p-1.5 shadow-xl",
5124
+ className: "z-[1000000] w-auto rounded-full border-white/10 bg-neutral-900 p-1.5 shadow-xl",
5033
5125
  children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5", children: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
5034
5126
  "button",
5035
5127
  {
5036
5128
  type: "button",
5037
- className: "flex size-10 items-center justify-center rounded-full text-xl transition-transform hover:scale-125 hover:bg-white/10",
5129
+ 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
5130
  "aria-label": `React with ${emoji}`,
5039
5131
  onClick: () => onReaction(emoji),
5040
5132
  children: emoji
@@ -5045,17 +5137,20 @@ var init_CallControls = __esm({
5045
5137
  )
5046
5138
  ] })
5047
5139
  ] }),
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
- )
5140
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
5141
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
5142
+ Button,
5143
+ {
5144
+ variant: "ghost",
5145
+ size: "icon",
5146
+ className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
5147
+ "aria-label": "End call",
5148
+ onClick: handleLeave,
5149
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 22 })
5150
+ }
5151
+ ) }),
5152
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "z-[1000000]", children: "Leave call" })
5153
+ ] })
5059
5154
  ] });
5060
5155
  };
5061
5156
  CallControls_default = CallControls;
@@ -5112,15 +5207,18 @@ var init_useCallReactions = __esm({
5112
5207
  }, []);
5113
5208
  const { send, localParticipant } = sdk.useMeeting({
5114
5209
  onData: (data) => {
5210
+ console.log("[realtimex-call] data channel received:", data);
5115
5211
  try {
5116
5212
  const raw = typeof data.payload === "string" ? data.payload : new TextDecoder().decode(data.payload);
5117
5213
  const message = JSON.parse(raw);
5214
+ console.log("[realtimex-call] data channel parsed:", message, "from", data.from);
5118
5215
  if (message.type === "reaction") {
5119
5216
  addFloatingReaction(message.emoji, data.from);
5120
5217
  } else if (message.type === "raise-hand") {
5121
5218
  setRaisedHands((prev) => ({ ...prev, [data.from]: message.raised }));
5122
5219
  }
5123
- } catch {
5220
+ } catch (err) {
5221
+ console.warn("[realtimex-call] data channel payload not decodable:", err, data);
5124
5222
  }
5125
5223
  }
5126
5224
  });
@@ -5133,12 +5231,22 @@ var init_useCallReactions = __esm({
5133
5231
  return next;
5134
5232
  });
5135
5233
  }, [localParticipant?.id]);
5234
+ const sendDataMessage = useCallback(
5235
+ (message) => {
5236
+ send(JSON.stringify(message)).then((ok) => {
5237
+ console.log("[realtimex-call] data channel send result:", ok, message);
5238
+ }).catch((err) => {
5239
+ console.warn("[realtimex-call] data channel send failed:", err, message);
5240
+ });
5241
+ },
5242
+ [send]
5243
+ );
5136
5244
  const sendReaction = useCallback(
5137
5245
  (emoji) => {
5138
5246
  if (localParticipant?.id) addFloatingReaction(emoji, localParticipant.id);
5139
- void send(JSON.stringify({ type: "reaction", emoji }));
5247
+ sendDataMessage({ type: "reaction", emoji });
5140
5248
  },
5141
- [send, localParticipant?.id, addFloatingReaction]
5249
+ [localParticipant?.id, addFloatingReaction, sendDataMessage]
5142
5250
  );
5143
5251
  const toggleRaiseHand = useCallback(() => {
5144
5252
  setIsHandRaised((prev) => {
@@ -5146,15 +5254,15 @@ var init_useCallReactions = __esm({
5146
5254
  if (localParticipant?.id) {
5147
5255
  setRaisedHands((hands) => ({ ...hands, [localParticipant.id]: next }));
5148
5256
  }
5149
- void send(JSON.stringify({ type: "raise-hand", raised: next }));
5257
+ sendDataMessage({ type: "raise-hand", raised: next });
5150
5258
  return next;
5151
5259
  });
5152
- }, [send, localParticipant?.id]);
5260
+ }, [localParticipant?.id, sendDataMessage]);
5153
5261
  return { floatingReactions, raisedHands, isHandRaised, sendReaction, toggleRaiseHand };
5154
5262
  };
5155
5263
  }
5156
5264
  });
5157
- var MeetingView, MeetingView_default;
5265
+ var RemoteWebcamWatcher, MeetingView, MeetingView_default;
5158
5266
  var init_MeetingView = __esm({
5159
5267
  "src/call/MeetingView.tsx"() {
5160
5268
  init_ParticipantView();
@@ -5165,11 +5273,37 @@ var init_MeetingView = __esm({
5165
5273
  init_useCallReactions();
5166
5274
  init_call_store();
5167
5275
  init_utils2();
5168
- MeetingView = ({ sdk, mode, peerName, peerAvatar }) => {
5276
+ RemoteWebcamWatcher = ({
5277
+ sdk,
5278
+ participantId,
5279
+ onChange
5280
+ }) => {
5281
+ const { webcamOn } = sdk.useParticipant(participantId);
5282
+ useEffect(() => {
5283
+ onChange(participantId, webcamOn);
5284
+ return () => onChange(participantId, false);
5285
+ }, [participantId, webcamOn]);
5286
+ return null;
5287
+ };
5288
+ MeetingView = ({
5289
+ sdk,
5290
+ mode,
5291
+ peerName,
5292
+ peerAvatar,
5293
+ isChatOpen,
5294
+ onToggleChat
5295
+ }) => {
5169
5296
  const endCall = useCallStore((s) => s.endCall);
5170
5297
  const [hasJoined, setHasJoined] = useState(false);
5171
5298
  const hasJoinedRef = useRef(false);
5172
5299
  const [groupView, setGroupView] = useState("grid");
5300
+ const [meetingState, setMeetingState] = useState("CONNECTING");
5301
+ const [remoteWebcamOn, setRemoteWebcamOn] = useState({});
5302
+ const handleRemoteWebcamChange = useCallback((participantId, on) => {
5303
+ setRemoteWebcamOn(
5304
+ (prev) => prev[participantId] === on ? prev : { ...prev, [participantId]: on }
5305
+ );
5306
+ }, []);
5173
5307
  const {
5174
5308
  join,
5175
5309
  participants,
@@ -5185,7 +5319,8 @@ var init_MeetingView = __esm({
5185
5319
  onMeetingLeft: () => {
5186
5320
  if (!hasJoinedRef.current) return;
5187
5321
  endCall();
5188
- }
5322
+ },
5323
+ onMeetingStateChanged: ({ state }) => setMeetingState(state)
5189
5324
  });
5190
5325
  const { floatingReactions, raisedHands, isHandRaised, sendReaction, toggleRaiseHand } = useCallReactions(sdk);
5191
5326
  useEffect(() => {
@@ -5201,149 +5336,47 @@ var init_MeetingView = __esm({
5201
5336
  (id) => id !== localParticipant?.id
5202
5337
  );
5203
5338
  const isGroup = remoteIds.length > 1;
5339
+ const webcamWatchers = remoteIds.map((id) => /* @__PURE__ */ jsx(RemoteWebcamWatcher, { sdk, participantId: id, onChange: handleRemoteWebcamChange }, id));
5204
5340
  const controls = /* @__PURE__ */ jsx(
5205
5341
  CallControls_default,
5206
5342
  {
5207
5343
  sdk,
5208
5344
  onReaction: sendReaction,
5209
5345
  isHandRaised,
5210
- onToggleRaiseHand: toggleRaiseHand
5346
+ onToggleRaiseHand: toggleRaiseHand,
5347
+ isChatOpen,
5348
+ onToggleChat
5211
5349
  }
5212
5350
  );
5351
+ const connectionStatusText = meetingState === "CONNECTING" ? "Connecting\u2026" : meetingState === "DISCONNECTED" ? "Reconnecting\u2026" : meetingState === "FAILED" ? "Connection lost" : null;
5352
+ 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: [
5353
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-amber-400" }),
5354
+ connectionStatusText
5355
+ ] });
5213
5356
  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,
5357
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5358
+ webcamWatchers,
5359
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5360
+ statusBanner,
5361
+ /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5362
+ /* @__PURE__ */ jsx(
5363
+ ScreenShareView_default,
5228
5364
  {
5229
5365
  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"
5366
+ presenterId,
5367
+ isLocal: presenterId === localParticipant?.id,
5368
+ className: "flex flex-1 flex-col overflow-hidden p-4 pb-2"
5234
5369
  }
5235
5370
  ),
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: [
5371
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 overflow-x-auto px-4 pb-2", children: [
5340
5372
  localParticipant?.id && /* @__PURE__ */ jsx(
5341
5373
  ParticipantView_default,
5342
5374
  {
5343
5375
  sdk,
5344
5376
  participantId: localParticipant.id,
5345
5377
  isLocal: true,
5346
- isHandRaised: !!raisedHands[localParticipant.id]
5378
+ isHandRaised: !!raisedHands[localParticipant.id],
5379
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5347
5380
  }
5348
5381
  ),
5349
5382
  remoteIds.map((id) => /* @__PURE__ */ jsx(
@@ -5353,14 +5386,137 @@ var init_MeetingView = __esm({
5353
5386
  participantId: id,
5354
5387
  fallbackName: peerName,
5355
5388
  fallbackImage: peerAvatar,
5356
- isHandRaised: !!raisedHands[id]
5389
+ isHandRaised: !!raisedHands[id],
5390
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5357
5391
  },
5358
5392
  id
5359
5393
  ))
5360
- ]
5361
- }
5362
- ),
5363
- controls
5394
+ ] }),
5395
+ controls
5396
+ ] })
5397
+ ] });
5398
+ }
5399
+ const anyRemoteWebcamOn = Object.values(remoteWebcamOn).some(Boolean);
5400
+ const showVideoGrid = mode === "video" || localWebcamOn || anyRemoteWebcamOn;
5401
+ if (!showVideoGrid) {
5402
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5403
+ webcamWatchers,
5404
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5405
+ statusBanner,
5406
+ /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5407
+ /* @__PURE__ */ jsx(
5408
+ AudioCallView_default,
5409
+ {
5410
+ sdk,
5411
+ remoteParticipantId: remoteIds[0],
5412
+ peerName,
5413
+ peerAvatar,
5414
+ isRemoteHandRaised: !!(remoteIds[0] && raisedHands[remoteIds[0]])
5415
+ }
5416
+ ),
5417
+ controls
5418
+ ] })
5419
+ ] });
5420
+ }
5421
+ const focusedId = groupView === "speaker" ? activeSpeakerId && activeSpeakerId !== localParticipant?.id ? activeSpeakerId : remoteIds[0] || localParticipant?.id : null;
5422
+ const stripIds = focusedId ? [localParticipant?.id, ...remoteIds].filter(
5423
+ (id) => !!id && id !== focusedId
5424
+ ) : [];
5425
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5426
+ webcamWatchers,
5427
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
5428
+ statusBanner,
5429
+ /* @__PURE__ */ jsx(FloatingReactions_default, { reactions: floatingReactions }),
5430
+ isGroup && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1 px-4 pt-2", children: [
5431
+ /* @__PURE__ */ jsx(
5432
+ "button",
5433
+ {
5434
+ type: "button",
5435
+ "aria-label": "Grid view",
5436
+ onClick: () => setGroupView("grid"),
5437
+ className: cn(
5438
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
5439
+ groupView === "grid" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5440
+ ),
5441
+ children: /* @__PURE__ */ jsx(LayoutGrid, { size: 16 })
5442
+ }
5443
+ ),
5444
+ /* @__PURE__ */ jsx(
5445
+ "button",
5446
+ {
5447
+ type: "button",
5448
+ "aria-label": "Speaker view",
5449
+ onClick: () => setGroupView("speaker"),
5450
+ className: cn(
5451
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
5452
+ groupView === "speaker" ? "bg-white/15 text-white" : "text-white/50 hover:text-white/80"
5453
+ ),
5454
+ children: /* @__PURE__ */ jsx(Rows3, { size: 16 })
5455
+ }
5456
+ )
5457
+ ] }),
5458
+ focusedId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 overflow-y-auto p-4", children: [
5459
+ /* @__PURE__ */ jsx(
5460
+ ParticipantView_default,
5461
+ {
5462
+ sdk,
5463
+ participantId: focusedId,
5464
+ isLocal: focusedId === localParticipant?.id,
5465
+ fallbackName: peerName,
5466
+ fallbackImage: peerAvatar,
5467
+ isHandRaised: !!raisedHands[focusedId],
5468
+ className: "min-h-[240px] flex-1"
5469
+ }
5470
+ ),
5471
+ stripIds.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-x-auto", children: stripIds.map((id) => /* @__PURE__ */ jsx(
5472
+ ParticipantView_default,
5473
+ {
5474
+ sdk,
5475
+ participantId: id,
5476
+ isLocal: id === localParticipant?.id,
5477
+ fallbackName: peerName,
5478
+ fallbackImage: peerAvatar,
5479
+ isHandRaised: !!raisedHands[id],
5480
+ className: "aspect-video h-20 min-h-0 w-32 shrink-0"
5481
+ },
5482
+ id
5483
+ )) })
5484
+ ] }) : /* @__PURE__ */ jsxs(
5485
+ "div",
5486
+ {
5487
+ className: "grid flex-1 auto-rows-fr gap-3 overflow-y-auto p-4",
5488
+ style: {
5489
+ gridTemplateColumns: `repeat(${Math.min(
5490
+ Math.max(remoteIds.length + 1, 1),
5491
+ 3
5492
+ )}, minmax(0, 1fr))`
5493
+ },
5494
+ children: [
5495
+ localParticipant?.id && /* @__PURE__ */ jsx(
5496
+ ParticipantView_default,
5497
+ {
5498
+ sdk,
5499
+ participantId: localParticipant.id,
5500
+ isLocal: true,
5501
+ isHandRaised: !!raisedHands[localParticipant.id]
5502
+ }
5503
+ ),
5504
+ remoteIds.map((id) => /* @__PURE__ */ jsx(
5505
+ ParticipantView_default,
5506
+ {
5507
+ sdk,
5508
+ participantId: id,
5509
+ fallbackName: peerName,
5510
+ fallbackImage: peerAvatar,
5511
+ isHandRaised: !!raisedHands[id]
5512
+ },
5513
+ id
5514
+ ))
5515
+ ]
5516
+ }
5517
+ ),
5518
+ controls
5519
+ ] })
5364
5520
  ] });
5365
5521
  };
5366
5522
  MeetingView_default = MeetingView;
@@ -5384,6 +5540,8 @@ var init_CallSession = __esm({
5384
5540
  mode,
5385
5541
  peerName,
5386
5542
  peerAvatar,
5543
+ isChatOpen,
5544
+ onToggleChat,
5387
5545
  onError
5388
5546
  }) => {
5389
5547
  const [sdk, setSdk] = useState(null);
@@ -5426,7 +5584,17 @@ var init_CallSession = __esm({
5426
5584
  token,
5427
5585
  reinitialiseMeetingOnConfigChange: false,
5428
5586
  joinWithoutUserInteraction: true,
5429
- children: /* @__PURE__ */ jsx(MeetingView_default, { sdk, mode, peerName, peerAvatar })
5587
+ children: /* @__PURE__ */ jsx(
5588
+ MeetingView_default,
5589
+ {
5590
+ sdk,
5591
+ mode,
5592
+ peerName,
5593
+ peerAvatar,
5594
+ isChatOpen,
5595
+ onToggleChat
5596
+ }
5597
+ )
5430
5598
  }
5431
5599
  );
5432
5600
  };
@@ -5438,67 +5606,7 @@ var init_CallSession = __esm({
5438
5606
  init_useStore();
5439
5607
  init_api_service();
5440
5608
  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
5609
+ init_tooltip();
5502
5610
  init_utils2();
5503
5611
 
5504
5612
  // src/config/chat-endpoints.ts
@@ -6419,11 +6527,153 @@ var useCallDuration = (startedAt) => {
6419
6527
  const pad = (n) => String(n).padStart(2, "0");
6420
6528
  return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
6421
6529
  };
6530
+
6531
+ // src/call/CallChatPanel.tsx
6532
+ init_useStore();
6533
+ init_store_helpers();
6534
+ init_utils2();
6535
+ init_button();
6536
+ var CallChatPanel = ({ conversationId, isGroupCall, onClose, className }) => {
6537
+ const rawMessages = useStore((s) => s.messagesByConversation[conversationId]);
6538
+ const loggedUserDetails = useStore((s) => s.loggedUserDetails);
6539
+ const conversations = useStore((s) => s.conversations);
6540
+ const emitDmSend = useStore((s) => s.emitDmSend);
6541
+ const emitGroupMessageSend2 = useStore((s) => s.emitGroupMessageSend);
6542
+ const [text, setText] = useState("");
6543
+ const [optimistic, setOptimistic] = useState([]);
6544
+ const listEndRef = useRef(null);
6545
+ const loggedUserId = String(loggedUserDetails?._id || "");
6546
+ const serverMessages = useMemo(() => {
6547
+ return (rawMessages || []).filter((m) => !m?.isSystemMessage && !m?.isDeletedForEveryone).map((m) => {
6548
+ const senderId = String(m.sender?._id || m.sender || "");
6549
+ return {
6550
+ id: String(m._id || m.id || ""),
6551
+ content: String(m.content || ""),
6552
+ senderName: senderId === loggedUserId ? "You" : m.sender?.name || "Unknown",
6553
+ isOwn: senderId === loggedUserId,
6554
+ timestamp: m.createdAt || m.timestamp || (/* @__PURE__ */ new Date()).toISOString()
6555
+ };
6556
+ });
6557
+ }, [rawMessages, loggedUserId]);
6558
+ useEffect(() => {
6559
+ if (!optimistic.length) return;
6560
+ setOptimistic(
6561
+ (prev) => prev.filter(
6562
+ (pending) => !serverMessages.some(
6563
+ (real) => real.isOwn && real.content === pending.content
6564
+ )
6565
+ )
6566
+ );
6567
+ }, [serverMessages]);
6568
+ const messages = useMemo(
6569
+ () => [...serverMessages, ...optimistic].sort(
6570
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
6571
+ ),
6572
+ [serverMessages, optimistic]
6573
+ );
6574
+ useEffect(() => {
6575
+ listEndRef.current?.scrollIntoView({ block: "end" });
6576
+ }, [messages.length]);
6577
+ const handleSend = () => {
6578
+ const content = text.trim();
6579
+ if (!content) return;
6580
+ setText("");
6581
+ setOptimistic((prev) => [
6582
+ ...prev,
6583
+ {
6584
+ id: `pending-${Date.now()}`,
6585
+ content,
6586
+ senderName: "You",
6587
+ isOwn: true,
6588
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6589
+ pending: true
6590
+ }
6591
+ ]);
6592
+ const payload = {
6593
+ tempId: `temp-${Date.now()}`,
6594
+ sender: loggedUserId,
6595
+ content,
6596
+ type: "text",
6597
+ conversationId
6598
+ };
6599
+ if (isGroupCall) {
6600
+ emitGroupMessageSend2(payload);
6601
+ } else {
6602
+ const conversation = findConversationById(conversations, conversationId);
6603
+ payload.receiver = getOtherParticipantId(conversation, loggedUserId);
6604
+ emitDmSend(payload);
6605
+ }
6606
+ };
6607
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col border-l border-white/10 bg-neutral-950/95", className), children: [
6608
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-white/10 px-4 py-3", children: [
6609
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-white/90", children: "In-call chat" }),
6610
+ /* @__PURE__ */ jsx(
6611
+ "button",
6612
+ {
6613
+ type: "button",
6614
+ "aria-label": "Close chat panel",
6615
+ onClick: onClose,
6616
+ className: "flex size-7 items-center justify-center rounded-lg text-white/60 transition-colors hover:bg-white/10 hover:text-white",
6617
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
6618
+ }
6619
+ )
6620
+ ] }),
6621
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-3 overflow-y-auto px-4 py-3", children: [
6622
+ messages.length === 0 && /* @__PURE__ */ jsx("p", { className: "pt-6 text-center text-xs text-white/40", children: "No messages yet \u2014 say hi without leaving the call." }),
6623
+ messages.map((message) => /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", message.isOwn && "items-end"), children: [
6624
+ /* @__PURE__ */ jsx("span", { className: "mb-0.5 text-[11px] font-medium text-white/40", children: message.isOwn ? "You" : message.senderName }),
6625
+ /* @__PURE__ */ jsx(
6626
+ "div",
6627
+ {
6628
+ className: cn(
6629
+ "max-w-[85%] rounded-2xl px-3 py-1.5 text-sm break-words",
6630
+ message.isOwn ? "rounded-br-sm bg-(--chat-theme,#4f46e5) text-white" : "rounded-bl-sm bg-white/10 text-white/90",
6631
+ message.pending && "opacity-60"
6632
+ ),
6633
+ children: message.content
6634
+ }
6635
+ )
6636
+ ] }, message.id)),
6637
+ /* @__PURE__ */ jsx("div", { ref: listEndRef })
6638
+ ] }),
6639
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-white/10 p-3", children: [
6640
+ /* @__PURE__ */ jsx(
6641
+ "input",
6642
+ {
6643
+ value: text,
6644
+ onChange: (e) => setText(e.target.value),
6645
+ onKeyDown: (e) => {
6646
+ if (e.key === "Enter" && !e.shiftKey) {
6647
+ e.preventDefault();
6648
+ handleSend();
6649
+ }
6650
+ },
6651
+ placeholder: "Message\u2026",
6652
+ className: "min-w-0 flex-1 rounded-full border border-white/10 bg-white/5 px-3.5 py-2 text-sm text-white placeholder:text-white/40 focus:outline-none focus:ring-1 focus:ring-white/30"
6653
+ }
6654
+ ),
6655
+ /* @__PURE__ */ jsx(
6656
+ Button,
6657
+ {
6658
+ variant: "ghost",
6659
+ size: "icon",
6660
+ "aria-label": "Send message",
6661
+ disabled: !text.trim(),
6662
+ onClick: handleSend,
6663
+ className: "size-9 shrink-0 rounded-full bg-white/10 text-white hover:bg-white/20 disabled:opacity-30",
6664
+ children: /* @__PURE__ */ jsx(Send, { size: 16 })
6665
+ }
6666
+ )
6667
+ ] })
6668
+ ] });
6669
+ };
6670
+ var CallChatPanel_default = CallChatPanel;
6422
6671
  var CallSession2 = React11__default.lazy(() => Promise.resolve().then(() => (init_CallSession(), CallSession_exports)));
6423
6672
  var CallOverlay = () => {
6424
6673
  const uiStatus = useCallStore((s) => s.uiStatus);
6425
6674
  const mode = useCallStore((s) => s.mode);
6426
6675
  const callId = useCallStore((s) => s.callId);
6676
+ const conversationId = useCallStore((s) => s.conversationId);
6427
6677
  const meetingId = useCallStore((s) => s.meetingId);
6428
6678
  const token = useCallStore((s) => s.token);
6429
6679
  const error = useCallStore((s) => s.error);
@@ -6435,112 +6685,111 @@ var CallOverlay = () => {
6435
6685
  const reset = useCallStore((s) => s.reset);
6436
6686
  const notifyServerCallFailed = useCallStore((s) => s.notifyServerCallFailed);
6437
6687
  const [sessionError, setSessionError] = useState(null);
6688
+ const [isChatOpen, setIsChatOpen] = useState(false);
6438
6689
  const secondsLeft = useRingCountdown(uiStatus === "ringing-out", callId);
6439
6690
  const duration = useCallDuration(callStartedAt);
6440
6691
  useEffect(() => {
6441
6692
  setSessionError(null);
6693
+ setIsChatOpen(false);
6442
6694
  }, [callId]);
6443
6695
  useEffect(() => {
6444
6696
  if (sessionError) notifyServerCallFailed();
6445
6697
  }, [sessionError, notifyServerCallFailed]);
6446
6698
  const isOpen = uiStatus !== "idle" && uiStatus !== "incoming";
6447
- if (!isOpen || typeof document === "undefined") return null;
6699
+ if (!isOpen) return null;
6448
6700
  const displayError = error || sessionError;
6449
6701
  const peerName = peer?.name || caller?.name || "";
6450
6702
  const peerAvatar = peer?.avatar || caller?.profileImage || void 0;
6451
6703
  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 })
6704
+ 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: [
6705
+ /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
6706
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: reset, children: [
6707
+ /* @__PURE__ */ jsx(X, { size: 16, className: "mr-1" }),
6708
+ " Close"
6709
+ ] })
6710
+ ] }) : uiStatus === "in-call" && meetingId && token && mode && conversationId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col min-h-0", children: [
6711
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6712
+ isGroupCall && /* @__PURE__ */ jsx(Users, { size: 14, className: "text-white/50" }),
6713
+ peerName && /* @__PURE__ */ jsx("span", { className: "font-medium text-white/90", children: peerName }),
6714
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6715
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: duration })
6716
+ ] }),
6717
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
6718
+ /* @__PURE__ */ jsx(
6719
+ React11__default.Suspense,
6720
+ {
6721
+ fallback: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6722
+ /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin" }),
6723
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6476
6724
  ] }),
6477
- /* @__PURE__ */ jsx(
6478
- React11__default.Suspense,
6725
+ children: /* @__PURE__ */ jsx(
6726
+ CallSession2,
6479
6727
  {
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
- )
6728
+ meetingId,
6729
+ token,
6730
+ mode,
6731
+ peerName,
6732
+ peerAvatar,
6733
+ isChatOpen,
6734
+ onToggleChat: () => setIsChatOpen((v) => !v),
6735
+ onError: setSessionError
6495
6736
  }
6496
6737
  )
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
- ] })
6524
- ] }),
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
- );
6738
+ }
6739
+ ),
6740
+ isChatOpen && /* @__PURE__ */ jsx(
6741
+ CallChatPanel_default,
6742
+ {
6743
+ conversationId,
6744
+ isGroupCall,
6745
+ onClose: () => setIsChatOpen(false),
6746
+ className: "hidden w-80 shrink-0 sm:flex"
6747
+ }
6748
+ )
6749
+ ] })
6750
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6751
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
6752
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxs(Fragment, { children: [
6753
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
6754
+ /* @__PURE__ */ jsx(
6755
+ "span",
6756
+ {
6757
+ className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
6758
+ style: { animationDelay: "0.6s" }
6759
+ }
6760
+ )
6761
+ ] }),
6762
+ peerName ? /* @__PURE__ */ jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
6763
+ /* @__PURE__ */ jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
6764
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
6765
+ ] }) : /* @__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 }) })
6766
+ ] }),
6767
+ /* @__PURE__ */ jsxs("div", { children: [
6768
+ peerName && /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-1.5 text-xl font-semibold tracking-tight", children: [
6769
+ isGroupCall && /* @__PURE__ */ jsx(Users, { size: 16, className: "text-white/50" }),
6770
+ peerName
6771
+ ] }),
6772
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
6773
+ uiStatus === "creating" && "Starting call\u2026",
6774
+ uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
6775
+ uiStatus === "joining" && "Connecting\u2026"
6776
+ ] })
6777
+ ] }),
6778
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
6779
+ /* @__PURE__ */ jsx(
6780
+ Button,
6781
+ {
6782
+ variant: "ghost",
6783
+ size: "icon",
6784
+ 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",
6785
+ "aria-label": "Cancel call",
6786
+ onClick: cancelCall,
6787
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 24 })
6788
+ }
6789
+ ),
6790
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
6791
+ ] })
6792
+ ] }) });
6544
6793
  };
6545
6794
  var CallOverlay_default = CallOverlay;
6546
6795
 
@@ -7376,7 +7625,7 @@ var Chat = ({
7376
7625
  }),
7377
7626
  [uiConfig.components, uiConfig.classNames, uiConfig.features]
7378
7627
  );
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: [
7628
+ 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
7629
  children,
7381
7630
  /* @__PURE__ */ jsx(CallLayer_default, {})
7382
7631
  ] }) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-gray-400 font-bold", children: "Initializing session..." }) }) }) }) });
@@ -7833,6 +8082,7 @@ init_avatar();
7833
8082
 
7834
8083
  // src/chat/ChatThemeToggle.tsx
7835
8084
  init_button();
8085
+ init_tooltip();
7836
8086
  init_utils2();
7837
8087
  var ChatThemeToggle = ({ className }) => {
7838
8088
  const { isDark, toggleColorMode } = useChatTheme();
@@ -7890,6 +8140,7 @@ var buildLoggedUserProfile = (loggedUserDetails) => ({
7890
8140
 
7891
8141
  // src/chat/sidebar/create-chat-dialog/CreateChatDialog.tsx
7892
8142
  init_button();
8143
+ init_tooltip();
7893
8144
  init_utils2();
7894
8145
 
7895
8146
  // src/chat/sidebar/create-chat-dialog/useCreateChatDialog.ts
@@ -8340,6 +8591,9 @@ function GroupCreateTab({
8340
8591
  ) })
8341
8592
  ] });
8342
8593
  }
8594
+
8595
+ // src/chat/components/AdminPermissionTooltip.tsx
8596
+ init_tooltip();
8343
8597
  init_utils2();
8344
8598
  function AdminPermissionTooltip({
8345
8599
  disabled,
@@ -8966,6 +9220,7 @@ var LoggedUserSettingsContent_default = LoggedUserSettingsContent;
8966
9220
 
8967
9221
  // src/chat/sidebar/LoggedUserSettingsTrigger.tsx
8968
9222
  init_button();
9223
+ init_tooltip();
8969
9224
  init_utils2();
8970
9225
  var LoggedUserSettingsTrigger = ({
8971
9226
  onClick,
@@ -10632,6 +10887,7 @@ init_call_store();
10632
10887
  init_utils2();
10633
10888
 
10634
10889
  // src/chat/header/header-right/OptionsMenuItem.tsx
10890
+ init_tooltip();
10635
10891
  init_utils2();
10636
10892
  function OptionsMenuItem({
10637
10893
  icon: Icon,
@@ -16314,6 +16570,7 @@ init_avatar();
16314
16570
 
16315
16571
  // src/chat/message/MessageReactions.tsx
16316
16572
  init_utils2();
16573
+ init_tooltip();
16317
16574
  var MessageReactions = ({
16318
16575
  groupedReactions,
16319
16576
  reactions,