@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.mjs
CHANGED
|
@@ -23,28 +23,35 @@ function resolveApiOrigin(key, apiUrlProp) {
|
|
|
23
23
|
throw new Error(`Unknown publishable key prefix "${parts[0]}".`);
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
return atob(parts.slice(3).join("_")).replace(/\/$/, "");
|
|
26
|
+
return atob(parts.slice(3).join("_")).replace(/\|.+$/, "").replace(/\/$/, "");
|
|
27
27
|
} catch {
|
|
28
28
|
throw new Error("Publishable key has an unreadable API origin payload.");
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
var
|
|
31
|
+
var TOKEN_COOKIE = "vaultix-token";
|
|
32
|
+
var COOKIE_DAYS = 30;
|
|
32
33
|
function storeJwt(jwt) {
|
|
34
|
+
if (typeof document === "undefined") return;
|
|
33
35
|
try {
|
|
34
|
-
|
|
36
|
+
const expires = new Date(Date.now() + COOKIE_DAYS * 864e5).toUTCString();
|
|
37
|
+
const secure = location.protocol === "https:" ? "; Secure" : "";
|
|
38
|
+
document.cookie = `${TOKEN_COOKIE}=${encodeURIComponent(jwt)}; path=/; expires=${expires}; SameSite=Lax${secure}`;
|
|
35
39
|
} catch {
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
function loadJwt() {
|
|
43
|
+
if (typeof document === "undefined") return null;
|
|
39
44
|
try {
|
|
40
|
-
|
|
45
|
+
const m = document.cookie.match(new RegExp(`(?:^|; )${TOKEN_COOKIE}=([^;]+)`));
|
|
46
|
+
return m?.[1] ? decodeURIComponent(m[1]) : null;
|
|
41
47
|
} catch {
|
|
42
48
|
return null;
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
function clearJwt() {
|
|
52
|
+
if (typeof document === "undefined") return;
|
|
46
53
|
try {
|
|
47
|
-
|
|
54
|
+
document.cookie = `${TOKEN_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
|
|
48
55
|
} catch {
|
|
49
56
|
}
|
|
50
57
|
}
|
|
@@ -115,26 +122,27 @@ function VaultixProvider({
|
|
|
115
122
|
async function hydrate() {
|
|
116
123
|
let jwt = null;
|
|
117
124
|
if (typeof window !== "undefined") {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
url.searchParams.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
jwt = loadJwt();
|
|
126
|
+
if (!jwt) {
|
|
127
|
+
const url = new URL(window.location.href);
|
|
128
|
+
const handshakeToken = url.searchParams.get("__vaultix_handshake");
|
|
129
|
+
if (handshakeToken) {
|
|
130
|
+
url.searchParams.delete("__vaultix_handshake");
|
|
131
|
+
window.history.replaceState({}, "", url.toString());
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: { "Content-Type": "application/json" },
|
|
136
|
+
body: JSON.stringify({ handshake_token: handshakeToken })
|
|
137
|
+
});
|
|
138
|
+
if (res.ok) {
|
|
139
|
+
const data = await res.json();
|
|
140
|
+
jwt = data.session_jwt;
|
|
141
|
+
storeJwt(jwt);
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
133
144
|
}
|
|
134
|
-
} catch {
|
|
135
145
|
}
|
|
136
|
-
} else {
|
|
137
|
-
jwt = loadJwt();
|
|
138
146
|
}
|
|
139
147
|
jwtRef.current = jwt;
|
|
140
148
|
}
|
|
@@ -175,7 +183,7 @@ function VaultixProvider({
|
|
|
175
183
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
176
184
|
};
|
|
177
185
|
}, [apiOrigin, publishableKey, scheduleRefresh]);
|
|
178
|
-
const signOut = useCallback(async () => {
|
|
186
|
+
const signOut = useCallback(async (redirectUrl) => {
|
|
179
187
|
const jwt = jwtRef.current;
|
|
180
188
|
clearAuth();
|
|
181
189
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
@@ -187,8 +195,29 @@ function VaultixProvider({
|
|
|
187
195
|
headers
|
|
188
196
|
}).catch(() => {
|
|
189
197
|
});
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
const dest = redirectUrl ?? afterSignUpUrl ?? "/";
|
|
199
|
+
if (typeof window !== "undefined") window.location.href = dest;
|
|
200
|
+
}, [apiOrigin, afterSignUpUrl, clearAuth]);
|
|
201
|
+
const updateUser = useCallback(async (params) => {
|
|
202
|
+
const userId = user?.id;
|
|
203
|
+
if (!userId) throw new Error("No active session");
|
|
204
|
+
const jwt = jwtRef.current;
|
|
205
|
+
const headers = { "Content-Type": "application/json" };
|
|
206
|
+
if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
|
|
207
|
+
const res = await fetch(`${apiOrigin}/api/v1/users/${userId}`, {
|
|
208
|
+
method: "PATCH",
|
|
209
|
+
credentials: jwt ? "omit" : "include",
|
|
210
|
+
headers,
|
|
211
|
+
body: JSON.stringify(params)
|
|
212
|
+
});
|
|
213
|
+
if (!res.ok) {
|
|
214
|
+
const err = await res.json().catch(() => ({ error: "Update failed" }));
|
|
215
|
+
throw new Error(err.error ?? "Update failed");
|
|
216
|
+
}
|
|
217
|
+
const updated = await res.json();
|
|
218
|
+
setUser(updated);
|
|
219
|
+
return updated;
|
|
220
|
+
}, [apiOrigin, user?.id]);
|
|
192
221
|
const connectPlatform = useCallback((provider, options = {}) => {
|
|
193
222
|
const params = new URLSearchParams({ mode: "connect" });
|
|
194
223
|
params.set("redirect_url", options.redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "/"));
|
|
@@ -206,7 +235,9 @@ function VaultixProvider({
|
|
|
206
235
|
organization,
|
|
207
236
|
isLoaded,
|
|
208
237
|
isSignedIn: !!session,
|
|
238
|
+
apiOrigin,
|
|
209
239
|
signOut,
|
|
240
|
+
updateUser,
|
|
210
241
|
connectPlatform
|
|
211
242
|
};
|
|
212
243
|
return /* @__PURE__ */ jsx(VaultixContext.Provider, { value, children: /* @__PURE__ */ jsx(
|
|
@@ -223,7 +254,7 @@ function VaultixProvider({
|
|
|
223
254
|
}
|
|
224
255
|
|
|
225
256
|
// src/hooks/index.ts
|
|
226
|
-
import { useCallback as useCallback2 } from "react";
|
|
257
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
227
258
|
function useVaultix() {
|
|
228
259
|
return useVaultixContext();
|
|
229
260
|
}
|
|
@@ -232,19 +263,58 @@ function useSession() {
|
|
|
232
263
|
return { session, isLoaded, isSignedIn };
|
|
233
264
|
}
|
|
234
265
|
function useUser() {
|
|
235
|
-
const { user, isLoaded, isSignedIn } = useVaultixContext();
|
|
236
|
-
return { user, isLoaded, isSignedIn };
|
|
266
|
+
const { user, isLoaded, isSignedIn, updateUser } = useVaultixContext();
|
|
267
|
+
return { user, isLoaded, isSignedIn, update: updateUser };
|
|
237
268
|
}
|
|
238
269
|
function useOrganization() {
|
|
239
270
|
const { organization, isLoaded } = useVaultixContext();
|
|
240
271
|
return { organization, isLoaded };
|
|
241
272
|
}
|
|
273
|
+
function usePlatformConnect(provider, defaultOptions) {
|
|
274
|
+
const { connectPlatform } = useVaultixContext();
|
|
275
|
+
const [connectedAccount, setConnectedAccount] = useState2(null);
|
|
276
|
+
const connect = useCallback2(
|
|
277
|
+
(overrides) => {
|
|
278
|
+
connectPlatform(provider, { ...defaultOptions, ...overrides });
|
|
279
|
+
},
|
|
280
|
+
[connectPlatform, provider, defaultOptions]
|
|
281
|
+
);
|
|
282
|
+
useEffect2(() => {
|
|
283
|
+
if (typeof window === "undefined") return;
|
|
284
|
+
const params = new URLSearchParams(window.location.search);
|
|
285
|
+
const connectToken = params.get("__vaultix_connect");
|
|
286
|
+
const returnedProvider = params.get("provider");
|
|
287
|
+
if (!connectToken || returnedProvider !== provider) return;
|
|
288
|
+
try {
|
|
289
|
+
const segment = connectToken.split(".")[1];
|
|
290
|
+
if (!segment) return;
|
|
291
|
+
const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
292
|
+
const payload = JSON.parse(atob(b64));
|
|
293
|
+
const result = {
|
|
294
|
+
provider: returnedProvider,
|
|
295
|
+
raw: payload,
|
|
296
|
+
...typeof payload["platform_user_id"] === "string" && { platformUserId: payload["platform_user_id"] },
|
|
297
|
+
...typeof payload["username"] === "string" && { username: payload["username"] },
|
|
298
|
+
...typeof payload["access_token"] === "string" && { accessToken: payload["access_token"] },
|
|
299
|
+
...typeof payload["scope"] === "string" && { scope: payload["scope"] }
|
|
300
|
+
};
|
|
301
|
+
setConnectedAccount(result);
|
|
302
|
+
const clean = new URL(window.location.href);
|
|
303
|
+
clean.searchParams.delete("__vaultix_connect");
|
|
304
|
+
clean.searchParams.delete("provider");
|
|
305
|
+
window.history.replaceState({}, "", clean.toString());
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}, [provider]);
|
|
309
|
+
return { connect, connectedAccount };
|
|
310
|
+
}
|
|
242
311
|
function useAuth() {
|
|
243
312
|
const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
|
|
244
313
|
const getToken = useCallback2(async () => {
|
|
245
314
|
if (!isSignedIn) return null;
|
|
246
315
|
try {
|
|
247
|
-
|
|
316
|
+
const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
|
|
317
|
+
return m?.[1] ? decodeURIComponent(m[1]) : null;
|
|
248
318
|
} catch {
|
|
249
319
|
return null;
|
|
250
320
|
}
|
|
@@ -262,7 +332,7 @@ function useAuth() {
|
|
|
262
332
|
|
|
263
333
|
// src/components/SignIn.tsx
|
|
264
334
|
import { clsx } from "clsx";
|
|
265
|
-
import { useRef as useRef2, useState as
|
|
335
|
+
import { useRef as useRef2, useState as useState3 } from "react";
|
|
266
336
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
267
337
|
function toBase64url(buf) {
|
|
268
338
|
return btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
@@ -299,18 +369,24 @@ function SignIn({
|
|
|
299
369
|
onSuccess,
|
|
300
370
|
onError,
|
|
301
371
|
apiOrigin: apiOriginProp,
|
|
302
|
-
className
|
|
372
|
+
className,
|
|
373
|
+
appearance
|
|
303
374
|
}) {
|
|
304
375
|
const apiOrigin = resolveApiOrigin2(apiOriginProp);
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const [
|
|
311
|
-
const [
|
|
312
|
-
const [
|
|
313
|
-
const [
|
|
376
|
+
const vars = appearance?.variables ?? {};
|
|
377
|
+
const primaryColor = vars.colorPrimary;
|
|
378
|
+
const cardBg = vars.colorBackground ?? "rgba(22,27,45,0.92)";
|
|
379
|
+
const textColor = vars.colorText ?? "rgba(255,255,255,0.9)";
|
|
380
|
+
const mutedColor = vars.colorTextMuted ?? "#475569";
|
|
381
|
+
const [step, setStep] = useState3("email");
|
|
382
|
+
const [email, setEmail] = useState3("");
|
|
383
|
+
const [password, setPassword] = useState3("");
|
|
384
|
+
const [totp, setTotp] = useState3("");
|
|
385
|
+
const [forgotCode, setForgotCode] = useState3("");
|
|
386
|
+
const [newPassword, setNewPassword] = useState3("");
|
|
387
|
+
const [error, setError] = useState3(null);
|
|
388
|
+
const [loading, setLoading] = useState3(false);
|
|
389
|
+
const [info, setInfo] = useState3(null);
|
|
314
390
|
const challengeIdRef = useRef2(null);
|
|
315
391
|
function setErr(msg) {
|
|
316
392
|
setError(msg);
|
|
@@ -358,6 +434,23 @@ function SignIn({
|
|
|
358
434
|
setLoading(false);
|
|
359
435
|
}
|
|
360
436
|
}
|
|
437
|
+
async function handleMagicLinkRequest() {
|
|
438
|
+
setError(null);
|
|
439
|
+
setLoading(true);
|
|
440
|
+
try {
|
|
441
|
+
const target = resolveAfterSignInUrl(redirectUrl);
|
|
442
|
+
await fetch(`${apiOrigin}/api/v1/auth/magic-link/send`, {
|
|
443
|
+
method: "POST",
|
|
444
|
+
headers: { "Content-Type": "application/json" },
|
|
445
|
+
body: JSON.stringify({ email, redirect_url: target })
|
|
446
|
+
});
|
|
447
|
+
setStep("magic-link-sent");
|
|
448
|
+
} catch {
|
|
449
|
+
setErr("Network error. Please try again.");
|
|
450
|
+
} finally {
|
|
451
|
+
setLoading(false);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
361
454
|
async function handlePasswordSubmit(e) {
|
|
362
455
|
e.preventDefault();
|
|
363
456
|
setError(null);
|
|
@@ -526,6 +619,7 @@ function SignIn({
|
|
|
526
619
|
password: "Enter password",
|
|
527
620
|
passkey: "Passkey sign-in",
|
|
528
621
|
totp: "Two-factor code",
|
|
622
|
+
"magic-link-sent": "Check your inbox",
|
|
529
623
|
forgot: "Forgot password",
|
|
530
624
|
"forgot-verify": "Enter reset code",
|
|
531
625
|
"forgot-reset": "New password"
|
|
@@ -535,6 +629,7 @@ function SignIn({
|
|
|
535
629
|
password: email,
|
|
536
630
|
passkey: email,
|
|
537
631
|
totp: "Enter the 6-digit code from your authenticator app",
|
|
632
|
+
"magic-link-sent": `We sent a sign-in link to ${email}`,
|
|
538
633
|
forgot: "We'll send a reset code to your email",
|
|
539
634
|
"forgot-verify": `Code sent to ${email}`,
|
|
540
635
|
"forgot-reset": "Choose a new password"
|
|
@@ -547,12 +642,19 @@ function SignIn({
|
|
|
547
642
|
"backdrop-blur-[16px]",
|
|
548
643
|
className
|
|
549
644
|
),
|
|
550
|
-
style: { background:
|
|
645
|
+
style: { background: cardBg },
|
|
551
646
|
children: [
|
|
552
647
|
/* @__PURE__ */ jsxs("div", { className: "px-8 pt-8 pb-6 text-center", children: [
|
|
553
|
-
/* @__PURE__ */ jsx2(
|
|
554
|
-
|
|
555
|
-
|
|
648
|
+
/* @__PURE__ */ jsx2(
|
|
649
|
+
"div",
|
|
650
|
+
{
|
|
651
|
+
className: "inline-flex items-center justify-center w-10 h-10 rounded-xl shadow-lg mb-4",
|
|
652
|
+
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)" },
|
|
653
|
+
children: /* @__PURE__ */ jsx2(LockIcon, {})
|
|
654
|
+
}
|
|
655
|
+
),
|
|
656
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold", style: { color: textColor }, children: title[step] }),
|
|
657
|
+
/* @__PURE__ */ jsx2("p", { className: "text-sm mt-1", style: { color: mutedColor }, children: subtitle[step] })
|
|
556
658
|
] }),
|
|
557
659
|
/* @__PURE__ */ jsxs("div", { className: "px-8 pb-8 space-y-4", children: [
|
|
558
660
|
error && /* @__PURE__ */ jsx2("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-xs text-red-400", children: error }) }),
|
|
@@ -563,7 +665,7 @@ function SignIn({
|
|
|
563
665
|
/* @__PURE__ */ jsx2(MetaButton, { onClick: handleMetaSignIn }),
|
|
564
666
|
/* @__PURE__ */ jsx2(LinkedInButton, { onClick: handleLinkedInSignIn }),
|
|
565
667
|
/* @__PURE__ */ jsx2(XButton, { onClick: handleXSignIn }),
|
|
566
|
-
/* @__PURE__ */ jsx2(Divider, {}),
|
|
668
|
+
/* @__PURE__ */ jsx2(Divider, { mutedColor }),
|
|
567
669
|
/* @__PURE__ */ jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-3", children: [
|
|
568
670
|
/* @__PURE__ */ jsx2(
|
|
569
671
|
Input,
|
|
@@ -573,10 +675,11 @@ function SignIn({
|
|
|
573
675
|
value: email,
|
|
574
676
|
onChange: setEmail,
|
|
575
677
|
autoFocus: true,
|
|
576
|
-
required: true
|
|
678
|
+
required: true,
|
|
679
|
+
primaryColor
|
|
577
680
|
}
|
|
578
681
|
),
|
|
579
|
-
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
|
|
682
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, primaryColor, children: "Continue" })
|
|
580
683
|
] })
|
|
581
684
|
] }),
|
|
582
685
|
step === "password" && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-3", children: [
|
|
@@ -588,23 +691,50 @@ function SignIn({
|
|
|
588
691
|
value: password,
|
|
589
692
|
onChange: setPassword,
|
|
590
693
|
autoFocus: true,
|
|
591
|
-
required: true
|
|
694
|
+
required: true,
|
|
695
|
+
primaryColor
|
|
592
696
|
}
|
|
593
697
|
),
|
|
594
|
-
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Sign in" }),
|
|
698
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, primaryColor, children: "Sign in" }),
|
|
699
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
700
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" }),
|
|
701
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px]", style: { color: mutedColor }, children: "or" }),
|
|
702
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" })
|
|
703
|
+
] }),
|
|
704
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: handleMagicLinkRequest, mutedColor, children: "\u2709 Email me a sign-in link" }),
|
|
595
705
|
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
|
|
596
706
|
setStep("forgot");
|
|
597
707
|
setError(null);
|
|
598
|
-
}, children: "Forgot password?" }),
|
|
599
|
-
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
|
|
708
|
+
}, mutedColor, children: "Forgot password?" }),
|
|
709
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), mutedColor, children: "\u2190 Back" })
|
|
710
|
+
] }),
|
|
711
|
+
step === "magic-link-sent" && /* @__PURE__ */ jsxs("div", { className: "space-y-4 text-center", children: [
|
|
712
|
+
/* @__PURE__ */ jsx2(
|
|
713
|
+
"div",
|
|
714
|
+
{
|
|
715
|
+
className: "w-14 h-14 rounded-2xl flex items-center justify-center mx-auto border",
|
|
716
|
+
style: primaryColor ? { background: `${primaryColor}26`, borderColor: `${primaryColor}4d` } : { background: "rgba(168,85,247,0.15)", borderColor: "rgba(168,85,247,0.3)" },
|
|
717
|
+
children: /* @__PURE__ */ jsx2(EnvelopeIcon, { color: primaryColor ?? "#a78bfa" })
|
|
718
|
+
}
|
|
719
|
+
),
|
|
720
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm leading-relaxed", style: { color: mutedColor }, children: [
|
|
721
|
+
"The link expires in ",
|
|
722
|
+
/* @__PURE__ */ jsx2("span", { style: { color: textColor }, className: "font-medium", children: "15 minutes" }),
|
|
723
|
+
" and can only be used once."
|
|
724
|
+
] }),
|
|
725
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: handleMagicLinkRequest, mutedColor, children: "Resend link" }),
|
|
726
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
|
|
727
|
+
setStep("password");
|
|
728
|
+
setError(null);
|
|
729
|
+
}, mutedColor, children: "Use password instead" })
|
|
600
730
|
] }),
|
|
601
731
|
step === "passkey" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
602
|
-
/* @__PURE__ */ jsxs(PrimaryButton, { loading, onClick: handlePasskeySignIn, children: [
|
|
732
|
+
/* @__PURE__ */ jsxs(PrimaryButton, { loading, onClick: handlePasskeySignIn, primaryColor, children: [
|
|
603
733
|
/* @__PURE__ */ jsx2(FingerprintIcon, {}),
|
|
604
734
|
"Authenticate with passkey"
|
|
605
735
|
] }),
|
|
606
|
-
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), children: "Use password instead" }),
|
|
607
|
-
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
|
|
736
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), mutedColor, children: "Use password instead" }),
|
|
737
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), mutedColor, children: "\u2190 Back" })
|
|
608
738
|
] }),
|
|
609
739
|
step === "totp" && /* @__PURE__ */ jsxs("form", { onSubmit: handleTotpSubmit, className: "space-y-3", children: [
|
|
610
740
|
/* @__PURE__ */ jsx2(
|
|
@@ -619,11 +749,12 @@ function SignIn({
|
|
|
619
749
|
onChange: setTotp,
|
|
620
750
|
autoFocus: true,
|
|
621
751
|
required: true,
|
|
622
|
-
className: "text-center tracking-[0.4em] text-lg"
|
|
752
|
+
className: "text-center tracking-[0.4em] text-lg",
|
|
753
|
+
primaryColor
|
|
623
754
|
}
|
|
624
755
|
),
|
|
625
|
-
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Verify" }),
|
|
626
|
-
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), children: "\u2190 Back" })
|
|
756
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, primaryColor, children: "Verify" }),
|
|
757
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), mutedColor, children: "\u2190 Back" })
|
|
627
758
|
] }),
|
|
628
759
|
step === "forgot" && /* @__PURE__ */ jsxs("form", { onSubmit: handleForgotSubmit, className: "space-y-3", children: [
|
|
629
760
|
/* @__PURE__ */ jsx2(
|
|
@@ -634,14 +765,15 @@ function SignIn({
|
|
|
634
765
|
value: email,
|
|
635
766
|
onChange: setEmail,
|
|
636
767
|
autoFocus: true,
|
|
637
|
-
required: true
|
|
768
|
+
required: true,
|
|
769
|
+
primaryColor
|
|
638
770
|
}
|
|
639
771
|
),
|
|
640
|
-
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Send reset code" }),
|
|
772
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, primaryColor, children: "Send reset code" }),
|
|
641
773
|
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
|
|
642
774
|
setStep("password");
|
|
643
775
|
setError(null);
|
|
644
|
-
}, children: "\u2190 Back" })
|
|
776
|
+
}, mutedColor, children: "\u2190 Back" })
|
|
645
777
|
] }),
|
|
646
778
|
step === "forgot-verify" && /* @__PURE__ */ jsxs("form", { onSubmit: handleForgotVerifySubmit, className: "space-y-3", children: [
|
|
647
779
|
/* @__PURE__ */ jsx2(
|
|
@@ -656,10 +788,11 @@ function SignIn({
|
|
|
656
788
|
onChange: setForgotCode,
|
|
657
789
|
autoFocus: true,
|
|
658
790
|
required: true,
|
|
659
|
-
className: "text-center tracking-[0.4em] text-lg"
|
|
791
|
+
className: "text-center tracking-[0.4em] text-lg",
|
|
792
|
+
primaryColor
|
|
660
793
|
}
|
|
661
794
|
),
|
|
662
|
-
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
|
|
795
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, primaryColor, children: "Continue" })
|
|
663
796
|
] }),
|
|
664
797
|
step === "forgot-reset" && /* @__PURE__ */ jsxs("form", { onSubmit: handleResetPasswordSubmit, className: "space-y-3", children: [
|
|
665
798
|
/* @__PURE__ */ jsx2(
|
|
@@ -671,18 +804,19 @@ function SignIn({
|
|
|
671
804
|
onChange: setNewPassword,
|
|
672
805
|
autoFocus: true,
|
|
673
806
|
required: true,
|
|
674
|
-
minLength: 8
|
|
807
|
+
minLength: 8,
|
|
808
|
+
primaryColor
|
|
675
809
|
}
|
|
676
810
|
),
|
|
677
811
|
/* @__PURE__ */ jsx2(PasswordStrength, { password: newPassword }),
|
|
678
|
-
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Reset password" })
|
|
812
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, primaryColor, children: "Reset password" })
|
|
679
813
|
] })
|
|
680
814
|
] })
|
|
681
815
|
]
|
|
682
816
|
}
|
|
683
817
|
);
|
|
684
818
|
}
|
|
685
|
-
function Input({ onChange, className, ...props }) {
|
|
819
|
+
function Input({ onChange, className, primaryColor, ...props }) {
|
|
686
820
|
return /* @__PURE__ */ jsx2(
|
|
687
821
|
"input",
|
|
688
822
|
{
|
|
@@ -691,39 +825,48 @@ function Input({ onChange, className, ...props }) {
|
|
|
691
825
|
className: clsx(
|
|
692
826
|
"w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5",
|
|
693
827
|
"text-sm text-white/90 placeholder:text-[#475569]",
|
|
694
|
-
"focus:outline-none
|
|
828
|
+
"focus:outline-none transition-colors",
|
|
829
|
+
!primaryColor && "focus:border-purple-500/60",
|
|
695
830
|
className
|
|
696
|
-
)
|
|
831
|
+
),
|
|
832
|
+
style: primaryColor ? { "--vx-focus": primaryColor } : void 0,
|
|
833
|
+
onFocus: (e) => {
|
|
834
|
+
if (primaryColor) e.currentTarget.style.borderColor = `${primaryColor}99`;
|
|
835
|
+
props.onFocus?.(e);
|
|
836
|
+
},
|
|
837
|
+
onBlur: (e) => {
|
|
838
|
+
if (primaryColor) e.currentTarget.style.borderColor = "";
|
|
839
|
+
props.onBlur?.(e);
|
|
840
|
+
}
|
|
697
841
|
}
|
|
698
842
|
);
|
|
699
843
|
}
|
|
700
|
-
function PrimaryButton({ children, loading, onClick }) {
|
|
844
|
+
function PrimaryButton({ children, loading, onClick, primaryColor }) {
|
|
701
845
|
return /* @__PURE__ */ jsx2(
|
|
702
846
|
"button",
|
|
703
847
|
{
|
|
704
848
|
type: onClick ? "button" : "submit",
|
|
705
849
|
onClick,
|
|
706
850
|
disabled: loading,
|
|
851
|
+
style: primaryColor ? { background: primaryColor, boxShadow: `0 10px 15px -3px ${primaryColor}33` } : void 0,
|
|
707
852
|
className: clsx(
|
|
708
853
|
"w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl",
|
|
709
|
-
"text-sm font-semibold text-white",
|
|
710
|
-
"
|
|
711
|
-
"hover:from-purple-500 hover:to-blue-500"
|
|
712
|
-
"shadow-lg shadow-purple-500/20",
|
|
713
|
-
"transition-all duration-150",
|
|
714
|
-
"disabled:opacity-60 disabled:cursor-not-allowed"
|
|
854
|
+
"text-sm font-semibold text-white transition-all duration-150",
|
|
855
|
+
"disabled:opacity-60 disabled:cursor-not-allowed",
|
|
856
|
+
!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"
|
|
715
857
|
),
|
|
716
858
|
children: loading ? /* @__PURE__ */ jsx2(Spinner, {}) : children
|
|
717
859
|
}
|
|
718
860
|
);
|
|
719
861
|
}
|
|
720
|
-
function GhostButton({ children, onClick }) {
|
|
862
|
+
function GhostButton({ children, onClick, mutedColor }) {
|
|
721
863
|
return /* @__PURE__ */ jsx2(
|
|
722
864
|
"button",
|
|
723
865
|
{
|
|
724
866
|
type: "button",
|
|
725
867
|
onClick,
|
|
726
|
-
className: "w-full text-xs
|
|
868
|
+
className: "w-full text-xs transition-colors py-1 hover:opacity-80",
|
|
869
|
+
style: { color: mutedColor ?? "#475569" },
|
|
727
870
|
children
|
|
728
871
|
}
|
|
729
872
|
);
|
|
@@ -767,10 +910,10 @@ function GitHubButton({ onClick }) {
|
|
|
767
910
|
function XButton({ onClick }) {
|
|
768
911
|
return /* @__PURE__ */ jsx2(OAuthButton, { icon: /* @__PURE__ */ jsx2(XIcon, {}), label: "Continue with X", onClick });
|
|
769
912
|
}
|
|
770
|
-
function Divider() {
|
|
913
|
+
function Divider({ mutedColor }) {
|
|
771
914
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
772
915
|
/* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" }),
|
|
773
|
-
/* @__PURE__ */ jsx2("span", { className: "text-[10px]
|
|
916
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] uppercase tracking-wider", style: { color: mutedColor ?? "#475569" }, children: "or" }),
|
|
774
917
|
/* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" })
|
|
775
918
|
] });
|
|
776
919
|
}
|
|
@@ -840,10 +983,16 @@ function GitHubIcon() {
|
|
|
840
983
|
function XIcon() {
|
|
841
984
|
return /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx2("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.737-8.835L1.254 2.25H8.08l4.253 5.622 5.911-5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) });
|
|
842
985
|
}
|
|
986
|
+
function EnvelopeIcon({ color = "#a78bfa" }) {
|
|
987
|
+
return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
988
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
989
|
+
/* @__PURE__ */ jsx2("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
|
|
990
|
+
] });
|
|
991
|
+
}
|
|
843
992
|
|
|
844
993
|
// src/components/SignUp.tsx
|
|
845
994
|
import { clsx as clsx2 } from "clsx";
|
|
846
|
-
import { useState as
|
|
995
|
+
import { useState as useState4 } from "react";
|
|
847
996
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
848
997
|
function resolveApiOrigin3(prop) {
|
|
849
998
|
if (prop) return prop;
|
|
@@ -853,6 +1002,13 @@ function resolveApiOrigin3(prop) {
|
|
|
853
1002
|
}
|
|
854
1003
|
return "";
|
|
855
1004
|
}
|
|
1005
|
+
function resolvePk2() {
|
|
1006
|
+
if (typeof document !== "undefined") {
|
|
1007
|
+
const el = document.querySelector("[data-vaultix-pk]");
|
|
1008
|
+
if (el) return el.getAttribute("data-vaultix-pk") ?? "";
|
|
1009
|
+
}
|
|
1010
|
+
return "";
|
|
1011
|
+
}
|
|
856
1012
|
function resolveAfterSignUpUrl(redirectUrlProp) {
|
|
857
1013
|
if (redirectUrlProp) return redirectUrlProp;
|
|
858
1014
|
if (typeof document !== "undefined") {
|
|
@@ -870,16 +1026,22 @@ function SignUp({
|
|
|
870
1026
|
onSuccess,
|
|
871
1027
|
onError,
|
|
872
1028
|
apiOrigin: apiOriginProp,
|
|
873
|
-
className
|
|
1029
|
+
className,
|
|
1030
|
+
appearance
|
|
874
1031
|
}) {
|
|
875
1032
|
const apiOrigin = resolveApiOrigin3(apiOriginProp);
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
const [
|
|
882
|
-
const [
|
|
1033
|
+
const vars = appearance?.variables ?? {};
|
|
1034
|
+
const primaryColor = vars.colorPrimary;
|
|
1035
|
+
const cardBg = vars.colorBackground ?? "rgba(22,27,45,0.92)";
|
|
1036
|
+
const textColor = vars.colorText ?? "rgba(255,255,255,0.9)";
|
|
1037
|
+
const mutedColor = vars.colorTextMuted ?? "#475569";
|
|
1038
|
+
const [step, setStep] = useState4("email");
|
|
1039
|
+
const [email, setEmail] = useState4("");
|
|
1040
|
+
const [password, setPassword] = useState4("");
|
|
1041
|
+
const [verificationCode, setVerificationCode] = useState4("");
|
|
1042
|
+
const [error, setError] = useState4(null);
|
|
1043
|
+
const [loading, setLoading] = useState4(false);
|
|
1044
|
+
const [registrationId, setRegistrationId] = useState4(null);
|
|
883
1045
|
function setErr(msg) {
|
|
884
1046
|
setError(msg);
|
|
885
1047
|
onError?.(msg);
|
|
@@ -891,10 +1053,12 @@ function SignUp({
|
|
|
891
1053
|
url.searchParams.set("__vaultix_handshake", handshakeToken);
|
|
892
1054
|
window.location.href = url.toString();
|
|
893
1055
|
}
|
|
894
|
-
function
|
|
1056
|
+
function oauthRedirect(provider) {
|
|
895
1057
|
const target = resolveAfterSignUpUrl(redirectUrl);
|
|
1058
|
+
const pk = resolvePk2();
|
|
896
1059
|
const params = new URLSearchParams({ redirect_url: target });
|
|
897
|
-
|
|
1060
|
+
if (pk) params.set("pk", pk);
|
|
1061
|
+
window.location.href = `${apiOrigin}/api/v1/auth/oauth/${provider}?${params}`;
|
|
898
1062
|
}
|
|
899
1063
|
async function handleEmailPassword(e) {
|
|
900
1064
|
e.preventDefault();
|
|
@@ -965,48 +1129,61 @@ function SignUp({
|
|
|
965
1129
|
"w-full max-w-sm mx-auto rounded-2xl border border-white/8 shadow-2xl backdrop-blur-[16px]",
|
|
966
1130
|
className
|
|
967
1131
|
),
|
|
968
|
-
style: { background:
|
|
1132
|
+
style: { background: cardBg },
|
|
969
1133
|
children: [
|
|
970
1134
|
/* @__PURE__ */ jsxs2("div", { className: "px-8 pt-8 pb-6 text-center", children: [
|
|
971
|
-
/* @__PURE__ */ jsx3(
|
|
972
|
-
|
|
973
|
-
|
|
1135
|
+
/* @__PURE__ */ jsx3(
|
|
1136
|
+
"div",
|
|
1137
|
+
{
|
|
1138
|
+
className: "inline-flex items-center justify-center w-10 h-10 rounded-xl shadow-lg mb-4",
|
|
1139
|
+
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)" },
|
|
1140
|
+
children: /* @__PURE__ */ jsx3(SparkleIcon, {})
|
|
1141
|
+
}
|
|
1142
|
+
),
|
|
1143
|
+
/* @__PURE__ */ jsx3("h1", { className: "text-xl font-semibold", style: { color: textColor }, children: step === "verify" ? "Check your email" : "Create an account" }),
|
|
1144
|
+
/* @__PURE__ */ jsx3("p", { className: "text-sm mt-1", style: { color: mutedColor }, children: step === "verify" ? `We sent a code to ${email}` : "Start your free trial today" })
|
|
974
1145
|
] }),
|
|
975
1146
|
/* @__PURE__ */ jsxs2("div", { className: "px-8 pb-8 space-y-4", children: [
|
|
976
1147
|
error && /* @__PURE__ */ jsx3("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2", children: /* @__PURE__ */ jsx3("p", { className: "text-xs text-red-400", children: error }) }),
|
|
977
1148
|
step === "email" && /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
|
|
978
|
-
/* @__PURE__ */ jsx3(
|
|
979
|
-
/* @__PURE__ */ jsx3(
|
|
1149
|
+
/* @__PURE__ */ jsx3(OAuthButton2, { icon: /* @__PURE__ */ jsx3(GoogleIcon2, {}), label: "Continue with Google", onClick: () => oauthRedirect("google") }),
|
|
1150
|
+
/* @__PURE__ */ jsx3(OAuthButton2, { icon: /* @__PURE__ */ jsx3(GitHubIcon2, {}), label: "Continue with GitHub", onClick: () => oauthRedirect("github") }),
|
|
1151
|
+
/* @__PURE__ */ jsx3(OAuthButton2, { icon: /* @__PURE__ */ jsx3(MetaIcon2, {}), label: "Continue with Facebook", onClick: () => oauthRedirect("meta") }),
|
|
1152
|
+
/* @__PURE__ */ jsx3(OAuthButton2, { icon: /* @__PURE__ */ jsx3(LinkedInIcon2, {}), label: "Continue with LinkedIn", onClick: () => oauthRedirect("linkedin") }),
|
|
1153
|
+
/* @__PURE__ */ jsx3(OAuthButton2, { icon: /* @__PURE__ */ jsx3(XIcon2, {}), label: "Continue with X", onClick: () => oauthRedirect("x") }),
|
|
1154
|
+
/* @__PURE__ */ jsx3(Divider2, { mutedColor }),
|
|
980
1155
|
/* @__PURE__ */ jsxs2("form", { onSubmit: handleEmailPassword, className: "space-y-3", children: [
|
|
981
1156
|
/* @__PURE__ */ jsx3(
|
|
982
|
-
|
|
1157
|
+
Input2,
|
|
983
1158
|
{
|
|
984
1159
|
type: "email",
|
|
985
1160
|
placeholder: "you@company.com",
|
|
986
1161
|
value: email,
|
|
987
1162
|
onChange: setEmail,
|
|
988
1163
|
autoFocus: true,
|
|
989
|
-
required: true
|
|
1164
|
+
required: true,
|
|
1165
|
+
primaryColor
|
|
990
1166
|
}
|
|
991
1167
|
),
|
|
992
1168
|
/* @__PURE__ */ jsx3(
|
|
993
|
-
|
|
1169
|
+
Input2,
|
|
994
1170
|
{
|
|
995
1171
|
type: "password",
|
|
996
1172
|
placeholder: "Create a password",
|
|
997
1173
|
value: password,
|
|
998
1174
|
onChange: setPassword,
|
|
999
1175
|
required: true,
|
|
1000
|
-
minLength: 8
|
|
1176
|
+
minLength: 8,
|
|
1177
|
+
primaryColor
|
|
1001
1178
|
}
|
|
1002
1179
|
),
|
|
1003
|
-
/* @__PURE__ */ jsx3(PasswordStrength2, { password }),
|
|
1004
|
-
/* @__PURE__ */ jsx3(
|
|
1180
|
+
/* @__PURE__ */ jsx3(PasswordStrength2, { password, primaryColor }),
|
|
1181
|
+
/* @__PURE__ */ jsx3(PrimaryButton2, { loading, primaryColor, children: "Create account" })
|
|
1005
1182
|
] })
|
|
1006
1183
|
] }),
|
|
1007
1184
|
step === "verify" && /* @__PURE__ */ jsxs2("form", { onSubmit: handleVerification, className: "space-y-3", children: [
|
|
1008
1185
|
/* @__PURE__ */ jsx3(
|
|
1009
|
-
|
|
1186
|
+
Input2,
|
|
1010
1187
|
{
|
|
1011
1188
|
type: "text",
|
|
1012
1189
|
inputMode: "numeric",
|
|
@@ -1017,16 +1194,18 @@ function SignUp({
|
|
|
1017
1194
|
onChange: setVerificationCode,
|
|
1018
1195
|
autoFocus: true,
|
|
1019
1196
|
required: true,
|
|
1020
|
-
className: "text-center tracking-[0.4em] text-lg"
|
|
1197
|
+
className: "text-center tracking-[0.4em] text-lg",
|
|
1198
|
+
primaryColor
|
|
1021
1199
|
}
|
|
1022
1200
|
),
|
|
1023
|
-
/* @__PURE__ */ jsx3(
|
|
1201
|
+
/* @__PURE__ */ jsx3(PrimaryButton2, { loading, primaryColor, children: "Verify email" }),
|
|
1024
1202
|
/* @__PURE__ */ jsx3(
|
|
1025
1203
|
"button",
|
|
1026
1204
|
{
|
|
1027
1205
|
type: "button",
|
|
1028
1206
|
onClick: resendCode,
|
|
1029
|
-
className: "w-full text-xs
|
|
1207
|
+
className: "w-full text-xs transition-colors py-1 hover:opacity-80",
|
|
1208
|
+
style: { color: mutedColor },
|
|
1030
1209
|
children: "Resend code"
|
|
1031
1210
|
}
|
|
1032
1211
|
)
|
|
@@ -1036,7 +1215,7 @@ function SignUp({
|
|
|
1036
1215
|
}
|
|
1037
1216
|
);
|
|
1038
1217
|
}
|
|
1039
|
-
function
|
|
1218
|
+
function Input2({ onChange, className, primaryColor, ...props }) {
|
|
1040
1219
|
return /* @__PURE__ */ jsx3(
|
|
1041
1220
|
"input",
|
|
1042
1221
|
{
|
|
@@ -1045,34 +1224,47 @@ function SignUpInput({ onChange, className, ...props }) {
|
|
|
1045
1224
|
className: clsx2(
|
|
1046
1225
|
"w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5",
|
|
1047
1226
|
"text-sm text-white/90 placeholder:text-[#475569]",
|
|
1048
|
-
"focus:outline-none
|
|
1227
|
+
"focus:outline-none transition-colors",
|
|
1228
|
+
!primaryColor && "focus:border-emerald-500/60",
|
|
1049
1229
|
className
|
|
1050
|
-
)
|
|
1230
|
+
),
|
|
1231
|
+
onFocus: (e) => {
|
|
1232
|
+
if (primaryColor) e.currentTarget.style.borderColor = `${primaryColor}99`;
|
|
1233
|
+
props.onFocus?.(e);
|
|
1234
|
+
},
|
|
1235
|
+
onBlur: (e) => {
|
|
1236
|
+
if (primaryColor) e.currentTarget.style.borderColor = "";
|
|
1237
|
+
props.onBlur?.(e);
|
|
1238
|
+
}
|
|
1051
1239
|
}
|
|
1052
1240
|
);
|
|
1053
1241
|
}
|
|
1054
|
-
function
|
|
1242
|
+
function PrimaryButton2({
|
|
1055
1243
|
children,
|
|
1056
|
-
loading
|
|
1244
|
+
loading,
|
|
1245
|
+
primaryColor
|
|
1057
1246
|
}) {
|
|
1058
1247
|
return /* @__PURE__ */ jsx3(
|
|
1059
1248
|
"button",
|
|
1060
1249
|
{
|
|
1061
1250
|
type: "submit",
|
|
1062
1251
|
disabled: loading,
|
|
1252
|
+
style: primaryColor ? { background: primaryColor, boxShadow: `0 10px 15px -3px ${primaryColor}33` } : void 0,
|
|
1063
1253
|
className: clsx2(
|
|
1064
1254
|
"w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl",
|
|
1065
|
-
"text-sm font-semibold text-white",
|
|
1066
|
-
"
|
|
1067
|
-
"hover:from-emerald-500 hover:to-teal-500"
|
|
1068
|
-
"shadow-lg shadow-emerald-500/20 transition-all duration-150",
|
|
1069
|
-
"disabled:opacity-60 disabled:cursor-not-allowed"
|
|
1255
|
+
"text-sm font-semibold text-white transition-all duration-150",
|
|
1256
|
+
"disabled:opacity-60 disabled:cursor-not-allowed",
|
|
1257
|
+
!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"
|
|
1070
1258
|
),
|
|
1071
1259
|
children: loading ? /* @__PURE__ */ jsx3(Spinner2, {}) : children
|
|
1072
1260
|
}
|
|
1073
1261
|
);
|
|
1074
1262
|
}
|
|
1075
|
-
function
|
|
1263
|
+
function OAuthButton2({
|
|
1264
|
+
icon,
|
|
1265
|
+
label,
|
|
1266
|
+
onClick
|
|
1267
|
+
}) {
|
|
1076
1268
|
return /* @__PURE__ */ jsxs2(
|
|
1077
1269
|
"button",
|
|
1078
1270
|
{
|
|
@@ -1086,20 +1278,20 @@ function GoogleButton2({ onClick }) {
|
|
|
1086
1278
|
"transition-all duration-150"
|
|
1087
1279
|
),
|
|
1088
1280
|
children: [
|
|
1089
|
-
|
|
1090
|
-
|
|
1281
|
+
icon,
|
|
1282
|
+
label
|
|
1091
1283
|
]
|
|
1092
1284
|
}
|
|
1093
1285
|
);
|
|
1094
1286
|
}
|
|
1095
|
-
function Divider2() {
|
|
1287
|
+
function Divider2({ mutedColor }) {
|
|
1096
1288
|
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
|
|
1097
1289
|
/* @__PURE__ */ jsx3("div", { className: "flex-1 h-px bg-white/8" }),
|
|
1098
|
-
/* @__PURE__ */ jsx3("span", { className: "text-[10px]
|
|
1290
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] uppercase tracking-wider", style: { color: mutedColor ?? "#475569" }, children: "or" }),
|
|
1099
1291
|
/* @__PURE__ */ jsx3("div", { className: "flex-1 h-px bg-white/8" })
|
|
1100
1292
|
] });
|
|
1101
1293
|
}
|
|
1102
|
-
function PasswordStrength2({ password }) {
|
|
1294
|
+
function PasswordStrength2({ password, primaryColor }) {
|
|
1103
1295
|
const score = (() => {
|
|
1104
1296
|
if (password.length === 0) return 0;
|
|
1105
1297
|
let s = 0;
|
|
@@ -1111,15 +1303,17 @@ function PasswordStrength2({ password }) {
|
|
|
1111
1303
|
})();
|
|
1112
1304
|
if (password.length === 0) return null;
|
|
1113
1305
|
const labels = ["", "Weak", "Fair", "Good", "Strong"];
|
|
1114
|
-
const
|
|
1306
|
+
const tailwindColors = ["", "bg-red-500", "bg-amber-400", "bg-blue-400", "bg-emerald-400"];
|
|
1115
1307
|
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
1116
1308
|
/* @__PURE__ */ jsx3("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx3(
|
|
1117
1309
|
"div",
|
|
1118
1310
|
{
|
|
1119
1311
|
className: clsx2(
|
|
1120
1312
|
"flex-1 h-0.5 rounded-full transition-all duration-300",
|
|
1121
|
-
i <= score ?
|
|
1122
|
-
|
|
1313
|
+
i <= score && !primaryColor ? tailwindColors[score] : void 0,
|
|
1314
|
+
i > score || !primaryColor && i <= score ? void 0 : void 0
|
|
1315
|
+
),
|
|
1316
|
+
style: i <= score && primaryColor ? { background: score >= 4 ? primaryColor : void 0 } : { background: i <= score ? void 0 : "rgba(255,255,255,0.1)" }
|
|
1123
1317
|
},
|
|
1124
1318
|
i
|
|
1125
1319
|
)) }),
|
|
@@ -1143,6 +1337,18 @@ function GoogleIcon2() {
|
|
|
1143
1337
|
/* @__PURE__ */ jsx3("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
|
|
1144
1338
|
] });
|
|
1145
1339
|
}
|
|
1340
|
+
function MetaIcon2() {
|
|
1341
|
+
return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "#1877F2", children: /* @__PURE__ */ jsx3("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" }) });
|
|
1342
|
+
}
|
|
1343
|
+
function LinkedInIcon2() {
|
|
1344
|
+
return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "#0A66C2", children: /* @__PURE__ */ jsx3("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" }) });
|
|
1345
|
+
}
|
|
1346
|
+
function GitHubIcon2() {
|
|
1347
|
+
return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx3("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" }) });
|
|
1348
|
+
}
|
|
1349
|
+
function XIcon2() {
|
|
1350
|
+
return /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", children: /* @__PURE__ */ jsx3("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" }) });
|
|
1351
|
+
}
|
|
1146
1352
|
|
|
1147
1353
|
// src/components/SignedIn.tsx
|
|
1148
1354
|
import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
|
|
@@ -1158,10 +1364,10 @@ function SignedOut({ children }) {
|
|
|
1158
1364
|
}
|
|
1159
1365
|
|
|
1160
1366
|
// src/components/RedirectComponents.tsx
|
|
1161
|
-
import { useEffect as
|
|
1367
|
+
import { useEffect as useEffect3 } from "react";
|
|
1162
1368
|
function RedirectToSignIn({ redirectUrl = "/sign-in" }) {
|
|
1163
1369
|
const { isLoaded, isSignedIn } = useVaultixContext();
|
|
1164
|
-
|
|
1370
|
+
useEffect3(() => {
|
|
1165
1371
|
if (isLoaded && !isSignedIn) {
|
|
1166
1372
|
window.location.href = redirectUrl;
|
|
1167
1373
|
}
|
|
@@ -1170,7 +1376,7 @@ function RedirectToSignIn({ redirectUrl = "/sign-in" }) {
|
|
|
1170
1376
|
}
|
|
1171
1377
|
function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
|
|
1172
1378
|
const { isLoaded, isSignedIn } = useVaultixContext();
|
|
1173
|
-
|
|
1379
|
+
useEffect3(() => {
|
|
1174
1380
|
if (isLoaded && !isSignedIn) {
|
|
1175
1381
|
window.location.href = redirectUrl;
|
|
1176
1382
|
}
|
|
@@ -1180,22 +1386,17 @@ function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
|
|
|
1180
1386
|
|
|
1181
1387
|
// src/components/UserButton.tsx
|
|
1182
1388
|
import { clsx as clsx3 } from "clsx";
|
|
1183
|
-
import { useEffect as
|
|
1184
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1185
|
-
function UserButton({
|
|
1186
|
-
showName = false,
|
|
1187
|
-
afterSignOutUrl,
|
|
1188
|
-
className
|
|
1189
|
-
}) {
|
|
1389
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
|
|
1390
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1391
|
+
function UserButton({ showName = false, afterSignOutUrl, className }) {
|
|
1190
1392
|
const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
|
|
1191
|
-
const [open, setOpen] =
|
|
1192
|
-
const [signingOut, setSigningOut] =
|
|
1393
|
+
const [open, setOpen] = useState5(false);
|
|
1394
|
+
const [signingOut, setSigningOut] = useState5(false);
|
|
1395
|
+
const [showProfile, setShowProfile] = useState5(false);
|
|
1193
1396
|
const containerRef = useRef3(null);
|
|
1194
|
-
|
|
1397
|
+
useEffect4(() => {
|
|
1195
1398
|
function onPointerDown(e) {
|
|
1196
|
-
if (containerRef.current && !containerRef.current.contains(e.target))
|
|
1197
|
-
setOpen(false);
|
|
1198
|
-
}
|
|
1399
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
|
|
1199
1400
|
}
|
|
1200
1401
|
document.addEventListener("pointerdown", onPointerDown);
|
|
1201
1402
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
@@ -1208,98 +1409,456 @@ function UserButton({
|
|
|
1208
1409
|
await signOut();
|
|
1209
1410
|
if (afterSignOutUrl) window.location.href = afterSignOutUrl;
|
|
1210
1411
|
}
|
|
1211
|
-
return /* @__PURE__ */ jsxs3(
|
|
1212
|
-
/* @__PURE__ */ jsxs3(
|
|
1412
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1413
|
+
/* @__PURE__ */ jsxs3("div", { ref: containerRef, className: clsx3("relative", className), children: [
|
|
1414
|
+
/* @__PURE__ */ jsxs3(
|
|
1415
|
+
"button",
|
|
1416
|
+
{
|
|
1417
|
+
onClick: () => setOpen((o) => !o),
|
|
1418
|
+
className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
|
|
1419
|
+
children: [
|
|
1420
|
+
/* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl }),
|
|
1421
|
+
showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
|
|
1422
|
+
]
|
|
1423
|
+
}
|
|
1424
|
+
),
|
|
1425
|
+
open && /* @__PURE__ */ jsxs3(
|
|
1426
|
+
"div",
|
|
1427
|
+
{
|
|
1428
|
+
className: clsx3("absolute right-0 mt-2 w-64 rounded-2xl border border-white/8", "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"),
|
|
1429
|
+
style: { background: "rgba(22,27,45,0.96)" },
|
|
1430
|
+
children: [
|
|
1431
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
|
|
1432
|
+
/* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
|
|
1433
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
|
|
1434
|
+
(user.firstName || user.lastName) && /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
|
|
1435
|
+
/* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] truncate", children: user.email })
|
|
1436
|
+
] })
|
|
1437
|
+
] }),
|
|
1438
|
+
session && /* @__PURE__ */ jsx5("div", { className: "px-4 py-2 border-b border-white/8", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
1439
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
|
|
1440
|
+
/* @__PURE__ */ jsx5(RiskBadge, { level: session.riskLevel })
|
|
1441
|
+
] }) }),
|
|
1442
|
+
/* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
|
|
1443
|
+
/* @__PURE__ */ jsx5(DropdownItem, { label: "Manage account", icon: /* @__PURE__ */ jsx5(UserIcon, {}), onClick: () => {
|
|
1444
|
+
setOpen(false);
|
|
1445
|
+
setShowProfile(true);
|
|
1446
|
+
} }),
|
|
1447
|
+
/* @__PURE__ */ jsx5("div", { className: "h-px bg-white/8 my-1" }),
|
|
1448
|
+
/* @__PURE__ */ jsx5(DropdownItem, { label: signingOut ? "Signing out\u2026" : "Sign out", icon: /* @__PURE__ */ jsx5(SignOutIcon, {}), onClick: handleSignOut, destructive: true })
|
|
1449
|
+
] })
|
|
1450
|
+
]
|
|
1451
|
+
}
|
|
1452
|
+
)
|
|
1453
|
+
] }),
|
|
1454
|
+
showProfile && /* @__PURE__ */ jsx5(ProfileModal, { onClose: () => setShowProfile(false) })
|
|
1455
|
+
] });
|
|
1456
|
+
}
|
|
1457
|
+
function ProfileModal({ onClose }) {
|
|
1458
|
+
const { user, updateUser } = useVaultixContext();
|
|
1459
|
+
const [tab, setTab] = useState5("profile");
|
|
1460
|
+
const [firstName, setFirstName] = useState5(user?.firstName ?? "");
|
|
1461
|
+
const [lastName, setLastName] = useState5(user?.lastName ?? "");
|
|
1462
|
+
const [imageUrl, setImageUrl] = useState5(user?.imageUrl ?? "");
|
|
1463
|
+
const [currentPassword, setCurrentPassword] = useState5("");
|
|
1464
|
+
const [newPassword, setNewPassword] = useState5("");
|
|
1465
|
+
const [confirmPassword, setConfirmPassword] = useState5("");
|
|
1466
|
+
const [saving, setSaving] = useState5(false);
|
|
1467
|
+
const [error, setError] = useState5(null);
|
|
1468
|
+
const [success, setSuccess] = useState5(null);
|
|
1469
|
+
useEffect4(() => {
|
|
1470
|
+
function onKey(e) {
|
|
1471
|
+
if (e.key === "Escape") onClose();
|
|
1472
|
+
}
|
|
1473
|
+
document.addEventListener("keydown", onKey);
|
|
1474
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
1475
|
+
}, [onClose]);
|
|
1476
|
+
async function saveProfile(e) {
|
|
1477
|
+
e.preventDefault();
|
|
1478
|
+
setError(null);
|
|
1479
|
+
setSuccess(null);
|
|
1480
|
+
setSaving(true);
|
|
1481
|
+
try {
|
|
1482
|
+
await updateUser({ firstName: firstName.trim() || null, lastName: lastName.trim() || null, imageUrl: imageUrl.trim() || null });
|
|
1483
|
+
setSuccess("Profile updated.");
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
setError(err instanceof Error ? err.message : "Update failed");
|
|
1486
|
+
} finally {
|
|
1487
|
+
setSaving(false);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
async function savePassword(e) {
|
|
1491
|
+
e.preventDefault();
|
|
1492
|
+
setError(null);
|
|
1493
|
+
setSuccess(null);
|
|
1494
|
+
if (newPassword !== confirmPassword) {
|
|
1495
|
+
setError("Passwords do not match.");
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (newPassword.length < 8) {
|
|
1499
|
+
setError("Password must be at least 8 characters.");
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
setSaving(true);
|
|
1503
|
+
try {
|
|
1504
|
+
await updateUser({ currentPassword, newPassword });
|
|
1505
|
+
setSuccess("Password changed.");
|
|
1506
|
+
setCurrentPassword("");
|
|
1507
|
+
setNewPassword("");
|
|
1508
|
+
setConfirmPassword("");
|
|
1509
|
+
} catch (err) {
|
|
1510
|
+
setError(err instanceof Error ? err.message : "Update failed");
|
|
1511
|
+
} finally {
|
|
1512
|
+
setSaving(false);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
const TABS = [
|
|
1516
|
+
{ id: "profile", label: "Profile" },
|
|
1517
|
+
{ id: "password", label: "Change password" },
|
|
1518
|
+
{ id: "security", label: "Security" }
|
|
1519
|
+
];
|
|
1520
|
+
return /* @__PURE__ */ jsx5(
|
|
1521
|
+
"div",
|
|
1522
|
+
{
|
|
1523
|
+
className: "fixed inset-0 z-[9999] flex items-center justify-center p-4",
|
|
1524
|
+
style: { background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" },
|
|
1525
|
+
onPointerDown: (e) => {
|
|
1526
|
+
if (e.target === e.currentTarget) onClose();
|
|
1527
|
+
},
|
|
1528
|
+
children: /* @__PURE__ */ jsxs3("div", { className: "w-full max-w-md rounded-2xl border border-white/8 shadow-2xl", style: { background: "rgba(22,27,45,0.98)" }, children: [
|
|
1529
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/8", children: [
|
|
1530
|
+
/* @__PURE__ */ jsx5("h2", { className: "text-sm font-semibold text-white/90", children: "Manage account" }),
|
|
1531
|
+
/* @__PURE__ */ jsx5("button", { onClick: onClose, className: "text-[#475569] hover:text-white transition-colors", children: /* @__PURE__ */ jsx5(CloseIcon, {}) })
|
|
1532
|
+
] }),
|
|
1533
|
+
/* @__PURE__ */ jsx5("div", { className: "flex gap-1 px-6 pt-4 flex-wrap", children: TABS.map(({ id, label }) => /* @__PURE__ */ jsx5(
|
|
1534
|
+
"button",
|
|
1535
|
+
{
|
|
1536
|
+
onClick: () => {
|
|
1537
|
+
setTab(id);
|
|
1538
|
+
setError(null);
|
|
1539
|
+
setSuccess(null);
|
|
1540
|
+
},
|
|
1541
|
+
className: clsx3(
|
|
1542
|
+
"px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
|
|
1543
|
+
tab === id ? "bg-purple-500/15 text-purple-300 border border-purple-500/30" : "text-[#475569] hover:text-white/70"
|
|
1544
|
+
),
|
|
1545
|
+
children: label
|
|
1546
|
+
},
|
|
1547
|
+
id
|
|
1548
|
+
)) }),
|
|
1549
|
+
/* @__PURE__ */ jsxs3("div", { className: "px-6 pt-3", children: [
|
|
1550
|
+
error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
|
|
1551
|
+
success && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-emerald-500/10 border border-emerald-500/30 px-3 py-2 text-xs text-emerald-400", children: success })
|
|
1552
|
+
] }),
|
|
1553
|
+
tab === "profile" && /* @__PURE__ */ jsxs3("form", { onSubmit: saveProfile, className: "px-6 py-4 space-y-4", children: [
|
|
1554
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1555
|
+
/* @__PURE__ */ jsx5(ModalField, { label: "First name", value: firstName, onChange: setFirstName, placeholder: "Jane" }),
|
|
1556
|
+
/* @__PURE__ */ jsx5(ModalField, { label: "Last name", value: lastName, onChange: setLastName, placeholder: "Smith" })
|
|
1557
|
+
] }),
|
|
1558
|
+
/* @__PURE__ */ jsx5(ModalField, { label: "Avatar URL", value: imageUrl, onChange: setImageUrl, placeholder: "https://\u2026", type: "url" }),
|
|
1559
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
1560
|
+
/* @__PURE__ */ jsx5(SaveButton, { saving }),
|
|
1561
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
|
|
1562
|
+
] })
|
|
1563
|
+
] }),
|
|
1564
|
+
tab === "password" && /* @__PURE__ */ jsxs3("form", { onSubmit: savePassword, className: "px-6 py-4 space-y-4", children: [
|
|
1565
|
+
/* @__PURE__ */ jsx5(ModalField, { label: "Current password", value: currentPassword, onChange: setCurrentPassword, type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }),
|
|
1566
|
+
/* @__PURE__ */ jsx5(ModalField, { label: "New password", value: newPassword, onChange: setNewPassword, type: "password", placeholder: "Min 8 characters" }),
|
|
1567
|
+
/* @__PURE__ */ jsx5(ModalField, { label: "Confirm new password", value: confirmPassword, onChange: setConfirmPassword, type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }),
|
|
1568
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
1569
|
+
/* @__PURE__ */ jsx5(SaveButton, { saving, label: "Change password" }),
|
|
1570
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: onClose, className: "text-sm text-[#475569] hover:text-white/70 transition-colors", children: "Cancel" })
|
|
1571
|
+
] })
|
|
1572
|
+
] }),
|
|
1573
|
+
tab === "security" && /* @__PURE__ */ jsx5(SecurityTab, {})
|
|
1574
|
+
] })
|
|
1575
|
+
}
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
function SecurityTab() {
|
|
1579
|
+
const { apiOrigin } = useVaultixContext();
|
|
1580
|
+
const [totpEnabled, setTotpEnabled] = useState5(null);
|
|
1581
|
+
const [backupCodesLeft, setBackupCodesLeft] = useState5(null);
|
|
1582
|
+
const [enrollStep, setEnrollStep] = useState5("idle");
|
|
1583
|
+
const [qrDataUrl, setQrDataUrl] = useState5("");
|
|
1584
|
+
const [manualKey, setManualKey] = useState5("");
|
|
1585
|
+
const [enrollCode, setEnrollCode] = useState5("");
|
|
1586
|
+
const [disableCode, setDisableCode] = useState5("");
|
|
1587
|
+
const [backupCodes, setBackupCodes] = useState5([]);
|
|
1588
|
+
const [loading, setLoading] = useState5(false);
|
|
1589
|
+
const [error, setError] = useState5(null);
|
|
1590
|
+
const [showDisable, setShowDisable] = useState5(false);
|
|
1591
|
+
const fetchStatus = useCallback3(async () => {
|
|
1592
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp/status`, { credentials: "include" });
|
|
1593
|
+
if (res.ok) {
|
|
1594
|
+
const d = await res.json();
|
|
1595
|
+
setTotpEnabled(d.enabled);
|
|
1596
|
+
setBackupCodesLeft(d.backup_codes_remaining);
|
|
1597
|
+
}
|
|
1598
|
+
}, [apiOrigin]);
|
|
1599
|
+
useEffect4(() => {
|
|
1600
|
+
void fetchStatus();
|
|
1601
|
+
}, [fetchStatus]);
|
|
1602
|
+
async function startEnroll() {
|
|
1603
|
+
setError(null);
|
|
1604
|
+
setLoading(true);
|
|
1605
|
+
try {
|
|
1606
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp/enroll`, { method: "POST", credentials: "include" });
|
|
1607
|
+
const d = await res.json();
|
|
1608
|
+
if (!res.ok) {
|
|
1609
|
+
setError(d.error ?? "Failed to start enrollment.");
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
setQrDataUrl(d.qr_data_url);
|
|
1613
|
+
setManualKey(d.manual_entry_key);
|
|
1614
|
+
setEnrollStep("qr");
|
|
1615
|
+
} finally {
|
|
1616
|
+
setLoading(false);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
async function verifyEnroll(e) {
|
|
1620
|
+
e.preventDefault();
|
|
1621
|
+
setError(null);
|
|
1622
|
+
setLoading(true);
|
|
1623
|
+
try {
|
|
1624
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp/verify-enroll`, {
|
|
1625
|
+
method: "POST",
|
|
1626
|
+
credentials: "include",
|
|
1627
|
+
headers: { "Content-Type": "application/json" },
|
|
1628
|
+
body: JSON.stringify({ code: enrollCode })
|
|
1629
|
+
});
|
|
1630
|
+
const d = await res.json();
|
|
1631
|
+
if (!res.ok) {
|
|
1632
|
+
setError(d.error ?? "Invalid code. Try again.");
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
setBackupCodes(d.backup_codes);
|
|
1636
|
+
setEnrollStep("backup-codes");
|
|
1637
|
+
setTotpEnabled(true);
|
|
1638
|
+
setBackupCodesLeft(d.backup_codes.length);
|
|
1639
|
+
} finally {
|
|
1640
|
+
setLoading(false);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
async function disableTotp(e) {
|
|
1644
|
+
e.preventDefault();
|
|
1645
|
+
setError(null);
|
|
1646
|
+
setLoading(true);
|
|
1647
|
+
try {
|
|
1648
|
+
const res = await fetch(`${apiOrigin}/api/v1/auth/totp`, {
|
|
1649
|
+
method: "DELETE",
|
|
1650
|
+
credentials: "include",
|
|
1651
|
+
headers: { "Content-Type": "application/json" },
|
|
1652
|
+
body: JSON.stringify({ code: disableCode })
|
|
1653
|
+
});
|
|
1654
|
+
const d = await res.json();
|
|
1655
|
+
if (!res.ok) {
|
|
1656
|
+
setError(d.error ?? "Invalid code.");
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
setTotpEnabled(false);
|
|
1660
|
+
setBackupCodesLeft(null);
|
|
1661
|
+
setShowDisable(false);
|
|
1662
|
+
setDisableCode("");
|
|
1663
|
+
} finally {
|
|
1664
|
+
setLoading(false);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (totpEnabled === null) {
|
|
1668
|
+
return /* @__PURE__ */ jsx5("div", { className: "px-6 py-8 text-center text-[#475569] text-sm", children: "Loading\u2026" });
|
|
1669
|
+
}
|
|
1670
|
+
if (enrollStep === "backup-codes") {
|
|
1671
|
+
return /* @__PURE__ */ jsxs3("div", { className: "px-6 py-4 space-y-4", children: [
|
|
1672
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-xl bg-emerald-500/10 border border-emerald-500/30 px-4 py-3", children: [
|
|
1673
|
+
/* @__PURE__ */ jsx5("p", { className: "text-xs font-semibold text-emerald-400 mb-1", children: "Authenticator app enabled!" }),
|
|
1674
|
+
/* @__PURE__ */ jsx5("p", { className: "text-[11px] text-emerald-300/70", children: "Save these backup codes somewhere safe. Each can be used once if you lose your phone." })
|
|
1675
|
+
] }),
|
|
1676
|
+
/* @__PURE__ */ jsx5("div", { className: "grid grid-cols-2 gap-1.5", children: backupCodes.map((c) => /* @__PURE__ */ jsx5("span", { className: "font-mono text-xs bg-white/5 border border-white/8 rounded-lg px-3 py-1.5 text-white/80 text-center", children: c }, c)) }),
|
|
1677
|
+
/* @__PURE__ */ jsx5(
|
|
1678
|
+
"button",
|
|
1679
|
+
{
|
|
1680
|
+
onClick: () => {
|
|
1681
|
+
const t = backupCodes.join("\n");
|
|
1682
|
+
navigator.clipboard.writeText(t).catch(() => {
|
|
1683
|
+
});
|
|
1684
|
+
},
|
|
1685
|
+
className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1 border border-white/8 rounded-lg",
|
|
1686
|
+
children: "Copy all codes"
|
|
1687
|
+
}
|
|
1688
|
+
),
|
|
1689
|
+
/* @__PURE__ */ jsx5(
|
|
1690
|
+
"button",
|
|
1691
|
+
{
|
|
1692
|
+
onClick: () => setEnrollStep("idle"),
|
|
1693
|
+
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",
|
|
1694
|
+
children: "Done"
|
|
1695
|
+
}
|
|
1696
|
+
)
|
|
1697
|
+
] });
|
|
1698
|
+
}
|
|
1699
|
+
if (enrollStep === "qr") {
|
|
1700
|
+
return /* @__PURE__ */ jsxs3("div", { className: "px-6 py-4 space-y-4", children: [
|
|
1701
|
+
/* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569]", children: "Scan this QR code with Google Authenticator, Authy, or any TOTP app." }),
|
|
1702
|
+
qrDataUrl && /* @__PURE__ */ jsx5("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx5("img", { src: qrDataUrl, alt: "TOTP QR code", className: "rounded-xl", width: 180, height: 180 }) }),
|
|
1703
|
+
/* @__PURE__ */ jsxs3("details", { className: "text-[11px] text-[#475569]", children: [
|
|
1704
|
+
/* @__PURE__ */ jsx5("summary", { className: "cursor-pointer hover:text-white/60", children: "Can't scan? Enter manually" }),
|
|
1705
|
+
/* @__PURE__ */ jsx5("p", { className: "mt-1 font-mono break-all bg-white/5 rounded px-2 py-1 text-white/60 select-all", children: manualKey })
|
|
1706
|
+
] }),
|
|
1707
|
+
error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
|
|
1708
|
+
/* @__PURE__ */ jsxs3("form", { onSubmit: verifyEnroll, className: "space-y-3", children: [
|
|
1709
|
+
/* @__PURE__ */ jsx5(
|
|
1710
|
+
"input",
|
|
1711
|
+
{
|
|
1712
|
+
type: "text",
|
|
1713
|
+
inputMode: "numeric",
|
|
1714
|
+
pattern: "[0-9]{6}",
|
|
1715
|
+
maxLength: 6,
|
|
1716
|
+
placeholder: "000000",
|
|
1717
|
+
value: enrollCode,
|
|
1718
|
+
onChange: (e) => setEnrollCode(e.target.value),
|
|
1719
|
+
autoFocus: true,
|
|
1720
|
+
required: true,
|
|
1721
|
+
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]"
|
|
1722
|
+
}
|
|
1723
|
+
),
|
|
1724
|
+
/* @__PURE__ */ jsx5(
|
|
1725
|
+
"button",
|
|
1726
|
+
{
|
|
1727
|
+
type: "submit",
|
|
1728
|
+
disabled: loading || enrollCode.length !== 6,
|
|
1729
|
+
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",
|
|
1730
|
+
children: loading ? "Verifying\u2026" : "Verify and enable"
|
|
1731
|
+
}
|
|
1732
|
+
),
|
|
1733
|
+
/* @__PURE__ */ jsx5(
|
|
1734
|
+
"button",
|
|
1735
|
+
{
|
|
1736
|
+
type: "button",
|
|
1737
|
+
onClick: () => {
|
|
1738
|
+
setEnrollStep("idle");
|
|
1739
|
+
setError(null);
|
|
1740
|
+
},
|
|
1741
|
+
className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1",
|
|
1742
|
+
children: "\u2190 Cancel"
|
|
1743
|
+
}
|
|
1744
|
+
)
|
|
1745
|
+
] })
|
|
1746
|
+
] });
|
|
1747
|
+
}
|
|
1748
|
+
return /* @__PURE__ */ jsxs3("div", { className: "px-6 py-4 space-y-4", children: [
|
|
1749
|
+
/* @__PURE__ */ jsx5("div", { className: "rounded-xl border border-white/8 p-4", style: { background: "rgba(255,255,255,0.03)" }, children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
1750
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
1751
|
+
/* @__PURE__ */ jsx5("p", { className: "text-sm font-medium text-white/90", children: "Authenticator app" }),
|
|
1752
|
+
/* @__PURE__ */ jsx5("p", { className: "text-[11px] text-[#475569] mt-0.5", children: totpEnabled ? `Enabled \xB7 ${backupCodesLeft ?? 0} backup code${backupCodesLeft === 1 ? "" : "s"} remaining` : "Add a second layer of security to your account" })
|
|
1753
|
+
] }),
|
|
1754
|
+
/* @__PURE__ */ jsx5("span", { className: clsx3(
|
|
1755
|
+
"text-[10px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-full border",
|
|
1756
|
+
totpEnabled ? "bg-emerald-500/15 text-emerald-400 border-emerald-500/30" : "bg-white/5 text-[#475569] border-white/8"
|
|
1757
|
+
), children: totpEnabled ? "On" : "Off" })
|
|
1758
|
+
] }) }),
|
|
1759
|
+
error && /* @__PURE__ */ jsx5("div", { className: "rounded-lg bg-red-500/10 border border-red-500/30 px-3 py-2 text-xs text-red-400", children: error }),
|
|
1760
|
+
!totpEnabled && /* @__PURE__ */ jsx5(
|
|
1213
1761
|
"button",
|
|
1214
1762
|
{
|
|
1215
|
-
onClick:
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
|
|
1220
|
-
]
|
|
1763
|
+
onClick: startEnroll,
|
|
1764
|
+
disabled: loading,
|
|
1765
|
+
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",
|
|
1766
|
+
children: loading ? "Loading\u2026" : "Enable authenticator app"
|
|
1221
1767
|
}
|
|
1222
1768
|
),
|
|
1223
|
-
|
|
1224
|
-
"
|
|
1769
|
+
totpEnabled && !showDisable && /* @__PURE__ */ jsx5(
|
|
1770
|
+
"button",
|
|
1225
1771
|
{
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1772
|
+
onClick: () => setShowDisable(true),
|
|
1773
|
+
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",
|
|
1774
|
+
children: "Disable authenticator app"
|
|
1775
|
+
}
|
|
1776
|
+
),
|
|
1777
|
+
totpEnabled && showDisable && /* @__PURE__ */ jsxs3("form", { onSubmit: disableTotp, className: "space-y-3", children: [
|
|
1778
|
+
/* @__PURE__ */ jsx5("p", { className: "text-[11px] text-[#475569]", children: "Enter your current 6-digit code (or a backup code) to confirm." }),
|
|
1779
|
+
/* @__PURE__ */ jsx5(
|
|
1780
|
+
"input",
|
|
1781
|
+
{
|
|
1782
|
+
type: "text",
|
|
1783
|
+
placeholder: "000000 or backup code",
|
|
1784
|
+
value: disableCode,
|
|
1785
|
+
onChange: (e) => setDisableCode(e.target.value),
|
|
1786
|
+
autoFocus: true,
|
|
1787
|
+
required: true,
|
|
1788
|
+
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"
|
|
1789
|
+
}
|
|
1790
|
+
),
|
|
1791
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
|
|
1792
|
+
/* @__PURE__ */ jsx5(
|
|
1793
|
+
"button",
|
|
1794
|
+
{
|
|
1795
|
+
type: "submit",
|
|
1796
|
+
disabled: loading || !disableCode,
|
|
1797
|
+
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",
|
|
1798
|
+
children: loading ? "Disabling\u2026" : "Confirm disable"
|
|
1799
|
+
}
|
|
1229
1800
|
),
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
(
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
/* @__PURE__ */ jsx5(
|
|
1262
|
-
DropdownItem,
|
|
1263
|
-
{
|
|
1264
|
-
label: signingOut ? "Signing out\u2026" : "Sign out",
|
|
1265
|
-
icon: /* @__PURE__ */ jsx5(SignOutIcon, {}),
|
|
1266
|
-
onClick: handleSignOut,
|
|
1267
|
-
destructive: true
|
|
1268
|
-
}
|
|
1269
|
-
)
|
|
1270
|
-
] })
|
|
1271
|
-
]
|
|
1801
|
+
/* @__PURE__ */ jsx5(
|
|
1802
|
+
"button",
|
|
1803
|
+
{
|
|
1804
|
+
type: "button",
|
|
1805
|
+
onClick: () => {
|
|
1806
|
+
setShowDisable(false);
|
|
1807
|
+
setError(null);
|
|
1808
|
+
setDisableCode("");
|
|
1809
|
+
},
|
|
1810
|
+
className: "px-4 py-2 rounded-xl text-xs text-[#475569] hover:text-white hover:bg-white/8 border border-white/8 transition-colors",
|
|
1811
|
+
children: "Cancel"
|
|
1812
|
+
}
|
|
1813
|
+
)
|
|
1814
|
+
] })
|
|
1815
|
+
] })
|
|
1816
|
+
] });
|
|
1817
|
+
}
|
|
1818
|
+
function ModalField({ label, value, onChange, placeholder, type = "text" }) {
|
|
1819
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
|
|
1820
|
+
/* @__PURE__ */ jsx5("label", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: label }),
|
|
1821
|
+
/* @__PURE__ */ jsx5(
|
|
1822
|
+
"input",
|
|
1823
|
+
{
|
|
1824
|
+
type,
|
|
1825
|
+
value,
|
|
1826
|
+
onChange: (e) => onChange(e.target.value),
|
|
1827
|
+
placeholder,
|
|
1828
|
+
className: clsx3(
|
|
1829
|
+
"w-full bg-white/5 border border-white/8 rounded-xl px-3 py-2 text-sm text-white/90",
|
|
1830
|
+
"placeholder:text-[#475569] focus:outline-none focus:border-purple-500/60 transition-colors"
|
|
1831
|
+
)
|
|
1272
1832
|
}
|
|
1273
1833
|
)
|
|
1274
1834
|
] });
|
|
1275
1835
|
}
|
|
1276
|
-
function
|
|
1277
|
-
const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
|
|
1278
|
-
if (imageUrl) {
|
|
1279
|
-
return (
|
|
1280
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
1281
|
-
/* @__PURE__ */ jsx5(
|
|
1282
|
-
"img",
|
|
1283
|
-
{
|
|
1284
|
-
src: imageUrl,
|
|
1285
|
-
alt: "",
|
|
1286
|
-
className: clsx3(dim, "rounded-full object-cover ring-2 ring-white/10")
|
|
1287
|
-
}
|
|
1288
|
-
)
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1836
|
+
function SaveButton({ saving, label = "Save changes" }) {
|
|
1291
1837
|
return /* @__PURE__ */ jsx5(
|
|
1292
|
-
"
|
|
1838
|
+
"button",
|
|
1293
1839
|
{
|
|
1840
|
+
type: "submit",
|
|
1841
|
+
disabled: saving,
|
|
1294
1842
|
className: clsx3(
|
|
1295
|
-
|
|
1296
|
-
"
|
|
1297
|
-
"
|
|
1843
|
+
"px-4 py-2 rounded-xl text-sm font-semibold text-white transition-all",
|
|
1844
|
+
"bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500",
|
|
1845
|
+
"shadow-lg shadow-purple-500/20 disabled:opacity-60"
|
|
1298
1846
|
),
|
|
1299
|
-
children:
|
|
1847
|
+
children: saving ? "Saving\u2026" : label
|
|
1300
1848
|
}
|
|
1301
1849
|
);
|
|
1302
1850
|
}
|
|
1851
|
+
function Avatar({ initials, imageUrl, size = "sm" }) {
|
|
1852
|
+
const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
|
|
1853
|
+
if (imageUrl) {
|
|
1854
|
+
return /* @__PURE__ */ jsx5("img", { src: imageUrl, alt: "", className: clsx3(dim, "rounded-full object-cover ring-2 ring-white/10") });
|
|
1855
|
+
}
|
|
1856
|
+
return /* @__PURE__ */ jsx5("div", { className: clsx3(
|
|
1857
|
+
dim,
|
|
1858
|
+
"rounded-full flex items-center justify-center font-semibold text-white",
|
|
1859
|
+
"bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
|
|
1860
|
+
), children: initials });
|
|
1861
|
+
}
|
|
1303
1862
|
function AvatarSkeleton() {
|
|
1304
1863
|
return /* @__PURE__ */ jsx5("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
|
|
1305
1864
|
}
|
|
@@ -1310,16 +1869,10 @@ function RiskBadge({ level }) {
|
|
|
1310
1869
|
high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
|
|
1311
1870
|
critical: "bg-red-500/15 text-red-400 border-red-500/30"
|
|
1312
1871
|
};
|
|
1313
|
-
return /* @__PURE__ */ jsx5(
|
|
1314
|
-
"
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
"inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
|
|
1318
|
-
styles[level] ?? styles.low
|
|
1319
|
-
),
|
|
1320
|
-
children: level
|
|
1321
|
-
}
|
|
1322
|
-
);
|
|
1872
|
+
return /* @__PURE__ */ jsx5("span", { className: clsx3(
|
|
1873
|
+
"inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
|
|
1874
|
+
styles[level] ?? styles.low
|
|
1875
|
+
), children: level });
|
|
1323
1876
|
}
|
|
1324
1877
|
function DropdownItem({ label, icon, onClick, destructive }) {
|
|
1325
1878
|
return /* @__PURE__ */ jsxs3(
|
|
@@ -1343,9 +1896,6 @@ function UserIcon() {
|
|
|
1343
1896
|
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "7", r: "4" })
|
|
1344
1897
|
] });
|
|
1345
1898
|
}
|
|
1346
|
-
function ShieldIcon() {
|
|
1347
|
-
return /* @__PURE__ */ jsx5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx5("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
|
|
1348
|
-
}
|
|
1349
1899
|
function SignOutIcon() {
|
|
1350
1900
|
return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1351
1901
|
/* @__PURE__ */ jsx5("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
@@ -1353,10 +1903,16 @@ function SignOutIcon() {
|
|
|
1353
1903
|
/* @__PURE__ */ jsx5("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
1354
1904
|
] });
|
|
1355
1905
|
}
|
|
1906
|
+
function CloseIcon() {
|
|
1907
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1908
|
+
/* @__PURE__ */ jsx5("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1909
|
+
/* @__PURE__ */ jsx5("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1910
|
+
] });
|
|
1911
|
+
}
|
|
1356
1912
|
|
|
1357
1913
|
// src/components/OrganizationSwitcher.tsx
|
|
1358
1914
|
import { clsx as clsx4 } from "clsx";
|
|
1359
|
-
import { useEffect as
|
|
1915
|
+
import { useEffect as useEffect5, useRef as useRef4, useState as useState6 } from "react";
|
|
1360
1916
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1361
1917
|
function resolveApiOrigin4() {
|
|
1362
1918
|
if (typeof document !== "undefined") {
|
|
@@ -1370,11 +1926,11 @@ function OrganizationSwitcher({
|
|
|
1370
1926
|
className
|
|
1371
1927
|
}) {
|
|
1372
1928
|
const { organization, isLoaded, isSignedIn } = useVaultixContext();
|
|
1373
|
-
const [open, setOpen] =
|
|
1374
|
-
const [orgs, setOrgs] =
|
|
1375
|
-
const [switching, setSwitching] =
|
|
1929
|
+
const [open, setOpen] = useState6(false);
|
|
1930
|
+
const [orgs, setOrgs] = useState6([]);
|
|
1931
|
+
const [switching, setSwitching] = useState6(null);
|
|
1376
1932
|
const containerRef = useRef4(null);
|
|
1377
|
-
|
|
1933
|
+
useEffect5(() => {
|
|
1378
1934
|
function onPointerDown(e) {
|
|
1379
1935
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
1380
1936
|
setOpen(false);
|
|
@@ -1383,7 +1939,7 @@ function OrganizationSwitcher({
|
|
|
1383
1939
|
document.addEventListener("pointerdown", onPointerDown);
|
|
1384
1940
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
1385
1941
|
}, []);
|
|
1386
|
-
|
|
1942
|
+
useEffect5(() => {
|
|
1387
1943
|
if (!open) return;
|
|
1388
1944
|
const api = resolveApiOrigin4();
|
|
1389
1945
|
fetch(`${api}/v1/me/organizations`, { credentials: "include" }).then((r) => r.json()).then(
|
|
@@ -1548,17 +2104,19 @@ function MiniSpinner() {
|
|
|
1548
2104
|
}
|
|
1549
2105
|
|
|
1550
2106
|
// src/index.ts
|
|
1551
|
-
var STORAGE_KEY2 = "vaultix_jwt";
|
|
1552
2107
|
function getStoredToken() {
|
|
2108
|
+
if (typeof document === "undefined") return null;
|
|
1553
2109
|
try {
|
|
1554
|
-
|
|
2110
|
+
const m = document.cookie.match(/(?:^|; )vaultix-token=([^;]+)/);
|
|
2111
|
+
return m?.[1] ? decodeURIComponent(m[1]) : null;
|
|
1555
2112
|
} catch {
|
|
1556
2113
|
return null;
|
|
1557
2114
|
}
|
|
1558
2115
|
}
|
|
1559
2116
|
function clearStoredToken() {
|
|
2117
|
+
if (typeof document === "undefined") return;
|
|
1560
2118
|
try {
|
|
1561
|
-
|
|
2119
|
+
document.cookie = "vaultix-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax";
|
|
1562
2120
|
} catch {
|
|
1563
2121
|
}
|
|
1564
2122
|
}
|
|
@@ -1576,6 +2134,7 @@ export {
|
|
|
1576
2134
|
getStoredToken,
|
|
1577
2135
|
useAuth,
|
|
1578
2136
|
useOrganization,
|
|
2137
|
+
usePlatformConnect,
|
|
1579
2138
|
useSession,
|
|
1580
2139
|
useUser,
|
|
1581
2140
|
useVaultix
|