@realtimexsco/live-chat 1.4.19 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import { Dialog as Dialog$1, Slot, Avatar as Avatar$1, Tooltip as Tooltip$1, Swi
9
9
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
10
  import * as React9 from 'react';
11
11
  import React9__default, { createContext, useState, useEffect, useMemo, useContext, useCallback, useRef, useLayoutEffect, Fragment as Fragment$1, useSyncExternalStore } from 'react';
12
- import { Loader2, X, Video, Phone, PhoneOff, Sun, Moon, Settings, Search, UserPlus, ChevronDown, ArrowLeft, Users, 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, MessageCircle, ExternalLink, Download, ChevronLeft, ZoomOut, ZoomIn, RotateCcw, Music, Mic, MicOff, VideoOff, XIcon, ArrowRight, MoreHorizontal, PinOff, StarOff, ImageIcon, Film, Play, Pause, FileText, FileSpreadsheet, FileIcon, MessageSquare, Eye, Captions } from 'lucide-react';
12
+ import { Loader2, X, Video, Phone, PhoneOff, Sun, Moon, Settings, Search, UserPlus, ChevronDown, ArrowLeft, Users, 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, MessageCircle, ExternalLink, Download, ChevronLeft, ZoomOut, ZoomIn, RotateCcw, Music, MicOff, Mic, VideoOff, XIcon, ArrowRight, MoreHorizontal, PinOff, StarOff, ImageIcon, Film, Play, Pause, FileText, FileSpreadsheet, FileIcon, MessageSquare, Eye, Captions } from 'lucide-react';
13
13
  import { flushSync } from 'react-dom';
14
14
  import EmojiPicker, { Theme } from 'emoji-picker-react';
15
15
  import { getDefaultClassNames, DayPicker } from 'react-day-picker';
@@ -657,12 +657,48 @@ var init_call_api = __esm({
657
657
  };
658
658
  }
659
659
  });
