@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.js CHANGED
@@ -29,6 +29,8 @@ __export(index_exports, {
29
29
  SignedOut: () => SignedOut,
30
30
  UserButton: () => UserButton,
31
31
  VaultixProvider: () => VaultixProvider,
32
+ clearStoredToken: () => clearStoredToken,
33
+ getStoredToken: () => getStoredToken,
32
34
  useAuth: () => useAuth,
33
35
  useOrganization: () => useOrganization,
34
36
  useSession: () => useSession,
@@ -55,28 +57,35 @@ function resolveApiOrigin(key, apiUrlProp) {
55
57
  throw new Error(`Unknown publishable key prefix "${parts[0]}".`);
56
58
  }
57
59
  try {
58
- return atob(parts.slice(3).join("_")).replace(/\/$/, "");
60
+ return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
59
61
  } catch {
60
62
  throw new Error("Publishable key has an unreadable API origin payload.");
61
63
  }
62
64
  }
63
- var STORAGE_KEY = "vaultix_jwt";
65
+ var TOKEN_COOKIE = "vaultix-token";
66
+ var COOKIE_DAYS = 30;
64
67
  function storeJwt(jwt) {
68
+ if (typeof document === "undefined") return;
65
69
  try {
66
- sessionStorage.setItem(STORAGE_KEY, jwt);
70
+ const expires = new Date(Date.now() + COOKIE_DAYS * 864e5).toUTCString();
71
+ const secure = location.protocol === "https:" ? "; Secure" : "";
72
+ document.cookie = `${TOKEN_COOKIE}=${encodeURIComponent(jwt)}; path=/; expires=${expires}; SameSite=Lax${secure}`;
67
73
  } catch {
68
74
  }
69
75
  }
70
76
  function loadJwt() {
77
+ if (typeof document === "undefined") return null;
71
78
  try {
72
- return sessionStorage.getItem(STORAGE_KEY);
79
+ const m = document.cookie.match(new RegExp(`(?:^|; )${TOKEN_COOKIE}=([^;]+)`));
80
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
73
81
  } catch {
74
82
  return null;
75
83
  }
76
84
  }
77
85
  function clearJwt() {
86
+ if (typeof document === "undefined") return;
78
87
  try {
79
- sessionStorage.removeItem(STORAGE_KEY);
88
+ document.cookie = `${TOKEN_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
80
89
  } catch {
81
90
  }
82
91
  }
@@ -147,26 +156,27 @@ function VaultixProvider({
147
156
  async function hydrate() {
148
157
  let jwt = null;
149
158
  if (typeof window !== "undefined") {
150
- const url = new URL(window.location.href);
151
- const handshakeToken = url.searchParams.get("__vaultix_handshake");
152
- if (handshakeToken) {
153
- url.searchParams.delete("__vaultix_handshake");
154
- window.history.replaceState({}, "", url.toString());
155
- try {
156
- const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
157
- method: "POST",
158
- headers: { "Content-Type": "application/json" },
159
- body: JSON.stringify({ handshake_token: handshakeToken })
160
- });
161
- if (res.ok) {
162
- const data = await res.json();
163
- jwt = data.session_jwt;
164
- storeJwt(jwt);
159
+ jwt = loadJwt();
160
+ if (!jwt) {
161
+ const url = new URL(window.location.href);
162
+ const handshakeToken = url.searchParams.get("__vaultix_handshake");
163
+ if (handshakeToken) {
164
+ url.searchParams.delete("__vaultix_handshake");
165
+ window.history.replaceState({}, "", url.toString());
166
+ try {
167
+ const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({ handshake_token: handshakeToken })
171
+ });
172
+ if (res.ok) {
173
+ const data = await res.json();
174
+ jwt = data.session_jwt;
175
+ storeJwt(jwt);
176
+ }
177
+ } catch {
165
178
  }
166
- } catch {
167
179
  }
168
- } else {
169
- jwt = loadJwt();
170
180
  }
171
181
  jwtRef.current = jwt;
172
182
  }
@@ -207,7 +217,7 @@ function VaultixProvider({
207
217
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
208
218
  };
209
219
  }, [apiOrigin, publishableKey, scheduleRefresh]);
210
- const signOut = (0, import_react.useCallback)(async () => {
220
+ const signOut = (0, import_react.useCallback)(async (redirectUrl) => {
211
221
  const jwt = jwtRef.current;
212
222
  clearAuth();
213
223
  if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
@@ -219,15 +229,50 @@ function VaultixProvider({
219
229
  headers
220
230
  }).catch(() => {
221
231
  });
222
- if (afterSignInUrl) window.location.href = afterSignInUrl;
223
- }, [apiOrigin, afterSignInUrl, clearAuth]);
232
+ const dest = redirectUrl ?? afterSignUpUrl ?? "/";
233
+ if (typeof window !== "undefined") window.location.href = dest;
234
+ }, [apiOrigin, afterSignUpUrl, clearAuth]);
235
+ const updateUser = (0, import_react.useCallback)(async (params) => {
236
+ const userId = user?.id;
237
+ if (!userId) throw new Error("No active session");
238
+ const jwt = jwtRef.current;
239
+ const headers = { "Content-Type": "application/json" };
240
+ if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
241
+ const res = await fetch(`${apiOrigin}/api/v1/users/${userId}`, {
242
+ method: "PATCH",
243
+ credentials: jwt ? "omit" : "include",
244
+ headers,
245
+ body: JSON.stringify(params)
246
+ });
247
+ if (!res.ok) {
248
+ const err = await res.json().catch(() => ({ error: "Update failed" }));
249
+ throw new Error(err.error ?? "Update failed");
250
+ }
251
+ const updated = await res.json();
252
+ setUser(updated);
253
+ return updated;
254
+ }, [apiOrigin, user?.id]);
255
+ const connectPlatform = (0, import_react.useCallback)((provider, options = {}) => {
256
+ const params = new URLSearchParams({ mode: "connect" });
257
+ params.set("redirect_url", options.redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "/"));
258
+ params.set("pk", publishableKey);
259
+ if (options.scopes?.length) params.set("scope", options.scopes.join(" "));
260
+ if (options.configId) params.set("config_id", options.configId);
261
+ if (options.extraParams) Object.entries(options.extraParams).forEach(([k, v]) => params.set(k, v));
262
+ if (typeof window !== "undefined") {
263
+ window.location.href = `${apiOrigin}/api/v1/auth/oauth/${provider}/connect?${params}`;
264
+ }
265
+ }, [apiOrigin, publishableKey]);
224
266
  const value = {
225
267
  user,
226
268
  session,
227
269
  organization,
228
270
  isLoaded,
229
271
  isSignedIn: !!session,
230
- signOut
272
+ apiOrigin,
273
+ signOut,
274
+ updateUser,
275
+ connectPlatform
231
276
  };
232
277
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VaultixContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
233
278
  "div",
@@ -252,8 +297,8 @@ function useSession() {
252
297
  return { session, isLoaded, isSignedIn };
253
298
  }
254
299
  function useUser() {
255
- const { user, isLoaded, isSignedIn } = useVaultixContext();
256
- return { user, isLoaded, isSignedIn };
300
+ const { user, isLoaded, isSignedIn, updateUser } = useVaultixContext();
301
+ return { user, isLoaded, isSignedIn, update: updateUser };
257
302
  }
258
303
  function useOrganization() {
259
304
  const { organization, isLoaded } = useVaultixContext();
@@ -264,7 +309,8 @@ function useAuth() {
264
309
  const getToken = (0, import_react2.useCallback)(async () => {
265
310
  if (!isSignedIn) return null;
266
311
  try {
267
- return sessionStorage.getItem("vaultix_jwt");
312
+ const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
313
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
268
314
  } catch {
269
315
  return null;
270
316
  }
@@ -378,6 +424,23 @@ function SignIn({
378
424
  setLoading(false);
379
425
  }
380
426
  }
427
+ async function handleMagicLinkRequest() {
428
+ setError(null);
429
+ setLoading(true);
430
+ try {
431
+ const target = resolveAfterSignInUrl(redirectUrl);
432
+ await fetch(`${apiOrigin}/api/v1/auth/magic-link/send`, {
433
+ method: "POST",
434
+ headers: { "Content-Type": "application/json" },
435
+ body: JSON.stringify({ email, redirect_url: target })
436
+ });
437
+ setStep("magic-link-sent");
438
+ } catch {
439
+ setErr("Network error. Please try again.");
440
+ } finally {
441
+ setLoading(false);
442
+ }
443
+ }
381
444
  async function handlePasswordSubmit(e) {
382
445
  e.preventDefault();
383
446
  setError(null);
@@ -546,6 +609,7 @@ function SignIn({
546
609
  password: "Enter password",
547
610
  passkey: "Passkey sign-in",
548
611
  totp: "Two-factor code",
612
+ "magic-link-sent": "Check your inbox",
549
613
  forgot: "Forgot password",
550
614
  "forgot-verify": "Enter reset code",
551
615
  "forgot-reset": "New password"
@@ -555,6 +619,7 @@ function SignIn({
555
619
  password: email,
556
620
  passkey: email,
557
621
  totp: "Enter the 6-digit code from your authenticator app",
622
+ "magic-link-sent": `We sent a sign-in link to ${email}`,
558
623
  forgot: "We'll send a reset code to your email",
559
624
  "forgot-verify": `Code sent to ${email}`,
560
625
  "forgot-reset": "Choose a new password"
@@ -612,12 +677,31 @@ function SignIn({
612
677
  }
613
678
  ),
614
679
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Sign in" }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 h-px bg-white/8" }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] text-[#475569]", children: "or" }),
683
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 h-px bg-white/8" })
684
+ ] }),
685
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: handleMagicLinkRequest, children: "\u2709 Email me a sign-in link" }),
615
686
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => {
616
687
  setStep("forgot");
617
688
  setError(null);
618
689
  }, children: "Forgot password?" }),
619
690
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
620
691
  ] }),
692
+ step === "magic-link-sent" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4 text-center", children: [
693
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)(EnvelopeIcon, {}) }),
694
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm text-[#94a3b8] leading-relaxed", children: [
695
+ "The link expires in ",
696
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-white/80 font-medium", children: "15 minutes" }),
697
+ " and can only be used once."
698
+ ] }),
699
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: handleMagicLinkRequest, children: "Resend link" }),
700
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => {
701
+ setStep("password");
702
+ setError(null);
703
+ }, children: "Use password instead" })
704
+ ] }),
621
705
  step === "passkey" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3", children: [
622
706
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(PrimaryButton, { loading, onClick: handlePasskeySignIn, children: [
623
707
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FingerprintIcon, {}),
@@ -860,6 +944,12 @@ function GitHubIcon() {
860
944
  function XIcon() {
861
945
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) });
862
946
  }
947
+ function EnvelopeIcon() {
948
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#a78bfa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
951
+ ] });
952
+ }
863
953
 
864
954
  // src/components/SignUp.tsx
865
955
  var import_clsx2 = require("clsx");
@@ -1202,20 +1292,15 @@ function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
1202
1292
  var import_clsx3 = require("clsx");
1203
1293
  var import_react6 = require("react");
1204
1294
  var import_jsx_runtime5 = require("react/jsx-runtime");
1205
- function UserButton({
1206
- showName = false,
1207
- afterSignOutUrl,
1208
- className
1209
- }) {
1295
+ function UserButton({ showName = false, afterSignOutUrl, className }) {
1210
1296
  const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
1211
1297
  const [open, setOpen] = (0, import_react6.useState)(false);
1212
1298
  const [signingOut, setSigningOut] = (0, import_react6.useState)(false);
1299
+ const [showProfile, setShowProfile] = (0, import_react6.useState)(false);
1213
1300
  const containerRef = (0, import_react6.useRef)(null);
1214
1301
  (0, import_react6.useEffect)(() => {
1215
1302
  function onPointerDown(e) {
1216
- if (containerRef.current && !containerRef.current.contains(e.target)) {
1217
- setOpen(false);
1218
- }
1303
+ if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
1219
1304
  }
1220
1305
  document.addEventListener("pointerdown", onPointerDown);
1221
1306
  return () => document.removeEventListener("pointerdown", onPointerDown);
@@ -1228,98 +1313,456 @@ function UserButton({
1228
1313
  await signOut();
1229
1314
  if (afterSignOutUrl) window.location.href = afterSignOutUrl;
1230
1315
  }
1231
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: containerRef, className: (0, import_clsx3.clsx)("relative", className), children: [
1232
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1316
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1317
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: containerRef, className: (0, import_clsx3.clsx)("relative", className), children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1319
+ "button",
1320
+ {
1321
+ onClick: () => setOpen((o) => !o),
1322
+ className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
1323
+ children: [
1324
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { initials, imageUrl: user.imageUrl }),
1325
+ showName && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
1326
+ ]
1327
+ }
1328
+ ),
1329
+ open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1330
+ "div",
1331
+ {
1332
+ className: (0, import_clsx3.clsx)("absolute right-0 mt-2 w-64 rounded-2xl border border-white/8", "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"),
1333
+ style: { background: "rgba(22,27,45,0.96)" },
1334
+ children: [
1335
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
1336
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
1337
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 min-w-0", children: [
1338
+ (user.firstName || user.lastName) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
1339
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-[#475569] truncate", children: user.email })
1340
+ ] })
1341
+ ] }),
1342
+ session && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-4 py-2 border-b border-white/8", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
1343
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
1344
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RiskBadge, { level: session.riskLevel })
1345
+ ] }) }),
1346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "p-2", children: [
1347
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DropdownItem, { label: "Manage account", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UserIcon, {}), onClick: () => {
1348
+ setOpen(false);
1349
+ setShowProfile(true);
1350
+ } }),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "h-px bg-white/8 my-1" }),
1352
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DropdownItem, { label: signingOut ? "Signing out\u2026" : "Sign out", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SignOutIcon, {}), onClick: handleSignOut, destructive: true })
1353
+ ] })
1354
+ ]
1355
+ }
1356
+ )
1357
+ ] }),
1358
+ showProfile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfileModal, { onClose: () => setShowProfile(false) })
1359
+ ] });
1360
+ }
1361
+ function ProfileModal({ onClose }) {
1362
+ const { user, updateUser } = useVaultixContext();
1363
+ const [tab, setTab] = (0, import_react6.useState)("profile");
1364
+ const [firstName, setFirstName] = (0, import_react6.useState)(user?.firstName ?? "");
1365
+ const [lastName, setLastName] = (0, import_react6.useState)(user?.lastName ?? "");
1366
+ const [imageUrl, setImageUrl] = (0, import_react6.useState)(user?.imageUrl ?? "");
1367
+ const [currentPassword, setCurrentPassword] = (0, import_react6.useState)("");
1368
+ const [newPassword, setNewPassword] = (0, import_react6.useState)("");
1369
+ const [confirmPassword, setConfirmPassword] = (0, import_react6.useState)("");
1370
+ const [saving, setSaving] = (0, import_react6.useState)(false);
1371
+ const [error, setError] = (0, import_react6.useState)(null);
1372
+ const [success, setSuccess] = (0, import_react6.useState)(null);
1373
+ (0, import_react6.useEffect)(() => {
1374
+ function onKey(e) {
1375
+ if (e.key === "Escape") onClose();
1376
+ }
1377
+ document.addEventListener("keydown", onKey);
1378
+ return () => document.removeEventListener("keydown", onKey);
1379
+ }, [onClose]);
1380
+ async function saveProfile(e) {
1381
+ e.preventDefault();
1382
+ setError(null);
1383
+ setSuccess(null);
1384
+ setSaving(true);
1385
+ try {
1386
+ await updateUser({ firstName: firstName.trim() || null, lastName: lastName.trim() || null, imageUrl: imageUrl.trim() || null });
1387
+ setSuccess("Profile updated.");
1388
+ } catch (err) {
1389
+ setError(err instanceof Error ? err.message : "Update failed");
1390
+ } finally {
1391
+ setSaving(false);
1392
+ }
1393
+ }
1394
+ async function savePassword(e) {
1395
+ e.preventDefault();
1396
+ setError(null);
1397
+ setSuccess(null);
1398
+ if (newPassword !== confirmPassword) {
1399
+ setError("Passwords do not match.");
1400
+ return;
1401
+ }
1402
+ if (newPassword.length < 8) {
1403
+ setError("Password must be at least 8 characters.");
1404
+ return;
1405
+ }
1406
+ setSaving(true);
1407
+ try {
1408
+ await updateUser({ currentPassword, newPassword });
1409
+ setSuccess("Password changed.");
1410
+ setCurrentPassword("");
1411
+ setNewPassword("");
1412
+ setConfirmPassword("");
1413
+ } catch (err) {
1414
+ setError(err instanceof Error ? err.message : "Update failed");
1415
+ } finally {
1416
+ setSaving(false);
1417
+ }
1418
+ }
1419
+ const TABS = [
1420
+ { id: "profile", label: "Profile" },
1421
+ { id: "password", label: "Change password" },
1422
+ { id: "security", label: "Security" }
1423
+ ];
1424
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1425
+ "div",
1426
+ {
1427
+ className: "fixed inset-0 z-[9999] flex items-center justify-center p-4",
1428
+ style: { background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" },
1429
+ onPointerDown: (e) => {
1430
+ if (e.target === e.currentTarget) onClose();
1431
+ },
1432
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-full max-w-md rounded-2xl border border-white/8 shadow-2xl", style: { background: "rgba(22,27,45,0.98)" }, children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/8", children: [
1434
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-sm font-semibold text-white/90", children: "Manage account" }),
1435
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: onClose, className: "text-[#475569] hover:text-white transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {}) })
1436
+ ] }),
1437
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex gap-1 px-6 pt-4 flex-wrap", children: TABS.map(({ id, label }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1438
+ "button",
1439
+ {
1440
+ onClick: () => {
1441
+ setTab(id);
1442
+ setError(null);
1443
+ setSuccess(null);
1444
+ },
1445
+ className: (0, import_clsx3.clsx)(
1446
+ "px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
1447
+ tab === id ? "bg-purple-500/15 text-purple-300 border border-purple-500/30" : "text-[#475569] hover:text-white/70"
1448
+ ),
1449
+ children: label
1450
+ },
1451
+ id
1452
+ )) }),
1453
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 pt-3", children: [
1454
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
1455
+ success && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rounded-lg bg-emerald-500/10 border border-emerald-500/30 px-3 py-2 text-xs text-emerald-400", children: success })
1456
+ ] }),
1457
+ tab === "profile" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: saveProfile, className: "px-6 py-4 space-y-4", children: [
1458
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-3", children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "First name", value: firstName, onChange: setFirstName, placeholder: "Jane" }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "Last name", value: lastName, onChange: setLastName, placeholder: "Smith" })
1461
+ ] }),
1462
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "Avatar URL", value: imageUrl, onChange: setImageUrl, placeholder: "https://\u2026", type: "url" }),
1463
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 pt-1", children: [
1464
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaveButton, { saving }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
1466
+ ] })
1467
+ ] }),
1468
+ tab === "password" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: savePassword, className: "px-6 py-4 space-y-4", children: [
1469
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "Current password", value: currentPassword, onChange: setCurrentPassword, type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }),
1470
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "New password", value: newPassword, onChange: setNewPassword, type: "password", placeholder: "Min 8 characters" }),
1471
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "Confirm new password", value: confirmPassword, onChange: setConfirmPassword, type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }),
1472
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 pt-1", children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaveButton, { saving, label: "Change password" }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
1475
+ ] })
1476
+ ] }),
1477
+ tab === "security" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SecurityTab, {})
1478
+ ] })
1479
+ }
1480
+ );
1481
+ }
1482
+ function SecurityTab() {
1483
+ const { apiOrigin } = useVaultixContext();
1484
+ const [totpEnabled, setTotpEnabled] = (0, import_react6.useState)(null);
1485
+ const [backupCodesLeft, setBackupCodesLeft] = (0, import_react6.useState)(null);
1486
+ const [enrollStep, setEnrollStep] = (0, import_react6.useState)("idle");
1487
+ const [qrDataUrl, setQrDataUrl] = (0, import_react6.useState)("");
1488
+ const [manualKey, setManualKey] = (0, import_react6.useState)("");
1489
+ const [enrollCode, setEnrollCode] = (0, import_react6.useState)("");
1490
+ const [disableCode, setDisableCode] = (0, import_react6.useState)("");
1491
+ const [backupCodes, setBackupCodes] = (0, import_react6.useState)([]);
1492
+ const [loading, setLoading] = (0, import_react6.useState)(false);
1493
+ const [error, setError] = (0, import_react6.useState)(null);
1494
+ const [showDisable, setShowDisable] = (0, import_react6.useState)(false);
1495
+ const fetchStatus = (0, import_react6.useCallback)(async () => {
1496
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp/status`, { credentials: "include" });
1497
+ if (res.ok) {
1498
+ const d = await res.json();
1499
+ setTotpEnabled(d.enabled);
1500
+ setBackupCodesLeft(d.backup_codes_remaining);
1501
+ }
1502
+ }, [apiOrigin]);
1503
+ (0, import_react6.useEffect)(() => {
1504
+ void fetchStatus();
1505
+ }, [fetchStatus]);
1506
+ async function startEnroll() {
1507
+ setError(null);
1508
+ setLoading(true);
1509
+ try {
1510
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp/enroll`, { method: "POST", credentials: "include" });
1511
+ const d = await res.json();
1512
+ if (!res.ok) {
1513
+ setError(d.error ?? "Failed to start enrollment.");
1514
+ return;
1515
+ }
1516
+ setQrDataUrl(d.qr_data_url);
1517
+ setManualKey(d.manual_entry_key);
1518
+ setEnrollStep("qr");
1519
+ } finally {
1520
+ setLoading(false);
1521
+ }
1522
+ }
1523
+ async function verifyEnroll(e) {
1524
+ e.preventDefault();
1525
+ setError(null);
1526
+ setLoading(true);
1527
+ try {
1528
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp/verify-enroll`, {
1529
+ method: "POST",
1530
+ credentials: "include",
1531
+ headers: { "Content-Type": "application/json" },
1532
+ body: JSON.stringify({ code: enrollCode })
1533
+ });
1534
+ const d = await res.json();
1535
+ if (!res.ok) {
1536
+ setError(d.error ?? "Invalid code. Try again.");
1537
+ return;
1538
+ }
1539
+ setBackupCodes(d.backup_codes);
1540
+ setEnrollStep("backup-codes");
1541
+ setTotpEnabled(true);
1542
+ setBackupCodesLeft(d.backup_codes.length);
1543
+ } finally {
1544
+ setLoading(false);
1545
+ }
1546
+ }
1547
+ async function disableTotp(e) {
1548
+ e.preventDefault();
1549
+ setError(null);
1550
+ setLoading(true);
1551
+ try {
1552
+ const res = await fetch(`${apiOrigin}/api/v1/auth/totp`, {
1553
+ method: "DELETE",
1554
+ credentials: "include",
1555
+ headers: { "Content-Type": "application/json" },
1556
+ body: JSON.stringify({ code: disableCode })
1557
+ });
1558
+ const d = await res.json();
1559
+ if (!res.ok) {
1560
+ setError(d.error ?? "Invalid code.");
1561
+ return;
1562
+ }
1563
+ setTotpEnabled(false);
1564
+ setBackupCodesLeft(null);
1565
+ setShowDisable(false);
1566
+ setDisableCode("");
1567
+ } finally {
1568
+ setLoading(false);
1569
+ }
1570
+ }
1571
+ if (totpEnabled === null) {
1572
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-6 py-8 text-center text-[#475569] text-sm", children: "Loading\u2026" });
1573
+ }
1574
+ if (enrollStep === "backup-codes") {
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 py-4 space-y-4", children: [
1576
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rounded-xl bg-emerald-500/10 border border-emerald-500/30 px-4 py-3", children: [
1577
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs font-semibold text-emerald-400 mb-1", children: "Authenticator app enabled!" }),
1578
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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." })
1579
+ ] }),
1580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "grid grid-cols-2 gap-1.5", children: backupCodes.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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)) }),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1582
+ "button",
1583
+ {
1584
+ onClick: () => {
1585
+ const t = backupCodes.join("\n");
1586
+ navigator.clipboard.writeText(t).catch(() => {
1587
+ });
1588
+ },
1589
+ className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1 border border-white/8 rounded-lg",
1590
+ children: "Copy all codes"
1591
+ }
1592
+ ),
1593
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1594
+ "button",
1595
+ {
1596
+ onClick: () => setEnrollStep("idle"),
1597
+ 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",
1598
+ children: "Done"
1599
+ }
1600
+ )
1601
+ ] });
1602
+ }
1603
+ if (enrollStep === "qr") {
1604
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 py-4 space-y-4", children: [
1605
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-[#475569]", children: "Scan this QR code with Google Authenticator, Authy, or any TOTP app." }),
1606
+ qrDataUrl && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: qrDataUrl, alt: "TOTP QR code", className: "rounded-xl", width: 180, height: 180 }) }),
1607
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "text-[11px] text-[#475569]", children: [
1608
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("summary", { className: "cursor-pointer hover:text-white/60", children: "Can't scan? Enter manually" }),
1609
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1 font-mono break-all bg-white/5 rounded px-2 py-1 text-white/60 select-all", children: manualKey })
1610
+ ] }),
1611
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
1612
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: verifyEnroll, className: "space-y-3", children: [
1613
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1614
+ "input",
1615
+ {
1616
+ type: "text",
1617
+ inputMode: "numeric",
1618
+ pattern: "[0-9]{6}",
1619
+ maxLength: 6,
1620
+ placeholder: "000000",
1621
+ value: enrollCode,
1622
+ onChange: (e) => setEnrollCode(e.target.value),
1623
+ autoFocus: true,
1624
+ required: true,
1625
+ 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]"
1626
+ }
1627
+ ),
1628
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1629
+ "button",
1630
+ {
1631
+ type: "submit",
1632
+ disabled: loading || enrollCode.length !== 6,
1633
+ 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",
1634
+ children: loading ? "Verifying\u2026" : "Verify and enable"
1635
+ }
1636
+ ),
1637
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1638
+ "button",
1639
+ {
1640
+ type: "button",
1641
+ onClick: () => {
1642
+ setEnrollStep("idle");
1643
+ setError(null);
1644
+ },
1645
+ className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1",
1646
+ children: "\u2190 Cancel"
1647
+ }
1648
+ )
1649
+ ] })
1650
+ ] });
1651
+ }
1652
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 py-4 space-y-4", children: [
1653
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rounded-xl border border-white/8 p-4", style: { background: "rgba(255,255,255,0.03)" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
1654
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1655
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-medium text-white/90", children: "Authenticator app" }),
1656
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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" })
1657
+ ] }),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: (0, import_clsx3.clsx)(
1659
+ "text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-full border",
1660
+ totpEnabled ? "bg-emerald-500/15 text-emerald-400 border-emerald-500/30" : "bg-white/5 text-[#475569] border-white/8"
1661
+ ), children: totpEnabled ? "On" : "Off" })
1662
+ ] }) }),
1663
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
1664
+ !totpEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1233
1665
  "button",
