@vaultix.ai/react 0.3.1 → 0.3.3

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
@@ -23,28 +23,35 @@ function resolveApiOrigin(key, apiUrlProp) {
23
23
  throw new Error(`Unknown publishable key prefix "${parts[0]}".`);
24
24
  }
25
25
  try {
26
- return atob(parts.slice(3).join("_")).replace(/\/$/, "");
26
+ return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
27
27
  } catch {
28
28
  throw new Error("Publishable key has an unreadable API origin payload.");
29
29
  }
30
30
  }
31
- var STORAGE_KEY = "vaultix_jwt";
31
+ var TOKEN_COOKIE = "vaultix-token";
32
+ var COOKIE_DAYS = 30;
32
33
  function storeJwt(jwt) {
34
+ if (typeof document === "undefined") return;
33
35
  try {
34
- sessionStorage.setItem(STORAGE_KEY, jwt);
36
+ const expires = new Date(Date.now() + COOKIE_DAYS * 864e5).toUTCString();
37
+ const secure = location.protocol === "https:" ? "; Secure" : "";
38
+ document.cookie = `${TOKEN_COOKIE}=${encodeURIComponent(jwt)}; path=/; expires=${expires}; SameSite=Lax${secure}`;
35
39
  } catch {
36
40
  }
37
41
  }
38
42
  function loadJwt() {
43
+ if (typeof document === "undefined") return null;
39
44
  try {
40
- return sessionStorage.getItem(STORAGE_KEY);
45
+ const m = document.cookie.match(new RegExp(`(?:^|; )${TOKEN_COOKIE}=([^;]+)`));
46
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
41
47
  } catch {
42
48
  return null;
43
49
  }
44
50
  }
