@vaultix.ai/react 0.1.0 → 0.2.1

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
@@ -28,6 +28,26 @@ function resolveApiOrigin(key, apiUrlProp) {
28
28
  throw new Error("Publishable key has an unreadable API origin payload.");
29
29
  }
30
30
  }
31
+ var STORAGE_KEY = "vaultix_jwt";
32
+ function storeJwt(jwt) {
33
+ try {
34
+ sessionStorage.setItem(STORAGE_KEY, jwt);
35
+ } catch {
36
+ }
37
+ }
38
+ function loadJwt() {
39
+ try {
40
+ return sessionStorage.getItem(STORAGE_KEY);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function clearJwt() {
46
+ try {
47
+ sessionStorage.removeItem(STORAGE_KEY);
48
+ } catch {
49
+ }
50
+ }
31
51
  var VaultixContext = createContext(null);
32
52
  function useVaultixContext() {
33
53
  const ctx = useContext(VaultixContext);
@@ -50,42 +70,89 @@ function VaultixProvider({
50
70
  const [user, setUser] = useState(null);
51
71
  const [session, setSession] = useState(null);
52
72
  const [organization, setOrganization] = useState(null);
73
+ const jwtRef = useRef(null);
53
74
  const refreshTimerRef = useRef(null);
75
+ const clearAuth = useCallback(() => {
76
+ jwtRef.current = null;
77
+ clearJwt();
78
+ setUser(null);
79
+ setSession(null);
80
+ setOrganization(null);
81
+ }, []);
54
82
  const scheduleRefresh = useCallback(
55
83
  (expiresAt) => {
56
84
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
57
- const msUntilRefresh = expiresAt * 1e3 - Date.now() - 1e4;
85
+ const msUntilRefresh = expiresAt * 1e3 - Date.now() - 3e4;
58
86
  if (msUntilRefresh <= 0) return;
59
87
  refreshTimerRef.current = setTimeout(async () => {
60
88
  try {
61
- const res = await fetch(`${apiOrigin}/v1/session/refresh`, {
89
+ const headers = {};
90
+ if (jwtRef.current) headers["Authorization"] = `Bearer ${jwtRef.current}`;
91
+ const res = await fetch(`${apiOrigin}/api/v1/session/refresh`, {
62
92
  method: "POST",
63
- credentials: "include"
93
+ credentials: jwtRef.current ? "omit" : "include",
94
+ headers
64
95
  });
65
96
  if (!res.ok) {
66
- setUser(null);
67
- setSession(null);
68
- setOrganization(null);
97
+ clearAuth();
69
98
  return;
70
99
  }
71
100
  const data = await res.json();
101
+ if (data.session_jwt) {
102
+ jwtRef.current = data.session_jwt;
103
+ storeJwt(data.session_jwt);
104
+ }
72
105
  setSession(data.session);
73
106
  scheduleRefresh(data.session.expiresAt);
74
107
  } catch {
75
108
  }
76
109
  }, msUntilRefresh);
77
110
  },
78
- [apiOrigin]
111
+ [apiOrigin, clearAuth]
79
112
  );
80
113
  useEffect(() => {
81
114
  let cancelled = false;
82
115
  async function hydrate() {
116
+ let jwt = null;
117
+ if (typeof window !== "undefined") {
118
+ const url = new URL(window.location.href);
119
+ const handshakeToken = url.searchParams.get("__vaultix_handshake");
120
+ if (handshakeToken) {
121
+ url.searchParams.delete("__vaultix_handshake");
122
+ window.history.replaceState({}, "", url.toString());
123
+ try {
124
+ const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({ handshake_token: handshakeToken })
128
+ });
129
+ if (res.ok) {
130
+ const data = await res.json();
131
+ jwt = data.session_jwt;
132
+ storeJwt(jwt);
133
+ }
134
+ } catch {
135
+ }
136
+ } else {
137
+ jwt = loadJwt();
138
+ }
139
+ jwtRef.current = jwt;
140
+ }
83
141
  try {
84
- const res = await fetch(`${apiOrigin}/v1/me`, {
85
- credentials: "include",
86
- headers: { "X-Vaultix-Publishable-Key": publishableKey }
142
+ const headers = {
143
+ "X-Vaultix-Publishable-Key": publishableKey
144
+ };
145
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
146
+ const res = await fetch(`${apiOrigin}/api/v1/me`, {
147
+ // Use Bearer token for cross-domain; fall back to cookie for same-domain
148
+ credentials: jwt ? "omit" : "include",
149
+ headers
87
150
  });
88
151
  if (!res.ok) {
152
+ if (jwt) {
153
+ clearJwt();
154
+ jwtRef.current = null;
155
+ }
89
156
  if (!cancelled) setIsLoaded(true);
90
157
  return;
91
158
  }
@@ -108,19 +175,21 @@ function VaultixProvider({
108
175
  };
109
176
  }, [apiOrigin, publishableKey, scheduleRefresh]);
110
177
  const signOut = useCallback(async () => {
178
+ const jwt = jwtRef.current;
179
+ clearAuth();
180
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
111
181
  try {
112
- await fetch(`${apiOrigin}/v1/session`, {
182
+ const headers = {};
183
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
184
+ await fetch(`${apiOrigin}/api/v1/session`, {
113
185
  method: "DELETE",
114
- credentials: "include"
186
+ credentials: jwt ? "omit" : "include",
187
+ headers
115
188
  });
116
189
  } catch {
117
- } finally {
118
- setUser(null);
119
- setSession(null);
120
- setOrganization(null);
121
- if (afterSignInUrl) window.location.href = afterSignInUrl;
122
190
  }
123
- }, [apiOrigin, afterSignInUrl]);
191
+ if (afterSignInUrl) window.location.href = afterSignInUrl;
192
+ }, [apiOrigin, afterSignInUrl, clearAuth]);
124
193
  const value = {
125
194
  user,
126
195
  session,
@@ -142,6 +211,7 @@ function VaultixProvider({
142
211
  }
143
212
 
144
213
  // src/hooks/index.ts
214
+ import { useCallback as useCallback2 } from "react";
145
215
  function useVaultix() {
146
216
  return useVaultixContext();
147
217
  }
@@ -157,6 +227,22 @@ function useOrganization() {
157
227
  const { organization, isLoaded } = useVaultixContext();
158
228
  return { organization, isLoaded };
159
229
  }
230
+ function useAuth() {
231
+ const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
232
+ const getToken = useCallback2(async () => {
233
+ if (!isSignedIn) return null;
234
+ return null;
235
+ }, [isSignedIn]);
236
+ return {
237
+ isLoaded,
238
+ isSignedIn,
239
+ userId: user?.id ?? null,
240
+ sessionId: session?.id ?? null,
241
+ orgId: session?.orgId ?? null,
242
+ signOut,
243
+ getToken
244
+ };
245
+ }
160
246
 
161
247
  // src/components/SignIn.tsx
162
248
  import { clsx } from "clsx";
@@ -173,6 +259,18 @@ function resolveApiOrigin2(prop) {
173
259
  }
174
260
  return "";
175
261
  }
262
+ function resolveAfterSignInUrl(redirectUrlProp) {
263
+ if (redirectUrlProp) return redirectUrlProp;
264
+ if (typeof document !== "undefined") {
265
+ const el = document.querySelector("[data-vaultix-after-sign-in]");
266
+ const attr = el?.getAttribute("data-vaultix-after-sign-in");
267
+ if (attr) return attr;
268
+ const params = new URLSearchParams(window.location.search);
269
+ const redirectUrl = params.get("redirect_url");
270
+ if (redirectUrl) return redirectUrl;
271
+ }
272
+ return "/";
273
+ }
176
274
  function SignIn({
177
275
  redirectUrl,
178
276
  onSuccess,
@@ -185,13 +283,28 @@ function SignIn({
185
283
  const [email, setEmail] = useState2("");
186
284
  const [password, setPassword] = useState2("");
187
285
  const [totp, setTotp] = useState2("");
286
+ const [forgotCode, setForgotCode] = useState2("");
287
+ const [newPassword, setNewPassword] = useState2("");
188
288
  const [error, setError] = useState2(null);
189
289
  const [loading, setLoading] = useState2(false);
290
+ const [info, setInfo] = useState2(null);
190
291
  const challengeIdRef = useRef2(null);
191
292
  function setErr(msg) {
192
293
  setError(msg);
193
294
  onError?.(msg);
194
295
  }
296
+ function handleSuccess(handshakeToken) {
297
+ onSuccess?.(handshakeToken);
298
+ const target = resolveAfterSignInUrl(redirectUrl);
299
+ const url = new URL(target, window.location.origin);
300
+ url.searchParams.set("__vaultix_handshake", handshakeToken);
301
+ window.location.href = url.toString();
302
+ }
303
+ function handleGoogleSignIn() {
304
+ const target = resolveAfterSignInUrl(redirectUrl);
305
+ const params = new URLSearchParams({ redirect_url: target });
306
+ window.location.href = `${apiOrigin}/v1/auth/oauth/google?${params}`;
307
+ }
195
308
  async function handleEmailSubmit(e) {
196
309
  e.preventDefault();
197
310
  setError(null);
@@ -236,7 +349,7 @@ function SignIn({
236
349
  setStep("totp");
237
350
  return;
238
351
  }
239
- handleSuccess(data.session_id);
352
+ handleSuccess(data.handshake_token);
240
353
  } catch {
241
354
  setErr("Network error. Please try again.");
242
355
  } finally {
@@ -292,7 +405,7 @@ function SignIn({
292
405
  setErr(verifyData.error ?? "Passkey verification failed.");
293
406
  return;
294
407
  }
295
- handleSuccess(verifyData.session_id);
408
+ handleSuccess(verifyData.handshake_token);
296
409
  } catch (err) {
297
410
  if (err instanceof Error && err.name === "NotAllowedError") {
298
411
  setErr("Passkey was cancelled or timed out.");
@@ -312,28 +425,86 @@ function SignIn({
312
425
  method: "POST",
313
426
  credentials: "include",
314
427
  headers: { "Content-Type": "application/json" },
315
- body: JSON.stringify({
316
- challenge_id: challengeIdRef.current,
317
- code: totp
318
- })
428
+ body: JSON.stringify({ challenge_id: challengeIdRef.current, code: totp })
319
429
  });
320
430
  const data = await res.json();
321
431
  if (!res.ok) {
322
432
  setErr(data.error ?? "Invalid code.");
323
433
  return;
324
434
  }
325
- handleSuccess(data.session_id);
435
+ handleSuccess(data.handshake_token);
436
+ } catch {
437
+ setErr("Network error. Please try again.");
438
+ } finally {
439
+ setLoading(false);
440
+ }
441
+ }
442
+ async function handleForgotSubmit(e) {
443
+ e.preventDefault();
444
+ setError(null);
445
+ setLoading(true);
446
+ try {
447
+ await fetch(`${apiOrigin}/v1/auth/forgot-password`, {
448
+ method: "POST",
449
+ credentials: "include",
450
+ headers: { "Content-Type": "application/json" },
451
+ body: JSON.stringify({ email })
452
+ });
453
+ setInfo("Check your email for a reset code.");
454
+ setStep("forgot-verify");
326
455
  } catch {
327
456
  setErr("Network error. Please try again.");
328
457
  } finally {
329
458
  setLoading(false);
330
459
  }
331
460
  }
332
- function handleSuccess(sessionId) {
333
- onSuccess?.(sessionId);
334
- const target = redirectUrl ?? document.querySelector("[data-vaultix-after-sign-in]")?.getAttribute("data-vaultix-after-sign-in") ?? "/";
335
- if (target) window.location.href = target;
461
+ async function handleForgotVerifySubmit(e) {
462
+ e.preventDefault();
463
+ setError(null);
464
+ setInfo(null);
465
+ setStep("forgot-reset");
466
+ }
467
+ async function handleResetPasswordSubmit(e) {
468
+ e.preventDefault();
469
+ setError(null);
470
+ setLoading(true);
471
+ try {
472
+ const res = await fetch(`${apiOrigin}/v1/auth/reset-password`, {
473
+ method: "POST",
474
+ credentials: "include",
475
+ headers: { "Content-Type": "application/json" },
476
+ body: JSON.stringify({ email, code: forgotCode, new_password: newPassword })
477
+ });
478
+ const data = await res.json();
479
+ if (!res.ok) {
480
+ setErr(data.error ?? "Could not reset password.");
481
+ return;
482
+ }
483
+ handleSuccess(data.handshake_token);
484
+ } catch {
485
+ setErr("Network error. Please try again.");
486
+ } finally {
487
+ setLoading(false);
488
+ }
336
489
  }
490
+ const title = {
491
+ email: "Sign in",
492
+ password: "Enter password",
493
+ passkey: "Passkey sign-in",
494
+ totp: "Two-factor code",
495
+ forgot: "Forgot password",
496
+ "forgot-verify": "Enter reset code",
497
+ "forgot-reset": "New password"
498
+ };
499
+ const subtitle = {
500
+ email: "Welcome back",
501
+ password: email,
502
+ passkey: email,
503
+ totp: "Enter the 6-digit code from your authenticator app",
504
+ forgot: "We'll send a reset code to your email",
505
+ "forgot-verify": `Code sent to ${email}`,
506
+ "forgot-reset": "Choose a new password"
507
+ };
337
508
  return /* @__PURE__ */ jsxs(
338
509
  "div",
339
510
  {
@@ -346,34 +517,29 @@ function SignIn({
346
517
  children: [
347
518
  /* @__PURE__ */ jsxs("div", { className: "px-8 pt-8 pb-6 text-center", children: [
348
519
  /* @__PURE__ */ jsx2("div", { className: "inline-flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-br from-purple-600 to-blue-600 shadow-lg shadow-purple-500/30 mb-4", children: /* @__PURE__ */ jsx2(LockIcon, {}) }),
349
- /* @__PURE__ */ jsxs("h1", { className: "text-xl font-semibold text-white/90", children: [
350
- step === "email" && "Sign in",
351
- step === "password" && "Enter password",
352
- step === "passkey" && "Passkey sign-in",
353
- step === "totp" && "Two-factor code"
354
- ] }),
355
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-[#475569] mt-1", children: [
356
- step === "email" && "Welcome back",
357
- step === "password" && email,
358
- step === "passkey" && email,
359
- step === "totp" && "Enter the 6-digit code from your authenticator app"
360
- ] })
520
+ /* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold text-white/90", children: title[step] }),
521
+ /* @__PURE__ */ jsx2("p", { className: "text-sm text-[#475569] mt-1", children: subtitle[step] })
361
522
  ] }),
362
523
  /* @__PURE__ */ jsxs("div", { className: "px-8 pb-8 space-y-4", children: [
363
524
  error && /* @__PURE__ */ jsx2("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-xs text-red-400", children: error }) }),
364
- step === "email" && /* @__PURE__ */ jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-3", children: [
365
- /* @__PURE__ */ jsx2(
366
- Input,
367
- {
368
- type: "email",
369
- placeholder: "you@company.com",
370
- value: email,
371
- onChange: setEmail,
372
- autoFocus: true,
373
- required: true
374
- }
375
- ),
376
- /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
525
+ info && /* @__PURE__ */ jsx2("div", { className: "rounded-lg bg-blue-500/10 border border-blue-500/30 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-xs text-blue-400", children: info }) }),
526
+ step === "email" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
527
+ /* @__PURE__ */ jsx2(GoogleButton, { onClick: handleGoogleSignIn }),
528
+ /* @__PURE__ */ jsx2(Divider, {}),
529
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-3", children: [
530
+ /* @__PURE__ */ jsx2(
531
+ Input,
532
+ {
533
+ type: "email",
534
+ placeholder: "you@company.com",
535
+ value: email,
536
+ onChange: setEmail,
537
+ autoFocus: true,
538
+ required: true
539
+ }
540
+ ),
541
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
542
+ ] })
377
543
  ] }),
378
544
  step === "password" && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-3", children: [
379
545
  /* @__PURE__ */ jsx2(
@@ -388,6 +554,10 @@ function SignIn({
388
554
  }
389
555
  ),
390
556
  /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Sign in" }),
557
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
558
+ setStep("forgot");
559
+ setError(null);
560
+ }, children: "Forgot password?" }),
391
561
  /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
392
562
  ] }),
393
563
  step === "passkey" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
@@ -416,6 +586,58 @@ function SignIn({
416
586
  ),
417
587
  /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Verify" }),
418
588
  /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), children: "\u2190 Back" })
589
+ ] }),
590
+ step === "forgot" && /* @__PURE__ */ jsxs("form", { onSubmit: handleForgotSubmit, className: "space-y-3", children: [
591
+ /* @__PURE__ */ jsx2(
592
+ Input,
593
+ {
594
+ type: "email",
595
+ placeholder: "you@company.com",
596
+ value: email,
597
+ onChange: setEmail,
598
+ autoFocus: true,
599
+ required: true
600
+ }
601
+ ),
602
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Send reset code" }),
603
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
604
+ setStep("password");
605
+ setError(null);
606
+ }, children: "\u2190 Back" })
607
+ ] }),
608
+ step === "forgot-verify" && /* @__PURE__ */ jsxs("form", { onSubmit: handleForgotVerifySubmit, className: "space-y-3", children: [
609
+ /* @__PURE__ */ jsx2(
610
+ Input,
611
+ {
612
+ type: "text",
613
+ inputMode: "numeric",
614
+ pattern: "[0-9]{6}",
615
+ maxLength: 6,
616
+ placeholder: "000000",
617
+ value: forgotCode,
618
+ onChange: setForgotCode,
619
+ autoFocus: true,
620
+ required: true,
621
+ className: "text-center tracking-[0.4em] text-lg"
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
625
+ ] }),
626
+ step === "forgot-reset" && /* @__PURE__ */ jsxs("form", { onSubmit: handleResetPasswordSubmit, className: "space-y-3", children: [
627
+ /* @__PURE__ */ jsx2(
628
+ Input,
629
+ {
630
+ type: "password",
631
+ placeholder: "New password",
632
+ value: newPassword,
633
+ onChange: setNewPassword,
634
+ autoFocus: true,
635
+ required: true,
636
+ minLength: 8
637
+ }
638
+ ),
639
+ /* @__PURE__ */ jsx2(PasswordStrength, { password: newPassword }),
640
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Reset password" })
419
641
  ] })
420
642
  ] })