1234
1666
  {
1235
- onClick: () => setOpen((o) => !o),
1236
- className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
1237
- children: [
1238
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { initials, imageUrl: user.imageUrl }),
1239
- showName && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
1240
- ]
1667
+ onClick: startEnroll,
1668
+ disabled: loading,
1669
+ 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",
1670
+ children: loading ? "Loading\u2026" : "Enable authenticator app"
1241
1671
  }
1242
1672
  ),
1243
- open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1244
- "div",
1673
+ totpEnabled && !showDisable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1674
+ "button",
1245
1675
  {
1246
- className: (0, import_clsx3.clsx)(
1247
- "absolute right-0 mt-2 w-64 rounded-2xl border border-white/8",
1248
- "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"
1676
+ onClick: () => setShowDisable(true),
1677
+ 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",
1678
+ children: "Disable authenticator app"
1679
+ }
1680
+ ),
1681
+ totpEnabled && showDisable && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: disableTotp, className: "space-y-3", children: [
1682
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[11px] text-[#475569]", children: "Enter your current 6-digit code (or a backup code) to confirm." }),
1683
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1684
+ "input",
1685
+ {
1686
+ type: "text",
1687
+ placeholder: "000000 or backup code",
1688
+ value: disableCode,
1689
+ onChange: (e) => setDisableCode(e.target.value),
1690
+ autoFocus: true,
1691
+ required: true,
1692
+ 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"
1693
+ }
1694
+ ),
1695
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex gap-2", children: [
1696
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1697
+ "button",
1698
+ {
1699
+ type: "submit",
1700
+ disabled: loading || !disableCode,
1701
+ 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",
1702
+ children: loading ? "Disabling\u2026" : "Confirm disable"
1703
+ }
1249
1704
  ),