660
- var idleState, useCallStore;
660
+
661
+ // src/call/call.types.ts
662
+ var CALL_MODES, CALL_STATUS, CALL_RING_TIMEOUT_MS, CLIENT_RING_FALLBACK_MS;
663
+ var init_call_types = __esm({
664
+ "src/call/call.types.ts"() {
665
+ CALL_MODES = {
666
+ AUDIO: "audio",
667
+ VIDEO: "video"
668
+ };
669
+ CALL_STATUS = {
670
+ RINGING: "ringing",
671
+ ONGOING: "ongoing",
672
+ ENDED: "ended",
673
+ MISSED: "missed",
674
+ REJECTED: "rejected",
675
+ CANCELLED: "cancelled"
676
+ };
677
+ CALL_RING_TIMEOUT_MS = 4e4;
678
+ CLIENT_RING_FALLBACK_MS = CALL_RING_TIMEOUT_MS + 5e3;
679
+ }
680
+ });
681
+ var ringTimer, clearRingTimer, scheduleRingTimer, idleState, useCallStore;
661
682
  var init_call_store = __esm({
662
683
  "src/call/call.store.ts"() {
663
684
  init_useStore();
664
685
  init_socket_service2();
665
686
  init_call_api();
687
+ init_call_types();
688
+ ringTimer = null;
689
+ clearRingTimer = () => {
690
+ if (ringTimer) clearTimeout(ringTimer);
691
+ ringTimer = null;
692
+ };
693
+ scheduleRingTimer = (callId, get, set) => {
694
+ clearRingTimer();
695
+ ringTimer = setTimeout(() => {
696
+ const state = get();
697
+ if (state.callId === callId && (state.uiStatus === "ringing-out" || state.uiStatus === "incoming")) {
698
+ set({ ...idleState });
699
+ }
700
+ }, CLIENT_RING_FALLBACK_MS);
701
+ };
666
702
  idleState = {
667
703
  uiStatus: "idle",
668
704
  callId: null,
@@ -672,13 +708,15 @@ var init_call_store = __esm({
672
708
  token: null,
673
709
  caller: null,
674
710
  participants: [],
675
- error: null
711
+ error: null,
712
+ callee: null,
713
+ callStartedAt: null
676
714
  };
677
715
  useCallStore = create((set, get) => ({
678
716
  ...idleState,
679
- startCall: async (conversationId, mode) => {
717
+ startCall: async (conversationId, mode, callee) => {
680
718
  if (get().uiStatus !== "idle") return;
681
- set({ ...idleState, uiStatus: "creating", conversationId, mode });
719
+ set({ ...idleState, uiStatus: "creating", conversationId, mode, callee: callee ?? null });
682
720
  try {
683
721
  const room = await apiCreateCallRoom(conversationId, mode);
684
722
  set({
@@ -690,8 +728,10 @@ var init_call_store = __esm({
690
728
  token: room.token,
691
729
  participants: room.participants
692
730
  });
731
+ scheduleRingTimer(room.callId, get, set);
693
732
  socket_service_default.emitCallInvite({ callId: room.callId });
694
733
  } catch (err) {
734
+ clearRingTimer();
695
735
  set({
696
736
  ...idleState,
697
737
  error: err instanceof Error ? err.message : "Could not start the call"
@@ -703,16 +743,18 @@ var init_call_store = __esm({
703
743
  if (callId && uiStatus === "ringing-out") {
704
744
  socket_service_default.emitCallCancel({ callId });
705
745
  }
746
+ clearRingTimer();
706
747
  set({ ...idleState });
707
748
  },
708
749
  acceptCall: async () => {
709
750
  const { callId, meetingId } = get();
710
751
  if (!callId || !meetingId) return;
752
+ clearRingTimer();
711
753
  set({ uiStatus: "joining" });
712
754
  try {
713
755
  const join = await apiFetchJoinToken(meetingId);
714
756
  socket_service_default.emitCallAccept({ callId });
715
- set({ uiStatus: "in-call", token: join.token });
757
+ set({ uiStatus: "in-call", token: join.token, callStartedAt: Date.now() });
716
758
  } catch (err) {
717
759
  set({
718
760
  ...idleState,
@@ -725,6 +767,7 @@ var init_call_store = __esm({
725
767
  if (callId) {
726
768
  socket_service_default.emitCallReject({ callId, reason });
727
769
  }
770
+ clearRingTimer();
728
771
  set({ ...idleState });
729
772
  },
730
773
  endCall: () => {
@@ -732,9 +775,13 @@ var init_call_store = __esm({
732
775
  if (callId) {
733
776
  socket_service_default.emitCallEnd({ callId });
734
777
  }
778
+ clearRingTimer();
779
+ set({ ...idleState });
780
+ },
781
+ reset: () => {
782
+ clearRingTimer();
735
783
  set({ ...idleState });
736
784
  },
737
- reset: () => set({ ...idleState }),
738
785
  _receiveIncoming: (payload) => {
739
786
  if (get().uiStatus !== "idle") return;
740
787
  set({
@@ -746,6 +793,7 @@ var init_call_store = __esm({
746
793
  caller: payload.caller,
747
794
  participants: payload.participants
748
795
  });
796
+ scheduleRingTimer(payload.callId, get, set);
749
797
  },
750
798
  _receiveAccepted: (payload) => {
751
799
  const state = get();
@@ -753,11 +801,17 @@ var init_call_store = __esm({
753
801
  if (state.uiStatus === "incoming") {
754
802
  const myUserId = useStore.getState().loggedUserDetails?._id;
755
803
  if (payload.userId === myUserId) {
804
+ clearRingTimer();
756
805
  set({ ...idleState });
757
806
  }
758
807
  return;
759
808
  }
760
- set({ uiStatus: "in-call", participants: payload.participants });
809
+ clearRingTimer();
810
+ set({
811
+ uiStatus: "in-call",
812
+ participants: payload.participants,
813
+ callStartedAt: Date.now()
814
+ });
761
815
  },
762
816
  // A single decline doesn't end a multi-callee call — the server only
763
817
  // moves the call to `rejected` (followed by `call_ended`) once every
@@ -771,17 +825,25 @@ var init_call_store = __esm({
771
825
  },
772
826
  _receiveCancelled: (payload) => {
773
827
  if (get().callId !== payload.callId) return;
828
+ clearRingTimer();
774
829
  set({ ...idleState });
775
830
  },
776
831
  _receiveEnded: (payload) => {
777
832
  if (get().callId !== payload.callId) return;
833
+ clearRingTimer();
778
834
  set({ ...idleState });
779
835
  },
780
836
  _receiveBusy: (payload) => {
781
837
  if (get().callId !== payload.callId) return;
838
+ clearRingTimer();
782
839
  set({ ...idleState, error: "That user is already on another call." });
783
840
  },
784
- _receiveMissed: () => {
841
+ // Guarded by callId like every other _receive* handler — a late/stale
842
+ // call_missed for a call that has already ended (accepted, rejected,
843
+ // or replaced by a new one) must not clobber whatever is happening now.
844
+ _receiveMissed: (payload) => {
845
+ if (get().callId !== payload.callId) return;
846
+ clearRingTimer();
785
847
  set({ ...idleState });
786
848
  }
787
849
  }));
@@ -813,8 +875,8 @@ var init_call_listeners = __esm({
813
875
  socket.on(SOCKET_EVENTS.CALL_BUSY, (payload) => {
814
876
  useCallStore.getState()._receiveBusy(payload);
815
877
  });
816
- socket.on(SOCKET_EVENTS.CALL_MISSED, () => {
817
- useCallStore.getState()._receiveMissed();
878
+ socket.on(SOCKET_EVENTS.CALL_MISSED, (payload) => {
879
+ useCallStore.getState()._receiveMissed(payload);
818
880
  });
819
881
  };
820
882
  }
@@ -4238,22 +4300,6 @@ var init_button = __esm({
4238
4300
  );
4239
4301
  }
4240
4302
  });
4241
-
4242
- // src/call/videosdk.loader.ts
4243
- var loadVideoSDK;
4244
- var init_videosdk_loader = __esm({
4245
- "src/call/videosdk.loader.ts"() {
4246
- loadVideoSDK = async () => {
4247
- try {
4248
- return await import('@videosdk.live/react-sdk');
4249
- } catch {
4250
- throw new Error(
4251
- 'Audio/video calling needs the "@videosdk.live/react-sdk" package \u2014 run: npm install @videosdk.live/react-sdk'
4252
- );
4253
- }
4254
- };
4255
- }
4256
- });
4257
4303
  function Avatar({
4258
4304
  className,
4259
4305
  size = "default",
@@ -4306,6 +4352,25 @@ var init_avatar = __esm({
4306
4352
  init_utils2();
4307
4353
  }
4308
4354
  });
4355
+
4356
+ // src/call/videosdk.loader.ts
4357
+ var loadVideoSDK;
4358
+ var init_videosdk_loader = __esm({
4359
+ "src/call/videosdk.loader.ts"() {
4360
+ loadVideoSDK = async () => {
4361
+ try {
4362
+ return await import('@videosdk.live/react-sdk');
4363
+ } catch (err) {
4364
+ console.error("[realtimex-call] failed to load @videosdk.live/react-sdk:", err);
4365
+ const message = err instanceof Error ? err.message : String(err);
4366
+ const looksMissing = /Failed to resolve module|Cannot find module|find package/i.test(message);
4367
+ throw new Error(
4368
+ looksMissing ? 'Audio/video calling needs the "@videosdk.live/react-sdk" package \u2014 run: npm install @videosdk.live/react-sdk' : `Audio/video calling failed to load: ${message}`
4369
+ );
4370
+ }
4371
+ };
4372
+ }
4373
+ });
4309
4374
  var ParticipantView, ParticipantView_default;
4310
4375
  var init_ParticipantView = __esm({
4311
4376
  "src/call/ParticipantView.tsx"() {
@@ -4319,7 +4384,7 @@ var init_ParticipantView = __esm({
4319
4384
  fallbackImage,
4320
4385
  className
4321
4386
  }) => {
4322
- const { webcamStream, micStream, webcamOn, micOn, displayName } = sdk.useParticipant(participantId);
4387
+ const { webcamStream, micStream, webcamOn, micOn, displayName, isActiveSpeaker } = sdk.useParticipant(participantId);
4323
4388
  const videoRef = useRef(null);
4324
4389
  const audioRef = useRef(null);
4325
4390
  useEffect(() => {
@@ -4348,7 +4413,8 @@ var init_ParticipantView = __esm({
4348
4413
  "div",
4349
4414
  {
4350
4415
  className: cn(
4351
- "relative flex aspect-video min-h-[120px] items-center justify-center overflow-hidden rounded-xl bg-neutral-900",
4416
+ "relative flex aspect-video min-h-[140px] items-center justify-center overflow-hidden rounded-2xl bg-gradient-to-br from-neutral-800 to-neutral-900 ring-1 ring-white/5 transition-shadow",
4417
+ isActiveSpeaker && micOn && "ring-2 ring-emerald-400/80",
4352
4418
  className
4353
4419
  ),
4354
4420
  children: [
@@ -4365,17 +4431,14 @@ var init_ParticipantView = __esm({
4365
4431
  }
4366
4432
  ),
4367
4433
  !isLocal && /* @__PURE__ */ jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true }),
4368
- !webcamOn && /* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "size-16", children: [
4434
+ !webcamOn && /* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "size-20", children: [
4369
4435
  /* @__PURE__ */ jsx(AvatarImage, { src: fallbackImage, alt: name }),
4370
- /* @__PURE__ */ jsx(AvatarFallback, { className: "text-lg", children: name.charAt(0).toUpperCase() })
4436
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-xl font-medium text-white", children: name.charAt(0).toUpperCase() })
4371
4437
  ] }),
4372
- /* @__PURE__ */ jsx("span", { className: "absolute bottom-2 left-2 rounded-md bg-black/50 px-2 py-0.5 text-xs text-white", children: isLocal ? `${name} (You)` : name }),
4373
- !micOn && /* @__PURE__ */ jsx("span", { className: "absolute top-2 right-2 rounded-full bg-black/50 p-1 text-white", children: /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
4374
- /* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
4375
- /* @__PURE__ */ jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" }),
4376
- /* @__PURE__ */ jsx("path", { d: "M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" }),
4377
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" })
4378
- ] }) })
4438
+ /* @__PURE__ */ jsxs("div", { className: "absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/70 to-transparent px-3 py-2", children: [
4439
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-medium text-white drop-shadow", children: isLocal ? `${name} (You)` : name }),
4440
+ !micOn && /* @__PURE__ */ jsx(MicOff, { size: 14, className: "shrink-0 text-white/80", strokeWidth: 2.25 })
4441
+ ] })
4379
4442
  ]
4380
4443
  }
4381
4444
  );
@@ -4389,7 +4452,7 @@ var init_CallControls = __esm({
4389
4452
  init_button();
4390
4453
  init_utils2();
4391
4454
  init_call_store();
4392
- circleBtn = "size-12 rounded-full text-white transition-colors disabled:opacity-40";
4455
+ circleBtn = "size-13 rounded-full text-white transition-all duration-150 disabled:opacity-40 hover:scale-105 active:scale-95";
4393
4456
  CallControls = ({ sdk, mode }) => {
4394
4457
  const { toggleMic, toggleWebcam, localMicOn, localWebcamOn, leave } = sdk.useMeeting();
4395
4458
  const endCall = useCallStore((s) => s.endCall);
@@ -4397,38 +4460,46 @@ var init_CallControls = __esm({
4397
4460
  leave();
4398
4461
  endCall();
4399
4462
  };
4400
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4 py-6", children: [
4401
- /* @__PURE__ */ jsx(
4402
- Button,
4403
- {
4404
- variant: "ghost",
4405
- size: "icon",
4406
- className: cn(circleBtn, localMicOn ? "bg-white/15 hover:bg-white/25" : "bg-white/90 text-neutral-900 hover:bg-white"),
4407
- "aria-label": localMicOn ? "Mute microphone" : "Unmute microphone",
4408
- onClick: () => toggleMic(),
4409
- children: localMicOn ? /* @__PURE__ */ jsx(Mic, { size: 20 }) : /* @__PURE__ */ jsx(MicOff, { size: 20 })
4410
- }
4411
- ),
4412
- mode === "video" && /* @__PURE__ */ jsx(
4413
- Button,
4414
- {
4415
- variant: "ghost",
4416
- size: "icon",
4417
- className: cn(circleBtn, localWebcamOn ? "bg-white/15 hover:bg-white/25" : "bg-white/90 text-neutral-900 hover:bg-white"),
4418
- "aria-label": localWebcamOn ? "Turn camera off" : "Turn camera on",
4419
- onClick: () => toggleWebcam(),
4420
- children: localWebcamOn ? /* @__PURE__ */ jsx(Video, { size: 20 }) : /* @__PURE__ */ jsx(VideoOff, { size: 20 })
4421
- }
4422
- ),
4463
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-5 py-6", children: [
4464
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-full bg-white/10 p-2 backdrop-blur-sm", children: [
4465
+ /* @__PURE__ */ jsx(
4466
+ Button,
4467
+ {
4468
+ variant: "ghost",
4469
+ size: "icon",
4470
+ className: cn(
4471
+ circleBtn,
4472
+ localMicOn ? "bg-white/10 hover:bg-white/20" : "bg-white text-neutral-900 hover:bg-white/90"
4473
+ ),
4474
+ "aria-label": localMicOn ? "Mute microphone" : "Unmute microphone",
4475
+ onClick: () => toggleMic(),
4476
+ children: localMicOn ? /* @__PURE__ */ jsx(Mic, { size: 20 }) : /* @__PURE__ */ jsx(MicOff, { size: 20 })
4477
+ }
4478
+ ),
4479
+ mode === "video" && /* @__PURE__ */ jsx(
4480
+ Button,
4481
+ {
4482
+ variant: "ghost",
4483
+ size: "icon",
4484
+ className: cn(
4485
+ circleBtn,
4486
+ localWebcamOn ? "bg-white/10 hover:bg-white/20" : "bg-white text-neutral-900 hover:bg-white/90"
4487
+ ),
4488
+ "aria-label": localWebcamOn ? "Turn camera off" : "Turn camera on",
4489
+ onClick: () => toggleWebcam(),
4490
+ children: localWebcamOn ? /* @__PURE__ */ jsx(Video, { size: 20 }) : /* @__PURE__ */ jsx(VideoOff, { size: 20 })
4491
+ }
4492
+ )
4493
+ ] }),
4423
4494
  /* @__PURE__ */ jsx(
4424
4495
  Button,
4425
4496
  {
4426
4497
  variant: "ghost",
4427
4498
  size: "icon",
4428
- className: cn(circleBtn, "bg-red-600 hover:bg-red-500"),
4499
+ className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
4429
4500
  "aria-label": "End call",
4430
4501
  onClick: handleLeave,
4431
- children: /* @__PURE__ */ jsx(Phone, { size: 20, className: "rotate-[135deg]" })
4502
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 22 })
4432
4503
  }
4433
4504
  )
4434
4505
  ] });
@@ -6033,22 +6104,69 @@ var ChatAlertDialogContent = ({
6033
6104
  };
6034
6105
 
6035
6106
  // src/call/CallOverlay.tsx
6107
+ init_avatar();
6036
6108
  init_button();
6037
6109
  init_utils2();
6038
6110
  init_call_store();
6111
+
6112
+ // src/call/useRingCountdown.ts
6113
+ init_call_types();
6114
+ var useRingCountdown = (active, callId) => {
6115
+ const [secondsLeft, setSecondsLeft] = useState(
6116
+ Math.ceil(CALL_RING_TIMEOUT_MS / 1e3)
6117
+ );
6118
+ useEffect(() => {
6119
+ if (!active) return;
6120
+ const startedAt = Date.now();
6121
+ setSecondsLeft(Math.ceil(CALL_RING_TIMEOUT_MS / 1e3));
6122
+ const id = setInterval(() => {
6123
+ const remainingMs = CALL_RING_TIMEOUT_MS - (Date.now() - startedAt);
6124
+ setSecondsLeft(Math.max(0, Math.ceil(remainingMs / 1e3)));
6125
+ }, 1e3);
6126
+ return () => clearInterval(id);
6127
+ }, [active, callId]);
6128
+ return secondsLeft;
6129
+ };
6130
+ var useCallDuration = (startedAt) => {
6131
+ const [elapsedMs, setElapsedMs] = useState(0);
6132
+ useEffect(() => {
6133
+ if (!startedAt) {
6134
+ setElapsedMs(0);
6135
+ return;
6136
+ }
6137
+ setElapsedMs(Date.now() - startedAt);
6138
+ const id = setInterval(() => setElapsedMs(Date.now() - startedAt), 1e3);
6139
+ return () => clearInterval(id);
6140
+ }, [startedAt]);
6141
+ const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1e3));
6142
+ const hours = Math.floor(totalSeconds / 3600);
6143
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
6144
+ const seconds = totalSeconds % 60;
6145
+ const pad = (n) => String(n).padStart(2, "0");
6146
+ return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
6147
+ };
6039
6148
  var CallSession2 = React9__default.lazy(() => Promise.resolve().then(() => (init_CallSession(), CallSession_exports)));
6040
6149
  var CallOverlay = () => {
6041
6150
  const uiStatus = useCallStore((s) => s.uiStatus);
6042
6151
  const mode = useCallStore((s) => s.mode);
6152
+ const callId = useCallStore((s) => s.callId);
6043
6153
  const meetingId = useCallStore((s) => s.meetingId);
6044
6154
  const token = useCallStore((s) => s.token);
6045
6155
  const error = useCallStore((s) => s.error);
6156
+ const callee = useCallStore((s) => s.callee);
6157
+ const caller = useCallStore((s) => s.caller);
6158
+ const callStartedAt = useCallStore((s) => s.callStartedAt);
6046
6159
  const cancelCall = useCallStore((s) => s.cancelCall);
6047
6160
  const reset = useCallStore((s) => s.reset);
6048
6161
  const [sessionError, setSessionError] = useState(null);
6162
+ const secondsLeft = useRingCountdown(uiStatus === "ringing-out", callId);
6163
+ const duration = useCallDuration(callStartedAt);
6049
6164
  const isOpen = uiStatus !== "idle" && uiStatus !== "incoming";
6050
6165
  if (!isOpen) return null;
6051
6166
  const displayError = error || sessionError;
6167
+ const peerName = callee?.name || caller?.name || "";
6168
+ const peerAvatar = callee?.avatar || caller?.profileImage || void 0;
6169
+ const isVideo = mode === "video";
6052
6170
  return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: () => void 0, children: /* @__PURE__ */ jsx(
6053
6171
  ChatDialogContent,
6054
6172
  {
@@ -6057,7 +6175,7 @@ var CallOverlay = () => {
6057
6175
  onPointerDownOutside: (e) => e.preventDefault(),
6058
6176
  className: cn(
6059
6177
  "fixed inset-0 top-0 left-0 z-100 flex h-screen w-screen max-w-none",
6060
- "translate-x-0 translate-y-0 flex-col rounded-none border-0 bg-neutral-950 p-0 text-white"
6178
+ "translate-x-0 translate-y-0 flex-col rounded-none border-0 bg-gradient-to-b from-neutral-900 via-neutral-950 to-black p-0 text-white"
6061
6179
  ),
6062
6180
  children: displayError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6 text-center", children: [
6063
6181
  /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
@@ -6065,41 +6183,69 @@ var CallOverlay = () => {
6065
6183
  /* @__PURE__ */ jsx(X, { size: 16, className: "mr-1" }),
6066
6184
  " Close"
6067
6185
  ] })
6068
- ] }) : uiStatus === "in-call" && meetingId && token && mode ? /* @__PURE__ */ jsx(
6069
- React9__default.Suspense,
6070
- {
6071
- fallback: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6072
- /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin" }),
6073
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6074
- ] }),
6075
- children: /* @__PURE__ */ jsx(
6076
- CallSession2,
6077
- {
6078
- meetingId,
6079
- token,
6080
- mode,
6081
- onError: setSessionError
6082
- }
6083
- )
6084
- }
6085
- ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6086
- /* @__PURE__ */ jsx("div", { className: "flex size-20 items-center justify-center rounded-full bg-white/10", children: mode === "video" ? /* @__PURE__ */ jsx(Video, { size: 32 }) : /* @__PURE__ */ jsx(Phone, { size: 32 }) }),
6087
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-white/80", children: [
6088
- uiStatus === "creating" && "Starting call\u2026",
6089
- uiStatus === "ringing-out" && "Calling\u2026",
6090
- uiStatus === "joining" && "Connecting\u2026"
6186
+ ] }) : uiStatus === "in-call" && meetingId && token && mode ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col", children: [
6187
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6188
+ peerName && /* @__PURE__ */ jsx("span", { className: "font-medium text-white/90", children: peerName }),
6189
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6190
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: duration })
6091
6191
  ] }),
6092
- uiStatus === "ringing-out" && /* @__PURE__ */ jsx(
6093
- Button,
6192
+ /* @__PURE__ */ jsx(
6193
+ React9__default.Suspense,
6094
6194
  {
6095
- variant: "ghost",
6096
- size: "icon",
6097
- className: "size-12 rounded-full bg-red-600 text-white hover:bg-red-500",
6098
- "aria-label": "Cancel call",
6099
- onClick: cancelCall,
6100
- children: /* @__PURE__ */ jsx(PhoneOff, { size: 20 })
6195
+ fallback: /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6196
+ /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin" }),
6197
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6198
+ ] }),
6199
+ children: /* @__PURE__ */ jsx(
6200
+ CallSession2,
6201
+ {
6202
+ meetingId,
6203
+ token,
6204
+ mode,
6205
+ onError: setSessionError
6206
+ }
6207
+ )
6101
6208
  }
6102
6209
  )
6210
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6211
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
6212
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxs(Fragment, { children: [
6213
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
6214
+ /* @__PURE__ */ jsx(
6215
+ "span",
6216
+ {
6217
+ className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
6218
+ style: { animationDelay: "0.6s" }
6219
+ }
6220
+ )
6221
+ ] }),
6222
+ peerName ? /* @__PURE__ */ jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
6223
+ /* @__PURE__ */ jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
6224
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
6225
+ ] }) : /* @__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 }) })
6226
+ ] }),
6227
+ /* @__PURE__ */ jsxs("div", { children: [
6228
+ peerName && /* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tracking-tight", children: peerName }),
6229
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
6230
+ uiStatus === "creating" && "Starting call\u2026",
6231
+ uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
6232
+ uiStatus === "joining" && "Connecting\u2026"
6233
+ ] })
6234
+ ] }),
6235
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
6236
+ /* @__PURE__ */ jsx(
6237
+ Button,
6238
+ {
6239
+ variant: "ghost",
6240
+ size: "icon",
6241
+ 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",
6242
+ "aria-label": "Cancel call",
6243
+ onClick: cancelCall,
6244
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 24 })
6245
+ }
6246
+ ),
6247
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
6248
+ ] })
6103
6249
  ] })