45
51
  function clearJwt() {
52
+ if (typeof document === "undefined") return;
46
53
  try {
47
- sessionStorage.removeItem(STORAGE_KEY);
54
+ document.cookie = `${TOKEN_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
48
55
  } catch {
49
56
  }
50
57
  }
@@ -115,26 +122,27 @@ function VaultixProvider({
115
122
  async function hydrate() {
116
123
  let jwt = null;
117
124
  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);
125
+ jwt = loadJwt();
126
+ if (!jwt) {
127
+ const url = new URL(window.location.href);
128
+ const handshakeToken = url.searchParams.get("__vaultix_handshake");
129
+ if (handshakeToken) {
130
+ url.searchParams.delete("__vaultix_handshake");
131
+ window.history.replaceState({}, "", url.toString());
132
+ try {
133
+ const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ body: JSON.stringify({ handshake_token: handshakeToken })
137
+ });
138
+ if (res.ok) {
139
+ const data = await res.json();
140
+ jwt = data.session_jwt;
141
+ storeJwt(jwt);
142
+ }
143
+ } catch {
133
144
  }
134
- } catch {
135
145
  }
136
- } else {
137
- jwt = loadJwt();
138
146
  }
139
147
  jwtRef.current = jwt;
140
148
  }
@@ -175,7 +183,7 @@ function VaultixProvider({
175
183
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
176
184
  };
177
185
  }, [apiOrigin, publishableKey, scheduleRefresh]);
178
- const signOut = useCallback(async () => {
186
+ const signOut = useCallback(async (redirectUrl) => {
179
187
  const jwt = jwtRef.current;
180
188
  clearAuth();
181
189
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
@@ -187,15 +195,50 @@ function VaultixProvider({
187
195
  headers
188
196
  }).catch(() => {
189
197
  });
190
- if (afterSignInUrl) window.location.href = afterSignInUrl;
191
- }, [apiOrigin, afterSignInUrl, clearAuth]);
198
+ const dest = redirectUrl ?? afterSignUpUrl ?? "/";
199
+ if (typeof window !== "undefined") window.location.href = dest;
200
+ }, [apiOrigin, afterSignUpUrl, clearAuth]);
201
+ const updateUser = useCallback(async (params) => {
202
+ const userId = user?.id;
203
+ if (!userId) throw new Error("No active session");
204
+ const jwt = jwtRef.current;
205
+ const headers = { "Content-Type": "application/json" };
206
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
207
+ const res = await fetch(`${apiOrigin}/api/v1/users/${userId}`, {
208
+ method: "PATCH",
209
+ credentials: jwt ? "omit" : "include",
210
+ headers,
211
+ body: JSON.stringify(params)
212
+ });
213
+ if (!res.ok) {
214
+ const err = await res.json().catch(() => ({ error: "Update failed" }));
215
+ throw new Error(err.error ?? "Update failed");
216
+ }
217
+ const updated = await res.json();
218
+ setUser(updated);
219
+ return updated;
220
+ }, [apiOrigin, user?.id]);
221
+ const connectPlatform = useCallback((provider, options = {}) => {
222
+ const params = new URLSearchParams({ mode: "connect" });
223
+ params.set("redirect_url", options.redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "/"));
224
+ params.set("pk", publishableKey);
225
+ if (options.scopes?.length) params.set("scope", options.scopes.join(" "));
226
+ if (options.configId) params.set("config_id", options.configId);
227
+ if (options.extraParams) Object.entries(options.extraParams).forEach(([k, v]) => params.set(k, v));
228
+ if (typeof window !== "undefined") {
229
+ window.location.href = `${apiOrigin}/api/v1/auth/oauth/${provider}/connect?${params}`;
230
+ }
231
+ }, [apiOrigin, publishableKey]);
192
232
  const value = {
193
233
  user,
194
234
  session,
195
235
  organization,
196
236
  isLoaded,
197
237
  isSignedIn: !!session,
198
- signOut
238
+ apiOrigin,
239
+ signOut,
240
+ updateUser,
241
+ connectPlatform
199
242
  };
200
243
  return /* @__PURE__ */ jsx(VaultixContext.Provider, { value, children: /* @__PURE__ */ jsx(
201
244
  "div",
@@ -220,8 +263,8 @@ function useSession() {
220
263
  return { session, isLoaded, isSignedIn };
221
264
  }
222
265
  function useUser() {
223
- const { user, isLoaded, isSignedIn } = useVaultixContext();
224
- return { user, isLoaded, isSignedIn };
266
+ const { user, isLoaded, isSignedIn, updateUser } = useVaultixContext();
267
+ return { user, isLoaded, isSignedIn, update: updateUser };
225
268
  }
226
269
  function useOrganization() {
227
270
  const { organization, isLoaded } = useVaultixContext();
@@ -232,7 +275,8 @@ function useAuth() {
232
275
  const getToken = useCallback2(async () => {
233
276
  if (!isSignedIn) return null;
234
277
  try {
235
- return sessionStorage.getItem("vaultix_jwt");
278
+ const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
279
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
236
280
  } catch {
237
281
  return null;
238
282
  }
@@ -346,6 +390,23 @@ function SignIn({
346
390
  setLoading(false);
347
391
  }
348
392
  }
393
+ async function handleMagicLinkRequest() {
394
+ setError(null);
395
+ setLoading(true);
396
+ try {
397
+ const target = resolveAfterSignInUrl(redirectUrl);
398
+ await fetch(`${apiOrigin}/api/v1/auth/magic-link/send`, {
399
+ method: "POST",
400
+ headers: { "Content-Type": "application/json" },
401
+ body: JSON.stringify({ email, redirect_url: target })
402
+ });
403
+ setStep("magic-link-sent");
404
+ } catch {
405
+ setErr("Network error. Please try again.");
406
+ } finally {
407
+ setLoading(false);
408
+ }
409
+ }
349
410
  async function handlePasswordSubmit(e) {
350
411
  e.preventDefault();
351
412
  setError(null);
@@ -514,6 +575,7 @@ function SignIn({
514
575
  password: "Enter password",
515
576
  passkey: "Passkey sign-in",
516
577
  totp: "Two-factor code",
578
+ "magic-link-sent": "Check your inbox",
517
579
  forgot: "Forgot password",
518
580
  "forgot-verify": "Enter reset code",
519
581
  "forgot-reset": "New password"
@@ -523,6 +585,7 @@ function SignIn({
523
585
  password: email,
524
586
  passkey: email,
525
587
  totp: "Enter the 6-digit code from your authenticator app",
588
+ "magic-link-sent": `We sent a sign-in link to ${email}`,
526
589
  forgot: "We'll send a reset code to your email",
527
590
  "forgot-verify": `Code sent to ${email}`,
528
591
  "forgot-reset": "Choose a new password"
@@ -580,12 +643,31 @@ function SignIn({
580
643
  }
581
644
  ),
582
645
  /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Sign in" }),
646
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
647
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" }),
648
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] text-[#475569]", children: "or" }),
649
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" })
650
+ ] }),
651
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: handleMagicLinkRequest, children: "\u2709 Email me a sign-in link" }),
583
652
  /* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
584
653
  setStep("forgot");
585
654
  setError(null);
586
655
  }, children: "Forgot password?" }),
587
656
  /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
588
657
  ] }),
658
+ step === "magic-link-sent" && /* @__PURE__ */ jsxs("div", { className: "space-y-4 text-center", children: [
659
+ /* @__PURE__ */ jsx2("div", { className: "w-14 h-14 rounded-2xl bg-purple-500/15 border border-purple-500/30 flex items-center justify-center mx-auto", children: /* @__PURE__ */ jsx2(EnvelopeIcon, {}) }),
660
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-[#94a3b8] leading-relaxed", children: [
661
+ "The link expires in ",
662
+ /* @__PURE__ */ jsx2("span", { className: "text-white/80 font-medium", children: "15 minutes" }),
663
+ " and can only be used once."
664
+ ] }),
665
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: handleMagicLinkRequest, children: "Resend link" }),
666
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
667
+ setStep("password");
668
+ setError(null);
669
+ }, children: "Use password instead" })
670
+ ] }),
589
671
  step === "passkey" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
590
672
  /* @__PURE__ */ jsxs(PrimaryButton, { loading, onClick: handlePasskeySignIn, children: [
591
673
  /* @__PURE__ */ jsx2(FingerprintIcon, {}),
@@ -828,6 +910,12 @@ function GitHubIcon() {
828
910
  function XIcon() {
829
911
  return /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx2("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.737-8.835L1.254 2.25H8.08l4.253 5.622 5.911-5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) });
830
912
  }
913
+ function EnvelopeIcon() {
914
+ return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#a78bfa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
915
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
916
+ /* @__PURE__ */ jsx2("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
917
+ ] });
918
+ }
831
919
 
832
920
  // src/components/SignUp.tsx
833
921
  import { clsx as clsx2 } from "clsx";
@@ -1168,22 +1256,17 @@ function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
1168
1256
 
1169
1257
  // src/components/UserButton.tsx
1170
1258
  import { clsx as clsx3 } from "clsx";
1171
- import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1172
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1173
- function UserButton({
1174
- showName = false,
1175
- afterSignOutUrl,
1176
- className
1177
- }) {
1259
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1260
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1261
+ function UserButton({ showName = false, afterSignOutUrl, className }) {
1178
1262
  const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
1179
1263
  const [open, setOpen] = useState4(false);
1180
1264
  const [signingOut, setSigningOut] = useState4(false);
1265
+ const [showProfile, setShowProfile] = useState4(false);
1181
1266
  const containerRef = useRef3(null);
1182
1267
  useEffect3(() => {
1183
1268
  function onPointerDown(e) {
1184
- if (containerRef.current && !containerRef.current.contains(e.target)) {
1185
- setOpen(false);
1186
- }
1269
+ if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
1187
1270
  }
1188
1271
  document.addEventListener("pointerdown", onPointerDown);
1189
1272
  return () => document.removeEventListener("pointerdown", onPointerDown);
@@ -1196,98 +1279,456 @@ function UserButton({
1196
1279
  await signOut();
1197
1280
  if (afterSignOutUrl) window.location.href = afterSignOutUrl;
1198
1281
  }
1199
- return /* @__PURE__ */ jsxs3("div", { ref: containerRef, className: clsx3("relative", className), children: [
1200
- /* @__PURE__ */ jsxs3(
1282
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1283
+ /* @__PURE__ */ jsxs3("div", { ref: containerRef, className: clsx3("relative", className), children: [
1284
+ /* @__PURE__ */ jsxs3(
1285
+ "button",
1286
+ {
1287
+ onClick: () => setOpen((o) => !o),
1288
+ className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
1289
+ children: [
1290
+ /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl }),
1291
+ showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
1292
+ ]
1293
+ }
1294
+ ),
1295
+ open && /* @__PURE__ */ jsxs3(
1296
+ "div",
1297
+ {
1298
+ className: clsx3("absolute right-0 mt-2 w-64 rounded-2xl border border-white/8", "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"),
1299
+ style: { background: "rgba(22,27,45,0.96)" },
1300
+ children: [
1301
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
1302
+ /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
1303
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
1304
+ (user.firstName || user.lastName) && /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
1305
+ /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] truncate", children: user.email })
1306
+ ] })
1307
+ ] }),
1308
+ 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: [
1309
+ /* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
1310
+ /* @__PURE__ */ jsx5(RiskBadge, { level: session.riskLevel })
1311
+ ] }) }),
1312
+ /* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
1313
+ /* @__PURE__ */ jsx5(DropdownItem, { label: "Manage account", icon: /* @__PURE__ */ jsx5(UserIcon, {}), onClick: () => {
1314
+ setOpen(false);
1315
+ setShowProfile(true);
1316
+ } }),
1317
+ /* @__PURE__ */ jsx5("div", { className: "h-px bg-white/8 my-1" }),
1318
+ /* @__PURE__ */ jsx5(DropdownItem, { label: signingOut ? "Signing out\u2026" : "Sign out", icon: /* @__PURE__ */ jsx5(SignOutIcon, {}), onClick: handleSignOut, destructive: true })
1319
+ ] })
1320
+ ]
1321
+ }
1322
+ )
1323
+ ] }),
1324
+ showProfile && /* @__PURE__ */ jsx5(ProfileModal, { onClose: () => setShowProfile(false) })
1325
+ ] });
1326
+ }
1327
+ function ProfileModal({ onClose }) {
1328
+ const { user, updateUser } = useVaultixContext();
1329
+ const [tab, setTab] = useState4("profile");
1330
+ const [firstName, setFirstName] = useState4(user?.firstName ?? "");
1331
+ const [lastName, setLastName] = useState4(user?.lastName ?? "");
1332
+ const [imageUrl, setImageUrl] = useState4(user?.imageUrl ?? "");
1333
+ const [currentPassword, setCurrentPassword] = useState4("");
1334
+ const [newPassword, setNewPassword] = useState4("");
1335
+ const [confirmPassword, setConfirmPassword] = useState4("");
1336
+ const [saving, setSaving] = useState4(false);
1337
+ const [error, setError] = useState4(null);
1338
+ const [success, setSuccess] = useState4(null);
1339
+ useEffect3(() => {
1340
+ function onKey(e) {
1341
+ if (e.key === "Escape") onClose();
1342
+ }
1343
+ document.addEventListener("keydown", onKey);
1344
+ return () => document.removeEventListener("keydown", onKey);
1345
+ }, [onClose]);
1346
+ async function saveProfile(e) {
1347
+ e.preventDefault();
1348
+ setError(null);
1349
+ setSuccess(null);
1350
+ setSaving(true);
1351
+ try {
1352
+ await updateUser({ firstName: firstName.trim() || null, lastName: lastName.trim() || null, imageUrl: imageUrl.trim() || null });
1353
+ setSuccess("Profile updated.");
1354
+ } catch (err) {
1355
+ setError(err instanceof Error ? err.message : "Update failed");
1356
+ } finally {
1357
+ setSaving(false);
1358
+ }
1359
+ }
1360
+ async function savePassword(e) {
1361
+ e.preventDefault();
1362
+ setError(null);
1363
+ setSuccess(null);
1364
+ if (newPassword !== confirmPassword) {
1365
+ setError("Passwords do not match.");
1366
+ return;
1367
+ }
1368
+ if (newPassword.length < 8) {
1369
+ setError("Password must be at least 8 characters.");
1370
+ return;
1371
+ }
1372
+ setSaving(true);
1373
+ try {
1374
+ await updateUser({ currentPassword, newPassword });
1375
+ setSuccess("Password changed.");
1376
+ setCurrentPassword("");
1377
+ setNewPassword("");
1378
+ setConfirmPassword("");
1379
+ } catch (err) {
1380
+ setError(err instanceof Error ? err.message : "Update failed");
1381
+ } finally {
1382
+ setSaving(false);
1383
+ }
1384
+ }
1385
+ const TABS = [
1386
+ { id: "profile", label: "Profile" },
1387
+ { id: "password", label: "Change password" },
1388
+ { id: "security", label: "Security" }
1389
+ ];
1390
+ return /* @__PURE__ */ jsx5(
1391
+ "div",
1392
+ {
1393
+ className: "fixed inset-0 z-[9999] flex items-center justify-center p-4",
1394
+ style: { background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" },
1395
+ onPointerDown: (e) => {
1396
+ if (e.target === e.currentTarget) onClose();
1397
+ },
1398
+ children: /* @__PURE__ */ jsxs3("div", { className: "w-full max-w-md rounded-2xl border border-white/8 shadow-2xl", style: { background: "rgba(22,27,45,0.98)" }, children: [
1399
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/8", children: [
1400
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-semibold text-white/90", children: "Manage account" }),
1401
+ /* @__PURE__ */ jsx5("button", { onClick: onClose, className: "text-[#475569] hover:text-white transition-colors", children: /* @__PURE__ */ jsx5(CloseIcon, {}) })
1402
+ ] }),
1403
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-1 px-6 pt-4 flex-wrap", children: TABS.map(({ id, label }) => /* @__PURE__ */ jsx5(
1404
+ "button",
1405
+ {
1406
+ onClick: () => {
1407
+ setTab(id);
1408
+ setError(null);
1409
+ setSuccess(null);
1410
+ },
1411
+ className: clsx3(
1412
+ "px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
1413
+ tab === id ? "bg-purple-500/15 text-purple-300 border border-purple-500/30" : "text-[#475569] hover:text-white/70"
1414
+ ),
1415
+ children: label
1416
+ },
1417
+ id
1418
+ )) }),
1419
+ /* @__PURE__ */ jsxs3("div", { className: "px-6 pt-3", children: [
1420
+ error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
1421
+ success && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-emerald-500/10 border border-emerald-500/30 px-3 py-2 text-xs text-emerald-400", children: success })
1422
+ ] }),
1423
+ tab === "profile" && /* @__PURE__ */ jsxs3("form", { onSubmit: saveProfile, className: "px-6 py-4 space-y-4", children: [
1424
+ /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-3", children: [
1425
+ /* @__PURE__ */ jsx5(ModalField, { label: "First name", value: firstName, onChange: setFirstName, placeholder: "Jane" }),
1426
+ /* @__PURE__ */ jsx5(ModalField, { label: "Last name", value: lastName, onChange: setLastName, placeholder: "Smith" })
1427
+ ] }),
1428
+ /* @__PURE__ */ jsx5(ModalField, { label: "Avatar URL", value: imageUrl, onChange: setImageUrl, placeholder: "https://\u2026", type: "url" }),
1429
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
1430
+ /* @__PURE__ */ jsx5(SaveButton, { saving }),
1431
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
1432
+ ] })
1433
+ ] }),
1434
+ tab === "password" && /* @__PURE__ */ jsxs3("form", { onSubmit: savePassword, className: "px-6 py-4 space-y-4", children: [
1435
+ /* @__PURE__ */ jsx5(ModalField, { label: "Current password", value: currentPassword, onChange: setCurrentPassword, type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }),
1436
+ /* @__PURE__ */ jsx5(ModalField, { label: "New password", value: newPassword, onChange: setNewPassword, type: "password", placeholder: "Min 8 characters" }),
1437
+ /* @__PURE__ */ jsx5(ModalField, { label: "Confirm new password", value: confirmPassword, onChange: setConfirmPassword, type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }),
1438
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
1439
+ /* @__PURE__ */ jsx5(SaveButton, { saving, label: "Change password" }),
1440
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
1441
+ ] })
1442
+ ] }),
1443
+ tab === "security" && /* @__PURE__ */ jsx5(SecurityTab, {})
1444
+ ] })
1445
+ }
1446
+ );
1447
+ }
1448
+ function SecurityTab() {
1449
+ const { apiOrigin } = useVaultixContext();
1450
+ const [totpEnabled, setTotpEnabled] = useState4(null);
1451
+ const [backupCodesLeft, setBackupCodesLeft] = useState4(null);
1452
+ const [enrollStep, setEnrollStep] = useState4("idle");
1453
+ const [qrDataUrl, setQrDataUrl] = useState4("");
1454
+ const [manualKey, setManualKey] = useState4("");
1455
+ const [enrollCode, setEnrollCode] = useState4("");
1456
+ const [disableCode, setDisableCode] = useState4("");
1457
+ const [backupCodes, setBackupCodes] = useState4([]);
1458
+ const [loading, setLoading] = useState4(false);
1459
+ const [error, setError] = useState4(null);
1460
+ const [showDisable, setShowDisable] = useState4(false);
1461
+ const fetchStatus = useCallback3(async () => {
1462
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp/status`, { credentials: "include" });
1463
+ if (res.ok) {
1464
+ const d = await res.json();
1465
+ setTotpEnabled(d.enabled);
1466
+ setBackupCodesLeft(d.backup_codes_remaining);
1467
+ }
1468
+ }, [apiOrigin]);
1469
+ useEffect3(() => {
1470
+ void fetchStatus();
1471
+ }, [fetchStatus]);
1472
+ async function startEnroll() {
1473
+ setError(null);
1474
+ setLoading(true);
1475
+ try {
1476
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp/enroll`, { method: "POST", credentials: "include" });
1477
+ const d = await res.json();
1478
+ if (!res.ok) {
1479
+ setError(d.error ?? "Failed to start enrollment.");
1480
+ return;
1481
+ }
1482
+ setQrDataUrl(d.qr_data_url);
1483
+ setManualKey(d.manual_entry_key);
1484
+ setEnrollStep("qr");
1485
+ } finally {
1486
+ setLoading(false);
1487
+ }
1488
+ }
1489
+ async function verifyEnroll(e) {
1490
+ e.preventDefault();
1491
+ setError(null);
1492
+ setLoading(true);
1493
+ try {
1494
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp/verify-enroll`, {
1495
+ method: "POST",
1496
+ credentials: "include",
1497
+ headers: { "Content-Type": "application/json" },
1498
+ body: JSON.stringify({ code: enrollCode })
1499
+ });
1500
+ const d = await res.json();
1501
+ if (!res.ok) {
1502
+ setError(d.error ?? "Invalid code. Try again.");
1503
+ return;
1504
+ }
1505
+ setBackupCodes(d.backup_codes);
1506
+ setEnrollStep("backup-codes");
1507
+ setTotpEnabled(true);
1508
+ setBackupCodesLeft(d.backup_codes.length);
1509
+ } finally {
1510
+ setLoading(false);
1511
+ }
1512
+ }
1513
+ async function disableTotp(e) {
1514
+ e.preventDefault();
1515
+ setError(null);
1516
+ setLoading(true);
1517
+ try {
1518
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp`, {
1519
+ method: "DELETE",
1520
+ credentials: "include",
1521
+ headers: { "Content-Type": "application/json" },
1522
+ body: JSON.stringify({ code: disableCode })
1523
+ });
1524
+ const d = await res.json();
1525
+ if (!res.ok) {
1526
+ setError(d.error ?? "Invalid code.");
1527
+ return;
1528
+ }
1529
+ setTotpEnabled(false);
1530
+ setBackupCodesLeft(null);
1531
+ setShowDisable(false);
1532
+ setDisableCode("");
1533
+ } finally {
1534
+ setLoading(false);
1535
+ }
1536
+ }
1537
+ if (totpEnabled === null) {
1538
+ return /* @__PURE__ */ jsx5("div", { className: "px-6 py-8 text-center text-[#475569] text-sm", children: "Loading\u2026" });
1539
+ }
1540
+ if (enrollStep === "backup-codes") {
1541
+ return /* @__PURE__ */ jsxs3("div", { className: "px-6 py-4 space-y-4", children: [
1542
+ /* @__PURE__ */ jsxs3("div", { className: "rounded-xl bg-emerald-500/10 border border-emerald-500/30 px-4 py-3", children: [
1543
+ /* @__PURE__ */ jsx5("p", { className: "text-xs font-semibold text-emerald-400 mb-1", children: "Authenticator app enabled!" }),
1544
+ /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-emerald-300/70", children: "Save these backup codes somewhere safe. Each can be used once if you lose your phone." })
1545
+ ] }),
1546
+ /* @__PURE__ */ jsx5("div", { className: "grid grid-cols-2 gap-1.5", children: backupCodes.map((c) => /* @__PURE__ */ jsx5("span", { className: "font-mono text-xs bg-white/5 border border-white/8 rounded-lg px-3 py-1.5 text-white/80 text-center", children: c }, c)) }),
1547
+ /* @__PURE__ */ jsx5(
1548
+ "button",
1549
+ {
1550
+ onClick: () => {
1551
+ const t = backupCodes.join("\n");
1552
+ navigator.clipboard.writeText(t).catch(() => {
1553
+ });
1554
+ },
1555
+ className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1 border border-white/8 rounded-lg",
1556
+ children: "Copy all codes"
1557
+ }
1558
+ ),
1559
+ /* @__PURE__ */ jsx5(
1560
+ "button",
1561
+ {
1562
+ onClick: () => setEnrollStep("idle"),
1563
+ className: "w-full py-2 rounded-xl text-xs font-semibold text-white bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 transition-all",
1564
+ children: "Done"
1565
+ }
1566
+ )
1567
+ ] });
1568
+ }
1569
+ if (enrollStep === "qr") {
1570
+ return /* @__PURE__ */ jsxs3("div", { className: "px-6 py-4 space-y-4", children: [
1571
+ /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569]", children: "Scan this QR code with Google Authenticator, Authy, or any TOTP app." }),
1572
+ qrDataUrl && /* @__PURE__ */ jsx5("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx5("img", { src: qrDataUrl, alt: "TOTP QR code", className: "rounded-xl", width: 180, height: 180 }) }),
1573
+ /* @__PURE__ */ jsxs3("details", { className: "text-[11px] text-[#475569]", children: [
1574
+ /* @__PURE__ */ jsx5("summary", { className: "cursor-pointer hover:text-white/60", children: "Can't scan? Enter manually" }),
1575
+ /* @__PURE__ */ jsx5("p", { className: "mt-1 font-mono break-all bg-white/5 rounded px-2 py-1 text-white/60 select-all", children: manualKey })
1576
+ ] }),
1577
+ error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
1578
+ /* @__PURE__ */ jsxs3("form", { onSubmit: verifyEnroll, className: "space-y-3", children: [
1579
+ /* @__PURE__ */ jsx5(
1580
+ "input",
1581
+ {
1582
+ type: "text",
1583
+ inputMode: "numeric",
1584
+ pattern: "[0-9]{6}",
1585
+ maxLength: 6,
1586
+ placeholder: "000000",
1587
+ value: enrollCode,
1588
+ onChange: (e) => setEnrollCode(e.target.value),
1589
+ autoFocus: true,
1590
+ required: true,
1591
+ className: "w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5 text-sm text-white/90 placeholder:text-[#475569] focus:outline-none focus:border-purple-500/60 transition-colors text-center tracking-[0.4em]"
1592
+ }
1593
+ ),
1594
+ /* @__PURE__ */ jsx5(
1595
+ "button",
1596
+ {
1597
+ type: "submit",
1598
+ disabled: loading || enrollCode.length !== 6,
1599
+ className: "w-full py-2.5 rounded-xl text-sm font-semibold text-white bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 transition-all disabled:opacity-60",
1600
+ children: loading ? "Verifying\u2026" : "Verify and enable"
1601
+ }
1602
+ ),
1603
+ /* @__PURE__ */ jsx5(
1604
+ "button",
1605
+ {
1606
+ type: "button",
1607
+ onClick: () => {
1608
+ setEnrollStep("idle");
1609
+ setError(null);
1610
+ },
1611
+ className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1",
1612
+ children: "\u2190 Cancel"
1613
+ }
1614
+ )
1615
+ ] })
1616
+ ] });
1617
+ }
1618
+ return /* @__PURE__ */ jsxs3("div", { className: "px-6 py-4 space-y-4", children: [
1619
+ /* @__PURE__ */ jsx5("div", { className: "rounded-xl border border-white/8 p-4", style: { background: "rgba(255,255,255,0.03)" }, children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
1620
+ /* @__PURE__ */ jsxs3("div", { children: [
1621
+ /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium text-white/90", children: "Authenticator app" }),
1622
+ /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-[#475569] mt-0.5", children: totpEnabled ? `Enabled \xB7 ${backupCodesLeft ?? 0} backup code${backupCodesLeft === 1 ? "" : "s"} remaining` : "Add a second layer of security to your account" })
1623
+ ] }),
1624
+ /* @__PURE__ */ jsx5("span", { className: clsx3(
1625
+ "text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-full border",
1626
+ totpEnabled ? "bg-emerald-500/15 text-emerald-400 border-emerald-500/30" : "bg-white/5 text-[#475569] border-white/8"
1627
+ ), children: totpEnabled ? "On" : "Off" })
1628
+ ] }) }),
1629
+ error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
1630
+ !totpEnabled && /* @__PURE__ */ jsx5(
1201
1631
  "button",
1202
1632
  {
1203
- onClick: () => setOpen((o) => !o),
1204
- className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
1205
- children: [
1206
- /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl }),
1207
- showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
1208
- ]
1633
+ onClick: startEnroll,
1634
+ disabled: loading,
1635
+ className: "w-full py-2.5 rounded-xl text-sm font-semibold text-white bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 transition-all disabled:opacity-60",
1636
+ children: loading ? "Loading\u2026" : "Enable authenticator app"
1209
1637
  }
1210
1638
  ),
1211
- open && /* @__PURE__ */ jsxs3(
1212
- "div",
1639
+ totpEnabled && !showDisable && /* @__PURE__ */ jsx5(
1640
+ "button",
1213
1641
  {
1214
- className: clsx3(
1215
- "absolute right-0 mt-2 w-64 rounded-2xl border border-white/8",
1216
- "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"
1642
+ onClick: () => setShowDisable(true),
1643
+ className: "w-full py-2 rounded-xl text-xs font-medium text-red-400 border border-red-500/20 hover:bg-red-500/8 transition-colors",
1644
+ children: "Disable authenticator app"
1645
+ }
1646
+ ),
1647
+ totpEnabled && showDisable && /* @__PURE__ */ jsxs3("form", { onSubmit: disableTotp, className: "space-y-3", children: [
1648
+ /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-[#475569]", children: "Enter your current 6-digit code (or a backup code) to confirm." }),
1649
+ /* @__PURE__ */ jsx5(
1650
+ "input",
1651
+ {
1652
+ type: "text",
1653
+ placeholder: "000000 or backup code",
1654
+ value: disableCode,
1655
+ onChange: (e) => setDisableCode(e.target.value),
1656
+ autoFocus: true,
1657
+ required: true,
1658
+ className: "w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5 text-sm text-white/90 placeholder:text-[#475569] focus:outline-none focus:border-red-500/60 transition-colors"
1659
+ }
1660
+ ),
1661
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
1662
+ /* @__PURE__ */ jsx5(
1663
+ "button",
1664
+ {
1665
+ type: "submit",
1666
+ disabled: loading || !disableCode,
1667
+ className: "flex-1 py-2 rounded-xl text-xs font-semibold text-white bg-red-600 hover:bg-red-500 transition-colors disabled:opacity-60",
1668
+ children: loading ? "Disabling\u2026" : "Confirm disable"
1669
+ }
1217
1670
  ),
1218
- style: { background: "rgba(22,27,45,0.96)" },
1219
- children: [
1220
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
1221
- /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
1222
- /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
1223
- (user.firstName || user.lastName) && /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
1224
- /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] truncate", children: user.email })
1225
- ] })
1226
- ] }),
1227
- 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: [
1228
- /* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
1229
- /* @__PURE__ */ jsx5(RiskBadge, { level: session.riskLevel })
1230
- ] }) }),
1231
- /* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
1232
- /* @__PURE__ */ jsx5(
1233
- DropdownItem,
1234
- {
1235
- label: "Manage account",
1236
- icon: /* @__PURE__ */ jsx5(UserIcon, {}),
1237
- onClick: () => setOpen(false)
1238
- }
1239
- ),
1240
- /* @__PURE__ */ jsx5(
1241
- DropdownItem,
1242
- {
1243
- label: "Security settings",
1244
- icon: /* @__PURE__ */ jsx5(ShieldIcon, {}),
1245
- onClick: () => setOpen(false)
1246
- }
1247
- ),
1248
- /* @__PURE__ */ jsx5("div", { className: "h-px bg-white/8 my-1" }),
1249
- /* @__PURE__ */ jsx5(
1250
- DropdownItem,
1251
- {
1252
- label: signingOut ? "Signing out\u2026" : "Sign out",
1253
- icon: /* @__PURE__ */ jsx5(SignOutIcon, {}),
1254
- onClick: handleSignOut,
1255
- destructive: true
1256
- }
1257
- )
1258
- ] })
1259
- ]
1671
+ /* @__PURE__ */ jsx5(
1672
+ "button",
1673
+ {
1674
+ type: "button",
1675
+ onClick: () => {
1676
+ setShowDisable(false);
1677
+ setError(null);
1678
+ setDisableCode("");
1679
+ },
1680
+ className: "px-4 py-2 rounded-xl text-xs text-[#475569] hover:text-white hover:bg-white/8 border border-white/8 transition-colors",
1681
+ children: "Cancel"
1682
+ }
1683
+ )
1684
+ ] })
1685
+ ] })
1686
+ ] });
1687
+ }
1688
+ function ModalField({ label, value, onChange, placeholder, type = "text" }) {
1689
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
1690
+ /* @__PURE__ */ jsx5("label", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: label }),
1691
+ /* @__PURE__ */ jsx5(
1692
+ "input",
1693
+ {
1694
+ type,
1695
+ value,
1696
+ onChange: (e) => onChange(e.target.value),
1697
+ placeholder,
1698
+ className: clsx3(
1699
+ "w-full bg-white/5 border border-white/8 rounded-xl px-3 py-2 text-sm text-white/90",
1700
+ "placeholder:text-[#475569] focus:outline-none focus:border-purple-500/60 transition-colors"
1701
+ )
1260
1702
  }
1261
1703
  )
1262
1704
  ] });
1263
1705
  }
1264
- function Avatar({ initials, imageUrl, size = "sm" }) {
1265
- const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
1266
- if (imageUrl) {
1267
- return (
1268
- // eslint-disable-next-line @next/next/no-img-element
1269
- /* @__PURE__ */ jsx5(
1270
- "img",
1271
- {
1272
- src: imageUrl,
1273
- alt: "",
1274
- className: clsx3(dim, "rounded-full object-cover ring-2 ring-white/10")
1275
- }
1276
- )
1277
- );
1278
- }
1706
+ function SaveButton({ saving, label = "Save changes" }) {
1279
1707
  return /* @__PURE__ */ jsx5(
1280
- "div",
1708
+ "button",
1281
1709
  {
1710
+ type: "submit",
1711
+ disabled: saving,
1282
1712
  className: clsx3(
1283
- dim,
1284
- "rounded-full flex items-center justify-center font-semibold text-white",
1285
- "bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
1713
+ "px-4 py-2 rounded-xl text-sm font-semibold text-white transition-all",
1714
+ "bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500",
1715
+ "shadow-lg shadow-purple-500/20 disabled:opacity-60"
1286
1716
  ),
1287
- children: initials
1717
+ children: saving ? "Saving\u2026" : label
1288
1718
  }
1289
1719
  );
1290
1720
  }
1721
+ function Avatar({ initials, imageUrl, size = "sm" }) {
1722
+ const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
1723
+ if (imageUrl) {
1724
+ return /* @__PURE__ */ jsx5("img", { src: imageUrl, alt: "", className: clsx3(dim, "rounded-full object-cover ring-2 ring-white/10") });
1725
+ }
1726
+ return /* @__PURE__ */ jsx5("div", { className: clsx3(
1727
+ dim,
1728
+ "rounded-full flex items-center justify-center font-semibold text-white",
1729
+ "bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
1730
+ ), children: initials });
1731
+ }
1291
1732
  function AvatarSkeleton() {
1292
1733
  return /* @__PURE__ */ jsx5("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
1293
1734
  }
@@ -1298,16 +1739,10 @@ function RiskBadge({ level }) {
1298
1739
  high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
1299
1740
  critical: "bg-red-500/15 text-red-400 border-red-500/30"
1300
1741
  };
1301
- return /* @__PURE__ */ jsx5(
1302
- "span",
1303
- {
1304
- className: clsx3(
1305
- "inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
1306
- styles[level] ?? styles.low
1307
- ),
1308
- children: level
1309
- }
1310
- );
1742
+ return /* @__PURE__ */ jsx5("span", { className: clsx3(
1743
+ "inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
1744
+ styles[level] ?? styles.low
1745
+ ), children: level });
1311
1746
  }
1312
1747
  function DropdownItem({ label, icon, onClick, destructive }) {
1313
1748
  return /* @__PURE__ */ jsxs3(
@@ -1331,9 +1766,6 @@ function UserIcon() {
1331
1766
  /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "7", r: "4" })
1332
1767
  ] });
1333
1768
  }
1334
- function ShieldIcon() {
1335
- 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" }) });
1336
- }
1337
1769
  function SignOutIcon() {
1338
1770
  return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1339
1771
  /* @__PURE__ */ jsx5("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
@@ -1341,6 +1773,12 @@ function SignOutIcon() {
1341
1773
  /* @__PURE__ */ jsx5("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
1342
1774
  ] });
1343
1775
  }
1776
+ function CloseIcon() {
1777
+ return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1778
+ /* @__PURE__ */ jsx5("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1779
+ /* @__PURE__ */ jsx5("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1780
+ ] });
1781
+ }
1344
1782
 
1345
1783
  // src/components/OrganizationSwitcher.tsx
1346
1784
  import { clsx as clsx4 } from "clsx";
@@ -1534,6 +1972,24 @@ function MiniSpinner() {
1534
1972
  /* @__PURE__ */ jsx6("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
1535
1973
  ] });
1536
1974
  }
1975
+
1976
+ // src/index.ts
1977
+ function getStoredToken() {
1978
+ if (typeof document === "undefined") return null;
1979
+ try {
1980
+ const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
1981
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
1982
+ } catch {
1983
+ return null;
1984
+ }
1985
+ }
1986
+ function clearStoredToken() {
1987
+ if (typeof document === "undefined") return;
1988
+ try {
1989
+ document.cookie = "vaultix-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
1990
+ } catch {
1991
+ }
1992
+ }
1537
1993
  export {
1538
1994
  OrganizationSwitcher,
1539
1995
  RedirectToSignIn,
@@ -1544,6 +2000,8 @@ export {
1544
2000
  SignedOut,
1545
2001
  UserButton,
1546
2002
  VaultixProvider,
2003
+ clearStoredToken,
2004
+ getStoredToken,
1547
2005
  useAuth,
1548
2006
  useOrganization,
1549
2007
  useSession,