1250
- style: { background: "rgba(22,27,45,0.96)" },
1251
- children: [
1252
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
1253
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 min-w-0", children: [
1255
- (user.firstName || user.lastName) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
1256
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-[#475569] truncate", children: user.email })
1257
- ] })
1258
- ] }),
1259
- session && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-4 py-2 border-b border-white/8", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
1260
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
1261
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RiskBadge, { level: session.riskLevel })
1262
- ] }) }),
1263
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "p-2", children: [
1264
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1265
- DropdownItem,
1266
- {
1267
- label: "Manage account",
1268
- icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UserIcon, {}),
1269
- onClick: () => setOpen(false)
1270
- }
1271
- ),
1272
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1273
- DropdownItem,
1274
- {
1275
- label: "Security settings",
1276
- icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ShieldIcon, {}),
1277
- onClick: () => setOpen(false)
1278
- }
1279
- ),
1280
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "h-px bg-white/8 my-1" }),
1281
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1282
- DropdownItem,
1283
- {
1284
- label: signingOut ? "Signing out\u2026" : "Sign out",
1285
- icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SignOutIcon, {}),
1286
- onClick: handleSignOut,
1287
- destructive: true
1288
- }
1289
- )
1290
- ] })
1291
- ]
1705
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1706
+ "button",
1707
+ {
1708
+ type: "button",
1709
+ onClick: () => {
1710
+ setShowDisable(false);
1711
+ setError(null);
1712
+ setDisableCode("");
1713
+ },
1714
+ className: "px-4 py-2 rounded-xl text-xs text-[#475569] hover:text-white hover:bg-white/8 border border-white/8 transition-colors",
1715
+ children: "Cancel"
1716
+ }
1717
+ )
1718
+ ] })
1719
+ ] })
1720
+ ] });
1721
+ }
1722
+ function ModalField({ label, value, onChange, placeholder, type = "text" }) {
1723
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
1724
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: label }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1726
+ "input",
1727
+ {
1728
+ type,
1729
+ value,
1730
+ onChange: (e) => onChange(e.target.value),
1731
+ placeholder,
1732
+ className: (0, import_clsx3.clsx)(
1733
+ "w-full bg-white/5 border border-white/8 rounded-xl px-3 py-2 text-sm text-white/90",
1734
+ "placeholder:text-[#475569] focus:outline-none focus:border-purple-500/60 transition-colors"
1735
+ )
1292
1736
  }