421
643
  ]
@@ -468,28 +690,63 @@ function GhostButton({ children, onClick }) {
468
690
  }
469
691
  );
470
692
  }
471
- function Spinner() {
693
+ function GoogleButton({ onClick }) {
472
694
  return /* @__PURE__ */ jsxs(
473
- "svg",
695
+ "button",
474
696
  {
475
- className: "animate-spin h-4 w-4 text-white/80",
476
- xmlns: "http://www.w3.org/2000/svg",
477
- fill: "none",
478
- viewBox: "0 0 24 24",
697
+ type: "button",
698
+ onClick,
699
+ className: clsx(
700
+ "w-full flex items-center justify-center gap-3 px-4 py-2.5 rounded-xl",
701
+ "text-sm font-medium text-white/80",
702
+ "bg-white/5 border border-white/10",
703
+ "hover:bg-white/10 hover:border-white/20",
704
+ "transition-all duration-150"
705
+ ),
479
706
  children: [
480
- /* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
481
- /* @__PURE__ */ jsx2(
482
- "path",
483
- {
484
- className: "opacity-75",
485
- fill: "currentColor",
486
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
487
- }
488
- )
707
+ /* @__PURE__ */ jsx2(GoogleIcon, {}),
708
+ "Continue with Google"
489
709
  ]
490
710
  }
491
711
  );
492
712
  }
713
+ function Divider() {
714
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
715
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" }),
716
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] text-[#475569] uppercase tracking-wider", children: "or" }),
717
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" })
718
+ ] });
719
+ }
720
+ function PasswordStrength({ password }) {
721
+ const score = (() => {
722
+ if (password.length === 0) return 0;
723
+ let s = 0;
724
+ if (password.length >= 8) s++;
725
+ if (/[A-Z]/.test(password)) s++;
726
+ if (/[0-9]/.test(password)) s++;
727
+ if (/[^A-Za-z0-9]/.test(password)) s++;
728
+ return s;
729
+ })();
730
+ if (password.length === 0) return null;
731
+ const labels = ["", "Weak", "Fair", "Good", "Strong"];
732
+ const colors = ["", "bg-red-500", "bg-amber-400", "bg-blue-400", "bg-emerald-400"];
733
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
734
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx2(
735
+ "div",
736
+ {
737
+ className: clsx("flex-1 h-0.5 rounded-full transition-all duration-300", i <= score ? colors[score] : "bg-white/10")
738
+ },
739
+ i
740
+ )) }),
741
+ /* @__PURE__ */ jsx2("p", { className: "text-[10px] text-[#475569]", children: labels[score] })
742
+ ] });
743
+ }
744
+ function Spinner() {
745
+ return /* @__PURE__ */ jsxs("svg", { className: "animate-spin h-4 w-4 text-white/80", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
746
+ /* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
747
+ /* @__PURE__ */ jsx2("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
748
+ ] });
749
+ }
493
750
  function LockIcon() {
494
751
  return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
495
752
  /* @__PURE__ */ jsx2("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
@@ -506,6 +763,14 @@ function FingerprintIcon() {
506
763
  /* @__PURE__ */ jsx2("path", { d: "M12 12v.01" })
507
764
  ] });
