@skippr/live-agent-sdk 0.14.0 → 0.15.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.
@@ -1,34 +1,175 @@
1
1
  // src/components/LiveAgent.tsx
2
2
  import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
3
- import { useCallback as useCallback5, useMemo as useMemo4, useState as useState5 } from "react";
3
+ import { useCallback as useCallback7, useMemo as useMemo4, useState as useState7 } from "react";
4
4
 
5
5
  // src/context/LiveAgentContext.tsx
6
6
  import { createContext } from "react";
7
7
  var LiveAgentContext = createContext(null);
8
8
 
9
- // src/hooks/useSession.ts
10
- import { useCallback, useState } from "react";
9
+ // src/hooks/useAuth.ts
10
+ import { useCallback, useEffect, useState } from "react";
11
11
  var API_URL = "https://skipprapi-production.up.railway.app";
12
- function resolveAuthHeaders(authToken, appKey) {
12
+ function storageKey(appKey) {
13
+ return `skippr_auth_${appKey}`;
14
+ }
15
+ function getStoredToken(appKey) {
16
+ try {
17
+ return localStorage.getItem(storageKey(appKey));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function setStoredToken(appKey, token) {
23
+ try {
24
+ localStorage.setItem(storageKey(appKey), token);
25
+ } catch (e) {
26
+ console.error("[Skippr] Failed to persist auth token:", e);
27
+ }
28
+ }
29
+ function clearStoredToken(appKey) {
30
+ try {
31
+ localStorage.removeItem(storageKey(appKey));
32
+ } catch (e) {
33
+ console.error("[Skippr] Failed to clear auth token:", e);
34
+ }
35
+ }
36
+ function useAuth({ appKey }) {
37
+ const [authToken, setAuthToken] = useState(null);
38
+ const [isValidating, setIsValidating] = useState(false);
39
+ const [isSubmitting, setIsSubmitting] = useState(false);
40
+ const [error, setError] = useState("");
41
+ const isAuthenticated = authToken !== null;
42
+ useEffect(() => {
43
+ if (!appKey)
44
+ return;
45
+ const stored = getStoredToken(appKey);
46
+ if (!stored)
47
+ return;
48
+ async function validateStoredToken() {
49
+ setIsValidating(true);
50
+ try {
51
+ const res = await fetch(`${API_URL}/v1/auth/validate-token`, {
52
+ headers: { Authorization: `Bearer ${stored}` }
53
+ });
54
+ if (res.ok) {
55
+ setAuthToken(stored);
56
+ } else {
57
+ clearStoredToken(appKey);
58
+ }
59
+ } catch {
60
+ clearStoredToken(appKey);
61
+ } finally {
62
+ setIsValidating(false);
63
+ }
64
+ }
65
+ validateStoredToken();
66
+ }, [appKey]);
67
+ useEffect(() => {
68
+ function handleLogout() {
69
+ setAuthToken(null);
70
+ setError("");
71
+ }
72
+ window.addEventListener("skippr:logout", handleLogout);
73
+ return () => window.removeEventListener("skippr:logout", handleLogout);
74
+ }, []);
75
+ const requestOtp = useCallback(async (email) => {
76
+ if (!appKey)
77
+ return false;
78
+ setIsSubmitting(true);
79
+ setError("");
80
+ try {
81
+ const resp = await fetch(`${API_URL}/v1/auth/request-otp`, {
82
+ method: "POST",
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ "X-App-Key": appKey
86
+ },
87
+ body: JSON.stringify({ email })
88
+ });
89
+ if (!resp.ok) {
90
+ const body = await resp.json().catch(() => ({}));
91
+ throw new Error(body.detail || "Failed to send verification code");
92
+ }
93
+ return true;
94
+ } catch (e) {
95
+ setError(e instanceof Error ? e.message : "Failed to send verification code");
96
+ return false;
97
+ } finally {
98
+ setIsSubmitting(false);
99
+ }
100
+ }, [appKey]);
101
+ const verifyOtp = useCallback(async (email, code) => {
102
+ if (!appKey)
103
+ return;
104
+ setIsSubmitting(true);
105
+ setError("");
106
+ try {
107
+ const resp = await fetch(`${API_URL}/v1/auth/verify-otp`, {
108
+ method: "POST",
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ "X-App-Key": appKey
112
+ },
113
+ body: JSON.stringify({ email, code, recaptchaToken: "sdk" })
114
+ });
115
+ if (!resp.ok) {
116
+ const body = await resp.json().catch(() => ({}));
117
+ throw new Error(body.detail || "Invalid or expired verification code");
118
+ }
119
+ const { token } = await resp.json();
120
+ setStoredToken(appKey, token);
121
+ setAuthToken(token);
122
+ } catch (e) {
123
+ setError(e instanceof Error ? e.message : "Verification failed");
124
+ return;
125
+ } finally {
126
+ setIsSubmitting(false);
127
+ }
128
+ }, [appKey]);
129
+ const logout = useCallback(() => {
130
+ if (appKey)
131
+ clearStoredToken(appKey);
132
+ setAuthToken(null);
133
+ setError("");
134
+ }, [appKey]);
135
+ return {
136
+ isAuthenticated,
137
+ isValidating,
138
+ authToken,
139
+ requestOtp,
140
+ verifyOtp,
141
+ logout,
142
+ error,
143
+ isSubmitting
144
+ };
145
+ }
146
+
147
+ // src/hooks/useSession.ts
148
+ import { useCallback as useCallback2, useState as useState2 } from "react";
149
+ var API_URL2 = "https://skipprapi-production.up.railway.app";
150
+ function resolveAuthHeaders(authToken, appKey, userToken) {
151
+ const headers = {};
13
152
  if (authToken)
14
- return { Authorization: `Bearer ${authToken}` };
153
+ headers.Authorization = `Bearer ${authToken}`;
15
154
  if (appKey)
16
- return { "X-App-Key": appKey };
17
- return {};
155
+ headers["X-App-Key"] = appKey;
156
+ if (userToken)
157
+ headers["X-User-Token"] = userToken;
158
+ return headers;
18
159
  }
19
- function useSession({ agentId, authToken, appKey }) {
20
- const [connection, setConnection] = useState(null);
21
- const [shouldConnect, setShouldConnect] = useState(false);
22
- const [isStarting, setIsStarting] = useState(false);
23
- const [error, setError] = useState("");
24
- const [sessionId, setSessionId] = useState(null);
25
- const [sessionToken, setSessionToken] = useState(null);
26
- const startSession = useCallback(async () => {
160
+ function useSession({ agentId, authToken, appKey, userToken }) {
161
+ const [connection, setConnection] = useState2(null);
162
+ const [shouldConnect, setShouldConnect] = useState2(false);
163
+ const [isStarting, setIsStarting] = useState2(false);
164
+ const [error, setError] = useState2("");
165
+ const [sessionId, setSessionId] = useState2(null);
166
+ const [sessionToken, setSessionToken] = useState2(null);
167
+ const startSession = useCallback2(async () => {
27
168
  setIsStarting(true);
28
169
  setError("");
29
170
  try {
30
- const authHeaders = resolveAuthHeaders(authToken, appKey);
31
- const createResp = await fetch(`${API_URL}/v1/sessions`, {
171
+ const authHeaders = resolveAuthHeaders(authToken, appKey, userToken);
172
+ const createResp = await fetch(`${API_URL2}/v1/sessions`, {
32
173
  method: "POST",
33
174
  headers: { "Content-Type": "application/json", ...authHeaders },
34
175
  body: JSON.stringify({ agentId })
@@ -38,7 +179,7 @@ function useSession({ agentId, authToken, appKey }) {
38
179
  }
39
180
  const { session } = await createResp.json();
40
181
  const sessionHeaders = { "X-Session-Token": session.sessionToken };
41
- const startResp = await fetch(`${API_URL}/v1/sessions/${session.id}/start`, {
182
+ const startResp = await fetch(`${API_URL2}/v1/sessions/${session.id}/start`, {
42
183
  method: "POST",
43
184
  headers: sessionHeaders
44
185
  });
@@ -58,12 +199,12 @@ function useSession({ agentId, authToken, appKey }) {
58
199
  } finally {
59
200
  setIsStarting(false);
60
201
  }
61
- }, [agentId, authToken, appKey]);
62
- const disconnect = useCallback(async () => {
202
+ }, [agentId, authToken, appKey, userToken]);
203
+ const disconnect = useCallback2(async () => {
63
204
  if (sessionId && sessionToken) {
64
205
  const sessionHeaders = { "X-Session-Token": sessionToken };
65
206
  try {
66
- await fetch(`${API_URL}/v1/sessions/${sessionId}/complete`, {
207
+ await fetch(`${API_URL2}/v1/sessions/${sessionId}/complete`, {
67
208
  method: "POST",
68
209
  headers: { "Content-Type": "application/json", ...sessionHeaders },
69
210
  body: JSON.stringify({})
@@ -82,7 +223,7 @@ function useSession({ agentId, authToken, appKey }) {
82
223
  // src/components/Sidebar.tsx
83
224
  import { useConnectionState } from "@livekit/components-react";
84
225
  import { ConnectionState } from "livekit-client";
85
- import { useEffect as useEffect4 } from "react";
226
+ import { useEffect as useEffect6 } from "react";
86
227
 
87
228
  // src/hooks/useCombinedMessages.ts
88
229
  import { useVoiceAssistant } from "@livekit/components-react";
@@ -176,15 +317,15 @@ function useLiveAgent() {
176
317
  }
177
318
 
178
319
  // src/hooks/usePhaseUpdates.ts
179
- import { useCallback as useCallback2 } from "react";
320
+ import { useCallback as useCallback3 } from "react";
180
321
 
181
322
  // src/hooks/useAgentState.ts
182
323
  import { useRemoteParticipants } from "@livekit/components-react";
183
- import { useEffect, useState as useState2 } from "react";
324
+ import { useEffect as useEffect2, useState as useState3 } from "react";
184
325
  function useAgentState(attributeKey, parse, initial) {
185
- const [value, setValue] = useState2(initial);
326
+ const [value, setValue] = useState3(initial);
186
327
  const remoteParticipants = useRemoteParticipants();
187
- useEffect(() => {
328
+ useEffect2(() => {
188
329
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
189
330
  if (agentParticipant) {
190
331
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -230,13 +371,13 @@ function parsePhases(json) {
230
371
  return null;
231
372
  }
232
373
  function usePhaseUpdates() {
233
- const parse = useCallback2(parsePhases, []);
374
+ const parse = useCallback3(parsePhases, []);
234
375
  const phases = useAgentState("phases", parse, []);
235
376
  return { phases };
236
377
  }
237
378
 
238
379
  // src/hooks/useQuestionUpdates.ts
239
- import { useCallback as useCallback3 } from "react";
380
+ import { useCallback as useCallback4 } from "react";
240
381
  function parseQuestions(json) {
241
382
  try {
242
383
  const data = JSON.parse(json);
@@ -247,7 +388,7 @@ function parseQuestions(json) {
247
388
  return null;
248
389
  }
249
390
  function useQuestionUpdates() {
250
- const parse = useCallback3(parseQuestions, []);
391
+ const parse = useCallback4(parseQuestions, []);
251
392
  const questions = useAgentState("questions", parse, []);
252
393
  return { questions };
253
394
  }
@@ -388,8 +529,14 @@ var Check = createLucideIcon("check", __iconNode5);
388
529
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/circle.js
389
530
  var __iconNode6 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
390
531
  var Circle = createLucideIcon("circle", __iconNode6);
391
- // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
532
+ // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mail.js
392
533
  var __iconNode7 = [
534
+ ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7", key: "132q7q" }],
535
+ ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }]
536
+ ];
537
+ var Mail = createLucideIcon("mail", __iconNode7);
538
+ // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
539
+ var __iconNode8 = [
393
540
  [
394
541
  "path",
395
542
  {
@@ -398,9 +545,9 @@ var __iconNode7 = [
398
545
  }
399
546
  ]
400
547
  ];
401
- var MessageCircle = createLucideIcon("message-circle", __iconNode7);
548
+ var MessageCircle = createLucideIcon("message-circle", __iconNode8);
402
549
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic-off.js
403
- var __iconNode8 = [
550
+ var __iconNode9 = [
404
551
  ["path", { d: "M12 19v3", key: "npa21l" }],
405
552
  ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
406
553
  ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2", key: "cqa7eg" }],
@@ -408,32 +555,32 @@ var __iconNode8 = [
408
555
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
409
556
  ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }]
410
557
  ];
411
- var MicOff = createLucideIcon("mic-off", __iconNode8);
558
+ var MicOff = createLucideIcon("mic-off", __iconNode9);
412
559
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic.js
413
- var __iconNode9 = [
560
+ var __iconNode10 = [
414
561
  ["path", { d: "M12 19v3", key: "npa21l" }],
415
562
  ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
416
563
  ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
417
564
  ];
418
- var Mic = createLucideIcon("mic", __iconNode9);
565
+ var Mic = createLucideIcon("mic", __iconNode10);
419
566
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor-off.js
420
- var __iconNode10 = [
567
+ var __iconNode11 = [
421
568
  ["path", { d: "M12 17v4", key: "1riwvh" }],
422
569
  ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826", key: "cv7jms" }],
423
570
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
424
571
  ["path", { d: "M8 21h8", key: "1ev6f3" }],
425
572
  ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042", key: "z8ni2w" }]
426
573
  ];
427
- var MonitorOff = createLucideIcon("monitor-off", __iconNode10);
574
+ var MonitorOff = createLucideIcon("monitor-off", __iconNode11);
428
575
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.js
429
- var __iconNode11 = [
576
+ var __iconNode12 = [
430
577
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
431
578
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
432
579
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
433
580
  ];
434
- var Monitor = createLucideIcon("monitor", __iconNode11);
581
+ var Monitor = createLucideIcon("monitor", __iconNode12);
435
582
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/phone-off.js
436
- var __iconNode12 = [
583
+ var __iconNode13 = [
437
584
  [
438
585
  "path",
439
586
  {
@@ -450,13 +597,13 @@ var __iconNode12 = [
450
597
  }
451
598
  ]
452
599
  ];
453
- var PhoneOff = createLucideIcon("phone-off", __iconNode12);
600
+ var PhoneOff = createLucideIcon("phone-off", __iconNode13);
454
601
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/x.js
455
- var __iconNode13 = [
602
+ var __iconNode14 = [
456
603
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
457
604
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
458
605
  ];
459
- var X = createLucideIcon("x", __iconNode13);
606
+ var X = createLucideIcon("x", __iconNode14);
460
607
  // src/components/ui/button.tsx
461
608
  import { forwardRef as forwardRef3 } from "react";
462
609
  import { jsx } from "react/jsx-runtime";
@@ -518,10 +665,257 @@ function ChatHeader({ onClose }) {
518
665
  });
519
666
  }
520
667
 
668
+ // src/components/LoginFlow.tsx
669
+ import { useCallback as useCallback5, useEffect as useEffect3, useRef, useState as useState4 } from "react";
670
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
671
+ var OTP_LENGTH = 6;
672
+ var DIGIT_KEYS = ["d0", "d1", "d2", "d3", "d4", "d5"];
673
+ function LoginFlow({ requestOtp, verifyOtp, error, isSubmitting }) {
674
+ const [step, setStep] = useState4("email");
675
+ const [email, setEmail] = useState4("");
676
+ const handleRequestOtp = useCallback5(async (emailValue) => {
677
+ const success = await requestOtp(emailValue);
678
+ if (success)
679
+ setStep("otp");
680
+ }, [requestOtp]);
681
+ const handleVerifyOtp = useCallback5(async (code) => {
682
+ await verifyOtp(email, code);
683
+ }, [verifyOtp, email]);
684
+ const handleBack = useCallback5(() => {
685
+ setStep("email");
686
+ }, []);
687
+ const handleResend = useCallback5(async () => {
688
+ await requestOtp(email);
689
+ }, [requestOtp, email]);
690
+ if (step === "otp") {
691
+ return /* @__PURE__ */ jsx3(OtpStep, {
692
+ email,
693
+ onSubmit: handleVerifyOtp,
694
+ onResend: handleResend,
695
+ onBack: handleBack,
696
+ error,
697
+ isSubmitting
698
+ });
699
+ }
700
+ return /* @__PURE__ */ jsx3(EmailStep, {
701
+ email,
702
+ onEmailChange: setEmail,
703
+ onSubmit: handleRequestOtp,
704
+ error,
705
+ isSubmitting
706
+ });
707
+ }
708
+ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
709
+ function handleSubmit(e) {
710
+ e.preventDefault();
711
+ if (email.trim())
712
+ onSubmit(email.trim());
713
+ }
714
+ return /* @__PURE__ */ jsxs2("div", {
715
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:px-4 skippr:py-4",
716
+ children: [
717
+ /* @__PURE__ */ jsxs2("div", {
718
+ className: "skippr:mb-4 skippr:text-center",
719
+ children: [
720
+ /* @__PURE__ */ jsx3(Mail, {
721
+ className: "skippr:mx-auto skippr:mb-2 skippr:size-6 skippr:text-primary"
722
+ }),
723
+ /* @__PURE__ */ jsx3("p", {
724
+ className: "skippr:text-sm skippr:font-medium skippr:text-foreground",
725
+ children: "Sign in to continue"
726
+ }),
727
+ /* @__PURE__ */ jsx3("p", {
728
+ className: "skippr:mt-1 skippr:text-xs skippr:text-muted-foreground",
729
+ children: "Your email will be used to identify you across sessions"
730
+ })
731
+ ]
732
+ }),
733
+ /* @__PURE__ */ jsxs2("form", {
734
+ onSubmit: handleSubmit,
735
+ className: "skippr:flex skippr:flex-col skippr:gap-3",
736
+ children: [
737
+ /* @__PURE__ */ jsx3("input", {
738
+ type: "email",
739
+ placeholder: "you@example.com",
740
+ value: email,
741
+ onChange: (e) => onEmailChange(e.target.value),
742
+ disabled: isSubmitting,
743
+ required: true,
744
+ className: "skippr:w-full skippr:rounded-md skippr:border skippr:border-border skippr:bg-background skippr:px-3 skippr:py-2 skippr:text-sm skippr:text-foreground skippr:placeholder-muted-foreground skippr:outline-none focus:skippr:ring-2 focus:skippr:ring-primary/30 focus:skippr:border-primary disabled:skippr:opacity-50"
745
+ }),
746
+ /* @__PURE__ */ jsx3(Button, {
747
+ type: "submit",
748
+ disabled: isSubmitting || !email.trim(),
749
+ className: "skippr:w-full",
750
+ children: isSubmitting ? /* @__PURE__ */ jsx3(LoaderCircle, {
751
+ className: "skippr:size-4 skippr:animate-spin"
752
+ }) : "Continue"
753
+ }),
754
+ error && /* @__PURE__ */ jsx3("p", {
755
+ className: "skippr:text-xs skippr:text-center skippr:text-destructive",
756
+ children: error
757
+ })
758
+ ]
759
+ })
760
+ ]
761
+ });
762
+ }
763
+ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
764
+ const [digits, setDigits] = useState4(Array(OTP_LENGTH).fill(""));
765
+ const [resendCooldown, setResendCooldown] = useState4(0);
766
+ const inputRefs = useRef([]);
767
+ const submittedRef = useRef(false);
768
+ useEffect3(() => {
769
+ inputRefs.current[0]?.focus();
770
+ }, []);
771
+ useEffect3(() => {
772
+ if (error)
773
+ submittedRef.current = false;
774
+ }, [error]);
775
+ useEffect3(() => {
776
+ if (resendCooldown <= 0)
777
+ return;
778
+ const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
779
+ return () => clearTimeout(timer);
780
+ }, [resendCooldown]);
781
+ const submitCode = useCallback5((code) => {
782
+ if (submittedRef.current || isSubmitting)
783
+ return;
784
+ submittedRef.current = true;
785
+ onSubmit(code);
786
+ }, [onSubmit, isSubmitting]);
787
+ const handleDigitChange = useCallback5((index2, value) => {
788
+ const digit = value.replace(/\D/g, "").slice(-1);
789
+ const newDigits = [...digits];
790
+ newDigits[index2] = digit;
791
+ setDigits(newDigits);
792
+ if (digit && index2 < OTP_LENGTH - 1) {
793
+ inputRefs.current[index2 + 1]?.focus();
794
+ }
795
+ if (digit && index2 === OTP_LENGTH - 1) {
796
+ const code = newDigits.join("");
797
+ if (code.length === OTP_LENGTH)
798
+ submitCode(code);
799
+ }
800
+ }, [digits, submitCode]);
801
+ const handleKeyDown = useCallback5((index2, e) => {
802
+ if (e.key === "Backspace" && !digits[index2] && index2 > 0) {
803
+ inputRefs.current[index2 - 1]?.focus();
804
+ }
805
+ }, [digits]);
806
+ const handlePaste = useCallback5((e) => {
807
+ e.preventDefault();
808
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
809
+ if (pasted.length > 0) {
810
+ const newDigits = Array(OTP_LENGTH).fill("");
811
+ for (let i = 0;i < pasted.length; i++) {
812
+ newDigits[i] = pasted[i];
813
+ }
814
+ setDigits(newDigits);
815
+ if (pasted.length === OTP_LENGTH)
816
+ submitCode(pasted);
817
+ else
818
+ inputRefs.current[pasted.length]?.focus();
819
+ }
820
+ }, [submitCode]);
821
+ function handleSubmit(e) {
822
+ e.preventDefault();
823
+ const code = digits.join("");
824
+ if (code.length === OTP_LENGTH)
825
+ submitCode(code);
826
+ }
827
+ function handleResend() {
828
+ onResend();
829
+ setResendCooldown(30);
830
+ setDigits(Array(OTP_LENGTH).fill(""));
831
+ submittedRef.current = false;
832
+ inputRefs.current[0]?.focus();
833
+ }
834
+ return /* @__PURE__ */ jsxs2("div", {
835
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:px-4 skippr:py-4",
836
+ children: [
837
+ /* @__PURE__ */ jsxs2("div", {
838
+ className: "skippr:mb-4 skippr:text-center",
839
+ children: [
840
+ /* @__PURE__ */ jsx3("p", {
841
+ className: "skippr:text-sm skippr:font-medium skippr:text-foreground",
842
+ children: "Enter verification code"
843
+ }),
844
+ /* @__PURE__ */ jsxs2("p", {
845
+ className: "skippr:mt-1 skippr:text-xs skippr:text-muted-foreground",
846
+ children: [
847
+ "We sent a 6-digit code to",
848
+ " ",
849
+ /* @__PURE__ */ jsx3("span", {
850
+ className: "skippr:font-medium skippr:text-foreground",
851
+ children: email
852
+ })
853
+ ]
854
+ })
855
+ ]
856
+ }),
857
+ /* @__PURE__ */ jsxs2("form", {
858
+ onSubmit: handleSubmit,
859
+ className: "skippr:flex skippr:flex-col skippr:gap-3",
860
+ children: [
861
+ /* @__PURE__ */ jsx3("div", {
862
+ className: "skippr:flex skippr:justify-center skippr:gap-1.5",
863
+ children: digits.map((digit, index2) => /* @__PURE__ */ jsx3("input", {
864
+ ref: (el) => {
865
+ inputRefs.current[index2] = el;
866
+ },
867
+ type: "text",
868
+ inputMode: "numeric",
869
+ maxLength: 1,
870
+ value: digit,
871
+ onChange: (e) => handleDigitChange(index2, e.target.value),
872
+ onKeyDown: (e) => handleKeyDown(index2, e),
873
+ onPaste: handlePaste,
874
+ disabled: isSubmitting,
875
+ className: "skippr:h-10 skippr:w-10 skippr:rounded-md skippr:border skippr:border-border skippr:bg-background skippr:text-center skippr:text-sm skippr:font-semibold skippr:text-foreground skippr:outline-none focus:skippr:ring-2 focus:skippr:ring-primary/30 focus:skippr:border-primary disabled:skippr:opacity-50"
876
+ }, DIGIT_KEYS[index2]))
877
+ }),
878
+ error && /* @__PURE__ */ jsx3("p", {
879
+ className: "skippr:text-xs skippr:text-center skippr:text-destructive",
880
+ children: error
881
+ }),
882
+ /* @__PURE__ */ jsx3(Button, {
883
+ type: "submit",
884
+ disabled: isSubmitting || digits.join("").length !== OTP_LENGTH,
885
+ className: "skippr:w-full",
886
+ children: isSubmitting ? /* @__PURE__ */ jsx3(LoaderCircle, {
887
+ className: "skippr:size-4 skippr:animate-spin"
888
+ }) : "Verify"
889
+ }),
890
+ /* @__PURE__ */ jsxs2("div", {
891
+ className: "skippr:flex skippr:items-center skippr:justify-between skippr:text-xs",
892
+ children: [
893
+ /* @__PURE__ */ jsx3("button", {
894
+ type: "button",
895
+ onClick: onBack,
896
+ disabled: isSubmitting,
897
+ className: "skippr:text-muted-foreground hover:skippr:text-foreground skippr:transition-colors disabled:skippr:opacity-50",
898
+ children: "Change email"
899
+ }),
900
+ /* @__PURE__ */ jsx3("button", {
901
+ type: "button",
902
+ onClick: handleResend,
903
+ disabled: isSubmitting || resendCooldown > 0,
904
+ className: "skippr:text-primary hover:skippr:opacity-80 skippr:transition-opacity disabled:skippr:opacity-50",
905
+ children: resendCooldown > 0 ? `Resend in ${resendCooldown}s` : "Resend code"
906
+ })
907
+ ]
908
+ })
909
+ ]
910
+ })
911
+ ]
912
+ });
913
+ }
914
+
521
915
  // src/components/MeetingControls.tsx
522
916
  import { useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react";
523
917
  import { ScreenSharePresets } from "livekit-client";
524
- import { useCallback as useCallback4, useEffect as useEffect2, useRef, useState as useState3 } from "react";
918
+ import { useCallback as useCallback6, useEffect as useEffect4, useRef as useRef2, useState as useState5 } from "react";
525
919
 
526
920
  // src/lib/format.ts
527
921
  function formatTime(seconds) {
@@ -535,12 +929,12 @@ function parseNumber(s) {
535
929
  }
536
930
 
537
931
  // src/components/SessionWarningBanner.tsx
538
- import { jsx as jsx3 } from "react/jsx-runtime";
932
+ import { jsx as jsx4 } from "react/jsx-runtime";
539
933
  var SESSION_WARNING_THRESHOLD_SECS = 60;
540
934
  function SessionWarningBanner({ remaining }) {
541
935
  if (remaining === null || remaining <= 0 || remaining > SESSION_WARNING_THRESHOLD_SECS)
542
936
  return null;
543
- return /* @__PURE__ */ jsx3("div", {
937
+ return /* @__PURE__ */ jsx4("div", {
544
938
  "data-testid": "session-warning-banner",
545
939
  className: "skippr:bg-red-50 skippr:px-4 skippr:py-1.5 skippr:text-center skippr:text-xs skippr:font-medium skippr:text-red-700",
546
940
  children: "Session ending soon"
@@ -548,15 +942,15 @@ function SessionWarningBanner({ remaining }) {
548
942
  }
549
943
 
550
944
  // src/components/MeetingControls.tsx
551
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
945
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
552
946
  function MeetingControls({ onHangUp }) {
553
947
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
554
948
  const { localParticipant } = useLocalParticipant3();
555
949
  const isMuted = !localParticipant.isMicrophoneEnabled;
556
950
  const isScreenSharing = localParticipant.isScreenShareEnabled;
557
- const endTimeRef = useRef(null);
558
- const [remaining, setRemaining] = useState3(null);
559
- useEffect2(() => {
951
+ const endTimeRef = useRef2(null);
952
+ const [remaining, setRemaining] = useState5(null);
953
+ useEffect4(() => {
560
954
  if (maxCallDuration === null || endTimeRef.current !== null)
561
955
  return;
562
956
  endTimeRef.current = Date.now() + maxCallDuration * 1000;
@@ -568,14 +962,14 @@ function MeetingControls({ onHangUp }) {
568
962
  const id = setInterval(tick, 1000);
569
963
  return () => clearInterval(id);
570
964
  }, [maxCallDuration]);
571
- const toggleMute = useCallback4(async () => {
965
+ const toggleMute = useCallback6(async () => {
572
966
  try {
573
967
  await localParticipant.setMicrophoneEnabled(isMuted);
574
968
  } catch (e) {
575
969
  console.error("Failed to toggle microphone:", e);
576
970
  }
577
971
  }, [localParticipant, isMuted]);
578
- const toggleScreenShare = useCallback4(async () => {
972
+ const toggleScreenShare = useCallback6(async () => {
579
973
  try {
580
974
  await localParticipant.setScreenShareEnabled(!isScreenSharing, {
581
975
  video: { displaySurface: "browser" },
@@ -586,54 +980,54 @@ function MeetingControls({ onHangUp }) {
586
980
  console.error("Failed to toggle screen share:", e);
587
981
  }
588
982
  }, [localParticipant, isScreenSharing]);
589
- useEffect2(() => {
983
+ useEffect4(() => {
590
984
  toggleMute().then(() => toggleScreenShare());
591
985
  }, []);
592
- return /* @__PURE__ */ jsxs2("div", {
986
+ return /* @__PURE__ */ jsxs3("div", {
593
987
  children: [
594
- /* @__PURE__ */ jsx4(SessionWarningBanner, {
988
+ /* @__PURE__ */ jsx5(SessionWarningBanner, {
595
989
  remaining
596
990
  }),
597
- /* @__PURE__ */ jsxs2("div", {
991
+ /* @__PURE__ */ jsxs3("div", {
598
992
  className: "skippr:flex skippr:items-center skippr:justify-between skippr:border-b skippr:px-4 skippr:py-3",
599
993
  children: [
600
- /* @__PURE__ */ jsxs2("div", {
994
+ /* @__PURE__ */ jsxs3("div", {
601
995
  className: "skippr:flex skippr:items-center skippr:gap-2",
602
996
  children: [
603
- /* @__PURE__ */ jsx4(Button, {
997
+ /* @__PURE__ */ jsx5(Button, {
604
998
  size: "icon-sm",
605
999
  variant: isMuted ? "destructive" : "outline",
606
1000
  onClick: toggleMute,
607
1001
  "aria-label": isMuted ? "Unmute" : "Mute",
608
- children: isMuted ? /* @__PURE__ */ jsx4(MicOff, {
1002
+ children: isMuted ? /* @__PURE__ */ jsx5(MicOff, {
609
1003
  className: "skippr:size-4"
610
- }) : /* @__PURE__ */ jsx4(Mic, {
1004
+ }) : /* @__PURE__ */ jsx5(Mic, {
611
1005
  className: "skippr:size-4"
612
1006
  })
613
1007
  }),
614
- /* @__PURE__ */ jsx4(Button, {
1008
+ /* @__PURE__ */ jsx5(Button, {
615
1009
  size: "icon-sm",
616
1010
  variant: isScreenSharing ? "default" : "outline",
617
1011
  onClick: toggleScreenShare,
618
1012
  "aria-label": isScreenSharing ? "Stop sharing" : "Share screen",
619
- children: isScreenSharing ? /* @__PURE__ */ jsx4(MonitorOff, {
1013
+ children: isScreenSharing ? /* @__PURE__ */ jsx5(MonitorOff, {
620
1014
  className: "skippr:size-4"
621
- }) : /* @__PURE__ */ jsx4(Monitor, {
1015
+ }) : /* @__PURE__ */ jsx5(Monitor, {
622
1016
  className: "skippr:size-4"
623
1017
  })
624
1018
  })
625
1019
  ]
626
1020
  }),
627
- remaining !== null && /* @__PURE__ */ jsx4("span", {
1021
+ remaining !== null && /* @__PURE__ */ jsx5("span", {
628
1022
  className: cn("skippr:text-sm skippr:font-medium skippr:tabular-nums", remaining <= SESSION_WARNING_THRESHOLD_SECS ? "skippr:text-red-600 skippr:animate-pulse" : "skippr:text-muted-foreground"),
629
1023
  children: formatTime(remaining)
630
1024
  }),
631
- /* @__PURE__ */ jsx4(Button, {
1025
+ /* @__PURE__ */ jsx5(Button, {
632
1026
  size: "icon-sm",
633
1027
  variant: "destructive",
634
1028
  onClick: onHangUp,
635
1029
  "aria-label": "Hang up",
636
- children: /* @__PURE__ */ jsx4(PhoneOff, {
1030
+ children: /* @__PURE__ */ jsx5(PhoneOff, {
637
1031
  className: "skippr:size-4"
638
1032
  })
639
1033
  })
@@ -644,13 +1038,13 @@ function MeetingControls({ onHangUp }) {
644
1038
  }
645
1039
 
646
1040
  // src/components/MessageList.tsx
647
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
1041
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
648
1042
 
649
1043
  // src/components/ChatInput.tsx
650
- import { useState as useState4 } from "react";
651
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1044
+ import { useState as useState6 } from "react";
1045
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
652
1046
  function ChatInput({ sendChatMessage, isSendingChat }) {
653
- const [inputText, setInputText] = useState4("");
1047
+ const [inputText, setInputText] = useState6("");
654
1048
  const canSend = inputText.trim().length > 0 && !isSendingChat;
655
1049
  function handleSubmit(e) {
656
1050
  e.preventDefault();
@@ -660,11 +1054,11 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
660
1054
  setInputText("");
661
1055
  sendChatMessage(text).catch(() => setInputText(text));
662
1056
  }
663
- return /* @__PURE__ */ jsxs3("form", {
1057
+ return /* @__PURE__ */ jsxs4("form", {
664
1058
  onSubmit: handleSubmit,
665
1059
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:border-t skippr:border-border skippr:px-3 skippr:py-2",
666
1060
  children: [
667
- /* @__PURE__ */ jsx5("input", {
1061
+ /* @__PURE__ */ jsx6("input", {
668
1062
  type: "text",
669
1063
  value: inputText,
670
1064
  onChange: (e) => setInputText(e.target.value),
@@ -672,12 +1066,12 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
672
1066
  className: cn("skippr:flex-1 skippr:rounded-lg skippr:border skippr:border-border skippr:bg-background", "skippr:px-3 skippr:py-2 skippr:text-sm skippr:text-foreground", "skippr:placeholder:text-muted-foreground skippr:outline-none", "skippr:focus:ring-1 skippr:focus:ring-ring"),
673
1067
  disabled: isSendingChat
674
1068
  }),
675
- /* @__PURE__ */ jsx5("button", {
1069
+ /* @__PURE__ */ jsx6("button", {
676
1070
  type: "submit",
677
1071
  disabled: !canSend,
678
1072
  "aria-label": "Send message",
679
1073
  className: cn("skippr:flex skippr:size-9 skippr:shrink-0 skippr:items-center skippr:justify-center", "skippr:rounded-lg skippr:bg-primary skippr:text-primary-foreground", "skippr:transition-opacity", !canSend && "skippr:opacity-50 skippr:cursor-not-allowed"),
680
- children: /* @__PURE__ */ jsx5(SendHorizontal, {
1074
+ children: /* @__PURE__ */ jsx6(SendHorizontal, {
681
1075
  className: "skippr:size-4"
682
1076
  })
683
1077
  })
@@ -686,12 +1080,12 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
686
1080
  }
687
1081
 
688
1082
  // src/components/ChatMessage.tsx
689
- import { jsx as jsx6 } from "react/jsx-runtime";
1083
+ import { jsx as jsx7 } from "react/jsx-runtime";
690
1084
  function ChatMessage({ message }) {
691
1085
  const isUser = message.role === "user";
692
- return /* @__PURE__ */ jsx6("div", {
1086
+ return /* @__PURE__ */ jsx7("div", {
693
1087
  className: cn("skippr:flex skippr:w-full skippr:px-4 skippr:py-1", isUser ? "skippr:justify-end" : "skippr:justify-start"),
694
- children: /* @__PURE__ */ jsx6("div", {
1088
+ children: /* @__PURE__ */ jsx7("div", {
695
1089
  className: cn("skippr:max-w-[85%] skippr:whitespace-pre-wrap skippr:rounded-2xl skippr:px-4 skippr:py-2.5 skippr:text-sm skippr:leading-relaxed", isUser ? "skippr:rounded-br-sm skippr:bg-primary skippr:text-primary-foreground" : "skippr:rounded-bl-sm skippr:bg-muted skippr:text-foreground"),
696
1090
  children: message.content
697
1091
  })
@@ -699,20 +1093,20 @@ function ChatMessage({ message }) {
699
1093
  }
700
1094
 
701
1095
  // src/components/TypingIndicator.tsx
702
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1096
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
703
1097
  function TypingIndicator() {
704
- return /* @__PURE__ */ jsx7("div", {
1098
+ return /* @__PURE__ */ jsx8("div", {
705
1099
  className: "skippr:flex skippr:items-center skippr:gap-1 skippr:px-4 skippr:py-3",
706
- children: /* @__PURE__ */ jsxs4("div", {
1100
+ children: /* @__PURE__ */ jsxs5("div", {
707
1101
  className: "skippr:flex skippr:items-center skippr:gap-1 skippr:rounded-2xl skippr:rounded-bl-sm skippr:bg-muted skippr:px-4 skippr:py-2.5",
708
1102
  children: [
709
- /* @__PURE__ */ jsx7("span", {
1103
+ /* @__PURE__ */ jsx8("span", {
710
1104
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:0ms]"
711
1105
  }),
712
- /* @__PURE__ */ jsx7("span", {
1106
+ /* @__PURE__ */ jsx8("span", {
713
1107
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:150ms]"
714
1108
  }),
715
- /* @__PURE__ */ jsx7("span", {
1109
+ /* @__PURE__ */ jsx8("span", {
716
1110
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:300ms]"
717
1111
  })
718
1112
  ]
@@ -721,38 +1115,38 @@ function TypingIndicator() {
721
1115
  }
722
1116
 
723
1117
  // src/components/MessageList.tsx
724
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1118
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
725
1119
  function MessageList({
726
1120
  messages,
727
1121
  isStreaming,
728
1122
  sendChatMessage,
729
1123
  isSendingChat
730
1124
  }) {
731
- const scrollRef = useRef2(null);
1125
+ const scrollRef = useRef3(null);
732
1126
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
733
- useEffect3(() => {
1127
+ useEffect5(() => {
734
1128
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
735
1129
  }, [messages.length, lastMessage?.content]);
736
1130
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
737
- return /* @__PURE__ */ jsxs5("div", {
1131
+ return /* @__PURE__ */ jsxs6("div", {
738
1132
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
739
1133
  children: [
740
- /* @__PURE__ */ jsx8("div", {
1134
+ /* @__PURE__ */ jsx9("div", {
741
1135
  className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto",
742
- children: /* @__PURE__ */ jsxs5("div", {
1136
+ children: /* @__PURE__ */ jsxs6("div", {
743
1137
  className: "skippr:flex skippr:flex-col skippr:gap-1 skippr:py-3",
744
1138
  children: [
745
- messages.map((message) => /* @__PURE__ */ jsx8(ChatMessage, {
1139
+ messages.map((message) => /* @__PURE__ */ jsx9(ChatMessage, {
746
1140
  message
747
1141
  }, message.id)),
748
- showTyping && /* @__PURE__ */ jsx8(TypingIndicator, {}),
749
- /* @__PURE__ */ jsx8("div", {
1142
+ showTyping && /* @__PURE__ */ jsx9(TypingIndicator, {}),
1143
+ /* @__PURE__ */ jsx9("div", {
750
1144
  ref: scrollRef
751
1145
  })
752
1146
  ]
753
1147
  })
754
1148
  }),
755
- /* @__PURE__ */ jsx8(ChatInput, {
1149
+ /* @__PURE__ */ jsx9(ChatInput, {
756
1150
  sendChatMessage,
757
1151
  isSendingChat
758
1152
  })
@@ -761,30 +1155,30 @@ function MessageList({
761
1155
  }
762
1156
 
763
1157
  // src/components/QuickActions.tsx
764
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1158
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
765
1159
  function QuickActions({ onStartSession, isStarting, error }) {
766
- return /* @__PURE__ */ jsxs6("div", {
1160
+ return /* @__PURE__ */ jsxs7("div", {
767
1161
  className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:gap-6 skippr:overflow-y-auto skippr:px-4 skippr:py-4",
768
1162
  children: [
769
- /* @__PURE__ */ jsx9("p", {
1163
+ /* @__PURE__ */ jsx10("p", {
770
1164
  className: "skippr:mb-1 skippr:text-sm skippr:text-muted-foreground",
771
1165
  children: "How can I help you today?"
772
1166
  }),
773
- /* @__PURE__ */ jsxs6(Button, {
1167
+ /* @__PURE__ */ jsxs7(Button, {
774
1168
  variant: "outline",
775
1169
  className: "skippr:h-auto skippr:flex-col skippr:gap-1.5 skippr:whitespace-normal skippr:py-3 skippr:text-xs",
776
1170
  onClick: onStartSession,
777
1171
  disabled: isStarting,
778
1172
  children: [
779
- isStarting ? /* @__PURE__ */ jsx9(LoaderCircle, {
1173
+ isStarting ? /* @__PURE__ */ jsx10(LoaderCircle, {
780
1174
  className: "skippr:size-4 skippr:animate-spin skippr:text-primary"
781
- }) : /* @__PURE__ */ jsx9(MessageCircleQuestionMark, {
1175
+ }) : /* @__PURE__ */ jsx10(MessageCircleQuestionMark, {
782
1176
  className: "skippr:size-4 skippr:text-primary"
783
1177
  }),
784
1178
  isStarting ? "Starting..." : "Start Session"
785
1179
  ]
786
1180
  }),
787
- error && /* @__PURE__ */ jsx9("p", {
1181
+ error && /* @__PURE__ */ jsx10("p", {
788
1182
  className: "skippr:text-xs skippr:text-destructive",
789
1183
  children: error
790
1184
  })
@@ -793,52 +1187,52 @@ function QuickActions({ onStartSession, isStarting, error }) {
793
1187
  }
794
1188
 
795
1189
  // src/components/SessionAgenda.tsx
796
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1190
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
797
1191
  function SessionAgenda({ phases, questions = [] }) {
798
1192
  if (phases.length === 0) {
799
- return /* @__PURE__ */ jsxs7("div", {
1193
+ return /* @__PURE__ */ jsxs8("div", {
800
1194
  className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
801
1195
  children: [
802
- /* @__PURE__ */ jsx10("h3", {
1196
+ /* @__PURE__ */ jsx11("h3", {
803
1197
  className: "skippr:text-sm skippr:font-semibold",
804
1198
  children: "Agenda"
805
1199
  }),
806
- /* @__PURE__ */ jsx10("p", {
1200
+ /* @__PURE__ */ jsx11("p", {
807
1201
  className: "skippr:text-xs skippr:text-muted-foreground",
808
1202
  children: "Waiting for session..."
809
1203
  })
810
1204
  ]
811
1205
  });
812
1206
  }
813
- return /* @__PURE__ */ jsxs7("div", {
1207
+ return /* @__PURE__ */ jsxs8("div", {
814
1208
  className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
815
1209
  children: [
816
- /* @__PURE__ */ jsx10("h3", {
1210
+ /* @__PURE__ */ jsx11("h3", {
817
1211
  className: "skippr:text-sm skippr:font-semibold",
818
1212
  children: "Agenda"
819
1213
  }),
820
- /* @__PURE__ */ jsx10("ul", {
1214
+ /* @__PURE__ */ jsx11("ul", {
821
1215
  className: "skippr:flex skippr:flex-col skippr:gap-2",
822
1216
  children: phases.map((phase) => {
823
1217
  const phaseQuestions = questions.filter((q) => q.phaseName === phase.name);
824
1218
  const answeredCount = phaseQuestions.filter((q) => q.status === "answered").length;
825
1219
  const totalCount = phaseQuestions.length;
826
- return /* @__PURE__ */ jsxs7("li", {
1220
+ return /* @__PURE__ */ jsxs8("li", {
827
1221
  className: "skippr:flex skippr:flex-col skippr:gap-0.5",
828
1222
  children: [
829
- /* @__PURE__ */ jsxs7("div", {
1223
+ /* @__PURE__ */ jsxs8("div", {
830
1224
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:text-sm",
831
1225
  children: [
832
- /* @__PURE__ */ jsx10(PhaseIcon, {
1226
+ /* @__PURE__ */ jsx11(PhaseIcon, {
833
1227
  status: phase.status
834
1228
  }),
835
- /* @__PURE__ */ jsx10("span", {
1229
+ /* @__PURE__ */ jsx11("span", {
836
1230
  className: cn(phase.status === "completed" && "skippr:text-muted-foreground skippr:line-through", phase.status === "active" && "skippr:font-medium skippr:text-primary"),
837
1231
  children: phase.name
838
1232
  })
839
1233
  ]
840
1234
  }),
841
- totalCount > 0 && /* @__PURE__ */ jsxs7("span", {
1235
+ totalCount > 0 && /* @__PURE__ */ jsxs8("span", {
842
1236
  className: "skippr:ml-6 skippr:text-xs skippr:text-muted-foreground",
843
1237
  children: [
844
1238
  answeredCount,
@@ -856,62 +1250,78 @@ function SessionAgenda({ phases, questions = [] }) {
856
1250
  }
857
1251
  function PhaseIcon({ status }) {
858
1252
  if (status === "completed") {
859
- return /* @__PURE__ */ jsx10("div", {
1253
+ return /* @__PURE__ */ jsx11("div", {
860
1254
  className: "skippr:flex skippr:size-4 skippr:shrink-0 skippr:items-center skippr:justify-center skippr:rounded-full skippr:bg-primary",
861
- children: /* @__PURE__ */ jsx10(Check, {
1255
+ children: /* @__PURE__ */ jsx11(Check, {
862
1256
  className: "skippr:size-2.5 skippr:text-primary-foreground",
863
1257
  strokeWidth: 3
864
1258
  })
865
1259
  });
866
1260
  }
867
1261
  if (status === "active") {
868
- return /* @__PURE__ */ jsx10(LoaderCircle, {
1262
+ return /* @__PURE__ */ jsx11(LoaderCircle, {
869
1263
  className: "skippr:size-4 skippr:shrink-0 skippr:text-primary skippr:animate-spin"
870
1264
  });
871
1265
  }
872
- return /* @__PURE__ */ jsx10(Circle, {
1266
+ return /* @__PURE__ */ jsx11(Circle, {
873
1267
  className: "skippr:size-4 skippr:shrink-0 skippr:text-muted-foreground"
874
1268
  });
875
1269
  }
876
1270
 
877
1271
  // src/components/Sidebar.tsx
878
- import { jsx as jsx11, jsxs as jsxs8, Fragment } from "react/jsx-runtime";
1272
+ import { jsx as jsx12, jsxs as jsxs9, Fragment } from "react/jsx-runtime";
879
1273
  function Sidebar() {
880
- const { isConnected, isStarting, error, startSession, disconnect, isPanelOpen, closePanel } = useLiveAgent();
881
- useEffect4(() => {
1274
+ const {
1275
+ isConnected,
1276
+ isStarting,
1277
+ error,
1278
+ startSession,
1279
+ disconnect,
1280
+ isPanelOpen,
1281
+ closePanel,
1282
+ isAuthenticated,
1283
+ isValidating,
1284
+ authError,
1285
+ requestOtp,
1286
+ verifyOtp,
1287
+ isAuthSubmitting
1288
+ } = useLiveAgent();
1289
+ useEffect6(() => {
882
1290
  document.body.style.transition = "margin-right 300ms ease-in-out";
883
1291
  document.body.style.marginRight = isPanelOpen ? `${SIDEBAR_WIDTH}px` : "0px";
884
1292
  }, [isPanelOpen]);
885
- useEffect4(() => {
1293
+ useEffect6(() => {
886
1294
  return () => {
887
1295
  document.body.style.marginRight = "";
888
1296
  document.body.style.transition = "";
889
1297
  };
890
1298
  }, []);
891
- return /* @__PURE__ */ jsx11("div", {
1299
+ return /* @__PURE__ */ jsxs9("div", {
892
1300
  className: cn("skippr:fixed skippr:top-0 skippr:right-0 skippr:h-full skippr:z-[9999]", "skippr:bg-background skippr:border-l skippr:border-border", "skippr:flex skippr:flex-col", "skippr:transition-all skippr:duration-300 skippr:ease-in-out skippr:overflow-hidden", !isPanelOpen && "skippr:w-0 skippr:border-l-0"),
893
1301
  style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
894
- children: isConnected ? /* @__PURE__ */ jsxs8(Fragment, {
895
- children: [
896
- /* @__PURE__ */ jsx11(ChatHeader, {
897
- onClose: closePanel
898
- }),
899
- /* @__PURE__ */ jsx11(ConnectedContent, {
900
- onDisconnect: disconnect
901
- })
902
- ]
903
- }) : /* @__PURE__ */ jsxs8(Fragment, {
904
- children: [
905
- /* @__PURE__ */ jsx11(ChatHeader, {
906
- onClose: closePanel
907
- }),
908
- /* @__PURE__ */ jsx11(QuickActions, {
909
- onStartSession: startSession,
910
- isStarting,
911
- error
1302
+ children: [
1303
+ /* @__PURE__ */ jsx12(ChatHeader, {
1304
+ onClose: closePanel
1305
+ }),
1306
+ isConnected ? /* @__PURE__ */ jsx12(ConnectedContent, {
1307
+ onDisconnect: disconnect
1308
+ }) : isValidating ? /* @__PURE__ */ jsx12("div", {
1309
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
1310
+ children: /* @__PURE__ */ jsx12("p", {
1311
+ className: "skippr:text-sm skippr:text-muted-foreground",
1312
+ children: "Loading..."
912
1313
  })
913
- ]
914
- })
1314
+ }) : isAuthenticated ? /* @__PURE__ */ jsx12(QuickActions, {
1315
+ onStartSession: startSession,
1316
+ isStarting,
1317
+ error
1318
+ }) : /* @__PURE__ */ jsx12(LoginFlow, {
1319
+ requestOtp,
1320
+ verifyOtp,
1321
+ error: authError,
1322
+ isSubmitting: isAuthSubmitting
1323
+ })
1324
+ ]
915
1325
  });
916
1326
  }
917
1327
  function ConnectedContent({ onDisconnect }) {
@@ -921,33 +1331,33 @@ function ConnectedContent({ onDisconnect }) {
921
1331
  const { phases } = usePhaseUpdates();
922
1332
  const { questions } = useQuestionUpdates();
923
1333
  if (!isConnected) {
924
- return /* @__PURE__ */ jsx11("div", {
1334
+ return /* @__PURE__ */ jsx12("div", {
925
1335
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
926
- children: /* @__PURE__ */ jsx11("p", {
1336
+ children: /* @__PURE__ */ jsx12("p", {
927
1337
  className: "skippr:text-sm skippr:text-muted-foreground",
928
1338
  children: "Connecting..."
929
1339
  })
930
1340
  });
931
1341
  }
932
1342
  const isAgentSpeaking = agentState === "speaking";
933
- return /* @__PURE__ */ jsxs8(Fragment, {
1343
+ return /* @__PURE__ */ jsxs9(Fragment, {
934
1344
  children: [
935
- /* @__PURE__ */ jsx11(MeetingControls, {
1345
+ /* @__PURE__ */ jsx12(MeetingControls, {
936
1346
  onHangUp: onDisconnect
937
1347
  }),
938
- /* @__PURE__ */ jsxs8("div", {
1348
+ /* @__PURE__ */ jsxs9("div", {
939
1349
  className: "skippr:flex skippr:min-h-0 skippr:flex-1",
940
1350
  children: [
941
- /* @__PURE__ */ jsx11("div", {
1351
+ /* @__PURE__ */ jsx12("div", {
942
1352
  className: "skippr:w-[260px] skippr:shrink-0 skippr:overflow-y-auto skippr:border-r",
943
- children: /* @__PURE__ */ jsx11(SessionAgenda, {
1353
+ children: /* @__PURE__ */ jsx12(SessionAgenda, {
944
1354
  phases,
945
1355
  questions
946
1356
  })
947
1357
  }),
948
- /* @__PURE__ */ jsx11("div", {
1358
+ /* @__PURE__ */ jsx12("div", {
949
1359
  className: "skippr:flex skippr:min-w-0 skippr:flex-1 skippr:flex-col",
950
- children: /* @__PURE__ */ jsx11(MessageList, {
1360
+ children: /* @__PURE__ */ jsx12(MessageList, {
951
1361
  messages: allMessages,
952
1362
  isStreaming: isAgentSpeaking,
953
1363
  sendChatMessage,
@@ -961,44 +1371,49 @@ function ConnectedContent({ onDisconnect }) {
961
1371
  }
962
1372
 
963
1373
  // src/components/SidebarTrigger.tsx
964
- import { jsx as jsx12 } from "react/jsx-runtime";
1374
+ import { jsx as jsx13 } from "react/jsx-runtime";
965
1375
  var TRIGGER_GAP = 16;
966
1376
  var TRIGGER_DEFAULT_RIGHT = 24;
967
1377
  function SidebarTrigger() {
968
1378
  const { isPanelOpen, togglePanel } = useLiveAgent();
969
- return /* @__PURE__ */ jsx12(Button, {
1379
+ return /* @__PURE__ */ jsx13(Button, {
970
1380
  size: "icon-lg",
971
1381
  onClick: togglePanel,
972
1382
  className: "skippr:fixed skippr:bottom-6 skippr:z-[9998] skippr:size-14 skippr:rounded-full skippr:shadow-lg skippr:transition-all skippr:duration-300",
973
1383
  style: { right: isPanelOpen ? SIDEBAR_WIDTH + TRIGGER_GAP : TRIGGER_DEFAULT_RIGHT },
974
1384
  title: isPanelOpen ? "Close chat" : "Chat with us",
975
- children: isPanelOpen ? /* @__PURE__ */ jsx12(X, {
1385
+ children: isPanelOpen ? /* @__PURE__ */ jsx13(X, {
976
1386
  className: "skippr:size-6"
977
- }) : /* @__PURE__ */ jsx12(MessageCircle, {
1387
+ }) : /* @__PURE__ */ jsx13(MessageCircle, {
978
1388
  className: "skippr:size-6"
979
1389
  })
980
1390
  });
981
1391
  }
982
1392
 
983
1393
  // src/components/LiveAgent.tsx
984
- import { jsx as jsx13, jsxs as jsxs9, Fragment as Fragment2 } from "react/jsx-runtime";
1394
+ import { jsx as jsx14, jsxs as jsxs10, Fragment as Fragment2 } from "react/jsx-runtime";
985
1395
  function LiveAgent({
986
1396
  agentId,
987
- authToken,
1397
+ authToken: authTokenProp,
988
1398
  appKey,
1399
+ userToken,
989
1400
  defaultOpen = false,
990
1401
  children
991
1402
  }) {
1403
+ const auth = useAuth({ appKey });
1404
+ const effectiveAuthToken = authTokenProp || auth.authToken || undefined;
992
1405
  const { connection, shouldConnect, isStarting, error, startSession, disconnect } = useSession({
993
1406
  agentId,
994
- authToken,
995
- appKey
1407
+ authToken: effectiveAuthToken,
1408
+ appKey,
1409
+ userToken
996
1410
  });
997
- const [isPanelOpen, setIsPanelOpen] = useState5(defaultOpen);
998
- const openPanel = useCallback5(() => setIsPanelOpen(true), []);
999
- const closePanel = useCallback5(() => setIsPanelOpen(false), []);
1000
- const togglePanel = useCallback5(() => setIsPanelOpen((prev) => !prev), []);
1411
+ const [isPanelOpen, setIsPanelOpen] = useState7(defaultOpen);
1412
+ const openPanel = useCallback7(() => setIsPanelOpen(true), []);
1413
+ const closePanel = useCallback7(() => setIsPanelOpen(false), []);
1414
+ const togglePanel = useCallback7(() => setIsPanelOpen((prev) => !prev), []);
1001
1415
  const isConnected = connection !== null;
1416
+ const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
1002
1417
  const ctx = useMemo4(() => ({
1003
1418
  connection,
1004
1419
  shouldConnect,
@@ -1010,7 +1425,14 @@ function LiveAgent({
1010
1425
  isPanelOpen,
1011
1426
  openPanel,
1012
1427
  closePanel,
1013
- togglePanel
1428
+ togglePanel,
1429
+ isAuthenticated,
1430
+ isValidating: auth.isValidating,
1431
+ authError: auth.error,
1432
+ requestOtp: auth.requestOtp,
1433
+ verifyOtp: auth.verifyOtp,
1434
+ logoutAuth: auth.logout,
1435
+ isAuthSubmitting: auth.isSubmitting
1014
1436
  }), [
1015
1437
  connection,
1016
1438
  shouldConnect,
@@ -1022,19 +1444,26 @@ function LiveAgent({
1022
1444
  isPanelOpen,
1023
1445
  openPanel,
1024
1446
  closePanel,
1025
- togglePanel
1447
+ togglePanel,
1448
+ isAuthenticated,
1449
+ auth.isValidating,
1450
+ auth.error,
1451
+ auth.requestOtp,
1452
+ auth.verifyOtp,
1453
+ auth.logout,
1454
+ auth.isSubmitting
1026
1455
  ]);
1027
- const widgetContent = /* @__PURE__ */ jsxs9(Fragment2, {
1456
+ const widgetContent = /* @__PURE__ */ jsxs10(Fragment2, {
1028
1457
  children: [
1029
- connection && /* @__PURE__ */ jsx13(RoomAudioRenderer, {}),
1030
- /* @__PURE__ */ jsx13(SidebarTrigger, {}),
1031
- /* @__PURE__ */ jsx13(Sidebar, {}),
1458
+ connection && /* @__PURE__ */ jsx14(RoomAudioRenderer, {}),
1459
+ /* @__PURE__ */ jsx14(SidebarTrigger, {}),
1460
+ /* @__PURE__ */ jsx14(Sidebar, {}),
1032
1461
  children
1033
1462
  ]
1034
1463
  });
1035
- return /* @__PURE__ */ jsx13(LiveAgentContext.Provider, {
1464
+ return /* @__PURE__ */ jsx14(LiveAgentContext.Provider, {
1036
1465
  value: ctx,
1037
- children: connection ? /* @__PURE__ */ jsx13(LiveKitRoom, {
1466
+ children: connection ? /* @__PURE__ */ jsx14(LiveKitRoom, {
1038
1467
  serverUrl: connection.livekitUrl,
1039
1468
  token: connection.token,
1040
1469
  connect: shouldConnect,