1293
1737
  )
1294
1738
  ] });
1295
1739
  }
1296
- function Avatar({ initials, imageUrl, size = "sm" }) {
1297
- const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
1298
- if (imageUrl) {
1299
- return (
1300
- // eslint-disable-next-line @next/next/no-img-element
1301
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1302
- "img",
1303
- {
1304
- src: imageUrl,
1305
- alt: "",
1306
- className: (0, import_clsx3.clsx)(dim, "rounded-full object-cover ring-2 ring-white/10")
1307
- }
1308
- )
1309
- );
1310
- }
1740
+ function SaveButton({ saving, label = "Save changes" }) {
1311
1741
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1312
- "div",
1742
+ "button",
1313
1743
  {
1744
+ type: "submit",
1745
+ disabled: saving,
1314
1746
  className: (0, import_clsx3.clsx)(
1315
- dim,
1316
- "rounded-full flex items-center justify-center font-semibold text-white",
1317
- "bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
1747
+ "px-4 py-2 rounded-xl text-sm font-semibold text-white transition-all",
1748
+ "bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500",
1749
+ "shadow-lg shadow-purple-500/20 disabled:opacity-60"
1318
1750
  ),
1319
- children: initials
1751
+ children: saving ? "Saving\u2026" : label
1320
1752
  }
1321
1753
  );
