@vaultix.ai/react 0.3.2 → 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,8 +195,29 @@ 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]);
192
221
  const connectPlatform = useCallback((provider, options = {}) => {
193
222
  const params = new URLSearchParams({ mode: "connect" });
194
223
  params.set("redirect_url", options.redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "/"));
@@ -206,7 +235,9 @@ function VaultixProvider({
206
235
  organization,
207
236
  isLoaded,
208
237
  isSignedIn: !!session,
238
+ apiOrigin,
209
239
  signOut,
240
+ updateUser,
210
241
  connectPlatform
211
242
  };
212
243
  return /* @__PURE__ */ jsx(VaultixContext.Provider, { value, children: /* @__PURE__ */ jsx(
@@ -232,8 +263,8 @@ function useSession() {
232
263
  return { session, isLoaded, isSignedIn };
233
264
  }
234
265
  function useUser() {
235
- const { user, isLoaded, isSignedIn } = useVaultixContext();
236
- return { user, isLoaded, isSignedIn };
266
+ const { user, isLoaded, isSignedIn, updateUser } = useVaultixContext();
267
+ return { user, isLoaded, isSignedIn, update: updateUser };
237
268
  }
238
269
  function useOrganization() {
239
270
  const { organization, isLoaded } = useVaultixContext();
@@ -244,7 +275,8 @@ function useAuth() {
244
275
  const getToken = useCallback2(async () => {
245
276
  if (!isSignedIn) return null;
246
277
  try {
247
- return sessionStorage.getItem("vaultix_jwt");
278
+ const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
279
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
248
280
  } catch {
249
281
  return null;
250
282
  }
@@ -358,6 +390,23 @@ function SignIn({
358
390
  setLoading(false);
359
391
  }
360
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
+ }
361
410
  async function handlePasswordSubmit(e) {
362
411
  e.preventDefault();
363
412
  setError(null);
@@ -526,6 +575,7 @@ function SignIn({
526
575
  password: "Enter password",
527
576
  passkey: "Passkey sign-in",
528
577
  totp: "Two-factor code",
578
+ "magic-link-sent": "Check your inbox",
529
579
  forgot: "Forgot password",
530
580
  "forgot-verify": "Enter reset code",
531
581
  "forgot-reset": "New password"
@@ -535,6 +585,7 @@ function SignIn({
535
585
  password: email,
536
586
  passkey: email,
537
587
  totp: "Enter the 6-digit code from your authenticator app",
588
+ "magic-link-sent": `We sent a sign-in link to ${email}`,
538
589
  forgot: "We'll send a reset code to your email",
539
590
  "forgot-verify": `Code sent to ${email}`,
540
591
  "forgot-reset": "Choose a new password"
@@ -592,12 +643,31 @@ function SignIn({
592
643
  }
593
644
  ),
594
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" }),
595
652
  /* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
596
653
  setStep("forgot");
597
654
  setError(null);
598
655
  }, children: "Forgot password?" }),
599
656
  /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
600
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
+ ] }),
601
671
  step === "passkey" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
602
672
  /* @__PURE__ */ jsxs(PrimaryButton, { loading, onClick: handlePasskeySignIn, children: [
603
673
  /* @__PURE__ */ jsx2(FingerprintIcon, {}),
@@ -840,6 +910,12 @@ function GitHubIcon() {
840
910
  function XIcon() {
841
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" }) });
842
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
+ }
843
919
 
844
920
  // src/components/SignUp.tsx
845
921
  import { clsx as clsx2 } from "clsx";
@@ -1180,22 +1256,17 @@ function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
1180
1256
 
1181
1257
  // src/components/UserButton.tsx
1182
1258
  import { clsx as clsx3 } from "clsx";
1183
- import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1184
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1185
- function UserButton({
1186
- showName = false,
1187
- afterSignOutUrl,
1188
- className
1189
- }) {
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 }) {
1190
1262
  const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
1191
1263
  const [open, setOpen] = useState4(false);
1192
1264
  const [signingOut, setSigningOut] = useState4(false);
1265
+ const [showProfile, setShowProfile] = useState4(false);
1193
1266
  const containerRef = useRef3(null);
1194
1267
  useEffect3(() => {
1195
1268
  function onPointerDown(e) {
1196
- if (containerRef.current && !containerRef.current.contains(e.target)) {
1197
- setOpen(false);
1198
- }
1269
+ if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
1199
1270
  }
1200
1271
  document.addEventListener("pointerdown", onPointerDown);
1201
1272
  return () => document.removeEventListener("pointerdown", onPointerDown);
@@ -1208,98 +1279,456 @@ function UserButton({
1208
1279
  await signOut();
1209
1280
  if (afterSignOutUrl) window.location.href = afterSignOutUrl;
1210
1281
  }
1211
- return /* @__PURE__ */ jsxs3("div", { ref: containerRef, className: clsx3("relative", className), children: [
1212
- /* @__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(
1213
1631
  "button",
1214
1632
  {
1215
- onClick: () => setOpen((o) => !o),
1216
- className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
1217
- children: [
1218
- /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl }),
1219
- showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
1220
- ]
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"
1221
1637
  }
1222
1638
  ),
1223
- open && /* @__PURE__ */ jsxs3(
1224
- "div",
1639
+ totpEnabled && !showDisable && /* @__PURE__ */ jsx5(
1640
+ "button",
1225
1641
  {
1226
- className: clsx3(
1227
- "absolute right-0 mt-2 w-64 rounded-2xl border border-white/8",
1228
- "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
+ }
1229
1670
  ),
1230
- style: { background: "rgba(22,27,45,0.96)" },
1231
- children: [
1232
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
1233
- /* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
1234
- /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
1235
- (user.firstName || user.lastName) && /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
1236
- /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] truncate", children: user.email })
1237
- ] })
1238
- ] }),
1239
- 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: [
1240
- /* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
1241
- /* @__PURE__ */ jsx5(RiskBadge, { level: session.riskLevel })
1242
- ] }) }),
1243
- /* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
1244
- /* @__PURE__ */ jsx5(
1245
- DropdownItem,
1246
- {
1247
- label: "Manage account",
1248
- icon: /* @__PURE__ */ jsx5(UserIcon, {}),
1249
- onClick: () => setOpen(false)
1250
- }
1251
- ),
1252
- /* @__PURE__ */ jsx5(
1253
- DropdownItem,
1254
- {
1255
- label: "Security settings",
1256
- icon: /* @__PURE__ */ jsx5(ShieldIcon, {}),
1257
- onClick: () => setOpen(false)
1258
- }
1259
- ),
1260
- /* @__PURE__ */ jsx5("div", { className: "h-px bg-white/8 my-1" }),
1261
- /* @__PURE__ */ jsx5(
1262
- DropdownItem,
1263
- {
1264
- label: signingOut ? "Signing out\u2026" : "Sign out",
1265
- icon: /* @__PURE__ */ jsx5(SignOutIcon, {}),
1266
- onClick: handleSignOut,
1267
- destructive: true
1268
- }
1269
- )
1270
- ] })
1271
- ]
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
+ )
1272
1702
  }
1273
1703
  )
1274
1704
  ] });
1275
1705
  }
1276
- function Avatar({ initials, imageUrl, size = "sm" }) {
1277
- const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
1278
- if (imageUrl) {
1279
- return (
1280
- // eslint-disable-next-line @next/next/no-img-element
1281
- /* @__PURE__ */ jsx5(
1282
- "img",
1283
- {
1284
- src: imageUrl,
1285
- alt: "",
1286
- className: clsx3(dim, "rounded-full object-cover ring-2 ring-white/10")
1287
- }
1288
- )
1289
- );
1290
- }
1706
+ function SaveButton({ saving, label = "Save changes" }) {
1291
1707
  return /* @__PURE__ */ jsx5(
1292
- "div",
1708
+ "button",
1293
1709
  {
1710
+ type: "submit",
1711
+ disabled: saving,
1294
1712
  className: clsx3(
1295
- dim,
1296
- "rounded-full flex items-center justify-center font-semibold text-white",
1297
- "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"
1298
1716
  ),
1299
- children: initials
1717
+ children: saving ? "Saving\u2026" : label
1300
1718
  }
1301
1719
  );
1302
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
+ }
1303
1732
  function AvatarSkeleton() {
1304
1733
  return /* @__PURE__ */ jsx5("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
1305
1734
  }
@@ -1310,16 +1739,10 @@ function RiskBadge({ level }) {
1310
1739
  high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
1311
1740
  critical: "bg-red-500/15 text-red-400 border-red-500/30"
1312
1741
  };
1313
- return /* @__PURE__ */ jsx5(
1314
- "span",
1315
- {
1316
- className: clsx3(
1317
- "inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
1318
- styles[level] ?? styles.low
1319
- ),
1320
- children: level
1321
- }
1322
- );
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 });
1323
1746
  }
1324
1747
  function DropdownItem({ label, icon, onClick, destructive }) {
1325
1748
  return /* @__PURE__ */ jsxs3(
@@ -1343,9 +1766,6 @@ function UserIcon() {
1343
1766
  /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "7", r: "4" })
1344
1767
  ] });
1345
1768
  }
1346
- function ShieldIcon() {
1347
- 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" }) });
1348
- }
1349
1769
  function SignOutIcon() {
1350
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: [
1351
1771
  /* @__PURE__ */ jsx5("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
@@ -1353,6 +1773,12 @@ function SignOutIcon() {
1353
1773
  /* @__PURE__ */ jsx5("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
1354
1774
  ] });
1355
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
+ }
1356
1782
 
1357
1783
  // src/components/OrganizationSwitcher.tsx
1358
1784
  import { clsx as clsx4 } from "clsx";
@@ -1548,17 +1974,19 @@ function MiniSpinner() {
1548
1974
  }
1549
1975
 
1550
1976
  // src/index.ts
1551
- var STORAGE_KEY2 = "vaultix_jwt";
1552
1977
  function getStoredToken() {
1978
+ if (typeof document === "undefined") return null;
1553
1979
  try {
1554
- return (typeof sessionStorage !== "undefined" ? sessionStorage : null)?.getItem(STORAGE_KEY2) ?? null;
1980
+ const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
1981
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
1555
1982
  } catch {
1556
1983
  return null;
1557
1984
  }
1558
1985
  }
1559
1986
  function clearStoredToken() {
1987
+ if (typeof document === "undefined") return;
1560
1988
  try {
1561
- if (typeof sessionStorage !== "undefined") sessionStorage.removeItem(STORAGE_KEY2);
1989
+ document.cookie = "vaultix-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
1562
1990
  } catch {
1563
1991
  }
1564
1992
  }