@vaultix.ai/react 0.1.0 → 0.2.1
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 +35 -4
- package/dist/index.d.ts +35 -4
- package/dist/index.js +568 -220
- package/dist/index.mjs +522 -179
- package/package.json +12 -3
package/dist/index.mjs
CHANGED
|
@@ -28,6 +28,26 @@ function resolveApiOrigin(key, apiUrlProp) {
|
|
|
28
28
|
throw new Error("Publishable key has an unreadable API origin payload.");
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
var STORAGE_KEY = "vaultix_jwt";
|
|
32
|
+
function storeJwt(jwt) {
|
|
33
|
+
try {
|
|
34
|
+
sessionStorage.setItem(STORAGE_KEY, jwt);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function loadJwt() {
|
|
39
|
+
try {
|
|
40
|
+
return sessionStorage.getItem(STORAGE_KEY);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function clearJwt() {
|
|
46
|
+
try {
|
|
47
|
+
sessionStorage.removeItem(STORAGE_KEY);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
31
51
|
var VaultixContext = createContext(null);
|
|
32
52
|
function useVaultixContext() {
|
|
33
53
|
const ctx = useContext(VaultixContext);
|
|
@@ -50,42 +70,89 @@ function VaultixProvider({
|
|
|
50
70
|
const [user, setUser] = useState(null);
|
|
51
71
|
const [session, setSession] = useState(null);
|
|
52
72
|
const [organization, setOrganization] = useState(null);
|
|
73
|
+
const jwtRef = useRef(null);
|
|
53
74
|
const refreshTimerRef = useRef(null);
|
|
75
|
+
const clearAuth = useCallback(() => {
|
|
76
|
+
jwtRef.current = null;
|
|
77
|
+
clearJwt();
|
|
78
|
+
setUser(null);
|
|
79
|
+
setSession(null);
|
|
80
|
+
setOrganization(null);
|
|
81
|
+
}, []);
|
|
54
82
|
const scheduleRefresh = useCallback(
|
|
55
83
|
(expiresAt) => {
|
|
56
84
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
57
|
-
const msUntilRefresh = expiresAt * 1e3 - Date.now() -
|
|
85
|
+
const msUntilRefresh = expiresAt * 1e3 - Date.now() - 3e4;
|
|
58
86
|
if (msUntilRefresh <= 0) return;
|
|
59
87
|
refreshTimerRef.current = setTimeout(async () => {
|
|
60
88
|
try {
|
|
61
|
-
const
|
|
89
|
+
const headers = {};
|
|
90
|
+
if (jwtRef.current) headers["Authorization"] = `Bearer ${jwtRef.current}`;
|
|
91
|
+
const res = await fetch(`${apiOrigin}/api/v1/session/refresh`, {
|
|
62
92
|
method: "POST",
|
|
63
|
-
credentials: "include"
|
|
93
|
+
credentials: jwtRef.current ? "omit" : "include",
|
|
94
|
+
headers
|
|
64
95
|
});
|
|
65
96
|
if (!res.ok) {
|
|
66
|
-
|
|
67
|
-
setSession(null);
|
|
68
|
-
setOrganization(null);
|
|
97
|
+
clearAuth();
|
|
69
98
|
return;
|
|
70
99
|
}
|
|
71
100
|
const data = await res.json();
|
|
101
|
+
if (data.session_jwt) {
|
|
102
|
+
jwtRef.current = data.session_jwt;
|
|
103
|
+
storeJwt(data.session_jwt);
|
|
104
|
+
}
|
|
72
105
|
setSession(data.session);
|
|
73
106
|
scheduleRefresh(data.session.expiresAt);
|
|
74
107
|
} catch {
|
|
75
108
|
}
|
|
76
109
|
}, msUntilRefresh);
|
|
77
110
|
},
|
|
78
|
-
[apiOrigin]
|
|
111
|
+
[apiOrigin, clearAuth]
|
|
79
112
|
);
|
|
80
113
|
useEffect(() => {
|
|
81
114
|
let cancelled = false;
|
|
82
115
|
async function hydrate() {
|
|
116
|
+
let jwt = null;
|
|
117
|
+
if (typeof window !== "undefined") {
|
|
118
|
+
const url = new URL(window.location.href);
|
|
119
|
+
const handshakeToken = url.searchParams.get("__vaultix_handshake");
|
|
120
|
+
if (handshakeToken) {
|
|
121
|
+
url.searchParams.delete("__vaultix_handshake");
|
|
122
|
+
window.history.replaceState({}, "", url.toString());
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`${apiOrigin}/api/v1/tokens/exchange`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "Content-Type": "application/json" },
|
|
127
|
+
body: JSON.stringify({ handshake_token: handshakeToken })
|
|
128
|
+
});
|
|
129
|
+
if (res.ok) {
|
|
130
|
+
const data = await res.json();
|
|
131
|
+
jwt = data.session_jwt;
|
|
132
|
+
storeJwt(jwt);
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
jwt = loadJwt();
|
|
138
|
+
}
|
|
139
|
+
jwtRef.current = jwt;
|
|
140
|
+
}
|
|
83
141
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
142
|
+
const headers = {
|
|
143
|
+
"X-Vaultix-Publishable-Key": publishableKey
|
|
144
|
+
};
|
|
145
|
+
if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
|
|
146
|
+
const res = await fetch(`${apiOrigin}/api/v1/me`, {
|
|
147
|
+
// Use Bearer token for cross-domain; fall back to cookie for same-domain
|
|
148
|
+
credentials: jwt ? "omit" : "include",
|
|
149
|
+
headers
|
|
87
150
|
});
|
|
88
151
|
if (!res.ok) {
|
|
152
|
+
if (jwt) {
|
|
153
|
+
clearJwt();
|
|
154
|
+
jwtRef.current = null;
|
|
155
|
+
}
|
|
89
156
|
if (!cancelled) setIsLoaded(true);
|
|
90
157
|
return;
|
|
91
158
|
}
|
|
@@ -108,19 +175,21 @@ function VaultixProvider({
|
|
|
108
175
|
};
|
|
109
176
|
}, [apiOrigin, publishableKey, scheduleRefresh]);
|
|
110
177
|
const signOut = useCallback(async () => {
|
|
178
|
+
const jwt = jwtRef.current;
|
|
179
|
+
clearAuth();
|
|
180
|
+
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
111
181
|
try {
|
|
112
|
-
|
|
182
|
+
const headers = {};
|
|
183
|
+
if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
|
|
184
|
+
await fetch(`${apiOrigin}/api/v1/session`, {
|
|
113
185
|
method: "DELETE",
|
|
114
|
-
credentials: "include"
|
|
186
|
+
credentials: jwt ? "omit" : "include",
|
|
187
|
+
headers
|
|
115
188
|
});
|
|
116
189
|
} catch {
|
|
117
|
-
} finally {
|
|
118
|
-
setUser(null);
|
|
119
|
-
setSession(null);
|
|
120
|
-
setOrganization(null);
|
|
121
|
-
if (afterSignInUrl) window.location.href = afterSignInUrl;
|
|
122
190
|
}
|
|
123
|
-
|
|
191
|
+
if (afterSignInUrl) window.location.href = afterSignInUrl;
|
|
192
|
+
}, [apiOrigin, afterSignInUrl, clearAuth]);
|
|
124
193
|
const value = {
|
|
125
194
|
user,
|
|
126
195
|
session,
|
|
@@ -142,6 +211,7 @@ function VaultixProvider({
|
|
|
142
211
|
}
|
|
143
212
|
|
|
144
213
|
// src/hooks/index.ts
|
|
214
|
+
import { useCallback as useCallback2 } from "react";
|
|
145
215
|
function useVaultix() {
|
|
146
216
|
return useVaultixContext();
|
|
147
217
|
}
|
|
@@ -157,6 +227,22 @@ function useOrganization() {
|
|
|
157
227
|
const { organization, isLoaded } = useVaultixContext();
|
|
158
228
|
return { organization, isLoaded };
|
|
159
229
|
}
|
|
230
|
+
function useAuth() {
|
|
231
|
+
const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
|
|
232
|
+
const getToken = useCallback2(async () => {
|
|
233
|
+
if (!isSignedIn) return null;
|
|
234
|
+
return null;
|
|
235
|
+
}, [isSignedIn]);
|
|
236
|
+
return {
|
|
237
|
+
isLoaded,
|
|
238
|
+
isSignedIn,
|
|
239
|
+
userId: user?.id ?? null,
|
|
240
|
+
sessionId: session?.id ?? null,
|
|
241
|
+
orgId: session?.orgId ?? null,
|
|
242
|
+
signOut,
|
|
243
|
+
getToken
|
|
244
|
+
};
|
|
245
|
+
}
|
|
160
246
|
|
|
161
247
|
// src/components/SignIn.tsx
|
|
162
248
|
import { clsx } from "clsx";
|
|
@@ -173,6 +259,18 @@ function resolveApiOrigin2(prop) {
|
|
|
173
259
|
}
|
|
174
260
|
return "";
|
|
175
261
|
}
|
|
262
|
+
function resolveAfterSignInUrl(redirectUrlProp) {
|
|
263
|
+
if (redirectUrlProp) return redirectUrlProp;
|
|
264
|
+
if (typeof document !== "undefined") {
|
|
265
|
+
const el = document.querySelector("[data-vaultix-after-sign-in]");
|
|
266
|
+
const attr = el?.getAttribute("data-vaultix-after-sign-in");
|
|
267
|
+
if (attr) return attr;
|
|
268
|
+
const params = new URLSearchParams(window.location.search);
|
|
269
|
+
const redirectUrl = params.get("redirect_url");
|
|
270
|
+
if (redirectUrl) return redirectUrl;
|
|
271
|
+
}
|
|
272
|
+
return "/";
|
|
273
|
+
}
|
|
176
274
|
function SignIn({
|
|
177
275
|
redirectUrl,
|
|
178
276
|
onSuccess,
|
|
@@ -185,13 +283,28 @@ function SignIn({
|
|
|
185
283
|
const [email, setEmail] = useState2("");
|
|
186
284
|
const [password, setPassword] = useState2("");
|
|
187
285
|
const [totp, setTotp] = useState2("");
|
|
286
|
+
const [forgotCode, setForgotCode] = useState2("");
|
|
287
|
+
const [newPassword, setNewPassword] = useState2("");
|
|
188
288
|
const [error, setError] = useState2(null);
|
|
189
289
|
const [loading, setLoading] = useState2(false);
|
|
290
|
+
const [info, setInfo] = useState2(null);
|
|
190
291
|
const challengeIdRef = useRef2(null);
|
|
191
292
|
function setErr(msg) {
|
|
192
293
|
setError(msg);
|
|
193
294
|
onError?.(msg);
|
|
194
295
|
}
|
|
296
|
+
function handleSuccess(handshakeToken) {
|
|
297
|
+
onSuccess?.(handshakeToken);
|
|
298
|
+
const target = resolveAfterSignInUrl(redirectUrl);
|
|
299
|
+
const url = new URL(target, window.location.origin);
|
|
300
|
+
url.searchParams.set("__vaultix_handshake", handshakeToken);
|
|
301
|
+
window.location.href = url.toString();
|
|
302
|
+
}
|
|
303
|
+
function handleGoogleSignIn() {
|
|
304
|
+
const target = resolveAfterSignInUrl(redirectUrl);
|
|
305
|
+
const params = new URLSearchParams({ redirect_url: target });
|
|
306
|
+
window.location.href = `${apiOrigin}/v1/auth/oauth/google?${params}`;
|
|
307
|
+
}
|
|
195
308
|
async function handleEmailSubmit(e) {
|
|
196
309
|
e.preventDefault();
|
|
197
310
|
setError(null);
|
|
@@ -236,7 +349,7 @@ function SignIn({
|
|
|
236
349
|
setStep("totp");
|
|
237
350
|
return;
|
|
238
351
|
}
|
|
239
|
-
handleSuccess(data.
|
|
352
|
+
handleSuccess(data.handshake_token);
|
|
240
353
|
} catch {
|
|
241
354
|
setErr("Network error. Please try again.");
|
|
242
355
|
} finally {
|
|
@@ -292,7 +405,7 @@ function SignIn({
|
|
|
292
405
|
setErr(verifyData.error ?? "Passkey verification failed.");
|
|
293
406
|
return;
|
|
294
407
|
}
|
|
295
|
-
handleSuccess(verifyData.
|
|
408
|
+
handleSuccess(verifyData.handshake_token);
|
|
296
409
|
} catch (err) {
|
|
297
410
|
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
298
411
|
setErr("Passkey was cancelled or timed out.");
|
|
@@ -312,28 +425,86 @@ function SignIn({
|
|
|
312
425
|
method: "POST",
|
|
313
426
|
credentials: "include",
|
|
314
427
|
headers: { "Content-Type": "application/json" },
|
|
315
|
-
body: JSON.stringify({
|
|
316
|
-
challenge_id: challengeIdRef.current,
|
|
317
|
-
code: totp
|
|
318
|
-
})
|
|
428
|
+
body: JSON.stringify({ challenge_id: challengeIdRef.current, code: totp })
|
|
319
429
|
});
|
|
320
430
|
const data = await res.json();
|
|
321
431
|
if (!res.ok) {
|
|
322
432
|
setErr(data.error ?? "Invalid code.");
|
|
323
433
|
return;
|
|
324
434
|
}
|
|
325
|
-
handleSuccess(data.
|
|
435
|
+
handleSuccess(data.handshake_token);
|
|
436
|
+
} catch {
|
|
437
|
+
setErr("Network error. Please try again.");
|
|
438
|
+
} finally {
|
|
439
|
+
setLoading(false);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function handleForgotSubmit(e) {
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
setError(null);
|
|
445
|
+
setLoading(true);
|
|
446
|
+
try {
|
|
447
|
+
await fetch(`${apiOrigin}/v1/auth/forgot-password`, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
credentials: "include",
|
|
450
|
+
headers: { "Content-Type": "application/json" },
|
|
451
|
+
body: JSON.stringify({ email })
|
|
452
|
+
});
|
|
453
|
+
setInfo("Check your email for a reset code.");
|
|
454
|
+
setStep("forgot-verify");
|
|
326
455
|
} catch {
|
|
327
456
|
setErr("Network error. Please try again.");
|
|
328
457
|
} finally {
|
|
329
458
|
setLoading(false);
|
|
330
459
|
}
|
|
331
460
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
461
|
+
async function handleForgotVerifySubmit(e) {
|
|
462
|
+
e.preventDefault();
|
|
463
|
+
setError(null);
|
|
464
|
+
setInfo(null);
|
|
465
|
+
setStep("forgot-reset");
|
|
466
|
+
}
|
|
467
|
+
async function handleResetPasswordSubmit(e) {
|
|
468
|
+
e.preventDefault();
|
|
469
|
+
setError(null);
|
|
470
|
+
setLoading(true);
|
|
471
|
+
try {
|
|
472
|
+
const res = await fetch(`${apiOrigin}/v1/auth/reset-password`, {
|
|
473
|
+
method: "POST",
|
|
474
|
+
credentials: "include",
|
|
475
|
+
headers: { "Content-Type": "application/json" },
|
|
476
|
+
body: JSON.stringify({ email, code: forgotCode, new_password: newPassword })
|
|
477
|
+
});
|
|
478
|
+
const data = await res.json();
|
|
479
|
+
if (!res.ok) {
|
|
480
|
+
setErr(data.error ?? "Could not reset password.");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
handleSuccess(data.handshake_token);
|
|
484
|
+
} catch {
|
|
485
|
+
setErr("Network error. Please try again.");
|
|
486
|
+
} finally {
|
|
487
|
+
setLoading(false);
|
|
488
|
+
}
|
|
336
489
|
}
|
|
490
|
+
const title = {
|
|
491
|
+
email: "Sign in",
|
|
492
|
+
password: "Enter password",
|
|
493
|
+
passkey: "Passkey sign-in",
|
|
494
|
+
totp: "Two-factor code",
|
|
495
|
+
forgot: "Forgot password",
|
|
496
|
+
"forgot-verify": "Enter reset code",
|
|
497
|
+
"forgot-reset": "New password"
|
|
498
|
+
};
|
|
499
|
+
const subtitle = {
|
|
500
|
+
email: "Welcome back",
|
|
501
|
+
password: email,
|
|
502
|
+
passkey: email,
|
|
503
|
+
totp: "Enter the 6-digit code from your authenticator app",
|
|
504
|
+
forgot: "We'll send a reset code to your email",
|
|
505
|
+
"forgot-verify": `Code sent to ${email}`,
|
|
506
|
+
"forgot-reset": "Choose a new password"
|
|
507
|
+
};
|
|
337
508
|
return /* @__PURE__ */ jsxs(
|
|
338
509
|
"div",
|
|
339
510
|
{
|
|
@@ -346,34 +517,29 @@ function SignIn({
|
|
|
346
517
|
children: [
|
|
347
518
|
/* @__PURE__ */ jsxs("div", { className: "px-8 pt-8 pb-6 text-center", children: [
|
|
348
519
|
/* @__PURE__ */ jsx2("div", { className: "inline-flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-br from-purple-600 to-blue-600 shadow-lg shadow-purple-500/30 mb-4", children: /* @__PURE__ */ jsx2(LockIcon, {}) }),
|
|
349
|
-
/* @__PURE__ */
|
|
350
|
-
|
|
351
|
-
step === "password" && "Enter password",
|
|
352
|
-
step === "passkey" && "Passkey sign-in",
|
|
353
|
-
step === "totp" && "Two-factor code"
|
|
354
|
-
] }),
|
|
355
|
-
/* @__PURE__ */ jsxs("p", { className: "text-sm text-[#475569] mt-1", children: [
|
|
356
|
-
step === "email" && "Welcome back",
|
|
357
|
-
step === "password" && email,
|
|
358
|
-
step === "passkey" && email,
|
|
359
|
-
step === "totp" && "Enter the 6-digit code from your authenticator app"
|
|
360
|
-
] })
|
|
520
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold text-white/90", children: title[step] }),
|
|
521
|
+
/* @__PURE__ */ jsx2("p", { className: "text-sm text-[#475569] mt-1", children: subtitle[step] })
|
|
361
522
|
] }),
|
|
362
523
|
/* @__PURE__ */ jsxs("div", { className: "px-8 pb-8 space-y-4", children: [
|
|
363
524
|
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 }) }),
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
525
|
+
info && /* @__PURE__ */ jsx2("div", { className: "rounded-lg bg-blue-500/10 border border-blue-500/30 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-xs text-blue-400", children: info }) }),
|
|
526
|
+
step === "email" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
527
|
+
/* @__PURE__ */ jsx2(GoogleButton, { onClick: handleGoogleSignIn }),
|
|
528
|
+
/* @__PURE__ */ jsx2(Divider, {}),
|
|
529
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-3", children: [
|
|
530
|
+
/* @__PURE__ */ jsx2(
|
|
531
|
+
Input,
|
|
532
|
+
{
|
|
533
|
+
type: "email",
|
|
534
|
+
placeholder: "you@company.com",
|
|
535
|
+
value: email,
|
|
536
|
+
onChange: setEmail,
|
|
537
|
+
autoFocus: true,
|
|
538
|
+
required: true
|
|
539
|
+
}
|
|
540
|
+
),
|
|
541
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
|
|
542
|
+
] })
|
|
377
543
|
] }),
|
|
378
544
|
step === "password" && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-3", children: [
|
|
379
545
|
/* @__PURE__ */ jsx2(
|
|
@@ -388,6 +554,10 @@ function SignIn({
|
|
|
388
554
|
}
|
|
389
555
|
),
|
|
390
556
|
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Sign in" }),
|
|
557
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
|
|
558
|
+
setStep("forgot");
|
|
559
|
+
setError(null);
|
|
560
|
+
}, children: "Forgot password?" }),
|
|
391
561
|
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
|
|
392
562
|
] }),
|
|
393
563
|
step === "passkey" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
@@ -416,6 +586,58 @@ function SignIn({
|
|
|
416
586
|
),
|
|
417
587
|
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Verify" }),
|
|
418
588
|
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), children: "\u2190 Back" })
|
|
589
|
+
] }),
|
|
590
|
+
step === "forgot" && /* @__PURE__ */ jsxs("form", { onSubmit: handleForgotSubmit, className: "space-y-3", children: [
|
|
591
|
+
/* @__PURE__ */ jsx2(
|
|
592
|
+
Input,
|
|
593
|
+
{
|
|
594
|
+
type: "email",
|
|
595
|
+
placeholder: "you@company.com",
|
|
596
|
+
value: email,
|
|
597
|
+
onChange: setEmail,
|
|
598
|
+
autoFocus: true,
|
|
599
|
+
required: true
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Send reset code" }),
|
|
603
|
+
/* @__PURE__ */ jsx2(GhostButton, { onClick: () => {
|
|
604
|
+
setStep("password");
|
|
605
|
+
setError(null);
|
|
606
|
+
}, children: "\u2190 Back" })
|
|
607
|
+
] }),
|
|
608
|
+
step === "forgot-verify" && /* @__PURE__ */ jsxs("form", { onSubmit: handleForgotVerifySubmit, className: "space-y-3", children: [
|
|
609
|
+
/* @__PURE__ */ jsx2(
|
|
610
|
+
Input,
|
|
611
|
+
{
|
|
612
|
+
type: "text",
|
|
613
|
+
inputMode: "numeric",
|
|
614
|
+
pattern: "[0-9]{6}",
|
|
615
|
+
maxLength: 6,
|
|
616
|
+
placeholder: "000000",
|
|
617
|
+
value: forgotCode,
|
|
618
|
+
onChange: setForgotCode,
|
|
619
|
+
autoFocus: true,
|
|
620
|
+
required: true,
|
|
621
|
+
className: "text-center tracking-[0.4em] text-lg"
|
|
622
|
+
}
|
|
623
|
+
),
|
|
624
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
|
|
625
|
+
] }),
|
|
626
|
+
step === "forgot-reset" && /* @__PURE__ */ jsxs("form", { onSubmit: handleResetPasswordSubmit, className: "space-y-3", children: [
|
|
627
|
+
/* @__PURE__ */ jsx2(
|
|
628
|
+
Input,
|
|
629
|
+
{
|
|
630
|
+
type: "password",
|
|
631
|
+
placeholder: "New password",
|
|
632
|
+
value: newPassword,
|
|
633
|
+
onChange: setNewPassword,
|
|
634
|
+
autoFocus: true,
|
|
635
|
+
required: true,
|
|
636
|
+
minLength: 8
|
|
637
|
+
}
|
|
638
|
+
),
|
|
639
|
+
/* @__PURE__ */ jsx2(PasswordStrength, { password: newPassword }),
|
|
640
|
+
/* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Reset password" })
|
|
419
641
|
] })
|
|
420
642
|
] })
|
|
421
643
|
]
|
|
@@ -468,28 +690,63 @@ function GhostButton({ children, onClick }) {
|
|
|
468
690
|
}
|
|
469
691
|
);
|
|
470
692
|
}
|
|
471
|
-
function
|
|
693
|
+
function GoogleButton({ onClick }) {
|
|
472
694
|
return /* @__PURE__ */ jsxs(
|
|
473
|
-
"
|
|
695
|
+
"button",
|
|
474
696
|
{
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
697
|
+
type: "button",
|
|
698
|
+
onClick,
|
|
699
|
+
className: clsx(
|
|
700
|
+
"w-full flex items-center justify-center gap-3 px-4 py-2.5 rounded-xl",
|
|
701
|
+
"text-sm font-medium text-white/80",
|
|
702
|
+
"bg-white/5 border border-white/10",
|
|
703
|
+
"hover:bg-white/10 hover:border-white/20",
|
|
704
|
+
"transition-all duration-150"
|
|
705
|
+
),
|
|
479
706
|
children: [
|
|
480
|
-
/* @__PURE__ */ jsx2(
|
|
481
|
-
|
|
482
|
-
"path",
|
|
483
|
-
{
|
|
484
|
-
className: "opacity-75",
|
|
485
|
-
fill: "currentColor",
|
|
486
|
-
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
487
|
-
}
|
|
488
|
-
)
|
|
707
|
+
/* @__PURE__ */ jsx2(GoogleIcon, {}),
|
|
708
|
+
"Continue with Google"
|
|
489
709
|
]
|
|
490
710
|
}
|
|
491
711
|
);
|
|
492
712
|
}
|
|
713
|
+
function Divider() {
|
|
714
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
715
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" }),
|
|
716
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[10px] text-[#475569] uppercase tracking-wider", children: "or" }),
|
|
717
|
+
/* @__PURE__ */ jsx2("div", { className: "flex-1 h-px bg-white/8" })
|
|
718
|
+
] });
|
|
719
|
+
}
|
|
720
|
+
function PasswordStrength({ password }) {
|
|
721
|
+
const score = (() => {
|
|
722
|
+
if (password.length === 0) return 0;
|
|
723
|
+
let s = 0;
|
|
724
|
+
if (password.length >= 8) s++;
|
|
725
|
+
if (/[A-Z]/.test(password)) s++;
|
|
726
|
+
if (/[0-9]/.test(password)) s++;
|
|
727
|
+
if (/[^A-Za-z0-9]/.test(password)) s++;
|
|
728
|
+
return s;
|
|
729
|
+
})();
|
|
730
|
+
if (password.length === 0) return null;
|
|
731
|
+
const labels = ["", "Weak", "Fair", "Good", "Strong"];
|
|
732
|
+
const colors = ["", "bg-red-500", "bg-amber-400", "bg-blue-400", "bg-emerald-400"];
|
|
733
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
734
|
+
/* @__PURE__ */ jsx2("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx2(
|
|
735
|
+
"div",
|
|
736
|
+
{
|
|
737
|
+
className: clsx("flex-1 h-0.5 rounded-full transition-all duration-300", i <= score ? colors[score] : "bg-white/10")
|
|
738
|
+
},
|
|
739
|
+
i
|
|
740
|
+
)) }),
|
|
741
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[10px] text-[#475569]", children: labels[score] })
|
|
742
|
+
] });
|
|
743
|
+
}
|
|
744
|
+
function Spinner() {
|
|
745
|
+
return /* @__PURE__ */ jsxs("svg", { className: "animate-spin h-4 w-4 text-white/80", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
|
|
746
|
+
/* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
747
|
+
/* @__PURE__ */ jsx2("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
|
|
748
|
+
] });
|
|
749
|
+
}
|
|
493
750
|
function LockIcon() {
|
|
494
751
|
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
495
752
|
/* @__PURE__ */ jsx2("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
|
|
@@ -506,6 +763,14 @@ function FingerprintIcon() {
|
|
|
506
763
|
/* @__PURE__ */ jsx2("path", { d: "M12 12v.01" })
|
|
507
764
|
] });
|
|
508
765
|
}
|
|
766
|
+
function GoogleIcon() {
|
|
767
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: [
|
|
768
|
+
/* @__PURE__ */ jsx2("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
|
|
769
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
|
|
770
|
+
/* @__PURE__ */ jsx2("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
|
|
771
|
+
/* @__PURE__ */ jsx2("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" })
|
|
772
|
+
] });
|
|
773
|
+
}
|
|
509
774
|
|
|
510
775
|
// src/components/SignUp.tsx
|
|
511
776
|
import { clsx as clsx2 } from "clsx";
|
|
@@ -519,6 +784,18 @@ function resolveApiOrigin3(prop) {
|
|
|
519
784
|
}
|
|
520
785
|
return "";
|
|
521
786
|
}
|
|
787
|
+
function resolveAfterSignUpUrl(redirectUrlProp) {
|
|
788
|
+
if (redirectUrlProp) return redirectUrlProp;
|
|
789
|
+
if (typeof document !== "undefined") {
|
|
790
|
+
const el = document.querySelector("[data-vaultix-after-sign-up]");
|
|
791
|
+
const attr = el?.getAttribute("data-vaultix-after-sign-up");
|
|
792
|
+
if (attr) return attr;
|
|
793
|
+
const params = new URLSearchParams(window.location.search);
|
|
794
|
+
const redirectUrl = params.get("redirect_url");
|
|
795
|
+
if (redirectUrl) return redirectUrl;
|
|
796
|
+
}
|
|
797
|
+
return "/";
|
|
798
|
+
}
|
|
522
799
|
function SignUp({
|
|
523
800
|
redirectUrl,
|
|
524
801
|
onSuccess,
|
|
@@ -538,6 +815,18 @@ function SignUp({
|
|
|
538
815
|
setError(msg);
|
|
539
816
|
onError?.(msg);
|
|
540
817
|
}
|
|
818
|
+
function handleSuccess(handshakeToken) {
|
|
819
|
+
onSuccess?.(handshakeToken);
|
|
820
|
+
const target = resolveAfterSignUpUrl(redirectUrl);
|
|
821
|
+
const url = new URL(target, window.location.origin);
|
|
822
|
+
url.searchParams.set("__vaultix_handshake", handshakeToken);
|
|
823
|
+
window.location.href = url.toString();
|
|
824
|
+
}
|
|
825
|
+
function handleGoogleSignUp() {
|
|
826
|
+
const target = resolveAfterSignUpUrl(redirectUrl);
|
|
827
|
+
const params = new URLSearchParams({ redirect_url: target });
|
|
828
|
+
window.location.href = `${apiOrigin}/v1/auth/oauth/google?${params}`;
|
|
829
|
+
}
|
|
541
830
|
async function handleEmailPassword(e) {
|
|
542
831
|
e.preventDefault();
|
|
543
832
|
setError(null);
|
|
@@ -581,9 +870,7 @@ function SignUp({
|
|
|
581
870
|
setErr(data.error ?? "Invalid code.");
|
|
582
871
|
return;
|
|
583
872
|
}
|
|
584
|
-
|
|
585
|
-
const target = redirectUrl ?? document.querySelector("[data-vaultix-after-sign-up]")?.getAttribute("data-vaultix-after-sign-up") ?? "/";
|
|
586
|
-
if (target) window.location.href = target;
|
|
873
|
+
handleSuccess(data.handshake_token);
|
|
587
874
|
} catch {
|
|
588
875
|
setErr("Network error. Please try again.");
|
|
589
876
|
} finally {
|
|
@@ -618,31 +905,35 @@ function SignUp({
|
|
|
618
905
|
] }),
|
|
619
906
|
/* @__PURE__ */ jsxs2("div", { className: "px-8 pb-8 space-y-4", children: [
|
|
620
907
|
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 }) }),
|
|
621
|
-
|
|
622
|
-
/* @__PURE__ */ jsx3(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
908
|
+
step === "email" && /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
|
|
909
|
+
/* @__PURE__ */ jsx3(GoogleButton2, { onClick: handleGoogleSignUp }),
|
|
910
|
+
/* @__PURE__ */ jsx3(Divider2, {}),
|
|
911
|
+
/* @__PURE__ */ jsxs2("form", { onSubmit: handleEmailPassword, className: "space-y-3", children: [
|
|
912
|
+
/* @__PURE__ */ jsx3(
|
|
913
|
+
SignUpInput,
|
|
914
|
+
{
|
|
915
|
+
type: "email",
|
|
916
|
+
placeholder: "you@company.com",
|
|
917
|
+
value: email,
|
|
918
|
+
onChange: setEmail,
|
|
919
|
+
autoFocus: true,
|
|
920
|
+
required: true
|
|
921
|
+
}
|
|
922
|
+
),
|
|
923
|
+
/* @__PURE__ */ jsx3(
|
|
924
|
+
SignUpInput,
|
|
925
|
+
{
|
|
926
|
+
type: "password",
|
|
927
|
+
placeholder: "Create a password",
|
|
928
|
+
value: password,
|
|
929
|
+
onChange: setPassword,
|
|
930
|
+
required: true,
|
|
931
|
+
minLength: 8
|
|
932
|
+
}
|
|
933
|
+
),
|
|
934
|
+
/* @__PURE__ */ jsx3(PasswordStrength2, { password }),
|
|
935
|
+
/* @__PURE__ */ jsx3(SignUpPrimaryButton, { loading, children: "Create account" })
|
|
936
|
+
] })
|
|
646
937
|
] }),
|
|
647
938
|
step === "verify" && /* @__PURE__ */ jsxs2("form", { onSubmit: handleVerification, className: "space-y-3", children: [
|
|
648
939
|
/* @__PURE__ */ jsx3(
|
|
@@ -712,7 +1003,34 @@ function SignUpPrimaryButton({
|
|
|
712
1003
|
}
|
|
713
1004
|
);
|
|
714
1005
|
}
|
|
715
|
-
function
|
|
1006
|
+
function GoogleButton2({ onClick }) {
|
|
1007
|
+
return /* @__PURE__ */ jsxs2(
|
|
1008
|
+
"button",
|
|
1009
|
+
{
|
|
1010
|
+
type: "button",
|
|
1011
|
+
onClick,
|
|
1012
|
+
className: clsx2(
|
|
1013
|
+
"w-full flex items-center justify-center gap-3 px-4 py-2.5 rounded-xl",
|
|
1014
|
+
"text-sm font-medium text-white/80",
|
|
1015
|
+
"bg-white/5 border border-white/10",
|
|
1016
|
+
"hover:bg-white/10 hover:border-white/20",
|
|
1017
|
+
"transition-all duration-150"
|
|
1018
|
+
),
|
|
1019
|
+
children: [
|
|
1020
|
+
/* @__PURE__ */ jsx3(GoogleIcon2, {}),
|
|
1021
|
+
"Continue with Google"
|
|
1022
|
+
]
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
function Divider2() {
|
|
1027
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
|
|
1028
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1 h-px bg-white/8" }),
|
|
1029
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] text-[#475569] uppercase tracking-wider", children: "or" }),
|
|
1030
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1 h-px bg-white/8" })
|
|
1031
|
+
] });
|
|
1032
|
+
}
|
|
1033
|
+
function PasswordStrength2({ password }) {
|
|
716
1034
|
const score = (() => {
|
|
717
1035
|
if (password.length === 0) return 0;
|
|
718
1036
|
let s = 0;
|
|
@@ -724,13 +1042,7 @@ function PasswordStrength({ password }) {
|
|
|
724
1042
|
})();
|
|
725
1043
|
if (password.length === 0) return null;
|
|
726
1044
|
const labels = ["", "Weak", "Fair", "Good", "Strong"];
|
|
727
|
-
const colors = [
|
|
728
|
-
"",
|
|
729
|
-
"bg-red-500",
|
|
730
|
-
"bg-amber-400",
|
|
731
|
-
"bg-blue-400",
|
|
732
|
-
"bg-emerald-400"
|
|
733
|
-
];
|
|
1045
|
+
const colors = ["", "bg-red-500", "bg-amber-400", "bg-blue-400", "bg-emerald-400"];
|
|
734
1046
|
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
735
1047
|
/* @__PURE__ */ jsx3("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx3(
|
|
736
1048
|
"div",
|
|
@@ -746,35 +1058,61 @@ function PasswordStrength({ password }) {
|
|
|
746
1058
|
] });
|
|
747
1059
|
}
|
|
748
1060
|
function Spinner2() {
|
|
749
|
-
return /* @__PURE__ */ jsxs2(
|
|
750
|
-
"
|
|
751
|
-
{
|
|
752
|
-
|
|
753
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
754
|
-
fill: "none",
|
|
755
|
-
viewBox: "0 0 24 24",
|
|
756
|
-
children: [
|
|
757
|
-
/* @__PURE__ */ jsx3("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
758
|
-
/* @__PURE__ */ jsx3(
|
|
759
|
-
"path",
|
|
760
|
-
{
|
|
761
|
-
className: "opacity-75",
|
|
762
|
-
fill: "currentColor",
|
|
763
|
-
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
764
|
-
}
|
|
765
|
-
)
|
|
766
|
-
]
|
|
767
|
-
}
|
|
768
|
-
);
|
|
1061
|
+
return /* @__PURE__ */ jsxs2("svg", { className: "animate-spin h-4 w-4 text-white/80", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
|
|
1062
|
+
/* @__PURE__ */ jsx3("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
1063
|
+
/* @__PURE__ */ jsx3("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
|
|
1064
|
+
] });
|
|
769
1065
|
}
|
|
770
1066
|
function SparkleIcon() {
|
|
771
1067
|
return /* @__PURE__ */ jsx3("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" }) });
|
|
772
1068
|
}
|
|
1069
|
+
function GoogleIcon2() {
|
|
1070
|
+
return /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: [
|
|
1071
|
+
/* @__PURE__ */ jsx3("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
|
|
1072
|
+
/* @__PURE__ */ jsx3("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
|
|
1073
|
+
/* @__PURE__ */ jsx3("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
|
|
1074
|
+
/* @__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" })
|
|
1075
|
+
] });
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/components/SignedIn.tsx
|
|
1079
|
+
import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
|
|
1080
|
+
function SignedIn({ children }) {
|
|
1081
|
+
const { isLoaded, isSignedIn } = useVaultixContext();
|
|
1082
|
+
if (!isLoaded || !isSignedIn) return null;
|
|
1083
|
+
return /* @__PURE__ */ jsx4(Fragment, { children });
|
|
1084
|
+
}
|
|
1085
|
+
function SignedOut({ children }) {
|
|
1086
|
+
const { isLoaded, isSignedIn } = useVaultixContext();
|
|
1087
|
+
if (!isLoaded || isSignedIn) return null;
|
|
1088
|
+
return /* @__PURE__ */ jsx4(Fragment, { children });
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// src/components/RedirectComponents.tsx
|
|
1092
|
+
import { useEffect as useEffect2 } from "react";
|
|
1093
|
+
function RedirectToSignIn({ redirectUrl = "/sign-in" }) {
|
|
1094
|
+
const { isLoaded, isSignedIn } = useVaultixContext();
|
|
1095
|
+
useEffect2(() => {
|
|
1096
|
+
if (isLoaded && !isSignedIn) {
|
|
1097
|
+
window.location.href = redirectUrl;
|
|
1098
|
+
}
|
|
1099
|
+
}, [isLoaded, isSignedIn, redirectUrl]);
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
function RedirectToSignUp({ redirectUrl = "/sign-up" }) {
|
|
1103
|
+
const { isLoaded, isSignedIn } = useVaultixContext();
|
|
1104
|
+
useEffect2(() => {
|
|
1105
|
+
if (isLoaded && !isSignedIn) {
|
|
1106
|
+
window.location.href = redirectUrl;
|
|
1107
|
+
}
|
|
1108
|
+
}, [isLoaded, isSignedIn, redirectUrl]);
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
773
1111
|
|
|
774
1112
|
// src/components/UserButton.tsx
|
|
775
1113
|
import { clsx as clsx3 } from "clsx";
|
|
776
|
-
import { useEffect as
|
|
777
|
-
import { jsx as
|
|
1114
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
|
|
1115
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
778
1116
|
function UserButton({
|
|
779
1117
|
showName = false,
|
|
780
1118
|
afterSignOutUrl,
|
|
@@ -784,7 +1122,7 @@ function UserButton({
|
|
|
784
1122
|
const [open, setOpen] = useState4(false);
|
|
785
1123
|
const [signingOut, setSigningOut] = useState4(false);
|
|
786
1124
|
const containerRef = useRef3(null);
|
|
787
|
-
|
|
1125
|
+
useEffect3(() => {
|
|
788
1126
|
function onPointerDown(e) {
|
|
789
1127
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
790
1128
|
setOpen(false);
|
|
@@ -793,7 +1131,7 @@ function UserButton({
|
|
|
793
1131
|
document.addEventListener("pointerdown", onPointerDown);
|
|
794
1132
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
795
1133
|
}, []);
|
|
796
|
-
if (!isLoaded) return /* @__PURE__ */
|
|
1134
|
+
if (!isLoaded) return /* @__PURE__ */ jsx5(AvatarSkeleton, {});
|
|
797
1135
|
if (!isSignedIn || !user) return null;
|
|
798
1136
|
const initials = [user.firstName, user.lastName].filter(Boolean).map((s) => s.charAt(0)).join("").toUpperCase() || user.email.charAt(0).toUpperCase();
|
|
799
1137
|
async function handleSignOut() {
|
|
@@ -808,8 +1146,8 @@ function UserButton({
|
|
|
808
1146
|
onClick: () => setOpen((o) => !o),
|
|
809
1147
|
className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
|
|
810
1148
|
children: [
|
|
811
|
-
/* @__PURE__ */
|
|
812
|
-
showName && /* @__PURE__ */
|
|
1149
|
+
/* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl }),
|
|
1150
|
+
showName && /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
|
|
813
1151
|
]
|
|
814
1152
|
}
|
|
815
1153
|
),
|
|
@@ -823,39 +1161,39 @@ function UserButton({
|
|
|
823
1161
|
style: { background: "rgba(22,27,45,0.96)" },
|
|
824
1162
|
children: [
|
|
825
1163
|
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
|
|
826
|
-
/* @__PURE__ */
|
|
1164
|
+
/* @__PURE__ */ jsx5(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
|
|
827
1165
|
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
|
|
828
|
-
(user.firstName || user.lastName) && /* @__PURE__ */
|
|
829
|
-
/* @__PURE__ */
|
|
1166
|
+
(user.firstName || user.lastName) && /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
|
|
1167
|
+
/* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] truncate", children: user.email })
|
|
830
1168
|
] })
|
|
831
1169
|
] }),
|
|
832
|
-
session && /* @__PURE__ */
|
|
833
|
-
/* @__PURE__ */
|
|
834
|
-
/* @__PURE__ */
|
|
1170
|
+
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: [
|
|
1171
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
|
|
1172
|
+
/* @__PURE__ */ jsx5(RiskBadge, { level: session.riskLevel })
|
|
835
1173
|
] }) }),
|
|
836
1174
|
/* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
|
|
837
|
-
/* @__PURE__ */
|
|
1175
|
+
/* @__PURE__ */ jsx5(
|
|
838
1176
|
DropdownItem,
|
|
839
1177
|
{
|
|
840
1178
|
label: "Manage account",
|
|
841
|
-
icon: /* @__PURE__ */
|
|
1179
|
+
icon: /* @__PURE__ */ jsx5(UserIcon, {}),
|
|
842
1180
|
onClick: () => setOpen(false)
|
|
843
1181
|
}
|
|
844
1182
|
),
|
|
845
|
-
/* @__PURE__ */
|
|
1183
|
+
/* @__PURE__ */ jsx5(
|
|
846
1184
|
DropdownItem,
|
|
847
1185
|
{
|
|
848
1186
|
label: "Security settings",
|
|
849
|
-
icon: /* @__PURE__ */
|
|
1187
|
+
icon: /* @__PURE__ */ jsx5(ShieldIcon, {}),
|
|
850
1188
|
onClick: () => setOpen(false)
|
|
851
1189
|
}
|
|
852
1190
|
),
|
|
853
|
-
/* @__PURE__ */
|
|
854
|
-
/* @__PURE__ */
|
|
1191
|
+
/* @__PURE__ */ jsx5("div", { className: "h-px bg-white/8 my-1" }),
|
|
1192
|
+
/* @__PURE__ */ jsx5(
|
|
855
1193
|
DropdownItem,
|
|
856
1194
|
{
|
|
857
1195
|
label: signingOut ? "Signing out\u2026" : "Sign out",
|
|
858
|
-
icon: /* @__PURE__ */
|
|
1196
|
+
icon: /* @__PURE__ */ jsx5(SignOutIcon, {}),
|
|
859
1197
|
onClick: handleSignOut,
|
|
860
1198
|
destructive: true
|
|
861
1199
|
}
|
|
@@ -871,7 +1209,7 @@ function Avatar({ initials, imageUrl, size = "sm" }) {
|
|
|
871
1209
|
if (imageUrl) {
|
|
872
1210
|
return (
|
|
873
1211
|
// eslint-disable-next-line @next/next/no-img-element
|
|
874
|
-
/* @__PURE__ */
|
|
1212
|
+
/* @__PURE__ */ jsx5(
|
|
875
1213
|
"img",
|
|
876
1214
|
{
|
|
877
1215
|
src: imageUrl,
|
|
@@ -881,7 +1219,7 @@ function Avatar({ initials, imageUrl, size = "sm" }) {
|
|
|
881
1219
|
)
|
|
882
1220
|
);
|
|
883
1221
|
}
|
|
884
|
-
return /* @__PURE__ */
|
|
1222
|
+
return /* @__PURE__ */ jsx5(
|
|
885
1223
|
"div",
|
|
886
1224
|
{
|
|
887
1225
|
className: clsx3(
|
|
@@ -894,7 +1232,7 @@ function Avatar({ initials, imageUrl, size = "sm" }) {
|
|
|
894
1232
|
);
|
|
895
1233
|
}
|
|
896
1234
|
function AvatarSkeleton() {
|
|
897
|
-
return /* @__PURE__ */
|
|
1235
|
+
return /* @__PURE__ */ jsx5("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
|
|
898
1236
|
}
|
|
899
1237
|
function RiskBadge({ level }) {
|
|
900
1238
|
const styles = {
|
|
@@ -903,7 +1241,7 @@ function RiskBadge({ level }) {
|
|
|
903
1241
|
high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
|
|
904
1242
|
critical: "bg-red-500/15 text-red-400 border-red-500/30"
|
|
905
1243
|
};
|
|
906
|
-
return /* @__PURE__ */
|
|
1244
|
+
return /* @__PURE__ */ jsx5(
|
|
907
1245
|
"span",
|
|
908
1246
|
{
|
|
909
1247
|
className: clsx3(
|
|
@@ -924,7 +1262,7 @@ function DropdownItem({ label, icon, onClick, destructive }) {
|
|
|
924
1262
|
destructive ? "text-red-400 hover:bg-red-500/10" : "text-white/70 hover:text-white hover:bg-white/8"
|
|
925
1263
|
),
|
|
926
1264
|
children: [
|
|
927
|
-
/* @__PURE__ */
|
|
1265
|
+
/* @__PURE__ */ jsx5("span", { className: "opacity-70", children: icon }),
|
|
928
1266
|
label
|
|
929
1267
|
]
|
|
930
1268
|
}
|
|
@@ -932,25 +1270,25 @@ function DropdownItem({ label, icon, onClick, destructive }) {
|
|
|
932
1270
|
}
|
|
933
1271
|
function UserIcon() {
|
|
934
1272
|
return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
935
|
-
/* @__PURE__ */
|
|
936
|
-
/* @__PURE__ */
|
|
1273
|
+
/* @__PURE__ */ jsx5("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
|
|
1274
|
+
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "7", r: "4" })
|
|
937
1275
|
] });
|
|
938
1276
|
}
|
|
939
1277
|
function ShieldIcon() {
|
|
940
|
-
return /* @__PURE__ */
|
|
1278
|
+
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" }) });
|
|
941
1279
|
}
|
|
942
1280
|
function SignOutIcon() {
|
|
943
1281
|
return /* @__PURE__ */ jsxs3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
944
|
-
/* @__PURE__ */
|
|
945
|
-
/* @__PURE__ */
|
|
946
|
-
/* @__PURE__ */
|
|
1282
|
+
/* @__PURE__ */ jsx5("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
1283
|
+
/* @__PURE__ */ jsx5("polyline", { points: "16 17 21 12 16 7" }),
|
|
1284
|
+
/* @__PURE__ */ jsx5("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
|
|
947
1285
|
] });
|
|
948
1286
|
}
|
|
949
1287
|
|
|
950
1288
|
// src/components/OrganizationSwitcher.tsx
|
|
951
1289
|
import { clsx as clsx4 } from "clsx";
|
|
952
|
-
import { useEffect as
|
|
953
|
-
import { jsx as
|
|
1290
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
|
|
1291
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
954
1292
|
function resolveApiOrigin4() {
|
|
955
1293
|
if (typeof document !== "undefined") {
|
|
956
1294
|
const el = document.querySelector("[data-vaultix-api]");
|
|
@@ -967,7 +1305,7 @@ function OrganizationSwitcher({
|
|
|
967
1305
|
const [orgs, setOrgs] = useState5([]);
|
|
968
1306
|
const [switching, setSwitching] = useState5(null);
|
|
969
1307
|
const containerRef = useRef4(null);
|
|
970
|
-
|
|
1308
|
+
useEffect4(() => {
|
|
971
1309
|
function onPointerDown(e) {
|
|
972
1310
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
973
1311
|
setOpen(false);
|
|
@@ -976,7 +1314,7 @@ function OrganizationSwitcher({
|
|
|
976
1314
|
document.addEventListener("pointerdown", onPointerDown);
|
|
977
1315
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
978
1316
|
}, []);
|
|
979
|
-
|
|
1317
|
+
useEffect4(() => {
|
|
980
1318
|
if (!open) return;
|
|
981
1319
|
const api = resolveApiOrigin4();
|
|
982
1320
|
fetch(`${api}/v1/me/organizations`, { credentials: "include" }).then((r) => r.json()).then(
|
|
@@ -1021,12 +1359,12 @@ function OrganizationSwitcher({
|
|
|
1021
1359
|
"transition-all duration-150"
|
|
1022
1360
|
),
|
|
1023
1361
|
children: [
|
|
1024
|
-
/* @__PURE__ */
|
|
1362
|
+
/* @__PURE__ */ jsx6(OrgAvatar, { initials }),
|
|
1025
1363
|
/* @__PURE__ */ jsxs4("div", { className: "text-left min-w-0", children: [
|
|
1026
|
-
/* @__PURE__ */
|
|
1027
|
-
organization && /* @__PURE__ */
|
|
1364
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm font-semibold text-white/90 truncate max-w-[120px]", children: displayName }),
|
|
1365
|
+
organization && /* @__PURE__ */ jsx6("p", { className: "text-[10px] text-[#475569] capitalize", children: organization.role })
|
|
1028
1366
|
] }),
|
|
1029
|
-
/* @__PURE__ */
|
|
1367
|
+
/* @__PURE__ */ jsx6(ChevronIcon, {})
|
|
1030
1368
|
]
|
|
1031
1369
|
}
|
|
1032
1370
|
),
|
|
@@ -1039,9 +1377,9 @@ function OrganizationSwitcher({
|
|
|
1039
1377
|
),
|
|
1040
1378
|
style: { background: "rgba(22,27,45,0.96)" },
|
|
1041
1379
|
children: [
|
|
1042
|
-
/* @__PURE__ */
|
|
1380
|
+
/* @__PURE__ */ jsx6("div", { className: "px-4 py-3 border-b border-white/8", children: /* @__PURE__ */ jsx6("p", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Switch organization" }) }),
|
|
1043
1381
|
/* @__PURE__ */ jsxs4("div", { className: "p-2 max-h-56 overflow-y-auto", children: [
|
|
1044
|
-
orgs.length === 0 && /* @__PURE__ */
|
|
1382
|
+
orgs.length === 0 && /* @__PURE__ */ jsx6("p", { className: "text-xs text-[#475569] text-center py-4", children: "Loading\u2026" }),
|
|
1045
1383
|
orgs.map((org) => {
|
|
1046
1384
|
const isActive = org.id === organization?.id;
|
|
1047
1385
|
const orgInitials = org.name.split(/\s+/).slice(0, 2).map((w) => w[0]).join("").toUpperCase();
|
|
@@ -1055,9 +1393,9 @@ function OrganizationSwitcher({
|
|
|
1055
1393
|
isActive ? "bg-purple-500/10 cursor-default" : "hover:bg-white/8 cursor-pointer"
|
|
1056
1394
|
),
|
|
1057
1395
|
children: [
|
|
1058
|
-
/* @__PURE__ */
|
|
1396
|
+
/* @__PURE__ */ jsx6(OrgAvatar, { initials: orgInitials, active: isActive }),
|
|
1059
1397
|
/* @__PURE__ */ jsxs4("div", { className: "flex-1 min-w-0", children: [
|
|
1060
|
-
/* @__PURE__ */
|
|
1398
|
+
/* @__PURE__ */ jsx6(
|
|
1061
1399
|
"p",
|
|
1062
1400
|
{
|
|
1063
1401
|
className: clsx4(
|
|
@@ -1067,17 +1405,17 @@ function OrganizationSwitcher({
|
|
|
1067
1405
|
children: org.name
|
|
1068
1406
|
}
|
|
1069
1407
|
),
|
|
1070
|
-
/* @__PURE__ */
|
|
1408
|
+
/* @__PURE__ */ jsx6("p", { className: "text-[10px] text-[#475569] capitalize", children: org.role })
|
|
1071
1409
|
] }),
|
|
1072
|
-
isActive && /* @__PURE__ */
|
|
1073
|
-
switching === org.id && /* @__PURE__ */
|
|
1410
|
+
isActive && /* @__PURE__ */ jsx6("span", { className: "w-1.5 h-1.5 rounded-full bg-purple-400 shrink-0" }),
|
|
1411
|
+
switching === org.id && /* @__PURE__ */ jsx6(MiniSpinner, {})
|
|
1074
1412
|
]
|
|
1075
1413
|
},
|
|
1076
1414
|
org.id
|
|
1077
1415
|
);
|
|
1078
1416
|
})
|
|
1079
1417
|
] }),
|
|
1080
|
-
/* @__PURE__ */
|
|
1418
|
+
/* @__PURE__ */ jsx6("div", { className: "p-2 border-t border-white/8", children: /* @__PURE__ */ jsxs4(
|
|
1081
1419
|
"button",
|
|
1082
1420
|
{
|
|
1083
1421
|
onClick: () => {
|
|
@@ -1085,7 +1423,7 @@ function OrganizationSwitcher({
|
|
|
1085
1423
|
},
|
|
1086
1424
|
className: "w-full flex items-center gap-2 px-3 py-2 rounded-xl text-sm text-[#475569] hover:text-white hover:bg-white/8 transition-colors",
|
|
1087
1425
|
children: [
|
|
1088
|
-
/* @__PURE__ */
|
|
1426
|
+
/* @__PURE__ */ jsx6(PlusIcon, {}),
|
|
1089
1427
|
"Create organization"
|
|
1090
1428
|
]
|
|
1091
1429
|
}
|
|
@@ -1099,7 +1437,7 @@ function OrgAvatar({
|
|
|
1099
1437
|
initials,
|
|
1100
1438
|
active = false
|
|
1101
1439
|
}) {
|
|
1102
|
-
return /* @__PURE__ */
|
|
1440
|
+
return /* @__PURE__ */ jsx6(
|
|
1103
1441
|
"div",
|
|
1104
1442
|
{
|
|
1105
1443
|
className: clsx4(
|
|
@@ -1111,7 +1449,7 @@ function OrgAvatar({
|
|
|
1111
1449
|
);
|
|
1112
1450
|
}
|
|
1113
1451
|
function ChevronIcon() {
|
|
1114
|
-
return /* @__PURE__ */
|
|
1452
|
+
return /* @__PURE__ */ jsx6(
|
|
1115
1453
|
"svg",
|
|
1116
1454
|
{
|
|
1117
1455
|
width: "12",
|
|
@@ -1123,28 +1461,33 @@ function ChevronIcon() {
|
|
|
1123
1461
|
strokeLinecap: "round",
|
|
1124
1462
|
strokeLinejoin: "round",
|
|
1125
1463
|
className: "text-[#475569] shrink-0",
|
|
1126
|
-
children: /* @__PURE__ */
|
|
1464
|
+
children: /* @__PURE__ */ jsx6("polyline", { points: "6 9 12 15 18 9" })
|
|
1127
1465
|
}
|
|
1128
1466
|
);
|
|
1129
1467
|
}
|
|
1130
1468
|
function PlusIcon() {
|
|
1131
1469
|
return /* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
/* @__PURE__ */
|
|
1470
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
1471
|
+
/* @__PURE__ */ jsx6("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
|
|
1134
1472
|
] });
|
|
1135
1473
|
}
|
|
1136
1474
|
function MiniSpinner() {
|
|
1137
1475
|
return /* @__PURE__ */ jsxs4("svg", { className: "animate-spin h-3 w-3 text-[#475569]", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
|
|
1138
|
-
/* @__PURE__ */
|
|
1139
|
-
/* @__PURE__ */
|
|
1476
|
+
/* @__PURE__ */ jsx6("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
1477
|
+
/* @__PURE__ */ jsx6("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
|
|
1140
1478
|
] });
|
|
1141
1479
|
}
|
|
1142
1480
|
export {
|
|
1143
1481
|
OrganizationSwitcher,
|
|
1482
|
+
RedirectToSignIn,
|
|
1483
|
+
RedirectToSignUp,
|
|
1144
1484
|
SignIn,
|
|
1145
1485
|
SignUp,
|
|
1486
|
+
SignedIn,
|
|
1487
|
+
SignedOut,
|
|
1146
1488
|
UserButton,
|
|
1147
1489
|
VaultixProvider,
|
|
1490
|
+
useAuth,
|
|
1148
1491
|
useOrganization,
|
|
1149
1492
|
useSession,
|
|
1150
1493
|
useUser,
|