1322
1754
  }
1755
+ function Avatar({ initials, imageUrl, size = "sm" }) {
1756
+ const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
1757
+ if (imageUrl) {
1758
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: imageUrl, alt: "", className: (0, import_clsx3.clsx)(dim, "rounded-full object-cover ring-2 ring-white/10") });
1759
+ }
1760
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: (0, import_clsx3.clsx)(
1761
+ dim,
1762
+ "rounded-full flex items-center justify-center font-semibold text-white",
1763
+ "bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
1764
+ ), children: initials });
1765
+ }
1323
1766
  function AvatarSkeleton() {
1324
1767
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
1325
1768
  }
@@ -1330,16 +1773,10 @@ function RiskBadge({ level }) {
1330
1773
  high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
1331
1774
  critical: "bg-red-500/15 text-red-400 border-red-500/30"
1332
1775
  };
1333
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1334
- "span",
1335
- {
1336
- className: (0, import_clsx3.clsx)(
1337
- "inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
1338
- styles[level] ?? styles.low
1339
- ),
1340
- children: level
1341
- }
1342
- );
1776
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: (0, import_clsx3.clsx)(
1777
+ "inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
1778
+ styles[level] ?? styles.low
1779
+ ), children: level });
1343
1780
  }
1344
1781
  function DropdownItem({ label, icon, onClick, destructive }) {
1345
1782
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
@@ -1363,9 +1800,6 @@ function UserIcon() {
1363
1800
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "12", cy: "7", r: "4" })
1364
1801
  ] });