6104
6250
  }
6105
6251
  ) });
@@ -6111,12 +6257,14 @@ init_utils2();
6111
6257
  init_call_store();
6112
6258
  var IncomingCallDialog = ({ ringtoneUrl }) => {
6113
6259
  const uiStatus = useCallStore((s) => s.uiStatus);
6260
+ const callId = useCallStore((s) => s.callId);
6114
6261
  const caller = useCallStore((s) => s.caller);
6115
6262
  const mode = useCallStore((s) => s.mode);
6116
6263
  const acceptCall = useCallStore((s) => s.acceptCall);
6117
6264
  const rejectCall = useCallStore((s) => s.rejectCall);
6118
6265
  const audioRef = useRef(null);
6119
6266
  const isOpen = uiStatus === "incoming";
6267
+ const secondsLeft = useRingCountdown(isOpen, callId);
6120
6268
  useEffect(() => {
6121
6269
  const el = audioRef.current;
6122
6270
  if (!el || !ringtoneUrl) return;
@@ -6129,6 +6277,7 @@ var IncomingCallDialog = ({ ringtoneUrl }) => {
6129
6277
  }, [isOpen, ringtoneUrl]);
6130
6278
  if (!isOpen) return null;
6131
6279
  const name = caller?.name || "Unknown caller";
6280
+ const isVideo = mode === "video";
6132
6281
  return /* @__PURE__ */ jsxs(Dialog, { open: isOpen, onOpenChange: () => void 0, children: [
6133
6282
  ringtoneUrl && /* @__PURE__ */ jsx("audio", { ref: audioRef, src: ringtoneUrl, loop: true }),
6134
6283
  /* @__PURE__ */ jsx(
@@ -6137,51 +6286,70 @@ var IncomingCallDialog = ({ ringtoneUrl }) => {
6137
6286
  showCloseButton: false,
6138
6287
  onEscapeKeyDown: (e) => e.preventDefault(),
6139
6288
  onPointerDownOutside: (e) => e.preventDefault(),
6140
- className: "max-w-xs rounded-2xl p-6 text-center",
6141
- children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
6142
- /* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "size-16", children: [
6143
- /* @__PURE__ */ jsx(AvatarImage, { src: caller?.profileImage, alt: name }),
6144
- /* @__PURE__ */ jsx(AvatarFallback, { className: "text-lg", children: name.charAt(0).toUpperCase() })
6289
+ className: "w-[min(340px,calc(100vw-2rem))] max-w-none overflow-hidden rounded-3xl border-0 bg-gradient-to-b from-neutral-800 to-neutral-950 p-0 text-white shadow-2xl",
6290
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-5 px-6 pt-9 pb-7", children: [
6291
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
6292
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex size-28 animate-ping rounded-full bg-emerald-400/20" }),
6293
+ /* @__PURE__ */ jsx(
6294
+ "span",
6295
+ {
6296
+ className: "absolute inline-flex size-28 animate-ping rounded-full bg-emerald-400/15",
6297
+ style: { animationDelay: "0.6s" }
6298
+ }
6299
+ ),
6300
+ /* @__PURE__ */ jsxs(Avatar, { className: "relative size-24 ring-4 ring-white/10", children: [
6301
+ /* @__PURE__ */ jsx(AvatarImage, { src: caller?.profileImage, alt: name }),
6302
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-neutral-700 text-2xl font-medium text-white", children: name.charAt(0).toUpperCase() })
6303
+ ] }),
6304
+ /* @__PURE__ */ jsx(
6305
+ "span",
6306
+ {
6307
+ className: cn(
6308
+ "absolute -right-1 -bottom-1 flex size-8 items-center justify-center rounded-full ring-4 ring-neutral-900",
6309
+ isVideo ? "bg-indigo-500" : "bg-emerald-500"
6310
+ ),
6311
+ children: isVideo ? /* @__PURE__ */ jsx(Video, { size: 14 }) : /* @__PURE__ */ jsx(Phone, { size: 14 })
6312
+ }
6313
+ )
6145
6314
  ] }),
6146
- /* @__PURE__ */ jsxs("div", { children: [
6147
- /* @__PURE__ */ jsx("p", { className: "text-base font-semibold text-(--chat-text)", children: name }),
6148
- /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-1 text-sm text-(--chat-muted)", children: [
6149
- mode === "video" ? /* @__PURE__ */ jsx(Video, { size: 14 }) : /* @__PURE__ */ jsx(Phone, { size: 14 }),
6315
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
6316
+ /* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tracking-tight", children: name }),
6317
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-white/50", children: [
6150
6318
  "Incoming ",
6151
- mode === "video" ? "video" : "voice",
6152
- " call\u2026"
6319
+ isVideo ? "video" : "voice",
6320
+ " call \xB7 ",
6321
+ secondsLeft,
6322
+ "s"
6153
6323
  ] })
6154
6324
  ] }),
6155
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-center gap-6", children: [
6156
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
6325
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center justify-center gap-10", children: [
6326
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
6157
6327
  /* @__PURE__ */ jsx(
6158
6328
  Button,
6159
6329
  {
6160
6330
  variant: "ghost",
6161
6331
  size: "icon",
6162
- className: cn(
6163
- "size-12 rounded-full bg-red-600 text-white hover:bg-red-500"
6164
- ),
6332
+ 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",
6165
6333
  "aria-label": "Decline call",
6166
6334
  onClick: () => rejectCall(),
6167
- children: /* @__PURE__ */ jsx(PhoneOff, { size: 20 })
6335
+ children: /* @__PURE__ */ jsx(PhoneOff, { size: 24 })
6168
6336
  }
6169
6337
  ),
6170
- /* @__PURE__ */ jsx("span", { className: "text-xs text-(--chat-muted)", children: "Decline" })
6338
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-white/60", children: "Decline" })
6171
6339
  ] }),
6172
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
6340
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
6173
6341
  /* @__PURE__ */ jsx(
6174
6342
  Button,
6175
6343
  {
6176
6344
  variant: "ghost",
6177
6345
  size: "icon",
6178
- className: "size-12 rounded-full bg-green-600 text-white hover:bg-green-500",
6346
+ className: "size-16 rounded-full bg-emerald-500 text-white shadow-lg shadow-emerald-500/30 transition-transform hover:scale-105 hover:bg-emerald-400 active:scale-95",
6179
6347
  "aria-label": "Accept call",
6180
6348
  onClick: () => acceptCall(),
6181
- children: /* @__PURE__ */ jsx(Phone, { size: 20 })
6349
+ children: /* @__PURE__ */ jsx(Phone, { size: 24 })
6182
6350
  }
6183
6351
  ),
6184
- /* @__PURE__ */ jsx("span", { className: "text-xs text-(--chat-muted)", children: "Accept" })
6352
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-white/60", children: "Accept" })
6185
6353
  ] })
6186
6354
  ] })
