@vaultix.ai/react 0.3.2 → 0.3.4
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.d.mts +72 -9
- package/dist/index.d.ts +72 -9
- package/dist/index.js +765 -205
- package/dist/index.mjs +797 -238
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
getStoredToken: () => getStoredToken,
|
|
34
34
|
useAuth: () => useAuth,
|
|
35
35
|
useOrganization: () => useOrganization,
|
|
36
|
+
usePlatformConnect: () => usePlatformConnect,
|
|
36
37
|
useSession: () => useSession,
|
|
37
38
|
useUser: () => useUser,
|
|
38
39
|
useVaultix: () => useVaultix
|
|
@@ -57,28 +58,35 @@ function resolveApiOrigin(key, apiUrlProp) {
|
|
|
57
58
|
throw new Error(`Unknown publishable key prefix "${parts[0]}".`);
|
|
58
59
|
}
|
|
59
60
|
try {
|
|
60
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
61
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
61
62
|
} catch {
|
|
62
63
|
throw new Error("Publishable key has an unreadable API origin payload.");
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
var
|
|
66
|
+
var TOKEN_COOKIE = "vaultix-token";
|
|
67
|
+
var COOKIE_DAYS = 30;
|
|
66
68
|
function storeJwt(jwt) {
|
|
69
|
+
if (typeof document === "undefined") return;
|
|
67
70
|
try {
|
|
68
|
-
|
|
71
|
+
const expires = new Date(Date.now() + COOKIE_DAYS * 864e5).toUTCString();
|
|
72
|
+
const secure = location.protocol === "https:" ? "; Secure" : "";
|
|
73
|
+
document.cookie = `${TOKEN_COOKIE}=${encodeURIComponent(jwt)}; path=/; expires=${expires}; SameSite=Lax${secure}`;
|
|
69
74
|
} catch {
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
function loadJwt() {
|
|
78
|
+
if (typeof document === "undefined") return null;
|
|
73
79
|
try {
|
|
74
|
-
|
|
80
|
+
const m = document.cookie.match(new RegExp(`(?:^|; )${TOKEN_COOKIE}=([^;]+)`));
|
|
81
|
+
return m?.[1] ? decodeURIComponent(m[1]) : null;
|
|
75
82
|
} catch {
|
|
76
83
|
return null;
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
function clearJwt() {
|
|
87
|
+
if (typeof document === "undefined") return;
|
|
80
88
|
try {
|
|
81
|
-
|
|
89
|
+
document.cookie = `${TOKEN_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
|
|
82
90
|
} catch {
|
|
83
91
|
}
|
|
84
92
|
}
|
|
@@ -149,26 +157,27 @@ function VaultixProvider({
|
|
|
149
157
|
async function hydrate() {
|
|
150
158
|
let jwt = null;
|
|
151
159
|
if (typeof window !== "undefined") {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
url.searchParams.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
160
|
+
jwt = loadJwt();
|
|
161
|
+
if (!jwt) {
|
|
162
|
+
const url = new URL(window.location.href);
|
|
163
|
+
const handshakeToken = url.searchParams.get("__vaultix_handshake");
|
|
164
|
+
if (handshakeToken) {
|
|
165
|
+
url.searchParams.delete("__vaultix_handshake");
|
|
166
|
+
window.history.replaceState({}, "", url.toString());
|
|
167
|
+
try {
|
|
168
|
+
const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: { "Content-Type": "application/json" },
|
|
171
|
+
body: JSON.stringify({ handshake_token: handshakeToken })
|
|
172
|
+
});
|
|
173
|
+
if (res.ok) {
|
|
174
|
+
const data = await res.json();
|
|
175
|
+
jwt = data.session_jwt;
|
|
176
|
+
storeJwt(jwt);
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
167
179
|
}
|
|
168
|
-
} catch {
|
|
169
180
|
}
|
|
170
|
-
} else {
|
|
171
|
-
jwt = loadJwt();
|
|
172
181
|
}
|
|
173
182
|
jwtRef.current = jwt;
|
|
174
183
|
}
|
|
@@ -209,7 +218,7 @@ function VaultixProvider({
|
|
|
209
218
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
210
219
|
};
|
|
211
220
|
}, [apiOrigin, publishableKey, scheduleRefresh]);
|
|
212
|
-
const signOut = (0, import_react.useCallback)(async () => {
|
|
221
|
+
const signOut = (0, import_react.useCallback)(async (redirectUrl) => {
|
|
213
222
|
const jwt = jwtRef.current;
|
|
214
223
|
clearAuth();
|
|
215
224
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
@@ -221,8 +230,29 @@ function VaultixProvider({
|
|
|
221
230
|
headers
|
|
222
231
|
}).catch(() => {
|
|
223
232
|
});
|
|
224
|
-
|
|
225
|
-
|
|
233
|
+
const dest = redirectUrl ?? afterSignUpUrl ?? "/";
|
|
234
|
+
if (typeof window !== "undefined") window.location.href = dest;
|
|
235
|
+
}, [apiOrigin, afterSignUpUrl, clearAuth]);
|
|
236
|
+
const updateUser = (0, import_react.useCallback)(async (params) => {
|
|
237
|
+
const userId = user?.id;
|
|
238
|
+
if (!userId) throw new Error("No active session");
|
|
239
|
+
const jwt = jwtRef.current;
|
|
240
|
+
const headers = { "Content-Type": "application/json" };
|
|
241
|
+
if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
|
|
242
|
+
const res = await fetch(`${apiOrigin}/api/v1/users/${userId}`, {
|
|
243
|
+
method: "PATCH",
|
|
244
|
+
credentials: jwt ? "omit" : "include",
|
|
245
|
+
headers,
|
|
246
|
+
body: JSON.stringify(params)
|
|
247
|
+
});
|
|
248
|
+
if (!res.ok) {
|
|
249
|
+
const err = await res.json().catch(() => ({ error: "Update failed" }));
|
|
250
|
+
throw new Error(err.error ?? "Update failed");
|
|
251
|
+
}
|
|
252
|
+
const updated = await res.json();
|
|
253
|
+
setUser(updated);
|
|
254
|
+
return updated;
|
|
255
|
+
}, [apiOrigin, user?.id]);
|
|
226
256
|
const connectPlatform = (0, import_react.useCallback)((provider, options = {}) => {
|
|
227
257
|
const params = new URLSearchParams({ mode: "connect" });
|
|
228
258
|
params.set("redirect_url", options.redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "/"));
|
|
@@ -240,7 +270,9 @@ function VaultixProvider({
|
|
|
240
270
|
organization,
|
|
241
271
|
isLoaded,
|
|
242
272
|
isSignedIn: !!session,
|
|
273
|
+
apiOrigin,
|
|
243
274
|
signOut,
|
|
275
|
+
updateUser,
|
|
244
276
|
connectPlatform
|
|
245
277
|
};
|
|
246
278
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VaultixContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -266,19 +298,58 @@ function useSession() {
|
|
|
266
298
|
return { session, isLoaded, isSignedIn };
|
|
267
299
|
}
|
|
268
300
|
function useUser() {
|
|
269
|
-
const { user, isLoaded, isSignedIn } = useVaultixContext();
|
|
270
|
-
return { user, isLoaded, isSignedIn };
|
|
301
|
+
const { user, isLoaded, isSignedIn, updateUser } = useVaultixContext();
|
|
302
|
+
return { user, isLoaded, isSignedIn, update: updateUser };
|
|
271
303
|
}
|
|
272
304
|
function useOrganization() {
|
|
273
305
|
const { organization, isLoaded } = useVaultixContext();
|
|
274
306
|
return { organization, isLoaded };
|
|
275
307
|
}
|
|
308
|
+
function usePlatformConnect(provider, defaultOptions) {
|
|
309
|
+
const { connectPlatform } = useVaultixContext();
|
|
310
|
+
const [connectedAccount, setConnectedAccount] = (0, import_react2.useState)(null);
|
|
311
|
+
const connect = (0, import_react2.useCallback)(
|
|
312
|
+
(overrides) => {
|
|
313
|
+
connectPlatform(provider, { ...defaultOptions, ...overrides });
|
|
314
|
+
},
|
|
315
|
+
[connectPlatform, provider, defaultOptions]
|
|
316
|
+
);
|
|
317
|
+
(0, import_react2.useEffect)(() => {
|
|
318
|
+
if (typeof window === "undefined") return;
|
|
319
|
+
const params = new URLSearchParams(window.location.search);
|
|
320
|
+
const connectToken = params.get("__vaultix_connect");
|
|
321
|
+
const returnedProvider = params.get("provider");
|
|
322
|
+
if (!connectToken || returnedProvider !== provider) return;
|
|
323
|
+
try {
|
|
324
|
+
const segment = connectToken.split(".")[1];
|
|
325
|
+
if (!segment) return;
|
|
326
|
+
const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
327
|
+
const payload = JSON.parse(atob(b64));
|
|
328
|
+
const result = {
|
|
329
|
+
provider: returnedProvider,
|
|
330
|
+
raw: payload,
|
|
331
|
+
...typeof payload["platform_user_id"] === "string" && { platformUserId: payload["platform_user_id"] },
|
|
332
|
+
...typeof payload["username"] === "string" && { username: payload["username"] },
|
|
333
|
+
...typeof payload["access_token"] === "string" && { accessToken: payload["access_token"] },
|
|
334
|
+
...typeof payload["scope"] === "string" && { scope: payload["scope"] }
|
|
335
|
+
};
|
|
336
|
+
setConnectedAccount(result);
|
|
337
|
+
const clean = new URL(window.location.href);
|
|
338
|
+
clean.searchParams.delete("__vaultix_connect");
|
|
339
|
+
clean.searchParams.delete("provider");
|
|
340
|
+
window.history.replaceState({}, "", clean.toString());
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}, [provider]);
|
|
344
|
+
return { connect, connectedAccount };
|
|
345
|
+
}
|
|
276
346
|
function useAuth() {
|
|
277
347
|
const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
|
|
278
348
|
const getToken = (0, import_react2.useCallback)(async () => {
|
|
279
349
|
if (!isSignedIn) return null;
|
|
280
350
|
try {
|
|
281
|
-
|
|
351
|
+
const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
|
|
352
|
+
return m?.[1] ? decodeURIComponent(m[1]) : null;
|
|
282
353
|
} catch {
|
|
283
354
|
return null;
|
|
284
355
|
}
|
|
@@ -333,9 +404,15 @@ function SignIn({
|
|
|
333
404
|
onSuccess,
|
|
334
405
|
onError,
|
|
335
406
|
apiOrigin: apiOriginProp,
|
|
336
|
-
className
|
|
407
|
+
className,
|
|
408
|
+
appearance
|
|
337
409
|
}) {
|
|
338
410
|
const apiOrigin = resolveApiOrigin2(apiOriginProp);
|
|
411
|
+
const vars = appearance?.variables ?? {};
|
|
412
|
+
const primaryColor = vars.colorPrimary;
|
|
413
|
+
const cardBg = vars.colorBackground ?? "rgba(22,27,45,0.92)";
|
|
414
|
+
const textColor = vars.colorText ?? "rgba(255,255,255,0.9)";
|
|
415
|
+
const mutedColor = vars.colorTextMuted ?? "#475569";
|
|
339
416
|
const [step, setStep] = (0, import_react3.useState)("email");
|
|
340
417
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
341
418
|
const [password, setPassword] = (0, import_react3.useState)("");
|
|
@@ -392,6 +469,23 @@ function SignIn({
|
|
|
392
469
|
setLoading(false);
|
|
393
470
|
}
|
|
394
471
|
}
|
|
472
|
+
async function handleMagicLinkRequest() {
|
|
473
|
+
setError(null);
|
|
474
|
+
setLoading(true);
|
|
475
|
+
try {
|
|
476
|
+
const target = resolveAfterSignInUrl(redirectUrl);
|
|
477
|
+
await fetch(`${apiOrigin}/api/v1/auth/magic-link/send`, {
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "Content-Type": "application/json" },
|
|
480
|
+
body: JSON.stringify({ email, redirect_url: target })
|
|
481
|
+
});
|
|
482
|
+
setStep("magic-link-sent");
|
|
483
|
+
} catch {
|
|
484
|
+
setErr("Network error. Please try again.");
|
|
485
|
+
} finally {
|
|
486
|
+
setLoading(false);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
395
489
|
async function handlePasswordSubmit(e) {
|
|
396
490
|
e.preventDefault();
|
|
397
491
|
setError(null);
|
|
@@ -560,6 +654,7 @@ function SignIn({
|
|
|
560
654
|
password: "Enter password",
|
|
561
655
|
passkey: "Passkey sign-in",
|
|
562
656
|
totp: "Two-factor code",
|
|
657
|
+
"magic-link-sent": "Check your inbox",
|
|
563
658
|
forgot: "Forgot password",
|
|
564
659
|
"forgot-verify": "Enter reset code",
|
|
565
660
|
"forgot-reset": "New password"
|
|
@@ -569,6 +664,7 @@ function SignIn({
|
|
|
569
664
|
password: email,
|
|
570
665
|
passkey: email,
|
|
571
666
|
totp: "Enter the 6-digit code from your authenticator app",
|
|
667
|
+
"magic-link-sent": `We sent a sign-in link to ${email}`,
|
|
572
668
|
forgot: "We'll send a reset code to your email",
|
|
573
669
|
"forgot-verify": `Code sent to ${email}`,
|
|
574
670
|
"forgot-reset": "Choose a new password"
|
|
@@ -581,12 +677,19 @@ function SignIn({
|
|
|
581
677
|
"backdrop-blur-[16px]",
|
|
582
678
|
className
|
|
583
679
|
),
|
|
584
|
-
style: { background:
|
|
680
|
+
style: { background: cardBg },
|
|
585
681
|
children: [
|
|
586
682
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "px-8 pt-8 pb-6 text-center", children: [
|
|
587
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
588
|
-
|
|
589
|
-
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
684
|
+
"div",
|
|
685
|
+
{
|
|
686
|
+
className: "inline-flex items-center justify-center w-10 h-10 rounded-xl shadow-lg mb-4",
|
|
687
|
+
style: primaryColor ? { background: primaryColor, boxShadow: `0 10px 15px -3px ${primaryColor}4d` } : { background: "linear-gradient(to bottom right, #7c3aed, #2563eb)", boxShadow: "0 10px 15px -3px rgba(124,58,237,0.3)" },
|
|
688
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LockIcon, {})
|
|
689
|
+
}
|
|
690
|
+
),
|
|
691
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { className: "text-xl font-semibold", style: { color: textColor }, children: title[step] }),
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm mt-1", style: { color: mutedColor }, children: subtitle[step] })
|
|
590
693
|
] }),
|
|
591
694
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "px-8 pb-8 space-y-4", children: [
|
|
592
695
|
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-red-400", children: error }) }),
|
|
@@ -597,7 +700,7 @@ function SignIn({
|
|
|
597
700
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetaButton, { onClick: handleMetaSignIn }),
|
|
598
701
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LinkedInButton, { onClick: handleLinkedInSignIn }),
|
|
599
702
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(XButton, { onClick: handleXSignIn }),
|
|
600
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Divider, {}),
|
|
703
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Divider, { mutedColor }),
|
|
601
704
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleEmailSubmit, className: "space-y-3", children: [
|
|
602
705
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
603
706
|
Input,
|
|
@@ -607,10 +710,11 @@ function SignIn({
|
|
|
607
710
|
value: email,
|
|
608
711
|
onChange: setEmail,
|
|
609
712
|
autoFocus: true,
|
|
610
|
-
required: true
|
|
713
|
+
required: true,
|
|
714
|
+
primaryColor
|
|
611
715
|
}
|
|
612
716
|
),
|
|
613
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Continue" })
|
|
717
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, primaryColor, children: "Continue" })
|
|
614
718
|
] })
|
|
615
719
|
] }),
|
|
616
720
|
step === "password" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handlePasswordSubmit, className: "space-y-3", children: [
|
|
@@ -622,23 +726,50 @@ function SignIn({
|
|
|
622
726
|
value: password,
|
|
623
727
|
onChange: setPassword,
|
|
624
728
|
autoFocus: true,
|
|
625
|
-
required: true
|
|
729
|
+
required: true,
|
|
730
|
+
primaryColor
|
|
626
731
|
}
|
|
627
732
|
),
|
|
628
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Sign in" }),
|
|
733
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, primaryColor, children: "Sign in" }),
|
|
734
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
735
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 h-px bg-white/8" }),
|
|
736
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px]", style: { color: mutedColor }, children: "or" }),
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 h-px bg-white/8" })
|
|
738
|
+
] }),
|
|
739
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: handleMagicLinkRequest, mutedColor, children: "\u2709 Email me a sign-in link" }),
|
|
629
740
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => {
|
|
630
741
|
setStep("forgot");
|
|
631
742
|
setError(null);
|
|
632
|
-
}, children: "Forgot password?" }),
|
|
633
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
|
|
743
|
+
}, mutedColor, children: "Forgot password?" }),
|
|
744
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("email"), mutedColor, children: "\u2190 Back" })
|
|
745
|
+
] }),
|
|
746
|
+
step === "magic-link-sent" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4 text-center", children: [
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
748
|
+
"div",
|
|
749
|
+
{
|
|
750
|
+
className: "w-14 h-14 rounded-2xl flex items-center justify-center mx-auto border",
|
|
751
|
+
style: primaryColor ? { background: `${primaryColor}26`, borderColor: `${primaryColor}4d` } : { background: "rgba(168,85,247,0.15)", borderColor: "rgba(168,85,247,0.3)" },
|
|
752
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EnvelopeIcon, { color: primaryColor ?? "#a78bfa" })
|
|
753
|
+
}
|
|
754
|
+
),
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm leading-relaxed", style: { color: mutedColor }, children: [
|
|
756
|
+
"The link expires in ",
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: textColor }, className: "font-medium", children: "15 minutes" }),
|
|
758
|
+
" and can only be used once."
|
|
759
|
+
] }),
|
|
760
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: handleMagicLinkRequest, mutedColor, children: "Resend link" }),
|
|
761
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => {
|
|
762
|
+
setStep("password");
|
|
763
|
+
setError(null);
|
|
764
|
+
}, mutedColor, children: "Use password instead" })
|
|
634
765
|
] }),
|
|
635
766
|
step === "passkey" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3", children: [
|
|
636
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(PrimaryButton, { loading, onClick: handlePasskeySignIn, children: [
|
|
767
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(PrimaryButton, { loading, onClick: handlePasskeySignIn, primaryColor, children: [
|
|
637
768
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FingerprintIcon, {}),
|
|
638
769
|
"Authenticate with passkey"
|
|
639
770
|
] }),
|
|
640
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("password"), children: "Use password instead" }),
|
|
641
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("password"), mutedColor, children: "Use password instead" }),
|
|
772
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("email"), mutedColor, children: "\u2190 Back" })
|
|
642
773
|
] }),
|
|
643
774
|
step === "totp" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleTotpSubmit, className: "space-y-3", children: [
|
|
644
775
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -653,11 +784,12 @@ function SignIn({
|
|
|
653
784
|
onChange: setTotp,
|
|
654
785
|
autoFocus: true,
|
|
655
786
|
required: true,
|
|
656
|
-
className: "text-center tracking-[0.4em] text-lg"
|
|
787
|
+
className: "text-center tracking-[0.4em] text-lg",
|
|
788
|
+
primaryColor
|
|
657
789
|
}
|
|
658
790
|
),
|
|
659
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Verify" }),
|
|
660
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("password"), children: "\u2190 Back" })
|
|
791
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, primaryColor, children: "Verify" }),
|
|
792
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => setStep("password"), mutedColor, children: "\u2190 Back" })
|
|
661
793
|
] }),
|
|
662
794
|
step === "forgot" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleForgotSubmit, className: "space-y-3", children: [
|
|
663
795
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -668,14 +800,15 @@ function SignIn({
|
|
|
668
800
|
value: email,
|
|
669
801
|
onChange: setEmail,
|
|
670
802
|
autoFocus: true,
|
|
671
|
-
required: true
|
|
803
|
+
required: true,
|
|
804
|
+
primaryColor
|
|
672
805
|
}
|
|
673
806
|
),
|
|
674
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Send reset code" }),
|
|
807
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, primaryColor, children: "Send reset code" }),
|
|
675
808
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GhostButton, { onClick: () => {
|
|
676
809
|
setStep("password");
|
|
677
810
|
setError(null);
|
|
678
|
-
}, children: "\u2190 Back" })
|
|
811
|
+
}, mutedColor, children: "\u2190 Back" })
|
|
679
812
|
] }),
|
|
680
813
|
step === "forgot-verify" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleForgotVerifySubmit, className: "space-y-3", children: [
|
|
681
814
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -690,10 +823,11 @@ function SignIn({
|
|
|
690
823
|
onChange: setForgotCode,
|
|
691
824
|
autoFocus: true,
|
|
692
825
|
required: true,
|
|
693
|
-
className: "text-center tracking-[0.4em] text-lg"
|
|
826
|
+
className: "text-center tracking-[0.4em] text-lg",
|
|
827
|
+
primaryColor
|
|
694
828
|
}
|
|
695
829
|
),
|
|
696
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Continue" })
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, primaryColor, children: "Continue" })
|
|
697
831
|
] }),
|
|
698
832
|
step === "forgot-reset" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleResetPasswordSubmit, className: "space-y-3", children: [
|
|
699
833
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -705,18 +839,19 @@ function SignIn({
|
|
|
705
839
|
onChange: setNewPassword,
|
|
706
840
|
autoFocus: true,
|
|
707
841
|
required: true,
|
|
708
|
-
minLength: 8
|
|
842
|
+
minLength: 8,
|
|
843
|
+
primaryColor
|
|
709
844
|
}
|
|
710
845
|
),
|
|
711
846
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PasswordStrength, { password: newPassword }),
|
|
712
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, children: "Reset password" })
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryButton, { loading, primaryColor, children: "Reset password" })
|
|
713
848
|
] })
|
|
714
849
|
] })
|
|
715
850
|
]
|
|
716
851
|
}
|
|
717
852
|
);
|
|
718
853
|
}
|
|
719
|
-
function Input({ onChange, className, ...props }) {
|
|
854
|
+
function Input({ onChange, className, primaryColor, ...props }) {
|
|
720
855
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
721
856
|
"input",
|
|
722
857
|
{
|
|
@@ -725,39 +860,48 @@ function Input({ onChange, className, ...props }) {
|
|
|
725
860
|
className: (0, import_clsx.clsx)(
|
|
726
861
|
"w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5",
|
|
727
862
|
"text-sm text-white/90 placeholder:text-[#475569]",
|
|
728
|
-
"focus:outline-none
|
|
863
|
+
"focus:outline-none transition-colors",
|
|
864
|
+
!primaryColor && "focus:border-purple-500/60",
|
|
729
865
|
className
|
|
730
|
-
)
|
|
866
|
+
),
|
|
867
|
+
style: primaryColor ? { "--vx-focus": primaryColor } : void 0,
|
|
868
|
+
onFocus: (e) => {
|
|
869
|
+
if (primaryColor) e.currentTarget.style.borderColor = `${primaryColor}99`;
|
|
870
|
+
props.onFocus?.(e);
|
|
871
|
+
},
|
|
872
|
+
onBlur: (e) => {
|
|
873
|
+
if (primaryColor) e.currentTarget.style.borderColor = "";
|
|
874
|
+
props.onBlur?.(e);
|
|
875
|
+
}
|
|
731
876
|
}
|
|
732
877
|
);
|
|
733
878
|
}
|
|
734
|
-
function PrimaryButton({ children, loading, onClick }) {
|
|
879
|
+
function PrimaryButton({ children, loading, onClick, primaryColor }) {
|
|
735
880
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
736
881
|
"button",
|
|
737
882
|
{
|
|
738
883
|
type: onClick ? "button" : "submit",
|
|
739
884
|
onClick,
|
|
740
885
|
disabled: loading,
|
|
886
|
+
style: primaryColor ? { background: primaryColor, boxShadow: `0 10px 15px -3px ${primaryColor}33` } : void 0,
|
|
741
887
|
className: (0, import_clsx.clsx)(
|
|
742
888
|
"w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl",
|
|
743
|
-
"text-sm font-semibold text-white",
|
|
744
|
-
"
|
|
745
|
-
"hover:from-purple-500 hover:to-blue-500"
|
|
746
|
-
"shadow-lg shadow-purple-500/20",
|
|
747
|
-
"transition-all duration-150",
|
|
748
|
-
"disabled:opacity-60 disabled:cursor-not-allowed"
|
|
889
|
+
"text-sm font-semibold text-white transition-all duration-150",
|
|
890
|
+
"disabled:opacity-60 disabled:cursor-not-allowed",
|
|
891
|
+
!primaryColor && "bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 shadow-lg shadow-purple-500/20"
|
|
749
892
|
),
|
|
750
893
|
children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, {}) : children
|
|
751
894
|
}
|
|
752
895
|
);
|
|
753
896
|
}
|
|
754
|
-
function GhostButton({ children, onClick }) {
|
|
897
|
+
function GhostButton({ children, onClick, mutedColor }) {
|
|
755
898
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
756
899
|
"button",
|
|
757
900
|
{
|
|
758
901
|
type: "button",
|
|
759
902
|
onClick,
|
|
760
|
-
className: "w-full text-xs
|
|
903
|
+
className: "w-full text-xs transition-colors py-1 hover:opacity-80",
|
|
904
|
+
style: { color: mutedColor ?? "#475569" },
|
|
761
905
|
children
|
|
762
906
|
}
|
|
763
907
|
);
|
|
@@ -801,10 +945,10 @@ function GitHubButton({ onClick }) {
|
|
|
801
945
|
function XButton({ onClick }) {
|
|
802
946
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(OAuthButton, { icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(XIcon, {}), label: "Continue with X", onClick });
|
|
803
947
|
}
|
|
804
|
-
function Divider() {
|
|
948
|
+
function Divider({ mutedColor }) {
|
|
805
949
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
806
950
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 h-px bg-white/8" }),
|
|
807
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px]
|
|
951
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] uppercase tracking-wider", style: { color: mutedColor ?? "#475569" }, children: "or" }),
|
|
808
952
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 h-px bg-white/8" })
|
|
809
953
|
] });
|
|
810
954
|
}
|
|
@@ -874,6 +1018,12 @@ function GitHubIcon() {
|
|
|
874
1018
|
function XIcon() {
|
|
875
1019
|
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" }) });
|
|
876
1020
|
}
|
|
1021
|
+
function EnvelopeIcon({ color = "#a78bfa" }) {
|
|
1022
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
1024
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
|
|
1025
|
+
] });
|
|
1026
|
+
}
|
|
877
1027
|
|
|
878
1028
|
// src/components/SignUp.tsx
|
|
879
1029
|
var import_clsx2 = require("clsx");
|
|
@@ -887,6 +1037,13 @@ function resolveApiOrigin3(prop) {
|
|
|
887
1037
|
}
|
|
888
1038
|
return "";
|
|
889
1039
|
}
|
|
1040
|
+
function resolvePk2() {
|
|
1041
|
+
if (typeof document !== "undefined") {
|
|
1042
|
+
const el = document.querySelector("[data-vaultix-pk]");
|
|
1043
|
+
if (el) return el.getAttribute("data-vaultix-pk") ?? "";
|
|
1044
|
+
}
|
|
1045
|
+
return "";
|
|
1046
|
+
}
|
|
890
1047
|
function resolveAfterSignUpUrl(redirectUrlProp) {
|
|
891
1048
|
if (redirectUrlProp) return redirectUrlProp;
|
|
892
1049
|
if (typeof document !== "undefined") {
|
|
@@ -904,9 +1061,15 @@ function SignUp({
|
|
|
904
1061
|
onSuccess,
|
|
905
1062
|
onError,
|
|
906
1063
|
apiOrigin: apiOriginProp,
|
|
907
|
-
className
|
|
1064
|
+
className,
|
|
1065
|
+
appearance
|
|
908
1066
|
}) {
|
|
909
1067
|
const apiOrigin = resolveApiOrigin3(apiOriginProp);
|
|
1068
|
+
const vars = appearance?.variables ?? {};
|
|
1069
|
+
const primaryColor = vars.colorPrimary;
|
|
1070
|
+
const cardBg = vars.colorBackground ?? "rgba(22,27,45,0.92)";
|
|
1071
|
+
const textColor = vars.colorText ?? "rgba(255,255,255,0.9)";
|
|
1072
|
+
const mutedColor = vars.colorTextMuted ?? "#475569";
|
|
910
1073
|
const [step, setStep] = (0, import_react4.useState)("email");
|
|
911
1074
|
const [email, setEmail] = (0, import_react4.useState)("");
|
|
912
1075
|
const [password, setPassword] = (0, import_react4.useState)("");
|
|
@@ -925,10 +1088,12 @@ function SignUp({
|
|
|
925
1088
|
url.searchParams.set("__vaultix_handshake", handshakeToken);
|
|
926
1089
|
window.location.href = url.toString();
|
|
927
1090
|
}
|
|
928
|
-
function
|
|
1091
|
+
function oauthRedirect(provider) {
|
|
929
1092
|
const target = resolveAfterSignUpUrl(redirectUrl);
|
|
1093
|
+
const pk = resolvePk2();
|
|
930
1094
|
const params = new URLSearchParams({ redirect_url: target });
|
|
931
|
-
|
|
1095
|
+
if (pk) params.set("pk", pk);
|
|
1096
|
+
window.location.href = `${apiOrigin}/api/v1/auth/oauth/${provider}?${params}`;
|
|
932
1097
|
}
|
|
933
1098
|
async function handleEmailPassword(e) {
|
|
934
1099
|
e.preventDefault();
|
|
@@ -999,48 +1164,61 @@ function SignUp({
|
|
|
999
1164
|
"w-full max-w-sm mx-auto rounded-2xl border border-white/8 shadow-2xl backdrop-blur-[16px]",
|
|
1000
1165
|
className
|
|
1001
1166
|
),
|
|
1002
|
-
style: { background:
|
|
1167
|
+
style: { background: cardBg },
|
|
1003
1168
|
children: [
|
|
1004
1169
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "px-8 pt-8 pb-6 text-center", children: [
|
|
1005
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1006
|
-
|
|
1007
|
-
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1171
|
+
"div",
|
|
1172
|
+
{
|
|
1173
|
+
className: "inline-flex items-center justify-center w-10 h-10 rounded-xl shadow-lg mb-4",
|
|
1174
|
+
style: primaryColor ? { background: primaryColor, boxShadow: `0 10px 15px -3px ${primaryColor}4d` } : { background: "linear-gradient(to bottom right, #059669, #0d9488)", boxShadow: "0 10px 15px -3px rgba(5,150,105,0.3)" },
|
|
1175
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, {})
|
|
1176
|
+
}
|
|
1177
|
+
),
|
|
1178
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "text-xl font-semibold", style: { color: textColor }, children: step === "verify" ? "Check your email" : "Create an account" }),
|
|
1179
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm mt-1", style: { color: mutedColor }, children: step === "verify" ? `We sent a code to ${email}` : "Start your free trial today" })
|
|
1008
1180
|
] }),
|
|
1009
1181
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "px-8 pb-8 space-y-4", children: [
|
|
1010
1182
|
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-red-400", children: error }) }),
|
|
1011
1183
|
step === "email" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
|
|
1012
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1013
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(OAuthButton2, { icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GoogleIcon2, {}), label: "Continue with Google", onClick: () => oauthRedirect("google") }),
|
|
1185
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(OAuthButton2, { icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GitHubIcon2, {}), label: "Continue with GitHub", onClick: () => oauthRedirect("github") }),
|
|
1186
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(OAuthButton2, { icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetaIcon2, {}), label: "Continue with Facebook", onClick: () => oauthRedirect("meta") }),
|
|
1187
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(OAuthButton2, { icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkedInIcon2, {}), label: "Continue with LinkedIn", onClick: () => oauthRedirect("linkedin") }),
|
|
1188
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(OAuthButton2, { icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(XIcon2, {}), label: "Continue with X", onClick: () => oauthRedirect("x") }),
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Divider2, { mutedColor }),
|
|
1014
1190
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleEmailPassword, className: "space-y-3", children: [
|
|
1015
1191
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1016
|
-
|
|
1192
|
+
Input2,
|
|
1017
1193
|
{
|
|
1018
1194
|
type: "email",
|
|
1019
1195
|
placeholder: "you@company.com",
|
|
1020
1196
|
value: email,
|
|
1021
1197
|
onChange: setEmail,
|
|
1022
1198
|
autoFocus: true,
|
|
1023
|
-
required: true
|
|
1199
|
+
required: true,
|
|
1200
|
+
primaryColor
|
|
1024
1201
|
}
|
|
1025
1202
|
),
|
|
1026
1203
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1027
|
-
|
|
1204
|
+
Input2,
|
|
1028
1205
|
{
|
|
1029
1206
|
type: "password",
|
|
1030
1207
|
placeholder: "Create a password",
|
|
1031
1208
|
value: password,
|
|
1032
1209
|
onChange: setPassword,
|
|
1033
1210
|
required: true,
|
|
1034
|
-
minLength: 8
|
|
1211
|
+
minLength: 8,
|
|
1212
|
+
primaryColor
|
|
1035
1213
|
}
|
|
1036
1214
|
),
|
|
1037
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PasswordStrength2, { password }),
|
|
1038
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1215
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PasswordStrength2, { password, primaryColor }),
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PrimaryButton2, { loading, primaryColor, children: "Create account" })
|
|
1039
1217
|
] })
|
|
1040
1218
|
] }),
|
|
1041
1219
|
step === "verify" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleVerification, className: "space-y-3", children: [
|
|
1042
1220
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1043
|
-
|
|
1221
|
+
Input2,
|
|
1044
1222
|
{
|
|
1045
1223
|
type: "text",
|
|
1046
1224
|
inputMode: "numeric",
|
|
@@ -1051,16 +1229,18 @@ function SignUp({
|
|
|
1051
1229
|
onChange: setVerificationCode,
|
|
1052
1230
|
autoFocus: true,
|
|
1053
1231
|
required: true,
|
|
1054
|
-
className: "text-center tracking-[0.4em] text-lg"
|
|
1232
|
+
className: "text-center tracking-[0.4em] text-lg",
|
|
1233
|
+
primaryColor
|
|
1055
1234
|
}
|
|
1056
1235
|
),
|
|
1057
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1236
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PrimaryButton2, { loading, primaryColor, children: "Verify email" }),
|
|
1058
1237
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1059
1238
|
"button",
|
|
1060
1239
|
{
|
|
1061
1240
|
type: "button",
|
|
1062
1241
|
onClick: resendCode,
|
|
1063
|
-
className: "w-full text-xs
|
|
1242
|
+
className: "w-full text-xs transition-colors py-1 hover:opacity-80",
|
|
1243
|
+
style: { color: mutedColor },
|
|
1064
1244
|
children: "Resend code"
|
|
1065
1245
|
}
|
|
1066
1246
|
)
|
|
@@ -1070,7 +1250,7 @@ function SignUp({
|
|
|
1070
1250
|
}
|
|
1071
1251
|
);
|
|
1072
1252
|
}
|
|
1073
|
-
function
|
|
1253
|
+
function Input2({ onChange, className, primaryColor, ...props }) {
|
|
1074
1254
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1075
1255
|
"input",
|
|
1076
1256
|
{
|
|
@@ -1079,34 +1259,47 @@ function SignUpInput({ onChange, className, ...props }) {
|
|
|
1079
1259
|
className: (0, import_clsx2.clsx)(
|
|
1080
1260
|
"w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5",
|
|
1081
1261
|
"text-sm text-white/90 placeholder:text-[#475569]",
|
|
1082
|
-
"focus:outline-none
|
|
1262
|
+
"focus:outline-none transition-colors",
|
|
1263
|
+
!primaryColor && "focus:border-emerald-500/60",
|
|
1083
1264
|
className
|
|
1084
|
-
)
|
|
1265
|
+
),
|
|
1266
|
+
onFocus: (e) => {
|
|
1267
|
+
if (primaryColor) e.currentTarget.style.borderColor = `${primaryColor}99`;
|
|
1268
|
+
props.onFocus?.(e);
|
|
1269
|
+
},
|
|
1270
|
+
onBlur: (e) => {
|
|
1271
|
+
if (primaryColor) e.currentTarget.style.borderColor = "";
|
|
1272
|
+
props.onBlur?.(e);
|
|
1273
|
+
}
|
|
1085
1274
|
}
|
|
1086
1275
|
);
|
|
1087
1276
|
}
|
|
1088
|
-
function
|
|
1277
|
+
function PrimaryButton2({
|
|
1089
1278
|
children,
|
|
1090
|
-
loading
|
|
1279
|
+
loading,
|
|
1280
|
+
primaryColor
|
|
1091
1281
|
}) {
|
|
1092
1282
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1093
1283
|
"button",
|
|
1094
1284
|
{
|
|
1095
1285
|
type: "submit",
|
|
1096
1286
|
disabled: loading,
|
|
1287
|
+
style: primaryColor ? { background: primaryColor, boxShadow: `0 10px 15px -3px ${primaryColor}33` } : void 0,
|
|
1097
1288
|
className: (0, import_clsx2.clsx)(
|
|
1098
1289
|
"w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl",
|
|
1099
|
-
"text-sm font-semibold text-white",
|
|
1100
|
-
"
|
|
1101
|
-
"hover:from-emerald-500 hover:to-teal-500"
|
|
1102
|
-
"shadow-lg shadow-emerald-500/20 transition-all duration-150",
|
|
1103
|
-
"disabled:opacity-60 disabled:cursor-not-allowed"
|
|
1290
|
+
"text-sm font-semibold text-white transition-all duration-150",
|
|
1291
|
+
"disabled:opacity-60 disabled:cursor-not-allowed",
|
|
1292
|
+
!primaryColor && "bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 shadow-lg shadow-emerald-500/20"
|
|
1104
1293
|
),
|
|
1105
1294
|
children: loading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner2, {}) : children
|
|
1106
1295
|
}
|
|
1107
1296
|
);
|
|
1108
1297
|
}
|
|
1109
|
-
function
|
|
1298
|
+
function OAuthButton2({
|
|
1299
|
+
icon,
|
|
1300
|
+
label,
|
|
1301
|
+
onClick
|
|
1302
|
+
}) {
|
|
1110
1303
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1111
1304
|
"button",
|
|
1112
1305
|
{
|
|
@@ -1120,20 +1313,20 @@ function GoogleButton2({ onClick }) {
|
|
|
1120
1313
|
"transition-all duration-150"
|
|
1121
1314
|
),
|
|
1122
1315
|
children: [
|
|
1123
|
-
|
|
1124
|
-
|
|
1316
|
+
icon,
|
|
1317
|
+
label
|
|
1125
1318
|
]
|
|
1126
1319
|
}
|
|
1127
1320
|
);
|
|
1128
1321
|
}
|
|
1129
|
-
function Divider2() {
|
|
1322
|
+
function Divider2({ mutedColor }) {
|
|
1130
1323
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
1131
1324
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 h-px bg-white/8" }),
|
|
1132
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px]
|
|
1325
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] uppercase tracking-wider", style: { color: mutedColor ?? "#475569" }, children: "or" }),
|
|
1133
1326
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 h-px bg-white/8" })
|
|
1134
1327
|
] });
|
|
1135
1328
|
}
|
|
1136
|
-
function PasswordStrength2({ password }) {
|
|
1329
|
+
function PasswordStrength2({ password, primaryColor }) {
|
|
1137
1330
|
const score = (() => {
|
|
1138
1331
|
if (password.length === 0) return 0;
|
|
1139
1332
|
let s = 0;
|
|
@@ -1145,15 +1338,17 @@ function PasswordStrength2({ password }) {
|
|
|
1145
1338
|
})();
|
|
1146
1339
|
if (password.length === 0) return null;
|
|
1147
1340
|
const labels = ["", "Weak", "Fair", "Good", "Strong"];
|
|
1148
|
-
const
|
|
1341
|
+
const tailwindColors = ["", "bg-red-500", "bg-amber-400", "bg-blue-400", "bg-emerald-400"];
|
|
1149
1342
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-1", children: [
|
|
1150
1343
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1151
1344
|
"div",
|
|
1152
1345
|
{
|
|
1153
1346
|
className: (0, import_clsx2.clsx)(
|
|
1154
1347
|
"flex-1 h-0.5 rounded-full transition-all duration-300",
|
|
1155
|
-
i <= score ?
|
|
1156
|
-
|
|
1348
|
+
i <= score && !primaryColor ? tailwindColors[score] : void 0,
|
|
1349
|
+
i > score || !primaryColor && i <= score ? void 0 : void 0
|
|
1350
|
+
),
|
|
1351
|
+
style: i <= score && primaryColor ? { background: score >= 4 ? primaryColor : void 0 } : { background: i <= score ? void 0 : "rgba(255,255,255,0.1)" }
|
|
1157
1352
|
},
|
|
1158
1353
|
i
|
|
1159
1354
|
)) }),
|
|
@@ -1177,6 +1372,18 @@ function GoogleIcon2() {
|
|
|
1177
1372
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
|
|
1178
1373
|
] });
|
|
1179
1374
|
}
|
|
1375
|
+
function MetaIcon2() {
|
|
1376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "#1877F2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" }) });
|
|
1377
|
+
}
|
|
1378
|
+
function LinkedInIcon2() {
|
|
1379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "#0A66C2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.063 2.063 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" }) });
|
|
1380
|
+
}
|
|
1381
|
+
function GitHubIcon2() {
|
|
1382
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z" }) });
|
|
1383
|
+
}
|
|
1384
|
+
function XIcon2() {
|
|
1385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ (0, import_jsx_runtime3.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" }) });
|
|
1386
|
+
}
|
|
1180
1387
|
|
|
1181
1388
|
// src/components/SignedIn.tsx
|
|
1182
1389
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -1216,20 +1423,15 @@ function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
|
|
|
1216
1423
|
var import_clsx3 = require("clsx");
|
|
1217
1424
|
var import_react6 = require("react");
|
|
1218
1425
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1219
|
-
function UserButton({
|
|
1220
|
-
showName = false,
|
|
1221
|
-
afterSignOutUrl,
|
|
1222
|
-
className
|
|
1223
|
-
}) {
|
|
1426
|
+
function UserButton({ showName = false, afterSignOutUrl, className }) {
|
|
1224
1427
|
const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
|
|
1225
1428
|
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
1226
1429
|
const [signingOut, setSigningOut] = (0, import_react6.useState)(false);
|
|
1430
|
+
const [showProfile, setShowProfile] = (0, import_react6.useState)(false);
|
|
1227
1431
|
const containerRef = (0, import_react6.useRef)(null);
|
|
1228
1432
|
(0, import_react6.useEffect)(() => {
|
|
1229
1433
|
function onPointerDown(e) {
|
|
1230
|
-
if (containerRef.current && !containerRef.current.contains(e.target))
|
|
1231
|
-
setOpen(false);
|
|
1232
|
-
}
|
|
1434
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
|
|
1233
1435
|
}
|
|
1234
1436
|
document.addEventListener("pointerdown", onPointerDown);
|
|
1235
1437
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
@@ -1242,98 +1444,456 @@ function UserButton({
|
|
|
1242
1444
|
await signOut();
|
|
1243
1445
|
if (afterSignOutUrl) window.location.href = afterSignOutUrl;
|
|
1244
1446
|
}
|
|
1245
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1246
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1448
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: containerRef, className: (0, import_clsx3.clsx)("relative", className), children: [
|
|
1449
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1450
|
+
"button",
|
|
1451
|
+
{
|
|
1452
|
+
onClick: () => setOpen((o) => !o),
|
|
1453
|
+
className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
|
|
1454
|
+
children: [
|
|
1455
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { initials, imageUrl: user.imageUrl }),
|
|
1456
|
+
showName && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
|
|
1457
|
+
]
|
|
1458
|
+
}
|
|
1459
|
+
),
|
|
1460
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1461
|
+
"div",
|
|
1462
|
+
{
|
|
1463
|
+
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"),
|
|
1464
|
+
style: { background: "rgba(22,27,45,0.96)" },
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
1469
|
+
(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(" ") }),
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-[#475569] truncate", children: user.email })
|
|
1471
|
+
] })
|
|
1472
|
+
] }),
|
|
1473
|
+
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: [
|
|
1474
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
|
|
1475
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RiskBadge, { level: session.riskLevel })
|
|
1476
|
+
] }) }),
|
|
1477
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "p-2", children: [
|
|
1478
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DropdownItem, { label: "Manage account", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UserIcon, {}), onClick: () => {
|
|
1479
|
+
setOpen(false);
|
|
1480
|
+
setShowProfile(true);
|
|
1481
|
+
} }),
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "h-px bg-white/8 my-1" }),
|
|
1483
|
+
/* @__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 })
|
|
1484
|
+
] })
|
|
1485
|
+
]
|
|
1486
|
+
}
|
|
1487
|
+
)
|
|
1488
|
+
] }),
|
|
1489
|
+
showProfile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfileModal, { onClose: () => setShowProfile(false) })
|
|
1490
|
+
] });
|
|
1491
|
+
}
|
|
1492
|
+
function ProfileModal({ onClose }) {
|
|
1493
|
+
const { user, updateUser } = useVaultixContext();
|
|
1494
|
+
const [tab, setTab] = (0, import_react6.useState)("profile");
|
|
1495
|
+
const [firstName, setFirstName] = (0, import_react6.useState)(user?.firstName ?? "");
|
|
1496
|
+
const [lastName, setLastName] = (0, import_react6.useState)(user?.lastName ?? "");
|
|
1497
|
+
const [imageUrl, setImageUrl] = (0, import_react6.useState)(user?.imageUrl ?? "");
|
|
1498
|
+
const [currentPassword, setCurrentPassword] = (0, import_react6.useState)("");
|
|
1499
|
+
const [newPassword, setNewPassword] = (0, import_react6.useState)("");
|
|
1500
|
+
const [confirmPassword, setConfirmPassword] = (0, import_react6.useState)("");
|
|
1501
|
+
const [saving, setSaving] = (0, import_react6.useState)(false);
|
|
1502
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1503
|
+
const [success, setSuccess] = (0, import_react6.useState)(null);
|
|
1504
|
+
(0, import_react6.useEffect)(() => {
|
|
1505
|
+
function onKey(e) {
|
|
1506
|
+
if (e.key === "Escape") onClose();
|
|
1507
|
+
}
|
|
1508
|
+
document.addEventListener("keydown", onKey);
|
|
1509
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
1510
|
+
}, [onClose]);
|
|
1511
|
+
async function saveProfile(e) {
|
|
1512
|
+
e.preventDefault();
|
|
1513
|
+
setError(null);
|
|
1514
|
+
setSuccess(null);
|
|
1515
|
+
setSaving(true);
|
|
1516
|
+
try {
|
|
1517
|
+
await updateUser({ firstName: firstName.trim() || null, lastName: lastName.trim() || null, imageUrl: imageUrl.trim() || null });
|
|
1518
|
+
setSuccess("Profile updated.");
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
setError(err instanceof Error ? err.message : "Update failed");
|
|
1521
|
+
} finally {
|
|
1522
|
+
setSaving(false);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
async function savePassword(e) {
|
|
1526
|
+
e.preventDefault();
|
|
1527
|
+
setError(null);
|
|
1528
|
+
setSuccess(null);
|
|
1529
|
+
if (newPassword !== confirmPassword) {
|
|
1530
|
+
setError("Passwords do not match.");
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
if (newPassword.length < 8) {
|
|
1534
|
+
setError("Password must be at least 8 characters.");
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
setSaving(true);
|
|
1538
|
+
try {
|
|
1539
|
+
await updateUser({ currentPassword, newPassword });
|
|
1540
|
+
setSuccess("Password changed.");
|
|
1541
|
+
setCurrentPassword("");
|
|
1542
|
+
setNewPassword("");
|
|
1543
|
+
setConfirmPassword("");
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
setError(err instanceof Error ? err.message : "Update failed");
|
|
1546
|
+
} finally {
|
|
1547
|
+
setSaving(false);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
const TABS = [
|
|
1551
|
+
{ id: "profile", label: "Profile" },
|
|
1552
|
+
{ id: "password", label: "Change password" },
|
|
1553
|
+
{ id: "security", label: "Security" }
|
|
1554
|
+
];
|
|
1555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1556
|
+
"div",
|
|
1557
|
+
{
|
|
1558
|
+
className: "fixed inset-0 z-[9999] flex items-center justify-center p-4",
|
|
1559
|
+
style: { background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" },
|
|
1560
|
+
onPointerDown: (e) => {
|
|
1561
|
+
if (e.target === e.currentTarget) onClose();
|
|
1562
|
+
},
|
|
1563
|
+
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: [
|
|
1564
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/8", children: [
|
|
1565
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-sm font-semibold text-white/90", children: "Manage account" }),
|
|
1566
|
+
/* @__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, {}) })
|
|
1567
|
+
] }),
|
|
1568
|
+
/* @__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)(
|
|
1569
|
+
"button",
|
|
1570
|
+
{
|
|
1571
|
+
onClick: () => {
|
|
1572
|
+
setTab(id);
|
|
1573
|
+
setError(null);
|
|
1574
|
+
setSuccess(null);
|
|
1575
|
+
},
|
|
1576
|
+
className: (0, import_clsx3.clsx)(
|
|
1577
|
+
"px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
|
|
1578
|
+
tab === id ? "bg-purple-500/15 text-purple-300 border border-purple-500/30" : "text-[#475569] hover:text-white/70"
|
|
1579
|
+
),
|
|
1580
|
+
children: label
|
|
1581
|
+
},
|
|
1582
|
+
id
|
|
1583
|
+
)) }),
|
|
1584
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 pt-3", children: [
|
|
1585
|
+
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 }),
|
|
1586
|
+
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 })
|
|
1587
|
+
] }),
|
|
1588
|
+
tab === "profile" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: saveProfile, className: "px-6 py-4 space-y-4", children: [
|
|
1589
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1590
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "First name", value: firstName, onChange: setFirstName, placeholder: "Jane" }),
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "Last name", value: lastName, onChange: setLastName, placeholder: "Smith" })
|
|
1592
|
+
] }),
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "Avatar URL", value: imageUrl, onChange: setImageUrl, placeholder: "https://\u2026", type: "url" }),
|
|
1594
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaveButton, { saving }),
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
|
|
1597
|
+
] })
|
|
1598
|
+
] }),
|
|
1599
|
+
tab === "password" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: savePassword, className: "px-6 py-4 space-y-4", children: [
|
|
1600
|
+
/* @__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" }),
|
|
1601
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModalField, { label: "New password", value: newPassword, onChange: setNewPassword, type: "password", placeholder: "Min 8 characters" }),
|
|
1602
|
+
/* @__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" }),
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
1604
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SaveButton, { saving, label: "Change password" }),
|
|
1605
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
|
|
1606
|
+
] })
|
|
1607
|
+
] }),
|
|
1608
|
+
tab === "security" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SecurityTab, {})
|
|
1609
|
+
] })
|
|
1610
|
+
}
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
function SecurityTab() {
|
|
1614
|
+
const { apiOrigin } = useVaultixContext();
|
|
1615
|
+
const [totpEnabled, setTotpEnabled] = (0, import_react6.useState)(null);
|
|
1616
|
+
const [backupCodesLeft, setBackupCodesLeft] = (0, import_react6.useState)(null);
|
|
1617
|
+
const [enrollStep, setEnrollStep] = (0, import_react6.useState)("idle");
|
|
1618
|
+
const [qrDataUrl, setQrDataUrl] = (0, import_react6.useState)("");
|
|
1619
|
+
const [manualKey, setManualKey] = (0, import_react6.useState)("");
|
|
1620
|
+
const [enrollCode, setEnrollCode] = (0, import_react6.useState)("");
|
|
1621
|
+
const [disableCode, setDisableCode] = (0, import_react6.useState)("");
|
|
1622
|
+
const [backupCodes, setBackupCodes] = (0, import_react6.useState)([]);
|
|
1623
|
+
const [loading, setLoading] = (0, import_react6.useState)(false);
|
|
1624
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1625
|
+
const [showDisable, setShowDisable] = (0, import_react6.useState)(false);
|
|
1626
|
+
const fetchStatus = (0, import_react6.useCallback)(async () => {
|
|
1627
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp/status`, { credentials: "include" });
|
|
1628
|
+
if (res.ok) {
|
|
1629
|
+
const d = await res.json();
|
|
1630
|
+
setTotpEnabled(d.enabled);
|
|
1631
|
+
setBackupCodesLeft(d.backup_codes_remaining);
|
|
1632
|
+
}
|
|
1633
|
+
}, [apiOrigin]);
|
|
1634
|
+
(0, import_react6.useEffect)(() => {
|
|
1635
|
+
void fetchStatus();
|
|
1636
|
+
}, [fetchStatus]);
|
|
1637
|
+
async function startEnroll() {
|
|
1638
|
+
setError(null);
|
|
1639
|
+
setLoading(true);
|
|
1640
|
+
try {
|
|
1641
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp/enroll`, { method: "POST", credentials: "include" });
|
|
1642
|
+
const d = await res.json();
|
|
1643
|
+
if (!res.ok) {
|
|
1644
|
+
setError(d.error ?? "Failed to start enrollment.");
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
setQrDataUrl(d.qr_data_url);
|
|
1648
|
+
setManualKey(d.manual_entry_key);
|
|
1649
|
+
setEnrollStep("qr");
|
|
1650
|
+
} finally {
|
|
1651
|
+
setLoading(false);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
async function verifyEnroll(e) {
|
|
1655
|
+
e.preventDefault();
|
|
1656
|
+
setError(null);
|
|
1657
|
+
setLoading(true);
|
|
1658
|
+
try {
|
|
1659
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp/verify-enroll`, {
|
|
1660
|
+
method: "POST",
|
|
1661
|
+
credentials: "include",
|
|
1662
|
+
headers: { "Content-Type": "application/json" },
|
|
1663
|
+
body: JSON.stringify({ code: enrollCode })
|
|
1664
|
+
});
|
|
1665
|
+
const d = await res.json();
|
|
1666
|
+
if (!res.ok) {
|
|
1667
|
+
setError(d.error ?? "Invalid code. Try again.");
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
setBackupCodes(d.backup_codes);
|
|
1671
|
+
setEnrollStep("backup-codes");
|
|
1672
|
+
setTotpEnabled(true);
|
|
1673
|
+
setBackupCodesLeft(d.backup_codes.length);
|
|
1674
|
+
} finally {
|
|
1675
|
+
setLoading(false);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async function disableTotp(e) {
|
|
1679
|
+
e.preventDefault();
|
|
1680
|
+
setError(null);
|
|
1681
|
+
setLoading(true);
|
|
1682
|
+
try {
|
|
1683
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp`, {
|
|
1684
|
+
method: "DELETE",
|
|
1685
|
+
credentials: "include",
|
|
1686
|
+
headers: { "Content-Type": "application/json" },
|
|
1687
|
+
body: JSON.stringify({ code: disableCode })
|
|
1688
|
+
});
|
|
1689
|
+
const d = await res.json();
|
|
1690
|
+
if (!res.ok) {
|
|
1691
|
+
setError(d.error ?? "Invalid code.");
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
setTotpEnabled(false);
|
|
1695
|
+
setBackupCodesLeft(null);
|
|
1696
|
+
setShowDisable(false);
|
|
1697
|
+
setDisableCode("");
|
|
1698
|
+
} finally {
|
|
1699
|
+
setLoading(false);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (totpEnabled === null) {
|
|
1703
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-6 py-8 text-center text-[#475569] text-sm", children: "Loading\u2026" });
|
|
1704
|
+
}
|
|
1705
|
+
if (enrollStep === "backup-codes") {
|
|
1706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 py-4 space-y-4", children: [
|
|
1707
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rounded-xl bg-emerald-500/10 border border-emerald-500/30 px-4 py-3", children: [
|
|
1708
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs font-semibold text-emerald-400 mb-1", children: "Authenticator app enabled!" }),
|
|
1709
|
+
/* @__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." })
|
|
1710
|
+
] }),
|
|
1711
|
+
/* @__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)) }),
|
|
1712
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1713
|
+
"button",
|
|
1714
|
+
{
|
|
1715
|
+
onClick: () => {
|
|
1716
|
+
const t = backupCodes.join("\n");
|
|
1717
|
+
navigator.clipboard.writeText(t).catch(() => {
|
|
1718
|
+
});
|
|
1719
|
+
},
|
|
1720
|
+
className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1 border border-white/8 rounded-lg",
|
|
1721
|
+
children: "Copy all codes"
|
|
1722
|
+
}
|
|
1723
|
+
),
|
|
1724
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1725
|
+
"button",
|
|
1726
|
+
{
|
|
1727
|
+
onClick: () => setEnrollStep("idle"),
|
|
1728
|
+
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",
|
|
1729
|
+
children: "Done"
|
|
1730
|
+
}
|
|
1731
|
+
)
|
|
1732
|
+
] });
|
|
1733
|
+
}
|
|
1734
|
+
if (enrollStep === "qr") {
|
|
1735
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 py-4 space-y-4", children: [
|
|
1736
|
+
/* @__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." }),
|
|
1737
|
+
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 }) }),
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("details", { className: "text-[11px] text-[#475569]", children: [
|
|
1739
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("summary", { className: "cursor-pointer hover:text-white/60", children: "Can't scan? Enter manually" }),
|
|
1740
|
+
/* @__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 })
|
|
1741
|
+
] }),
|
|
1742
|
+
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 }),
|
|
1743
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: verifyEnroll, className: "space-y-3", children: [
|
|
1744
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1745
|
+
"input",
|
|
1746
|
+
{
|
|
1747
|
+
type: "text",
|
|
1748
|
+
inputMode: "numeric",
|
|
1749
|
+
pattern: "[0-9]{6}",
|
|
1750
|
+
maxLength: 6,
|
|
1751
|
+
placeholder: "000000",
|
|
1752
|
+
value: enrollCode,
|
|
1753
|
+
onChange: (e) => setEnrollCode(e.target.value),
|
|
1754
|
+
autoFocus: true,
|
|
1755
|
+
required: true,
|
|
1756
|
+
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]"
|
|
1757
|
+
}
|
|
1758
|
+
),
|
|
1759
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1760
|
+
"button",
|
|
1761
|
+
{
|
|
1762
|
+
type: "submit",
|
|
1763
|
+
disabled: loading || enrollCode.length !== 6,
|
|
1764
|
+
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",
|
|
1765
|
+
children: loading ? "Verifying\u2026" : "Verify and enable"
|
|
1766
|
+
}
|
|
1767
|
+
),
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1769
|
+
"button",
|
|
1770
|
+
{
|
|
1771
|
+
type: "button",
|
|
1772
|
+
onClick: () => {
|
|
1773
|
+
setEnrollStep("idle");
|
|
1774
|
+
setError(null);
|
|
1775
|
+
},
|
|
1776
|
+
className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1",
|
|
1777
|
+
children: "\u2190 Cancel"
|
|
1778
|
+
}
|
|
1779
|
+
)
|
|
1780
|
+
] })
|
|
1781
|
+
] });
|
|
1782
|
+
}
|
|
1783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-6 py-4 space-y-4", children: [
|
|
1784
|
+
/* @__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: [
|
|
1785
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
1786
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-medium text-white/90", children: "Authenticator app" }),
|
|
1787
|
+
/* @__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" })
|
|
1788
|
+
] }),
|
|
1789
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: (0, import_clsx3.clsx)(
|
|
1790
|
+
"text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-full border",
|
|
1791
|
+
totpEnabled ? "bg-emerald-500/15 text-emerald-400 border-emerald-500/30" : "bg-white/5 text-[#475569] border-white/8"
|
|
1792
|
+
), children: totpEnabled ? "On" : "Off" })
|
|
1793
|
+
] }) }),
|
|
1794
|
+
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 }),
|
|
1795
|
+
!totpEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1247
1796
|
"button",
|
|
1248
1797
|
{
|
|
1249
|
-
onClick:
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
showName && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
|
|
1254
|
-
]
|
|
1798
|
+
onClick: startEnroll,
|
|
1799
|
+
disabled: loading,
|
|
1800
|
+
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",
|
|
1801
|
+
children: loading ? "Loading\u2026" : "Enable authenticator app"
|
|
1255
1802
|
}
|
|
1256
1803
|
),
|
|
1257
|
-
|
|
1258
|
-
"
|
|
1804
|
+
totpEnabled && !showDisable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1805
|
+
"button",
|
|
1259
1806
|
{
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1807
|
+
onClick: () => setShowDisable(true),
|
|
1808
|
+
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",
|
|
1809
|
+
children: "Disable authenticator app"
|
|
1810
|
+
}
|
|
1811
|
+
),
|
|
1812
|
+
totpEnabled && showDisable && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: disableTotp, className: "space-y-3", children: [
|
|
1813
|
+
/* @__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." }),
|
|
1814
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1815
|
+
"input",
|
|
1816
|
+
{
|
|
1817
|
+
type: "text",
|
|
1818
|
+
placeholder: "000000 or backup code",
|
|
1819
|
+
value: disableCode,
|
|
1820
|
+
onChange: (e) => setDisableCode(e.target.value),
|
|
1821
|
+
autoFocus: true,
|
|
1822
|
+
required: true,
|
|
1823
|
+
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"
|
|
1824
|
+
}
|
|
1825
|
+
),
|
|
1826
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex gap-2", children: [
|
|
1827
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1828
|
+
"button",
|
|
1829
|
+
{
|
|
1830
|
+
type: "submit",
|
|
1831
|
+
disabled: loading || !disableCode,
|
|
1832
|
+
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",
|
|
1833
|
+
children: loading ? "Disabling\u2026" : "Confirm disable"
|
|
1834
|
+
}
|
|
1263
1835
|
),
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
(
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1296
|
-
DropdownItem,
|
|
1297
|
-
{
|
|
1298
|
-
label: signingOut ? "Signing out\u2026" : "Sign out",
|
|
1299
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SignOutIcon, {}),
|
|
1300
|
-
onClick: handleSignOut,
|
|
1301
|
-
destructive: true
|
|
1302
|
-
}
|
|
1303
|
-
)
|
|
1304
|
-
] })
|
|
1305
|
-
]
|
|
1836
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1837
|
+
"button",
|
|
1838
|
+
{
|
|
1839
|
+
type: "button",
|
|
1840
|
+
onClick: () => {
|
|
1841
|
+
setShowDisable(false);
|
|
1842
|
+
setError(null);
|
|
1843
|
+
setDisableCode("");
|
|
1844
|
+
},
|
|
1845
|
+
className: "px-4 py-2 rounded-xl text-xs text-[#475569] hover:text-white hover:bg-white/8 border border-white/8 transition-colors",
|
|
1846
|
+
children: "Cancel"
|
|
1847
|
+
}
|
|
1848
|
+
)
|
|
1849
|
+
] })
|
|
1850
|
+
] })
|
|
1851
|
+
] });
|
|
1852
|
+
}
|
|
1853
|
+
function ModalField({ label, value, onChange, placeholder, type = "text" }) {
|
|
1854
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
|
|
1855
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: label }),
|
|
1856
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1857
|
+
"input",
|
|
1858
|
+
{
|
|
1859
|
+
type,
|
|
1860
|
+
value,
|
|
1861
|
+
onChange: (e) => onChange(e.target.value),
|
|
1862
|
+
placeholder,
|
|
1863
|
+
className: (0, import_clsx3.clsx)(
|
|
1864
|
+
"w-full bg-white/5 border border-white/8 rounded-xl px-3 py-2 text-sm text-white/90",
|
|
1865
|
+
"placeholder:text-[#475569] focus:outline-none focus:border-purple-500/60 transition-colors"
|
|
1866
|
+
)
|
|
1306
1867
|
}
|
|
1307
1868
|
)
|
|
1308
1869
|
] });
|
|
1309
1870
|
}
|
|
1310
|
-
function
|
|
1311
|
-
const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
|
|
1312
|
-
if (imageUrl) {
|
|
1313
|
-
return (
|
|
1314
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
1315
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1316
|
-
"img",
|
|
1317
|
-
{
|
|
1318
|
-
src: imageUrl,
|
|
1319
|
-
alt: "",
|
|
1320
|
-
className: (0, import_clsx3.clsx)(dim, "rounded-full object-cover ring-2 ring-white/10")
|
|
1321
|
-
}
|
|
1322
|
-
)
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1871
|
+
function SaveButton({ saving, label = "Save changes" }) {
|
|
1325
1872
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1326
|
-
"
|
|
1873
|
+
"button",
|
|
1327
1874
|
{
|
|
1875
|
+
type: "submit",
|
|
1876
|
+
disabled: saving,
|
|
1328
1877
|
className: (0, import_clsx3.clsx)(
|
|
1329
|
-
|
|
1330
|
-
"
|
|
1331
|
-
"
|
|
1878
|
+
"px-4 py-2 rounded-xl text-sm font-semibold text-white transition-all",
|
|
1879
|
+
"bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500",
|
|
1880
|
+
"shadow-lg shadow-purple-500/20 disabled:opacity-60"
|
|
1332
1881
|
),
|
|
1333
|
-
children:
|
|
1882
|
+
children: saving ? "Saving\u2026" : label
|
|
1334
1883
|
}
|
|
1335
1884
|
);
|
|
1336
1885
|
}
|
|
1886
|
+
function Avatar({ initials, imageUrl, size = "sm" }) {
|
|
1887
|
+
const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
|
|
1888
|
+
if (imageUrl) {
|
|
1889
|
+
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") });
|
|
1890
|
+
}
|
|
1891
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: (0, import_clsx3.clsx)(
|
|
1892
|
+
dim,
|
|
1893
|
+
"rounded-full flex items-center justify-center font-semibold text-white",
|
|
1894
|
+
"bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
|
|
1895
|
+
), children: initials });
|
|
1896
|
+
}
|
|
1337
1897
|
function AvatarSkeleton() {
|
|
1338
1898
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
|
|
1339
1899
|
}
|
|
@@ -1344,16 +1904,10 @@ function RiskBadge({ level }) {
|
|
|
1344
1904
|
high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
|
|
1345
1905
|
critical: "bg-red-500/15 text-red-400 border-red-500/30"
|
|
1346
1906
|
};
|
|
1347
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1348
|
-
"
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
"inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
|
|
1352
|
-
styles[level] ?? styles.low
|
|
1353
|
-
),
|
|
1354
|
-
children: level
|
|
1355
|
-
}
|
|
1356
|
-
);
|
|
1907
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: (0, import_clsx3.clsx)(
|
|
1908
|
+
"inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
|
|
1909
|
+
styles[level] ?? styles.low
|
|
1910
|
+
), children: level });
|
|
1357
1911
|
}
|
|
1358
1912
|
function DropdownItem({ label, icon, onClick, destructive }) {
|
|
1359
1913
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
@@ -1377,9 +1931,6 @@ function UserIcon() {
|
|
|
1377
1931
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "12", cy: "7", r: "4" })
|
|
1378
1932
|
] });
|
|
1379
1933
|
}
|
|
1380
|
-
function ShieldIcon() {
|
|
1381
|
-
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" }) });
|
|
1382
|
-
}
|
|
1383
1934
|
function SignOutIcon() {
|
|
1384
1935
|
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: [
|
|
1385
1936
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
@@ -1387,6 +1938,12 @@ function SignOutIcon() {
|
|
|
1387
1938
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
1388
1939
|
] });
|
|
1389
1940
|
}
|
|
1941
|
+
function CloseIcon() {
|
|
1942
|
+
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: [
|
|
1943
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1944
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1945
|
+
] });
|
|
1946
|
+
}
|
|
1390
1947
|
|
|
1391
1948
|
// src/components/OrganizationSwitcher.tsx
|
|
1392
1949
|
var import_clsx4 = require("clsx");
|
|
@@ -1582,17 +2139,19 @@ function MiniSpinner() {
|
|
|
1582
2139
|
}
|
|
1583
2140
|
|
|
1584
2141
|
// src/index.ts
|
|
1585
|
-
var STORAGE_KEY2 = "vaultix_jwt";
|
|
1586
2142
|
function getStoredToken() {
|
|
2143
|
+
if (typeof document === "undefined") return null;
|
|
1587
2144
|
try {
|
|
1588
|
-
|
|
2145
|
+
const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
|
|
2146
|
+
return m?.[1] ? decodeURIComponent(m[1]) : null;
|
|
1589
2147
|
} catch {
|
|
1590
2148
|
return null;
|
|
1591
2149
|
}
|
|
1592
2150
|
}
|
|
1593
2151
|
function clearStoredToken() {
|
|
2152
|
+
if (typeof document === "undefined") return;
|
|
1594
2153
|
try {
|
|
1595
|
-
|
|
2154
|
+
document.cookie = "vaultix-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
|
|
1596
2155
|
} catch {
|
|
1597
2156
|
}
|
|
1598
2157
|
}
|
|
@@ -1611,6 +2170,7 @@ function clearStoredToken() {
|
|
|
1611
2170
|
getStoredToken,
|
|
1612
2171
|
useAuth,
|
|
1613
2172
|
useOrganization,
|
|
2173
|
+
usePlatformConnect,
|
|
1614
2174
|
useSession,
|
|
1615
2175
|
useUser,
|
|
1616
2176
|
useVaultix
|