1365
1802
  }
1366
- function ShieldIcon() {
1367
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
1368
- }
1369
1803
  function SignOutIcon() {
1370
1804
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1371
1805
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
@@ -1373,6 +1807,12 @@ function SignOutIcon() {
1373
1807
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
1374
1808
  ] });
1375
1809
  }
1810
+ function CloseIcon() {
1811
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1812
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1813
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1814
+ ] });
1815
+ }
1376
1816
 
1377
1817
  // src/components/OrganizationSwitcher.tsx
1378
1818
  var import_clsx4 = require("clsx");
@@ -1566,6 +2006,24 @@ function MiniSpinner() {
1566
2006
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
1567
2007
  ] });
1568
2008
  }
2009
+
2010
+ // src/index.ts
2011
+ function getStoredToken() {
2012
+ if (typeof document === "undefined") return null;
2013
+ try {
2014
+ const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
2015
+ return m?.[1] ? decodeURIComponent(m[1]) : null;
2016
+ } catch {
2017
+ return null;
2018
+ }
2019
+ }
2020
+ function clearStoredToken() {
2021
+ if (typeof document === "undefined") return;
2022
+ try {
2023
+ document.cookie = "vaultix-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
2024
+ } catch {
2025
+ }
2026
+ }
1569
2027
  // Annotate the CommonJS export names for ESM import in node:
1570
2028
  0 && (module.exports = {
1571
2029
  OrganizationSwitcher,
@@ -1577,6 +2035,8 @@ function MiniSpinner() {
1577
2035
  SignedOut,
1578
2036
  UserButton,
1579
2037
  VaultixProvider,
2038
+ clearStoredToken,
2039
+ getStoredToken,
1580
2040
  useAuth,
1581
2041
  useOrganization,
1582
2042
  useSession,