6187
6355
  ] })
@@ -10002,19 +10170,20 @@ var HeaderRightSide = ({
10002
10170
  const startCall = useCallStore((s) => s.startCall);
10003
10171
  const showPhoneCall = features.showPhoneCall ?? defaultChatFeatures.showPhoneCall;
10004
10172
  const showVideoCall = features.showVideoCall ?? defaultChatFeatures.showVideoCall;
10173
+ const callee = { name: activeChannel.name, avatar: activeChannel.avatar || activeChannel.image };
10005
10174
  const handlePhoneCall = () => {
10006
10175
  if (features.onPhoneCall) {
10007
10176
  features.onPhoneCall({ activeChannel, conversationId });
10008
10177
  return;
10009
10178
  }
10010
- if (conversationId) startCall(conversationId, "audio");
10179
+ if (conversationId) startCall(conversationId, "audio", callee);
10011
10180
  };
10012
10181
  const handleVideoCall = () => {
10013
10182
  if (features.onVideoCall) {
10014
10183
  features.onVideoCall({ activeChannel, conversationId });
10015
10184
  return;
10016
10185
  }
10017
- if (conversationId) startCall(conversationId, "video");
10186
+ if (conversationId) startCall(conversationId, "video", callee);
10018
10187
  };
10019
10188
  const headerItemGapClass = "gap-0.5";
10020
10189
  const headerIconBtnClass = "inline-flex size-6 shrink-0 items-center justify-center rounded-lg p-0 text-(--chat-muted) transition-colors hover:bg-(--chat-hover) hover:text-(--chat-text) has-[>svg]:p-0";
@@ -20716,22 +20885,7 @@ init_settings_types();
20716
20885
  init_api_service();
20717
20886
  init_call_store();
20718
20887
  init_call_api();
20719
-
20720
- // src/call/call.types.ts
20721
- var CALL_MODES = {
20722
- AUDIO: "audio",
20723
- VIDEO: "video"
20724
- };
20725
- var CALL_STATUS = {
20726
- RINGING: "ringing",
20727
- ONGOING: "ongoing",
20728
- ENDED: "ended",
20729
- MISSED: "missed",
20730
- REJECTED: "rejected",
20731
- CANCELLED: "cancelled"
20732
- };
20733
-
20734
- // src/index.ts
20888
+ init_call_types();
20735
20889
  var index_default = ChatMain_default;
20736
20890
 
20737
20891
  export { ADMIN_PERMISSION_DENIED_MESSAGE, AdminPermissionTooltip, CALLS_NOT_ALLOWED_BY_PLATFORM_MESSAGE, CALLS_NOT_CONFIGURED_MESSAGE, CALL_MODES, CALL_STATUS, CHAT_API_KEYS, CHAT_GET_API_KEYS, Calendar, CalendarDayButton, CallOverlay_default as CallOverlay, Channel, ChannelHeaderView_default as ChannelHeader, ChannelListView_default as ChannelList, ChannelListItem_default as ChannelListItem, Chat, ChatAlertsProvider, ChatAvatar_default as ChatAvatar, ChatCustomizationProvider, ChatDateJumpMenu, ChatEffectiveSettingsProvider, ChatLayout_default as ChatLayout, ChatLocaleProvider, ChatMain_default as ChatMain, ChatThemeProvider, ChatViewport, DEFAULT_EFFECTIVE_SETTINGS, DefaultChannelListItem, DefaultChatAvatar, DefaultChatLayout_default as DefaultChatLayout, ForegroundPushBridge, ForwardModalView as ForwardModal, HeaderCallActions_default as HeaderCallActions, IncomingCallDialog_default as IncomingCallDialog, LoggedUserDetails_default as LoggedUserDetails, LoggedUserSettingsContent_default as LoggedUserSettingsContent, LoggedUserSettingsDialog_default as LoggedUserSettingsDialog, LoggedUserSettingsTrigger_default as LoggedUserSettingsTrigger, ChannelMessageBoxView_default as MessageInput, MessageItemView as MessageItem, ActiveChannelMessagesView_default as MessageList, NOTIFICATION_CLICK_SW_TYPE, OPEN_CONVERSATION_EVENT, PUSH_BROADCAST_CHANNEL, PUSH_CHANGED_EVENT, PUSH_DATA_CACHE_NAME, PushNotificationToggle, apiCreateCallRoom, apiFetchCallDetail, apiFetchCallHistory, apiFetchJoinToken, apiGetPushConfig, apiGetPushPreference, apiRegisterPushToken, apiRemovePushToken, apiUpdatePushPreference, buildChannelFromPushRequest, buildLoggedUserProfile, buildVersionedApiUrl, capitalizeFirst, clampJumpDate, consumePendingOpenConversation, consumePendingOpenFromPushCache, consumePendingOpenRequest, index_default as default, defaultChatClassNames, defaultChatComponents, defaultChatFeatures, defaultChatLayoutConfig, disablePushOnThisDevice, downloadFile, enablePushOnThisDevice, fetchEffectiveSettings, getChatApiVersions, getChatBaseUrl, getChatQueryParamApis, getChatQueryParams, getChatQueryParamsByApi, getChatSocketUrl, getFirstAndLastNameOneCharacter, getResponsiveWidth, getSocketConfig, getStoredPushToken, installNotificationClickBridge, isEmpty, isPushSupported, mergeChatCustomization, mergeChatLayoutConfig, onForegroundPush, packageDefaultComponents, peekPendingOpenConversation, peekPendingOpenRequest, requestOpenConversation, resolveApiRoot, resolveApiUrl, resolveChatApiKey, resolveConversationIdFromPushData, resolveJumpDateBounds, resolveJumpPreset, resolveJumpStep, resolveSocketUrl, setChatAccessToken, setChatApiVersions, setChatBaseURL, setChatClientId, setChatPushApiVersion, setChatQueryParamApis, setChatQueryParams, setChatQueryParamsByApi, setChatSocketUrl, showNotification, smoothScrollToMessage, socket_service_default as socketService, startOfLocalDay, toDateInputValue, uniqueList, useAdminFeatureGate, useCallStore, useChatAlerts, useChatContext, useChatController, useChatCustomization, useChatCustomizationOptional, useChatFeatures, useChatFeaturesOptional, useChatLocale, useStore as useChatStore, useChatTheme, useEffectiveSettings, useEffectiveSettingsOptional, usePushNotifications };