@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.cjs CHANGED
@@ -685,12 +685,48 @@ var init_call_api = __esm({
685
685
  };
686
686
  }
687
687
  });
688
- var idleState; exports.useCallStore = void 0;
688
+
689
+ // src/call/call.types.ts
690
+ exports.CALL_MODES = void 0; exports.CALL_STATUS = void 0; var CALL_RING_TIMEOUT_MS, CLIENT_RING_FALLBACK_MS;
691
+ var init_call_types = __esm({
692
+ "src/call/call.types.ts"() {
693
+ exports.CALL_MODES = {
694
+ AUDIO: "audio",
695
+ VIDEO: "video"
696
+ };
697
+ exports.CALL_STATUS = {
698
+ RINGING: "ringing",
699
+ ONGOING: "ongoing",
700
+ ENDED: "ended",
701
+ MISSED: "missed",
702
+ REJECTED: "rejected",
703
+ CANCELLED: "cancelled"
704
+ };
705
+ CALL_RING_TIMEOUT_MS = 4e4;
706
+ CLIENT_RING_FALLBACK_MS = CALL_RING_TIMEOUT_MS + 5e3;
707
+ }
708
+ });
709
+ var ringTimer, clearRingTimer, scheduleRingTimer, idleState; exports.useCallStore = void 0;
689
710
  var init_call_store = __esm({
690
711
  "src/call/call.store.ts"() {
691
712
  init_useStore();
692
713
  init_socket_service2();
693
714
  init_call_api();
715
+ init_call_types();
716
+ ringTimer = null;
717
+ clearRingTimer = () => {
718
+ if (ringTimer) clearTimeout(ringTimer);
719
+ ringTimer = null;
720
+ };
721
+ scheduleRingTimer = (callId, get, set) => {
722
+ clearRingTimer();
723
+ ringTimer = setTimeout(() => {
724
+ const state = get();
725
+ if (state.callId === callId && (state.uiStatus === "ringing-out" || state.uiStatus === "incoming")) {
726
+ set({ ...idleState });
727
+ }
728
+ }, CLIENT_RING_FALLBACK_MS);
729
+ };
694
730
  idleState = {
695
731
  uiStatus: "idle",
696
732
  callId: null,
@@ -700,13 +736,15 @@ var init_call_store = __esm({
700
736
  token: null,
701
737
  caller: null,
702
738
  participants: [],
703
- error: null
739
+ error: null,
740
+ callee: null,
741
+ callStartedAt: null
704
742
  };
705
743
  exports.useCallStore = zustand.create((set, get) => ({
706
744
  ...idleState,
707
- startCall: async (conversationId, mode) => {
745
+ startCall: async (conversationId, mode, callee) => {
708
746
  if (get().uiStatus !== "idle") return;
709
- set({ ...idleState, uiStatus: "creating", conversationId, mode });
747
+ set({ ...idleState, uiStatus: "creating", conversationId, mode, callee: callee ?? null });
710
748
  try {
711
749
  const room = await exports.apiCreateCallRoom(conversationId, mode);
712
750
  set({
@@ -718,8 +756,10 @@ var init_call_store = __esm({
718
756
  token: room.token,
719
757
  participants: room.participants
720
758
  });
759
+ scheduleRingTimer(room.callId, get, set);
721
760
  exports.socketService.emitCallInvite({ callId: room.callId });
722
761
  } catch (err) {
762
+ clearRingTimer();
723
763
  set({
724
764
  ...idleState,
725
765
  error: err instanceof Error ? err.message : "Could not start the call"
@@ -731,16 +771,18 @@ var init_call_store = __esm({
731
771
  if (callId && uiStatus === "ringing-out") {
732
772
  exports.socketService.emitCallCancel({ callId });
733
773
  }
774
+ clearRingTimer();
734
775
  set({ ...idleState });
735
776
  },
736
777
  acceptCall: async () => {
737
778
  const { callId, meetingId } = get();
738
779
  if (!callId || !meetingId) return;
780
+ clearRingTimer();
739
781
  set({ uiStatus: "joining" });
740
782
  try {
741
783
  const join = await exports.apiFetchJoinToken(meetingId);
742
784
  exports.socketService.emitCallAccept({ callId });
743
- set({ uiStatus: "in-call", token: join.token });
785
+ set({ uiStatus: "in-call", token: join.token, callStartedAt: Date.now() });
744
786
  } catch (err) {
745
787
  set({
746
788
  ...idleState,
@@ -753,6 +795,7 @@ var init_call_store = __esm({
753
795
  if (callId) {
754
796
  exports.socketService.emitCallReject({ callId, reason });
755
797
  }
798
+ clearRingTimer();
756
799
  set({ ...idleState });
757
800
  },
758
801
  endCall: () => {
@@ -760,9 +803,13 @@ var init_call_store = __esm({
760
803
  if (callId) {
761
804
  exports.socketService.emitCallEnd({ callId });
762
805
  }
806
+ clearRingTimer();
807
+ set({ ...idleState });
808
+ },
809
+ reset: () => {
810
+ clearRingTimer();
763
811
  set({ ...idleState });
764
812
  },
765
- reset: () => set({ ...idleState }),
766
813
  _receiveIncoming: (payload) => {
767
814
  if (get().uiStatus !== "idle") return;
768
815
  set({
@@ -774,6 +821,7 @@ var init_call_store = __esm({
774
821
  caller: payload.caller,
775
822
  participants: payload.participants
776
823
  });
824
+ scheduleRingTimer(payload.callId, get, set);
777
825
  },
778
826
  _receiveAccepted: (payload) => {
779
827
  const state = get();
@@ -781,11 +829,17 @@ var init_call_store = __esm({
781
829
  if (state.uiStatus === "incoming") {
782
830
  const myUserId = exports.useChatStore.getState().loggedUserDetails?._id;
783
831
  if (payload.userId === myUserId) {
832
+ clearRingTimer();
784
833
  set({ ...idleState });
785
834
  }
786
835
  return;
787
836
  }
788
- set({ uiStatus: "in-call", participants: payload.participants });
837
+ clearRingTimer();
838
+ set({
839
+ uiStatus: "in-call",
840
+ participants: payload.participants,
841
+ callStartedAt: Date.now()
842
+ });
789
843
  },
790
844
  // A single decline doesn't end a multi-callee call — the server only
791
845
  // moves the call to `rejected` (followed by `call_ended`) once every
@@ -799,17 +853,25 @@ var init_call_store = __esm({
799
853
  },
800
854
  _receiveCancelled: (payload) => {
801
855
  if (get().callId !== payload.callId) return;
856
+ clearRingTimer();
802
857
  set({ ...idleState });
803
858
  },
804
859
  _receiveEnded: (payload) => {
805
860
  if (get().callId !== payload.callId) return;
861
+ clearRingTimer();
806
862
  set({ ...idleState });
807
863
  },
808
864
  _receiveBusy: (payload) => {
809
865
  if (get().callId !== payload.callId) return;
866
+ clearRingTimer();
810
867
  set({ ...idleState, error: "That user is already on another call." });
811
868
  },
812
- _receiveMissed: () => {
869
+ // Guarded by callId like every other _receive* handler — a late/stale
870
+ // call_missed for a call that has already ended (accepted, rejected,
871
+ // or replaced by a new one) must not clobber whatever is happening now.
872
+ _receiveMissed: (payload) => {
873
+ if (get().callId !== payload.callId) return;
874
+ clearRingTimer();
813
875
  set({ ...idleState });
814
876
  }
815
877
  }));
@@ -841,8 +903,8 @@ var init_call_listeners = __esm({
841
903
  socket.on(SOCKET_EVENTS.CALL_BUSY, (payload) => {
842
904
  exports.useCallStore.getState()._receiveBusy(payload);
843
905
  });
844
- socket.on(SOCKET_EVENTS.CALL_MISSED, () => {
845
- exports.useCallStore.getState()._receiveMissed();
906
+ socket.on(SOCKET_EVENTS.CALL_MISSED, (payload) => {
907
+ exports.useCallStore.getState()._receiveMissed(payload);
846
908
  });
847
909
  };
848
910
  }
@@ -4266,22 +4328,6 @@ var init_button = __esm({
4266
4328
  );
4267
4329
  }
4268
4330
  });
4269
-
4270
- // src/call/videosdk.loader.ts
4271
- var loadVideoSDK;
4272
- var init_videosdk_loader = __esm({
4273
- "src/call/videosdk.loader.ts"() {
4274
- loadVideoSDK = async () => {
4275
- try {
4276
- return await import('@videosdk.live/react-sdk');
4277
- } catch {
4278
- throw new Error(
4279
- 'Audio/video calling needs the "@videosdk.live/react-sdk" package \u2014 run: npm install @videosdk.live/react-sdk'
4280
- );
4281
- }
4282
- };
4283
- }
4284
- });
4285
4331
  function Avatar({
4286
4332
  className,
4287
4333
  size = "default",
@@ -4334,6 +4380,25 @@ var init_avatar = __esm({
4334
4380
  init_utils2();
4335
4381
  }
4336
4382
  });
4383
+
4384
+ // src/call/videosdk.loader.ts
4385
+ var loadVideoSDK;
4386
+ var init_videosdk_loader = __esm({
4387
+ "src/call/videosdk.loader.ts"() {
4388
+ loadVideoSDK = async () => {
4389
+ try {
4390
+ return await import('@videosdk.live/react-sdk');
4391
+ } catch (err) {
4392
+ console.error("[realtimex-call] failed to load @videosdk.live/react-sdk:", err);
4393
+ const message = err instanceof Error ? err.message : String(err);
4394
+ const looksMissing = /Failed to resolve module|Cannot find module|find package/i.test(message);
4395
+ throw new Error(
4396
+ 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}`
4397
+ );
4398
+ }
4399
+ };
4400
+ }
4401
+ });
4337
4402
  var ParticipantView, ParticipantView_default;
4338
4403
  var init_ParticipantView = __esm({
4339
4404
  "src/call/ParticipantView.tsx"() {
@@ -4347,7 +4412,7 @@ var init_ParticipantView = __esm({
4347
4412
  fallbackImage,
4348
4413
  className
4349
4414
  }) => {
4350
- const { webcamStream, micStream, webcamOn, micOn, displayName } = sdk.useParticipant(participantId);
4415
+ const { webcamStream, micStream, webcamOn, micOn, displayName, isActiveSpeaker } = sdk.useParticipant(participantId);
4351
4416
  const videoRef = React9.useRef(null);
4352
4417
  const audioRef = React9.useRef(null);
4353
4418
  React9.useEffect(() => {
@@ -4376,7 +4441,8 @@ var init_ParticipantView = __esm({
4376
4441
  "div",
4377
4442
  {
4378
4443
  className: cn(
4379
- "relative flex aspect-video min-h-[120px] items-center justify-center overflow-hidden rounded-xl bg-neutral-900",
4444
+ "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",
4445
+ isActiveSpeaker && micOn && "ring-2 ring-emerald-400/80",
4380
4446
  className
4381
4447
  ),
4382
4448
  children: [
@@ -4393,17 +4459,14 @@ var init_ParticipantView = __esm({
4393
4459
  }
4394
4460
  ),
4395
4461
  !isLocal && /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true }),
4396
- !webcamOn && /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { size: "lg", className: "size-16", children: [
4462
+ !webcamOn && /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { size: "lg", className: "size-20", children: [
4397
4463
  /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: fallbackImage, alt: name }),
4398
- /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "text-lg", children: name.charAt(0).toUpperCase() })
4464
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-neutral-700 text-xl font-medium text-white", children: name.charAt(0).toUpperCase() })
4399
4465
  ] }),
4400
- /* @__PURE__ */ jsxRuntime.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 }),
4401
- !micOn && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-2 right-2 rounded-full bg-black/50 p-1 text-white", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
4402
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
4403
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" }),
4404
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" }),
4405
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" })
4406
- ] }) })
4466
+ /* @__PURE__ */ jsxRuntime.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: [
4467
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs font-medium text-white drop-shadow", children: isLocal ? `${name} (You)` : name }),
4468
+ !micOn && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { size: 14, className: "shrink-0 text-white/80", strokeWidth: 2.25 })
4469
+ ] })
4407
4470
  ]
4408
4471
  }
4409
4472
  );
@@ -4417,7 +4480,7 @@ var init_CallControls = __esm({
4417
4480
  init_button();
4418
4481
  init_utils2();
4419
4482
  init_call_store();
4420
- circleBtn = "size-12 rounded-full text-white transition-colors disabled:opacity-40";
4483
+ circleBtn = "size-13 rounded-full text-white transition-all duration-150 disabled:opacity-40 hover:scale-105 active:scale-95";
4421
4484
  CallControls = ({ sdk, mode }) => {
4422
4485
  const { toggleMic, toggleWebcam, localMicOn, localWebcamOn, leave } = sdk.useMeeting();
4423
4486
  const endCall = exports.useCallStore((s) => s.endCall);
@@ -4425,38 +4488,46 @@ var init_CallControls = __esm({
4425
4488
  leave();
4426
4489
  endCall();
4427
4490
  };
4428
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-4 py-6", children: [
4429
- /* @__PURE__ */ jsxRuntime.jsx(
4430
- Button,
4431
- {
4432
- variant: "ghost",
4433
- size: "icon",
4434
- className: cn(circleBtn, localMicOn ? "bg-white/15 hover:bg-white/25" : "bg-white/90 text-neutral-900 hover:bg-white"),
4435
- "aria-label": localMicOn ? "Mute microphone" : "Unmute microphone",
4436
- onClick: () => toggleMic(),
4437
- children: localMicOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { size: 20 })
4438
- }
4439
- ),
4440
- mode === "video" && /* @__PURE__ */ jsxRuntime.jsx(
4441
- Button,
4442
- {
4443
- variant: "ghost",
4444
- size: "icon",
4445
- className: cn(circleBtn, localWebcamOn ? "bg-white/15 hover:bg-white/25" : "bg-white/90 text-neutral-900 hover:bg-white"),
4446
- "aria-label": localWebcamOn ? "Turn camera off" : "Turn camera on",
4447
- onClick: () => toggleWebcam(),
4448
- children: localWebcamOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { size: 20 })
4449
- }
4450
- ),
4491
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-5 py-6", children: [
4492
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 rounded-full bg-white/10 p-2 backdrop-blur-sm", children: [
4493
+ /* @__PURE__ */ jsxRuntime.jsx(
4494
+ Button,
4495
+ {
4496
+ variant: "ghost",
4497
+ size: "icon",
4498
+ className: cn(
4499
+ circleBtn,
4500
+ localMicOn ? "bg-white/10 hover:bg-white/20" : "bg-white text-neutral-900 hover:bg-white/90"
4501
+ ),
4502
+ "aria-label": localMicOn ? "Mute microphone" : "Unmute microphone",
4503
+ onClick: () => toggleMic(),
4504
+ children: localMicOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, { size: 20 })
4505
+ }
4506
+ ),
4507
+ mode === "video" && /* @__PURE__ */ jsxRuntime.jsx(
4508
+ Button,
4509
+ {
4510
+ variant: "ghost",
4511
+ size: "icon",
4512
+ className: cn(
4513
+ circleBtn,
4514
+ localWebcamOn ? "bg-white/10 hover:bg-white/20" : "bg-white text-neutral-900 hover:bg-white/90"
4515
+ ),
4516
+ "aria-label": localWebcamOn ? "Turn camera off" : "Turn camera on",
4517
+ onClick: () => toggleWebcam(),
4518
+ children: localWebcamOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, { size: 20 })
4519
+ }
4520
+ )
4521
+ ] }),
4451
4522
  /* @__PURE__ */ jsxRuntime.jsx(
4452
4523
  Button,
4453
4524
  {
4454
4525
  variant: "ghost",
4455
4526
  size: "icon",
4456
- className: cn(circleBtn, "bg-red-600 hover:bg-red-500"),
4527
+ className: cn(circleBtn, "bg-red-600 shadow-lg shadow-red-600/30 hover:bg-red-500"),
4457
4528
  "aria-label": "End call",
4458
4529
  onClick: handleLeave,
4459
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 20, className: "rotate-[135deg]" })
4530
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 22 })
4460
4531
  }
4461
4532
  )
4462
4533
  ] });
@@ -6061,22 +6132,69 @@ var ChatAlertDialogContent = ({
6061
6132
  };
6062
6133
 
6063
6134
  // src/call/CallOverlay.tsx
6135
+ init_avatar();
6064
6136
  init_button();
6065
6137
  init_utils2();
6066
6138
  init_call_store();
6139
+
6140
+ // src/call/useRingCountdown.ts
6141
+ init_call_types();
6142
+ var useRingCountdown = (active, callId) => {
6143
+ const [secondsLeft, setSecondsLeft] = React9.useState(
6144
+ Math.ceil(CALL_RING_TIMEOUT_MS / 1e3)
6145
+ );
6146
+ React9.useEffect(() => {
6147
+ if (!active) return;
6148
+ const startedAt = Date.now();
6149
+ setSecondsLeft(Math.ceil(CALL_RING_TIMEOUT_MS / 1e3));
6150
+ const id = setInterval(() => {
6151
+ const remainingMs = CALL_RING_TIMEOUT_MS - (Date.now() - startedAt);
6152
+ setSecondsLeft(Math.max(0, Math.ceil(remainingMs / 1e3)));
6153
+ }, 1e3);
6154
+ return () => clearInterval(id);
6155
+ }, [active, callId]);
6156
+ return secondsLeft;
6157
+ };
6158
+ var useCallDuration = (startedAt) => {
6159
+ const [elapsedMs, setElapsedMs] = React9.useState(0);
6160
+ React9.useEffect(() => {
6161
+ if (!startedAt) {
6162
+ setElapsedMs(0);
6163
+ return;
6164
+ }
6165
+ setElapsedMs(Date.now() - startedAt);
6166
+ const id = setInterval(() => setElapsedMs(Date.now() - startedAt), 1e3);
6167
+ return () => clearInterval(id);
6168
+ }, [startedAt]);
6169
+ const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1e3));
6170
+ const hours = Math.floor(totalSeconds / 3600);
6171
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
6172
+ const seconds = totalSeconds % 60;
6173
+ const pad = (n) => String(n).padStart(2, "0");
6174
+ return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
6175
+ };
6067
6176
  var CallSession2 = React9__namespace.default.lazy(() => Promise.resolve().then(() => (init_CallSession(), CallSession_exports)));
6068
6177
  var CallOverlay = () => {
6069
6178
  const uiStatus = exports.useCallStore((s) => s.uiStatus);
6070
6179
  const mode = exports.useCallStore((s) => s.mode);
6180
+ const callId = exports.useCallStore((s) => s.callId);
6071
6181
  const meetingId = exports.useCallStore((s) => s.meetingId);
6072
6182
  const token = exports.useCallStore((s) => s.token);
6073
6183
  const error = exports.useCallStore((s) => s.error);
6184
+ const callee = exports.useCallStore((s) => s.callee);
6185
+ const caller = exports.useCallStore((s) => s.caller);
6186
+ const callStartedAt = exports.useCallStore((s) => s.callStartedAt);
6074
6187
  const cancelCall = exports.useCallStore((s) => s.cancelCall);
6075
6188
  const reset = exports.useCallStore((s) => s.reset);
6076
6189
  const [sessionError, setSessionError] = React9.useState(null);
6190
+ const secondsLeft = useRingCountdown(uiStatus === "ringing-out", callId);
6191
+ const duration = useCallDuration(callStartedAt);
6077
6192
  const isOpen = uiStatus !== "idle" && uiStatus !== "incoming";
6078
6193
  if (!isOpen) return null;
6079
6194
  const displayError = error || sessionError;
6195
+ const peerName = callee?.name || caller?.name || "";
6196
+ const peerAvatar = callee?.avatar || caller?.profileImage || void 0;
6197
+ const isVideo = mode === "video";
6080
6198
  return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open: isOpen, onOpenChange: () => void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
6081
6199
  ChatDialogContent,
6082
6200
  {
@@ -6085,7 +6203,7 @@ var CallOverlay = () => {
6085
6203
  onPointerDownOutside: (e) => e.preventDefault(),
6086
6204
  className: cn(
6087
6205
  "fixed inset-0 top-0 left-0 z-100 flex h-screen w-screen max-w-none",
6088
- "translate-x-0 translate-y-0 flex-col rounded-none border-0 bg-neutral-950 p-0 text-white"
6206
+ "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"
6089
6207
  ),
6090
6208
  children: displayError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6 text-center", children: [
6091
6209
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm text-white/80", children: displayError }),
@@ -6093,41 +6211,69 @@ var CallOverlay = () => {
6093
6211
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, className: "mr-1" }),
6094
6212
  " Close"
6095
6213
  ] })
6096
- ] }) : uiStatus === "in-call" && meetingId && token && mode ? /* @__PURE__ */ jsxRuntime.jsx(
6097
- React9__namespace.default.Suspense,
6098
- {
6099
- fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6100
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-8 animate-spin" }),
6101
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6102
- ] }),
6103
- children: /* @__PURE__ */ jsxRuntime.jsx(
6104
- CallSession2,
6105
- {
6106
- meetingId,
6107
- token,
6108
- mode,
6109
- onError: setSessionError
6110
- }
6111
- )
6112
- }
6113
- ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6114
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-20 items-center justify-center rounded-full bg-white/10", children: mode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 32 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 32 }) }),
6115
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-white/80", children: [
6116
- uiStatus === "creating" && "Starting call\u2026",
6117
- uiStatus === "ringing-out" && "Calling\u2026",
6118
- uiStatus === "joining" && "Connecting\u2026"
6214
+ ] }) : uiStatus === "in-call" && meetingId && token && mode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col", children: [
6215
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-3 text-sm text-white/70", children: [
6216
+ peerName && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-white/90", children: peerName }),
6217
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-white/30", children: "\xB7" }),
6218
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: duration })
6119
6219
  ] }),
6120
- uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsx(
6121
- Button,
6220
+ /* @__PURE__ */ jsxRuntime.jsx(
6221
+ React9__namespace.default.Suspense,
6122
6222
  {
6123
- variant: "ghost",
6124
- size: "icon",
6125
- className: "size-12 rounded-full bg-red-600 text-white hover:bg-red-500",
6126
- "aria-label": "Cancel call",
6127
- onClick: cancelCall,
6128
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 20 })
6223
+ fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-white/80", children: [
6224
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-8 animate-spin" }),
6225
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Loading call\u2026" })
6226
+ ] }),
6227
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6228
+ CallSession2,
6229
+ {
6230
+ meetingId,
6231
+ token,
6232
+ mode,
6233
+ onError: setSessionError
6234
+ }
6235
+ )
6129
6236
  }
6130
6237
  )
6238
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-6 text-center", children: [
6239
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
6240
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/10" }),
6242
+ /* @__PURE__ */ jsxRuntime.jsx(
6243
+ "span",
6244
+ {
6245
+ className: "absolute inline-flex size-32 animate-ping rounded-full bg-white/5",
6246
+ style: { animationDelay: "0.6s" }
6247
+ }
6248
+ )
6249
+ ] }),
6250
+ peerName ? /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "relative size-28 ring-4 ring-white/10", children: [
6251
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: peerAvatar, alt: peerName }),
6252
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-neutral-700 text-3xl font-medium text-white", children: peerName.charAt(0).toUpperCase() })
6253
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex size-28 items-center justify-center rounded-full bg-white/10", children: isVideo ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 36 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 36 }) })
6254
+ ] }),
6255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6256
+ peerName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xl font-semibold tracking-tight", children: peerName }),
6257
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 text-sm text-white/60", children: [
6258
+ uiStatus === "creating" && "Starting call\u2026",
6259
+ uiStatus === "ringing-out" && `${isVideo ? "Video calling" : "Calling"}\u2026 \xB7 ${secondsLeft}s`,
6260
+ uiStatus === "joining" && "Connecting\u2026"
6261
+ ] })
6262
+ ] }),
6263
+ uiStatus === "ringing-out" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex flex-col items-center gap-2", children: [
6264
+ /* @__PURE__ */ jsxRuntime.jsx(
6265
+ Button,
6266
+ {
6267
+ variant: "ghost",
6268
+ size: "icon",
6269
+ 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",
6270
+ "aria-label": "Cancel call",
6271
+ onClick: cancelCall,
6272
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 24 })
6273
+ }
6274
+ ),
6275
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/60", children: "Cancel" })
6276
+ ] })
6131
6277
  ] })
6132
6278
  }
6133
6279
  ) });
@@ -6139,12 +6285,14 @@ init_utils2();
6139
6285
  init_call_store();
6140
6286
  var IncomingCallDialog = ({ ringtoneUrl }) => {
6141
6287
  const uiStatus = exports.useCallStore((s) => s.uiStatus);
6288
+ const callId = exports.useCallStore((s) => s.callId);
6142
6289
  const caller = exports.useCallStore((s) => s.caller);
6143
6290
  const mode = exports.useCallStore((s) => s.mode);
6144
6291
  const acceptCall = exports.useCallStore((s) => s.acceptCall);
6145
6292
  const rejectCall = exports.useCallStore((s) => s.rejectCall);
6146
6293
  const audioRef = React9.useRef(null);
6147
6294
  const isOpen = uiStatus === "incoming";
6295
+ const secondsLeft = useRingCountdown(isOpen, callId);
6148
6296
  React9.useEffect(() => {
6149
6297
  const el = audioRef.current;
6150
6298
  if (!el || !ringtoneUrl) return;
@@ -6157,6 +6305,7 @@ var IncomingCallDialog = ({ ringtoneUrl }) => {
6157
6305
  }, [isOpen, ringtoneUrl]);
6158
6306
  if (!isOpen) return null;
6159
6307
  const name = caller?.name || "Unknown caller";
6308
+ const isVideo = mode === "video";
6160
6309
  return /* @__PURE__ */ jsxRuntime.jsxs(Dialog, { open: isOpen, onOpenChange: () => void 0, children: [
6161
6310
  ringtoneUrl && /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, src: ringtoneUrl, loop: true }),
6162
6311
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -6165,51 +6314,70 @@ var IncomingCallDialog = ({ ringtoneUrl }) => {
6165
6314
  showCloseButton: false,
6166
6315
  onEscapeKeyDown: (e) => e.preventDefault(),
6167
6316
  onPointerDownOutside: (e) => e.preventDefault(),
6168
- className: "max-w-xs rounded-2xl p-6 text-center",
6169
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", children: [
6170
- /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { size: "lg", className: "size-16", children: [
6171
- /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: caller?.profileImage, alt: name }),
6172
- /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "text-lg", children: name.charAt(0).toUpperCase() })
6317
+ 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",
6318
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-5 px-6 pt-9 pb-7", children: [
6319
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
6320
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex size-28 animate-ping rounded-full bg-emerald-400/20" }),
6321
+ /* @__PURE__ */ jsxRuntime.jsx(
6322
+ "span",
6323
+ {
6324
+ className: "absolute inline-flex size-28 animate-ping rounded-full bg-emerald-400/15",
6325
+ style: { animationDelay: "0.6s" }
6326
+ }
6327
+ ),
6328
+ /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: "relative size-24 ring-4 ring-white/10", children: [
6329
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src: caller?.profileImage, alt: name }),
6330
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-neutral-700 text-2xl font-medium text-white", children: name.charAt(0).toUpperCase() })
6331
+ ] }),
6332
+ /* @__PURE__ */ jsxRuntime.jsx(
6333
+ "span",
6334
+ {
6335
+ className: cn(
6336
+ "absolute -right-1 -bottom-1 flex size-8 items-center justify-center rounded-full ring-4 ring-neutral-900",
6337
+ isVideo ? "bg-indigo-500" : "bg-emerald-500"
6338
+ ),
6339
+ children: isVideo ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 14 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 14 })
6340
+ }
6341
+ )
6173
6342
  ] }),
6174
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6175
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base font-semibold text-(--chat-text)", children: name }),
6176
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center justify-center gap-1 text-sm text-(--chat-muted)", children: [
6177
- mode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 14 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 14 }),
6343
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
6344
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xl font-semibold tracking-tight", children: name }),
6345
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 text-sm text-white/50", children: [
6178
6346
  "Incoming ",
6179
- mode === "video" ? "video" : "voice",
6180
- " call\u2026"
6347
+ isVideo ? "video" : "voice",
6348
+ " call \xB7 ",
6349
+ secondsLeft,
6350
+ "s"
6181
6351
  ] })
6182
6352
  ] }),
6183
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center justify-center gap-6", children: [
6184
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
6353
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center justify-center gap-10", children: [
6354
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
6185
6355
  /* @__PURE__ */ jsxRuntime.jsx(
6186
6356
  Button,
6187
6357
  {
6188
6358
  variant: "ghost",
6189
6359
  size: "icon",
6190
- className: cn(
6191
- "size-12 rounded-full bg-red-600 text-white hover:bg-red-500"
6192
- ),
6360
+ 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",
6193
6361
  "aria-label": "Decline call",
6194
6362
  onClick: () => rejectCall(),
6195
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 20 })
6363
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, { size: 24 })
6196
6364
  }
6197
6365
  ),
6198
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-(--chat-muted)", children: "Decline" })
6366
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/60", children: "Decline" })
6199
6367
  ] }),
6200
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
6368
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
6201
6369
  /* @__PURE__ */ jsxRuntime.jsx(
6202
6370
  Button,
6203
6371
  {
6204
6372
  variant: "ghost",
6205
6373
  size: "icon",
6206
- className: "size-12 rounded-full bg-green-600 text-white hover:bg-green-500",
6374
+ 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",
6207
6375
  "aria-label": "Accept call",
6208
6376
  onClick: () => acceptCall(),
6209
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 20 })
6377
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Phone, { size: 24 })
6210
6378
  }
6211
6379
  ),
6212
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-(--chat-muted)", children: "Accept" })
6380
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-white/60", children: "Accept" })
6213
6381
  ] })
6214
6382
  ] })
6215
6383
  ] })
@@ -10030,19 +10198,20 @@ var HeaderRightSide = ({
10030
10198
  const startCall = exports.useCallStore((s) => s.startCall);
10031
10199
  const showPhoneCall = features.showPhoneCall ?? defaultChatFeatures.showPhoneCall;
10032
10200
  const showVideoCall = features.showVideoCall ?? defaultChatFeatures.showVideoCall;
10201
+ const callee = { name: activeChannel.name, avatar: activeChannel.avatar || activeChannel.image };
10033
10202
  const handlePhoneCall = () => {
10034
10203
  if (features.onPhoneCall) {
10035
10204
  features.onPhoneCall({ activeChannel, conversationId });
10036
10205
  return;
10037
10206
  }
10038
- if (conversationId) startCall(conversationId, "audio");
10207
+ if (conversationId) startCall(conversationId, "audio", callee);
10039
10208
  };
10040
10209
  const handleVideoCall = () => {
10041
10210
  if (features.onVideoCall) {
10042
10211
  features.onVideoCall({ activeChannel, conversationId });
10043
10212
  return;
10044
10213
  }
10045
- if (conversationId) startCall(conversationId, "video");
10214
+ if (conversationId) startCall(conversationId, "video", callee);
10046
10215
  };
10047
10216
  const headerItemGapClass = "gap-0.5";
10048
10217
  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";
@@ -20744,27 +20913,10 @@ init_settings_types();
20744
20913
  init_api_service();
20745
20914
  init_call_store();
20746
20915
  init_call_api();
20747
-
20748
- // src/call/call.types.ts
20749
- var CALL_MODES = {
20750
- AUDIO: "audio",
20751
- VIDEO: "video"
20752
- };
20753
- var CALL_STATUS = {
20754
- RINGING: "ringing",
20755
- ONGOING: "ongoing",
20756
- ENDED: "ended",
20757
- MISSED: "missed",
20758
- REJECTED: "rejected",
20759
- CANCELLED: "cancelled"
20760
- };
20761
-
20762
- // src/index.ts
20916
+ init_call_types();
20763
20917
  var index_default = ChatMain_default;
20764
20918
 
20765
20919
  exports.AdminPermissionTooltip = AdminPermissionTooltip;
20766
- exports.CALL_MODES = CALL_MODES;
20767
- exports.CALL_STATUS = CALL_STATUS;
20768
20920
  exports.Calendar = Calendar;
20769
20921
  exports.CalendarDayButton = CalendarDayButton;
20770
20922
  exports.CallOverlay = CallOverlay_default;