508
765
  }
766
+ function GoogleIcon() {
767
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: [
768
+ /* @__PURE__ */ jsx2("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
769
+ /* @__PURE__ */ jsx2("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
770
+ /* @__PURE__ */ jsx2("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
771
+ /* @__PURE__ */ jsx2("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
772
+ ] });
773
+ }
509
774
 
510
775
  // src/components/SignUp.tsx
511
776
  import { clsx as clsx2 } from "clsx";
@@ -519,6 +784,18 @@ function resolveApiOrigin3(prop) {
519
784
  }
520
785
  return "";
521
786
  }
787
+ function resolveAfterSignUpUrl(redirectUrlProp) {
788
+ if (redirectUrlProp) return redirectUrlProp;
789
+ if (typeof document !== "undefined") {
790
+ const el = document.querySelector("[data-vaultix-after-sign-up]");
791
+ const attr = el?.getAttribute("data-vaultix-after-sign-up");
792
+ if (attr) return attr;
793
+ const params = new URLSearchParams(window.location.search);
794
+ const redirectUrl = params.get("redirect_url");
795
+ if (redirectUrl) return redirectUrl;
796
+ }
797
+ return "/";
798
+ }
522
799
  function SignUp({
523
800
  redirectUrl,
524
801
  onSuccess,
@@ -538,6 +815,18 @@ function SignUp({
538
815
  setError(msg);
539
816
  onError?.(msg);
540
817
  }
818
+ function handleSuccess(handshakeToken) {
819
+ onSuccess?.(handshakeToken);
820
+ const target = resolveAfterSignUpUrl(redirectUrl);
821
+ const url = new URL(target, window.location.origin);
822
+ url.searchParams.set("__vaultix_handshake", handshakeToken);
823
+ window.location.href = url.toString();
824
+ }
825
+ function handleGoogleSignUp() {
826
+ const target = resolveAfterSignUpUrl(redirectUrl);
827
+ const params = new URLSearchParams({ redirect_url: target });
828
+ window.location.href = `${apiOrigin}/v1/auth/oauth/google?${params}`;
829
+ }
541
830
  async function handleEmailPassword(e) {
542
831
  e.preventDefault();
543
832
  setError(null);
@@ -581,9 +870,7 @@ function SignUp({
581
870
  setErr(data.error ?? "Invalid code.");
582
871
  return;
583
872
  }
584
- onSuccess?.(data.session_id);
585
- const target = redirectUrl ?? document.querySelector("[data-vaultix-after-sign-up]")?.getAttribute("data-vaultix-after-sign-up") ?? "/";
586
- if (target) window.location.href = target;
873
+ handleSuccess(data.handshake_token);
587
874
  } catch {
588
875
  setErr("Network error. Please try again.");
589
876
  } finally {
@@ -618,31 +905,35 @@ function SignUp({
618
905
  ] }),
619
906
  /* @__PURE__ */ jsxs2("div", { className: "px-8 pb-8 space-y-4", children: [
620
907
  error && /* @__PURE__ */ jsx3("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2", children: /* @__PURE__ */ jsx3("p", { className: "text-xs text-red-400", children: error }) }),
621
- (step === "email" || step === "password") && /* @__PURE__ */ jsxs2("form", { onSubmit: handleEmailPassword, className: "space-y-3", children: [
622
- /* @__PURE__ */ jsx3(
623
- SignUpInput,
624
- {
625
- type: "email",
626
- placeholder: "you@company.com",
627
- value: email,
628
- onChange: setEmail,
629
- autoFocus: true,
630
- required: true
631
- }
632
- ),
633
- /* @__PURE__ */ jsx3(
634
- SignUpInput,
635
- {
636
- type: "password",
637
- placeholder: "Create a password",
638
- value: password,
639
- onChange: setPassword,
640
- required: true,
641
- minLength: 8
642
- }
643
- ),
644
- /* @__PURE__ */ jsx3(PasswordStrength, { password }),
645
- /* @__PURE__ */ jsx3(SignUpPrimaryButton, { loading, children: "Create account" })
908
+ step === "email" && /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
909
+ /* @__PURE__ */ jsx3(GoogleButton2, { onClick: handleGoogleSignUp }),
910
+ /* @__PURE__ */ jsx3(Divider2, {}),
911
+ /* @__PURE__ */ jsxs2("form", { onSubmit: handleEmailPassword, className: "space-y-3", children: [
912
+ /* @__PURE__ */ jsx3(
913
+ SignUpInput,
914
+ {
915
+ type: "email",
916
+ placeholder: "you@company.com",
917
+ value: email,
918
+ onChange: setEmail,
919
+ autoFocus: true,
920
+ required: true
921
+ }
922
+ ),
923
+ /* @__PURE__ */ jsx3(
924
+ SignUpInput,
925
+ {
926
+ type: "password",
927
+ placeholder: "Create a password",
928
+ value: password,
929
+ onChange: setPassword,
930
+ required: true,
931
+ minLength: 8
932
+ }
933
+ ),
934
+ /* @__PURE__ */ jsx3(PasswordStrength2, { password }),
935
+ /* @__PURE__ */ jsx3(SignUpPrimaryButton, { loading, children: "Create account" })
936
+ ] })
646
937
  ] }),
647
938
  step === "verify" && /* @__PURE__ */ jsxs2("form", { onSubmit: handleVerification, className: "space-y-3", children: [
648
939
  /* @__PURE__ */ jsx3(
@@ -712,7 +1003,34 @@ function SignUpPrimaryButton({
712
1003
  }
713
1004
  );
714
1005
  }
715
- function PasswordStrength({ password }) {
1006
+ function GoogleButton2({ onClick }) {
1007
+ return /* @__PURE__ */ jsxs2(
1008
+ "button",
1009
+ {
1010
+ type: "button",
1011
+ onClick,
1012
+ className: clsx2(
1013
+ "w-full flex items-center justify-center gap-3 px-4 py-2.5 rounded-xl",
1014
+ "text-sm font-medium text-white/80",
1015
+ "bg-white/5 border border-white/10",
1016
+ "hover:bg-white/10 hover:border-white/20",
1017
+ "transition-all duration-150"
1018
+ ),
1019
+ children: [
1020
+ /* @__PURE__ */ jsx3(GoogleIcon2, {}),
1021
+ "Continue with Google"
1022
+ ]
1023
+ }
1024
+ );
1025
+ }
1026
+ function Divider2() {
1027
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
1028
+ /* @__PURE__ */ jsx3("div", { className: "flex-1 h-px bg-white/8" }),
1029
+ /* @__PURE__ */ jsx3("span", { className: "text-[10px] text-[#475569] uppercase tracking-wider", children: "or" }),
1030
+ /* @__PURE__ */ jsx3("div", { className: "flex-1 h-px bg-white/8" })
1031
+ ] });
1032
+ }
1033
+ function PasswordStrength2({ password }) {
716
1034
  const score = (() => {
717
1035
  if (password.length === 0) return 0;
718
1036
  let s = 0;
@@ -724,13 +1042,7 @@ function PasswordStrength({ password }) {
724
1042
  })();
725
1043
  if (password.length === 0) return null;
726
1044
  const labels = ["", "Weak", "Fair", "Good", "Strong"];
727
- const colors = [
728
- "",
729
- "bg-red-500",
730
- "bg-amber-400",
731
- "bg-blue-400",
732
- "bg-emerald-400"
733
- ];
1045
+ const colors = ["", "bg-red-500", "bg-amber-400", "bg-blue-400", "bg-emerald-400"];
734
1046
  return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
735
1047
  /* @__PURE__ */ jsx3("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx3(
736
1048
  "div",
@@ -746,35 +1058,61 @@ function PasswordStrength({ password }) {
746
1058
  ] });
747
1059
  }
748
1060
  function Spinner2() {
749
- return /* @__PURE__ */ jsxs2(
750
- "svg",
751
- {
752
- className: "animate-spin h-4 w-4 text-white/80",
753
- xmlns: "http://www.w3.org/2000/svg",
754
- fill: "none",
755
- viewBox: "0 0 24 24",
756
- children: [
757
- /* @__PURE__ */ jsx3("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
758
- /* @__PURE__ */ jsx3(
759
- "path",
760
- {
761
- className: "opacity-75",
762
- fill: "currentColor",
763
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
764
- }
765
- )
766
- ]
767
- }
768
- );
1061
+ return /* @__PURE__ */ jsxs2("svg", { className: "animate-spin h-4 w-4 text-white/80", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
1062
+ /* @__PURE__ */ jsx3("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
1063
+ /* @__PURE__ */ jsx3("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
1064
+ ] });
769
1065
  }
770
1066
  function SparkleIcon() {
771
1067
  return /* @__PURE__ */ jsx3("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" }) });
772
1068
  }
1069
+ function GoogleIcon2() {
1070
+ return /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: [
1071
+ /* @__PURE__ */ jsx3("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
1072
+ /* @__PURE__ */ jsx3("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
1073
+ /* @__PURE__ */ jsx3("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
1074
+ /* @__PURE__ */ jsx3("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
1075
+ ] });
1076
+ }
1077
+
1078
+ // src/components/SignedIn.tsx
1079
+ import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
1080
+ function SignedIn({ children }) {
1081
+ const { isLoaded, isSignedIn } = useVaultixContext();
1082
+ if (!isLoaded || !isSignedIn) return null;
1083
+ return /* @__PURE__ */ jsx4(Fragment, { children });
1084
+ }
1085
+ function SignedOut({ children }) {
1086
+ const { isLoaded, isSignedIn } = useVaultixContext();
1087
+ if (!isLoaded || isSignedIn) return null;
1088
+ return /* @__PURE__ */ jsx4(Fragment, { children });
1089
+ }
1090
+
1091
+ // src/components/RedirectComponents.tsx
1092
+ import { useEffect as useEffect2 } from "react";
1093
+ function RedirectToSignIn({ redirectUrl = "/sign-in" }) {
1094
+ const { isLoaded, isSignedIn } = useVaultixContext();
1095
+ useEffect2(() => {
1096
+ if (isLoaded && !isSignedIn) {
1097
+ window.location.href = redirectUrl;
1098
+ }
1099
+ }, [isLoaded, isSignedIn, redirectUrl]);
1100
+ return null;
1101
+ }
1102
+ function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
1103
+ const { isLoaded, isSignedIn } = useVaultixContext();
1104
+ useEffect2(() => {
1105
+ if (isLoaded && !isSignedIn) {
1106
+ window.location.href = redirectUrl;
1107
+ }
1108
+ }, [isLoaded, isSignedIn, redirectUrl]);
1109
+ return null;
1110
+ }
773
1111
 
774
1112
  // src/components/UserButton.tsx
775
1113
  import { clsx as clsx3 } from "clsx";
776
- import { useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
777
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1114
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1115
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
778
1116
  function UserButton({
779
1117
  showName = false,
780
1118
  afterSignOutUrl,
@@ -784,7 +1122,7 @@ function UserButton({
784
1122
  const [open, setOpen] = useState4(false);
785
1123
  const [signingOut, setSigningOut] = useState4(false);
786
1124
  const containerRef = useRef3(null);
787
- useEffect2(() => {
1125
+ useEffect3(() => {
788
1126
  function onPointerDown(e) {
789
1127
  if (containerRef.current && !containerRef.current.contains(e.target)) {
790
1128
  setOpen(false);
@@ -793,7 +1131,7 @@ function UserButton({
793
1131
  document.addEventListener("pointerdown", onPointerDown);
794
1132
  return () => document.removeEventListener("pointerdown", onPointerDown);
795
1133
  }, []);
796
- if (!isLoaded) return /* @__PURE__ */ jsx4(AvatarSkeleton, {});
1134
+ if (!isLoaded) return /* @__PURE__ */ jsx5(AvatarSkeleton, {});
797
1135
  if (!isSignedIn || !user) return null;
798
1136
  const initials = [user.firstName, user.lastName].filter(Boolean).map((s) => s.charAt(0)).join("").toUpperCase() || user.email.charAt(0).toUpperCase();
799
1137
  async function handleSignOut() {
@@ -808,8 +1146,8 @@ function UserButton({
808
1146
  onClick: () => setOpen((o) => !o),
809
1147
  className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
810
1148
  children: [
811
- /* @__PURE__ */ jsx4(Avatar, { initials, imageUrl: user.imageUrl }),
812
- showName && /* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
1149
+ /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl }),
1150
+ showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
813
1151
  ]
814
1152
  }
815
1153
  ),
@@ -823,39 +1161,39 @@ function UserButton({
823
1161
  style: { background: "rgba(22,27,45,0.96)" },
824
1162
  children: [
825
1163
  /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
826
- /* @__PURE__ */ jsx4(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
1164
+ /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
827
1165
  /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
828
- (user.firstName || user.lastName) && /* @__PURE__ */ jsx4("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
829
- /* @__PURE__ */ jsx4("p", { className: "text-xs text-[#475569] truncate", children: user.email })
1166
+ (user.firstName || user.lastName) && /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
1167
+ /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] truncate", children: user.email })
830
1168
  ] })
831
1169
  ] }),
832
- session && /* @__PURE__ */ jsx4("div", { className: "px-4 py-2 border-b border-white/8", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
833
- /* @__PURE__ */ jsx4("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
834
- /* @__PURE__ */ jsx4(RiskBadge, { level: session.riskLevel })
1170
+ session && /* @__PURE__ */ jsx5("div", { className: "px-4 py-2 border-b border-white/8", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
1171
+ /* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
1172
+ /* @__PURE__ */ jsx5(RiskBadge, { level: session.riskLevel })
835
1173
  ] }) }),
836
1174
  /* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
837
- /* @__PURE__ */ jsx4(
1175
+ /* @__PURE__ */ jsx5(
838
1176
  DropdownItem,
839
1177
  {
840
1178
  label: "Manage account",
841
- icon: /* @__PURE__ */ jsx4(UserIcon, {}),
1179
+ icon: /* @__PURE__ */ jsx5(UserIcon, {}),
842
1180
  onClick: () => setOpen(false)
843
1181
  }
844
1182
  ),
845
- /* @__PURE__ */ jsx4(
1183
+ /* @__PURE__ */ jsx5(
846
1184
  DropdownItem,
847
1185
  {
848
1186
  label: "Security settings",
849
- icon: /* @__PURE__ */ jsx4(ShieldIcon, {}),
1187
+ icon: /* @__PURE__ */ jsx5(ShieldIcon, {}),
850
1188
  onClick: () => setOpen(false)
851
1189
  }
852
1190
  ),
853
- /* @__PURE__ */ jsx4("div", { className: "h-px bg-white/8 my-1" }),
854
- /* @__PURE__ */ jsx4(
1191
+ /* @__PURE__ */ jsx5("div", { className: "h-px bg-white/8 my-1" }),
1192
+ /* @__PURE__ */ jsx5(
855
1193
  DropdownItem,
856
1194
  {
857
1195
  label: signingOut ? "Signing out\u2026" : "Sign out",
858
- icon: /* @__PURE__ */ jsx4(SignOutIcon, {}),
1196
+ icon: /* @__PURE__ */ jsx5(SignOutIcon, {}),
859
1197
  onClick: handleSignOut,
860
1198
  destructive: true
861
1199
  }
@@ -871,7 +1209,7 @@ function Avatar({ initials, imageUrl, size = "sm" }) {
871
1209
  if (imageUrl) {
872
1210
  return (
873
1211
  // eslint-disable-next-line @next/next/no-img-element
874
- /* @__PURE__ */ jsx4(
1212
+ /* @__PURE__ */ jsx5(
875
1213
  "img",
876
1214
  {
877
1215
  src: imageUrl,
@@ -881,7 +1219,7 @@ function Avatar({ initials, imageUrl, size = "sm" }) {
881
1219
  )
882
1220
  );
883
1221
  }
884
- return /* @__PURE__ */ jsx4(
1222
+ return /* @__PURE__ */ jsx5(
885
1223
  "div",
886
1224
  {
887
1225
  className: clsx3(
@@ -894,7 +1232,7 @@ function Avatar({ initials, imageUrl, size = "sm" }) {
894
1232
  );
895
1233
  }
896
1234
  function AvatarSkeleton() {
897
- return /* @__PURE__ */ jsx4("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
1235
+ return /* @__PURE__ */ jsx5("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
898
1236
  }
899
1237
  function RiskBadge({ level }) {
900
1238
  const styles = {
@@ -903,7 +1241,7 @@ function RiskBadge({ level }) {
903
1241
  high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
904
1242
  critical: "bg-red-500/15 text-red-400 border-red-500/30"
905
1243
  };
906
- return /* @__PURE__ */ jsx4(
1244
+ return /* @__PURE__ */ jsx5(
907
1245
  "span",
908
1246
  {
909
1247
  className: clsx3(
@@ -924,7 +1262,7 @@ function DropdownItem({ label, icon, onClick, destructive }) {
924
1262
  destructive ? "text-red-400 hover:bg-red-500/10" : "text-white/70 hover:text-white hover:bg-white/8"
925
1263
  ),
926
1264
  children: [
927
- /* @__PURE__ */ jsx4("span", { className: "opacity-70", children: icon }),
1265
+ /* @__PURE__ */ jsx5("span", { className: "opacity-70", children: icon }),
928
1266
  label
929
1267
  ]
930
1268
  }
@@ -932,25 +1270,25 @@ function DropdownItem({ label, icon, onClick, destructive }) {
932
1270
  }
933
1271
  function UserIcon() {
934
1272
  return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
935
- /* @__PURE__ */ jsx4("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
936
- /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "7", r: "4" })
1273
+ /* @__PURE__ */ jsx5("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
1274
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "7", r: "4" })
937
1275
  ] });
938
1276
  }
939
1277
  function ShieldIcon() {
940
- return /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
1278
+ return /* @__PURE__ */ jsx5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx5("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
941
1279
  }
942
1280
  function SignOutIcon() {
943
1281
  return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
944
- /* @__PURE__ */ jsx4("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
945
- /* @__PURE__ */ jsx4("polyline", { points: "16 17 21 12 16 7" }),
946
- /* @__PURE__ */ jsx4("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
1282
+ /* @__PURE__ */ jsx5("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
1283
+ /* @__PURE__ */ jsx5("polyline", { points: "16 17 21 12 16 7" }),
1284
+ /* @__PURE__ */ jsx5("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
947
1285
  ] });
948
1286
  }
949
1287
 
950
1288
  // src/components/OrganizationSwitcher.tsx
951
1289
  import { clsx as clsx4 } from "clsx";
952
- import { useEffect as useEffect3, useRef as useRef4, useState as useState5 } from "react";
953
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1290
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
1291
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
954
1292
  function resolveApiOrigin4() {
955
1293
  if (typeof document !== "undefined") {
956
1294
  const el = document.querySelector("[data-vaultix-api]");
@@ -967,7 +1305,7 @@ function OrganizationSwitcher({
967
1305
  const [orgs, setOrgs] = useState5([]);
968
1306
  const [switching, setSwitching] = useState5(null);
969
1307
  const containerRef = useRef4(null);
970
- useEffect3(() => {
1308
+ useEffect4(() => {
971
1309
  function onPointerDown(e) {
972
1310
  if (containerRef.current && !containerRef.current.contains(e.target)) {
973
1311
  setOpen(false);
@@ -976,7 +1314,7 @@ function OrganizationSwitcher({
976
1314
  document.addEventListener("pointerdown", onPointerDown);
977
1315
  return () => document.removeEventListener("pointerdown", onPointerDown);
978
1316
  }, []);
979
- useEffect3(() => {
1317
+ useEffect4(() => {
980
1318
  if (!open) return;
981
1319
  const api = resolveApiOrigin4();
982
1320
  fetch(`${api}/v1/me/organizations`, { credentials: "include" }).then((r) => r.json()).then(
@@ -1021,12 +1359,12 @@ function OrganizationSwitcher({
1021
1359
  "transition-all duration-150"
1022
1360
  ),
1023
1361
  children: [
1024
- /* @__PURE__ */ jsx5(OrgAvatar, { initials }),
1362
+ /* @__PURE__ */ jsx6(OrgAvatar, { initials }),
1025
1363
  /* @__PURE__ */ jsxs4("div", { className: "text-left min-w-0", children: [
1026
- /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate max-w-[120px]", children: displayName }),
1027
- organization && /* @__PURE__ */ jsx5("p", { className: "text-[10px] text-[#475569] capitalize", children: organization.role })
1364
+ /* @__PURE__ */ jsx6("p", { className: "text-sm font-semibold text-white/90 truncate max-w-[120px]", children: displayName }),
1365
+ organization && /* @__PURE__ */ jsx6("p", { className: "text-[10px] text-[#475569] capitalize", children: organization.role })
1028
1366
  ] }),
1029
- /* @__PURE__ */ jsx5(ChevronIcon, {})
1367
+ /* @__PURE__ */ jsx6(ChevronIcon, {})
1030
1368
  ]
1031
1369
  }
1032
1370
  ),
@@ -1039,9 +1377,9 @@ function OrganizationSwitcher({
1039
1377
  ),
1040
1378
  style: { background: "rgba(22,27,45,0.96)" },
1041
1379
  children: [
1042
- /* @__PURE__ */ jsx5("div", { className: "px-4 py-3 border-b border-white/8", children: /* @__PURE__ */ jsx5("p", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Switch organization" }) }),
1380
+ /* @__PURE__ */ jsx6("div", { className: "px-4 py-3 border-b border-white/8", children: /* @__PURE__ */ jsx6("p", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Switch organization" }) }),
1043
1381
  /* @__PURE__ */ jsxs4("div", { className: "p-2 max-h-56 overflow-y-auto", children: [
1044
- orgs.length === 0 && /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] text-center py-4", children: "Loading\u2026" }),
1382
+ orgs.length === 0 && /* @__PURE__ */ jsx6("p", { className: "text-xs text-[#475569] text-center py-4", children: "Loading\u2026" }),
1045
1383
  orgs.map((org) => {
1046
1384
  const isActive = org.id === organization?.id;
1047
1385
  const orgInitials = org.name.split(/\s+/).slice(0, 2).map((w) => w[0]).join("").toUpperCase();
@@ -1055,9 +1393,9 @@ function OrganizationSwitcher({
1055
1393
  isActive ? "bg-purple-500/10 cursor-default" : "hover:bg-white/8 cursor-pointer"
1056
1394
  ),
1057
1395
  children: [
1058
- /* @__PURE__ */ jsx5(OrgAvatar, { initials: orgInitials, active: isActive }),
1396
+ /* @__PURE__ */ jsx6(OrgAvatar, { initials: orgInitials, active: isActive }),
1059
1397
  /* @__PURE__ */ jsxs4("div", { className: "flex-1 min-w-0", children: [
1060
- /* @__PURE__ */ jsx5(
1398
+ /* @__PURE__ */ jsx6(
1061
1399
  "p",
1062
1400
  {
1063
1401
  className: clsx4(
@@ -1067,17 +1405,17 @@ function OrganizationSwitcher({
1067
1405
  children: org.name
1068
1406
  }
1069
1407
  ),
1070
- /* @__PURE__ */ jsx5("p", { className: "text-[10px] text-[#475569] capitalize", children: org.role })
1408
+ /* @__PURE__ */ jsx6("p", { className: "text-[10px] text-[#475569] capitalize", children: org.role })
1071
1409
  ] }),
1072
- isActive && /* @__PURE__ */ jsx5("span", { className: "w-1.5 h-1.5 rounded-full bg-purple-400 shrink-0" }),
1073
- switching === org.id && /* @__PURE__ */ jsx5(MiniSpinner, {})
1410
+ isActive && /* @__PURE__ */ jsx6("span", { className: "w-1.5 h-1.5 rounded-full bg-purple-400 shrink-0" }),
1411
+ switching === org.id && /* @__PURE__ */ jsx6(MiniSpinner, {})
1074
1412
  ]
1075
1413
  },
1076
1414
  org.id
1077
1415
  );
1078
1416
  })
1079
1417
  ] }),
1080
- /* @__PURE__ */ jsx5("div", { className: "p-2 border-t border-white/8", children: /* @__PURE__ */ jsxs4(
1418
+ /* @__PURE__ */ jsx6("div", { className: "p-2 border-t border-white/8", children: /* @__PURE__ */ jsxs4(
1081
1419
  "button",
1082
1420
  {
1083
1421
  onClick: () => {
@@ -1085,7 +1423,7 @@ function OrganizationSwitcher({
1085
1423
  },
1086
1424
  className: "w-full flex items-center gap-2 px-3 py-2 rounded-xl text-sm text-[#475569] hover:text-white hover:bg-white/8 transition-colors",
1087
1425
  children: [
1088
- /* @__PURE__ */ jsx5(PlusIcon, {}),
1426
+ /* @__PURE__ */ jsx6(PlusIcon, {}),
1089
1427
  "Create organization"
1090
1428
  ]
1091
1429
  }
@@ -1099,7 +1437,7 @@ function OrgAvatar({
1099
1437
  initials,
1100
1438
  active = false
1101
1439
  }) {
1102
- return /* @__PURE__ */ jsx5(
1440
+ return /* @__PURE__ */ jsx6(
1103
1441
  "div",
1104
1442
  {
1105
1443
  className: clsx4(
@@ -1111,7 +1449,7 @@ function OrgAvatar({
1111
1449
  );
1112
1450
  }
1113
1451
  function ChevronIcon() {
1114
- return /* @__PURE__ */ jsx5(
1452
+ return /* @__PURE__ */ jsx6(
1115
1453
  "svg",
1116
1454
  {
1117
1455
  width: "12",
@@ -1123,28 +1461,33 @@ function ChevronIcon() {
1123
1461
  strokeLinecap: "round",
1124
1462
  strokeLinejoin: "round",
1125
1463
  className: "text-[#475569] shrink-0",
1126
- children: /* @__PURE__ */ jsx5("polyline", { points: "6 9 12 15 18 9" })
1464
+ children: /* @__PURE__ */ jsx6("polyline", { points: "6 9 12 15 18 9" })
1127
1465
  }
1128
1466
  );
1129
1467
  }
1130
1468
  function PlusIcon() {
1131
1469
  return /* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1132
- /* @__PURE__ */ jsx5("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1133
- /* @__PURE__ */ jsx5("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1470
+ /* @__PURE__ */ jsx6("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1471
+ /* @__PURE__ */ jsx6("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1134
1472
  ] });
1135
1473
  }
1136
1474
  function MiniSpinner() {
1137
1475
  return /* @__PURE__ */ jsxs4("svg", { className: "animate-spin h-3 w-3 text-[#475569]", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
1138
- /* @__PURE__ */ jsx5("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
1139
- /* @__PURE__ */ jsx5("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
1476
+ /* @__PURE__ */ jsx6("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
1477
+ /* @__PURE__ */ jsx6("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
1140
1478
  ] });
1141
1479
  }
1142
1480
  export {
1143
1481
  OrganizationSwitcher,
1482
+ RedirectToSignIn,
1483
+ RedirectToSignUp,
1144
1484
  SignIn,
1145
1485
  SignUp,
1486
+ SignedIn,
1487
+ SignedOut,
1146
1488
  UserButton,
1147
1489
  VaultixProvider,
1490
+ useAuth,
1148
1491
  useOrganization,
1149
1492
  useSession,
1150
1493
  useUser,