@vaultix.ai/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1152 @@
1
+ // src/context/VaultixProvider.tsx
2
+ import {
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import { jsx } from "react/jsx-runtime";
11
+ function resolveApiOrigin(key, apiUrlProp) {
12
+ if (apiUrlProp) return apiUrlProp.replace(/\/$/, "");
13
+ const parts = key.split("_");
14
+ if (parts.length < 4 || parts[1] !== "pk") {
15
+ throw new Error("Invalid publishable key format.");
16
+ }
17
+ if (parts[0] === "smritix") {
18
+ throw new Error(
19
+ 'smritix_ publishable keys require the "apiUrl" prop on <VaultixProvider>.\nExample: <VaultixProvider publishableKey="..." apiUrl="https://your-auth-engine.up.railway.app">'
20
+ );
21
+ }
22
+ if (parts[0] !== "vaultix") {
23
+ throw new Error(`Unknown publishable key prefix "${parts[0]}".`);
24
+ }
25
+ try {
26
+ return atob(parts.slice(3).join("_")).replace(/\/$/, "");
27
+ } catch {
28
+ throw new Error("Publishable key has an unreadable API origin payload.");
29
+ }
30
+ }
31
+ var VaultixContext = createContext(null);
32
+ function useVaultixContext() {
33
+ const ctx = useContext(VaultixContext);
34
+ if (!ctx) {
35
+ throw new Error(
36
+ '<VaultixProvider> is missing from the component tree. Wrap your app root with <VaultixProvider publishableKey="...">.'
37
+ );
38
+ }
39
+ return ctx;
40
+ }
41
+ function VaultixProvider({
42
+ publishableKey,
43
+ apiUrl,
44
+ children,
45
+ afterSignInUrl,
46
+ afterSignUpUrl
47
+ }) {
48
+ const apiOrigin = resolveApiOrigin(publishableKey, apiUrl);
49
+ const [isLoaded, setIsLoaded] = useState(false);
50
+ const [user, setUser] = useState(null);
51
+ const [session, setSession] = useState(null);
52
+ const [organization, setOrganization] = useState(null);
53
+ const refreshTimerRef = useRef(null);
54
+ const scheduleRefresh = useCallback(
55
+ (expiresAt) => {
56
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
57
+ const msUntilRefresh = expiresAt * 1e3 - Date.now() - 1e4;
58
+ if (msUntilRefresh <= 0) return;
59
+ refreshTimerRef.current = setTimeout(async () => {
60
+ try {
61
+ const res = await fetch(`${apiOrigin}/v1/session/refresh`, {
62
+ method: "POST",
63
+ credentials: "include"
64
+ });
65
+ if (!res.ok) {
66
+ setUser(null);
67
+ setSession(null);
68
+ setOrganization(null);
69
+ return;
70
+ }
71
+ const data = await res.json();
72
+ setSession(data.session);
73
+ scheduleRefresh(data.session.expiresAt);
74
+ } catch {
75
+ }
76
+ }, msUntilRefresh);
77
+ },
78
+ [apiOrigin]
79
+ );
80
+ useEffect(() => {
81
+ let cancelled = false;
82
+ async function hydrate() {
83
+ try {
84
+ const res = await fetch(`${apiOrigin}/v1/me`, {
85
+ credentials: "include",
86
+ headers: { "X-Vaultix-Publishable-Key": publishableKey }
87
+ });
88
+ if (!res.ok) {
89
+ if (!cancelled) setIsLoaded(true);
90
+ return;
91
+ }
92
+ const data = await res.json();
93
+ if (!cancelled) {
94
+ setUser(data.user);
95
+ setSession(data.session);
96
+ setOrganization(data.organization);
97
+ setIsLoaded(true);
98
+ scheduleRefresh(data.session.expiresAt);
99
+ }
100
+ } catch {
101
+ if (!cancelled) setIsLoaded(true);
102
+ }
103
+ }
104
+ hydrate();
105
+ return () => {
106
+ cancelled = true;
107
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
108
+ };
109
+ }, [apiOrigin, publishableKey, scheduleRefresh]);
110
+ const signOut = useCallback(async () => {
111
+ try {
112
+ await fetch(`${apiOrigin}/v1/session`, {
113
+ method: "DELETE",
114
+ credentials: "include"
115
+ });
116
+ } catch {
117
+ } finally {
118
+ setUser(null);
119
+ setSession(null);
120
+ setOrganization(null);
121
+ if (afterSignInUrl) window.location.href = afterSignInUrl;
122
+ }
123
+ }, [apiOrigin, afterSignInUrl]);
124
+ const value = {
125
+ user,
126
+ session,
127
+ organization,
128
+ isLoaded,
129
+ isSignedIn: !!session,
130
+ signOut
131
+ };
132
+ return /* @__PURE__ */ jsx(VaultixContext.Provider, { value, children: /* @__PURE__ */ jsx(
133
+ "div",
134
+ {
135
+ "data-vaultix-after-sign-in": afterSignInUrl ?? "",
136
+ "data-vaultix-after-sign-up": afterSignUpUrl ?? "",
137
+ "data-vaultix-api": apiOrigin,
138
+ style: { display: "contents" },
139
+ children
140
+ }
141
+ ) });
142
+ }
143
+
144
+ // src/hooks/index.ts
145
+ function useVaultix() {
146
+ return useVaultixContext();
147
+ }
148
+ function useSession() {
149
+ const { session, isLoaded, isSignedIn } = useVaultixContext();
150
+ return { session, isLoaded, isSignedIn };
151
+ }
152
+ function useUser() {
153
+ const { user, isLoaded, isSignedIn } = useVaultixContext();
154
+ return { user, isLoaded, isSignedIn };
155
+ }
156
+ function useOrganization() {
157
+ const { organization, isLoaded } = useVaultixContext();
158
+ return { organization, isLoaded };
159
+ }
160
+
161
+ // src/components/SignIn.tsx
162
+ import { clsx } from "clsx";
163
+ import { useRef as useRef2, useState as useState2 } from "react";
164
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
165
+ function toBase64url(buf) {
166
+ return btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
167
+ }
168
+ function resolveApiOrigin2(prop) {
169
+ if (prop) return prop;
170
+ if (typeof document !== "undefined") {
171
+ const el = document.querySelector("[data-vaultix-api]");
172
+ if (el) return el.getAttribute("data-vaultix-api") ?? "";
173
+ }
174
+ return "";
175
+ }
176
+ function SignIn({
177
+ redirectUrl,
178
+ onSuccess,
179
+ onError,
180
+ apiOrigin: apiOriginProp,
181
+ className
182
+ }) {
183
+ const apiOrigin = resolveApiOrigin2(apiOriginProp);
184
+ const [step, setStep] = useState2("email");
185
+ const [email, setEmail] = useState2("");
186
+ const [password, setPassword] = useState2("");
187
+ const [totp, setTotp] = useState2("");
188
+ const [error, setError] = useState2(null);
189
+ const [loading, setLoading] = useState2(false);
190
+ const challengeIdRef = useRef2(null);
191
+ function setErr(msg) {
192
+ setError(msg);
193
+ onError?.(msg);
194
+ }
195
+ async function handleEmailSubmit(e) {
196
+ e.preventDefault();
197
+ setError(null);
198
+ setLoading(true);
199
+ try {
200
+ const res = await fetch(`${apiOrigin}/v1/auth/lookup`, {
201
+ method: "POST",
202
+ credentials: "include",
203
+ headers: { "Content-Type": "application/json" },
204
+ body: JSON.stringify({ email })
205
+ });
206
+ const data = await res.json();
207
+ if (!res.ok) {
208
+ setErr(data.error ?? "Could not look up account.");
209
+ return;
210
+ }
211
+ setStep(data.preferred_challenge === "passkey" ? "passkey" : "password");
212
+ } catch {
213
+ setErr("Network error. Please try again.");
214
+ } finally {
215
+ setLoading(false);
216
+ }
217
+ }
218
+ async function handlePasswordSubmit(e) {
219
+ e.preventDefault();
220
+ setError(null);
221
+ setLoading(true);
222
+ try {
223
+ const res = await fetch(`${apiOrigin}/v1/auth/sign-in`, {
224
+ method: "POST",
225
+ credentials: "include",
226
+ headers: { "Content-Type": "application/json" },
227
+ body: JSON.stringify({ email, password })
228
+ });
229
+ const data = await res.json();
230
+ if (!res.ok) {
231
+ setErr(data.error ?? "Invalid credentials.");
232
+ return;
233
+ }
234
+ if (data.requires_totp) {
235
+ challengeIdRef.current = data.challenge_id ?? null;
236
+ setStep("totp");
237
+ return;
238
+ }
239
+ handleSuccess(data.session_id);
240
+ } catch {
241
+ setErr("Network error. Please try again.");
242
+ } finally {
243
+ setLoading(false);
244
+ }
245
+ }
246
+ async function handlePasskeySignIn() {
247
+ setError(null);
248
+ setLoading(true);
249
+ try {
250
+ const optRes = await fetch(
251
+ `${apiOrigin}/v1/auth/passkey/authenticate/options`,
252
+ {
253
+ method: "POST",
254
+ credentials: "include",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify({ email })
257
+ }
258
+ );
259
+ if (!optRes.ok) {
260
+ setErr("Could not start passkey authentication.");
261
+ return;
262
+ }
263
+ const options = await optRes.json();
264
+ const credential = await navigator.credentials.get({
265
+ publicKey: options
266
+ });
267
+ const verifyRes = await fetch(
268
+ `${apiOrigin}/v1/auth/passkey/authenticate/verify`,
269
+ {
270
+ method: "POST",
271
+ credentials: "include",
272
+ headers: { "Content-Type": "application/json" },
273
+ body: JSON.stringify({
274
+ email,
275
+ id: credential.id,
276
+ rawId: toBase64url(credential.rawId),
277
+ response: {
278
+ authenticatorData: toBase64url(
279
+ credential.response.authenticatorData
280
+ ),
281
+ clientDataJSON: toBase64url(credential.response.clientDataJSON),
282
+ signature: toBase64url(
283
+ credential.response.signature
284
+ )
285
+ },
286
+ type: credential.type
287
+ })
288
+ }
289
+ );
290
+ const verifyData = await verifyRes.json();
291
+ if (!verifyRes.ok) {
292
+ setErr(verifyData.error ?? "Passkey verification failed.");
293
+ return;
294
+ }
295
+ handleSuccess(verifyData.session_id);
296
+ } catch (err) {
297
+ if (err instanceof Error && err.name === "NotAllowedError") {
298
+ setErr("Passkey was cancelled or timed out.");
299
+ } else {
300
+ setErr("Passkey authentication failed.");
301
+ }
302
+ } finally {
303
+ setLoading(false);
304
+ }
305
+ }
306
+ async function handleTotpSubmit(e) {
307
+ e.preventDefault();
308
+ setError(null);
309
+ setLoading(true);
310
+ try {
311
+ const res = await fetch(`${apiOrigin}/v1/auth/totp/verify`, {
312
+ method: "POST",
313
+ credentials: "include",
314
+ headers: { "Content-Type": "application/json" },
315
+ body: JSON.stringify({
316
+ challenge_id: challengeIdRef.current,
317
+ code: totp
318
+ })
319
+ });
320
+ const data = await res.json();
321
+ if (!res.ok) {
322
+ setErr(data.error ?? "Invalid code.");
323
+ return;
324
+ }
325
+ handleSuccess(data.session_id);
326
+ } catch {
327
+ setErr("Network error. Please try again.");
328
+ } finally {
329
+ setLoading(false);
330
+ }
331
+ }
332
+ function handleSuccess(sessionId) {
333
+ onSuccess?.(sessionId);
334
+ const target = redirectUrl ?? document.querySelector("[data-vaultix-after-sign-in]")?.getAttribute("data-vaultix-after-sign-in") ?? "/";
335
+ if (target) window.location.href = target;
336
+ }
337
+ return /* @__PURE__ */ jsxs(
338
+ "div",
339
+ {
340
+ className: clsx(
341
+ "w-full max-w-sm mx-auto rounded-2xl border border-white/8 shadow-2xl",
342
+ "backdrop-blur-[16px]",
343
+ className
344
+ ),
345
+ style: { background: "rgba(22,27,45,0.92)" },
346
+ children: [
347
+ /* @__PURE__ */ jsxs("div", { className: "px-8 pt-8 pb-6 text-center", children: [
348
+ /* @__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__ */ jsxs("h1", { className: "text-xl font-semibold text-white/90", children: [
350
+ step === "email" && "Sign in",
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
+ ] })
361
+ ] }),
362
+ /* @__PURE__ */ jsxs("div", { className: "px-8 pb-8 space-y-4", children: [
363
+ 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
+ step === "email" && /* @__PURE__ */ jsxs("form", { onSubmit: handleEmailSubmit, className: "space-y-3", children: [
365
+ /* @__PURE__ */ jsx2(
366
+ Input,
367
+ {
368
+ type: "email",
369
+ placeholder: "you@company.com",
370
+ value: email,
371
+ onChange: setEmail,
372
+ autoFocus: true,
373
+ required: true
374
+ }
375
+ ),
376
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Continue" })
377
+ ] }),
378
+ step === "password" && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-3", children: [
379
+ /* @__PURE__ */ jsx2(
380
+ Input,
381
+ {
382
+ type: "password",
383
+ placeholder: "Password",
384
+ value: password,
385
+ onChange: setPassword,
386
+ autoFocus: true,
387
+ required: true
388
+ }
389
+ ),
390
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Sign in" }),
391
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
392
+ ] }),
393
+ step === "passkey" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
394
+ /* @__PURE__ */ jsxs(PrimaryButton, { loading, onClick: handlePasskeySignIn, children: [
395
+ /* @__PURE__ */ jsx2(FingerprintIcon, {}),
396
+ "Authenticate with passkey"
397
+ ] }),
398
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), children: "Use password instead" }),
399
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("email"), children: "\u2190 Back" })
400
+ ] }),
401
+ step === "totp" && /* @__PURE__ */ jsxs("form", { onSubmit: handleTotpSubmit, className: "space-y-3", children: [
402
+ /* @__PURE__ */ jsx2(
403
+ Input,
404
+ {
405
+ type: "text",
406
+ inputMode: "numeric",
407
+ pattern: "[0-9]{6}",
408
+ maxLength: 6,
409
+ placeholder: "000000",
410
+ value: totp,
411
+ onChange: setTotp,
412
+ autoFocus: true,
413
+ required: true,
414
+ className: "text-center tracking-[0.4em] text-lg"
415
+ }
416
+ ),
417
+ /* @__PURE__ */ jsx2(PrimaryButton, { loading, children: "Verify" }),
418
+ /* @__PURE__ */ jsx2(GhostButton, { onClick: () => setStep("password"), children: "\u2190 Back" })
419
+ ] })
420
+ ] })
421
+ ]
422
+ }
423
+ );
424
+ }
425
+ function Input({ onChange, className, ...props }) {
426
+ return /* @__PURE__ */ jsx2(
427
+ "input",
428
+ {
429
+ ...props,
430
+ onChange: (e) => onChange(e.target.value),
431
+ className: clsx(
432
+ "w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5",
433
+ "text-sm text-white/90 placeholder:text-[#475569]",
434
+ "focus:outline-none focus:border-purple-500/60 transition-colors",
435
+ className
436
+ )
437
+ }
438
+ );
439
+ }
440
+ function PrimaryButton({ children, loading, onClick }) {
441
+ return /* @__PURE__ */ jsx2(
442
+ "button",
443
+ {
444
+ type: onClick ? "button" : "submit",
445
+ onClick,
446
+ disabled: loading,
447
+ className: clsx(
448
+ "w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl",
449
+ "text-sm font-semibold text-white",
450
+ "bg-gradient-to-r from-purple-600 to-blue-600",
451
+ "hover:from-purple-500 hover:to-blue-500",
452
+ "shadow-lg shadow-purple-500/20",
453
+ "transition-all duration-150",
454
+ "disabled:opacity-60 disabled:cursor-not-allowed"
455
+ ),
456
+ children: loading ? /* @__PURE__ */ jsx2(Spinner, {}) : children
457
+ }
458
+ );
459
+ }
460
+ function GhostButton({ children, onClick }) {
461
+ return /* @__PURE__ */ jsx2(
462
+ "button",
463
+ {
464
+ type: "button",
465
+ onClick,
466
+ className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1",
467
+ children
468
+ }
469
+ );
470
+ }
471
+ function Spinner() {
472
+ return /* @__PURE__ */ jsxs(
473
+ "svg",
474
+ {
475
+ className: "animate-spin h-4 w-4 text-white/80",
476
+ xmlns: "http://www.w3.org/2000/svg",
477
+ fill: "none",
478
+ viewBox: "0 0 24 24",
479
+ children: [
480
+ /* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
481
+ /* @__PURE__ */ jsx2(
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
+ )
489
+ ]
490
+ }
491
+ );
492
+ }
493
+ function LockIcon() {
494
+ 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
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
496
+ /* @__PURE__ */ jsx2("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
497
+ ] });
498
+ }
499
+ function FingerprintIcon() {
500
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
501
+ /* @__PURE__ */ jsx2("path", { d: "M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4" }),
502
+ /* @__PURE__ */ jsx2("path", { d: "M5 19.5C5.5 18 6 15 6 12c0-1.7.7-3.2 1.8-4.3" }),
503
+ /* @__PURE__ */ jsx2("path", { d: "M17.5 10c.4.8.5 1.3.5 2 0 2-.7 5-1 6.5" }),
504
+ /* @__PURE__ */ jsx2("path", { d: "M22 12c0 1-.2 2.4-.6 3.5" }),
505
+ /* @__PURE__ */ jsx2("path", { d: "M9 12c0 1.2.6 2.5 1.5 3.2" }),
506
+ /* @__PURE__ */ jsx2("path", { d: "M12 12v.01" })
507
+ ] });
508
+ }
509
+
510
+ // src/components/SignUp.tsx
511
+ import { clsx as clsx2 } from "clsx";
512
+ import { useState as useState3 } from "react";
513
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
514
+ function resolveApiOrigin3(prop) {
515
+ if (prop) return prop;
516
+ if (typeof document !== "undefined") {
517
+ const el = document.querySelector("[data-vaultix-api]");
518
+ if (el) return el.getAttribute("data-vaultix-api") ?? "";
519
+ }
520
+ return "";
521
+ }
522
+ function SignUp({
523
+ redirectUrl,
524
+ onSuccess,
525
+ onError,
526
+ apiOrigin: apiOriginProp,
527
+ className
528
+ }) {
529
+ const apiOrigin = resolveApiOrigin3(apiOriginProp);
530
+ const [step, setStep] = useState3("email");
531
+ const [email, setEmail] = useState3("");
532
+ const [password, setPassword] = useState3("");
533
+ const [verificationCode, setVerificationCode] = useState3("");
534
+ const [error, setError] = useState3(null);
535
+ const [loading, setLoading] = useState3(false);
536
+ const [registrationId, setRegistrationId] = useState3(null);
537
+ function setErr(msg) {
538
+ setError(msg);
539
+ onError?.(msg);
540
+ }
541
+ async function handleEmailPassword(e) {
542
+ e.preventDefault();
543
+ setError(null);
544
+ setLoading(true);
545
+ try {
546
+ const res = await fetch(`${apiOrigin}/v1/auth/sign-up`, {
547
+ method: "POST",
548
+ credentials: "include",
549
+ headers: { "Content-Type": "application/json" },
550
+ body: JSON.stringify({ email, password })
551
+ });
552
+ const data = await res.json();
553
+ if (!res.ok) {
554
+ setErr(data.error ?? "Could not create account.");
555
+ return;
556
+ }
557
+ setRegistrationId(data.registration_id ?? null);
558
+ setStep("verify");
559
+ } catch {
560
+ setErr("Network error. Please try again.");
561
+ } finally {
562
+ setLoading(false);
563
+ }
564
+ }
565
+ async function handleVerification(e) {
566
+ e.preventDefault();
567
+ setError(null);
568
+ setLoading(true);
569
+ try {
570
+ const res = await fetch(`${apiOrigin}/v1/auth/email/verify`, {
571
+ method: "POST",
572
+ credentials: "include",
573
+ headers: { "Content-Type": "application/json" },
574
+ body: JSON.stringify({
575
+ registration_id: registrationId,
576
+ code: verificationCode
577
+ })
578
+ });
579
+ const data = await res.json();
580
+ if (!res.ok) {
581
+ setErr(data.error ?? "Invalid code.");
582
+ return;
583
+ }
584
+ onSuccess?.(data.session_id);
585
+ const target = redirectUrl ?? document.querySelector("[data-vaultix-after-sign-up]")?.getAttribute("data-vaultix-after-sign-up") ?? "/";
586
+ if (target) window.location.href = target;
587
+ } catch {
588
+ setErr("Network error. Please try again.");
589
+ } finally {
590
+ setLoading(false);
591
+ }
592
+ }
593
+ async function resendCode() {
594
+ if (!registrationId) return;
595
+ try {
596
+ await fetch(`${apiOrigin}/v1/auth/email/resend`, {
597
+ method: "POST",
598
+ credentials: "include",
599
+ headers: { "Content-Type": "application/json" },
600
+ body: JSON.stringify({ registration_id: registrationId })
601
+ });
602
+ } catch {
603
+ }
604
+ }
605
+ return /* @__PURE__ */ jsxs2(
606
+ "div",
607
+ {
608
+ className: clsx2(
609
+ "w-full max-w-sm mx-auto rounded-2xl border border-white/8 shadow-2xl backdrop-blur-[16px]",
610
+ className
611
+ ),
612
+ style: { background: "rgba(22,27,45,0.92)" },
613
+ children: [
614
+ /* @__PURE__ */ jsxs2("div", { className: "px-8 pt-8 pb-6 text-center", children: [
615
+ /* @__PURE__ */ jsx3("div", { className: "inline-flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-600 to-teal-600 shadow-lg shadow-emerald-500/30 mb-4", children: /* @__PURE__ */ jsx3(SparkleIcon, {}) }),
616
+ /* @__PURE__ */ jsx3("h1", { className: "text-xl font-semibold text-white/90", children: step === "verify" ? "Check your email" : "Create an account" }),
617
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-[#475569] mt-1", children: step === "verify" ? `We sent a code to ${email}` : "Start your free trial today" })
618
+ ] }),
619
+ /* @__PURE__ */ jsxs2("div", { className: "px-8 pb-8 space-y-4", children: [
620
+ 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
+ (step === "email" || step === "password") && /* @__PURE__ */ jsxs2("form", { onSubmit: handleEmailPassword, className: "space-y-3", children: [
622
+ /* @__PURE__ */ jsx3(
623
+ SignUpInput,
624
+ {
625
+ type: "email",
626
+ placeholder: "you@company.com",
627
+ value: email,
628
+ onChange: setEmail,
629
+ autoFocus: true,
630
+ required: true
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsx3(
634
+ SignUpInput,
635
+ {
636
+ type: "password",
637
+ placeholder: "Create a password",
638
+ value: password,
639
+ onChange: setPassword,
640
+ required: true,
641
+ minLength: 8
642
+ }
643
+ ),
644
+ /* @__PURE__ */ jsx3(PasswordStrength, { password }),
645
+ /* @__PURE__ */ jsx3(SignUpPrimaryButton, { loading, children: "Create account" })
646
+ ] }),
647
+ step === "verify" && /* @__PURE__ */ jsxs2("form", { onSubmit: handleVerification, className: "space-y-3", children: [
648
+ /* @__PURE__ */ jsx3(
649
+ SignUpInput,
650
+ {
651
+ type: "text",
652
+ inputMode: "numeric",
653
+ pattern: "[0-9]{6}",
654
+ maxLength: 6,
655
+ placeholder: "000000",
656
+ value: verificationCode,
657
+ onChange: setVerificationCode,
658
+ autoFocus: true,
659
+ required: true,
660
+ className: "text-center tracking-[0.4em] text-lg"
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsx3(SignUpPrimaryButton, { loading, children: "Verify email" }),
664
+ /* @__PURE__ */ jsx3(
665
+ "button",
666
+ {
667
+ type: "button",
668
+ onClick: resendCode,
669
+ className: "w-full text-xs text-[#475569] hover:text-white/70 transition-colors py-1",
670
+ children: "Resend code"
671
+ }
672
+ )
673
+ ] })
674
+ ] })
675
+ ]
676
+ }
677
+ );
678
+ }
679
+ function SignUpInput({ onChange, className, ...props }) {
680
+ return /* @__PURE__ */ jsx3(
681
+ "input",
682
+ {
683
+ ...props,
684
+ onChange: (e) => onChange(e.target.value),
685
+ className: clsx2(
686
+ "w-full bg-white/5 border border-white/8 rounded-xl px-4 py-2.5",
687
+ "text-sm text-white/90 placeholder:text-[#475569]",
688
+ "focus:outline-none focus:border-emerald-500/60 transition-colors",
689
+ className
690
+ )
691
+ }
692
+ );
693
+ }
694
+ function SignUpPrimaryButton({
695
+ children,
696
+ loading
697
+ }) {
698
+ return /* @__PURE__ */ jsx3(
699
+ "button",
700
+ {
701
+ type: "submit",
702
+ disabled: loading,
703
+ className: clsx2(
704
+ "w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl",
705
+ "text-sm font-semibold text-white",
706
+ "bg-gradient-to-r from-emerald-600 to-teal-600",
707
+ "hover:from-emerald-500 hover:to-teal-500",
708
+ "shadow-lg shadow-emerald-500/20 transition-all duration-150",
709
+ "disabled:opacity-60 disabled:cursor-not-allowed"
710
+ ),
711
+ children: loading ? /* @__PURE__ */ jsx3(Spinner2, {}) : children
712
+ }
713
+ );
714
+ }
715
+ function PasswordStrength({ password }) {
716
+ const score = (() => {
717
+ if (password.length === 0) return 0;
718
+ let s = 0;
719
+ if (password.length >= 8) s++;
720
+ if (/[A-Z]/.test(password)) s++;
721
+ if (/[0-9]/.test(password)) s++;
722
+ if (/[^A-Za-z0-9]/.test(password)) s++;
723
+ return s;
724
+ })();
725
+ if (password.length === 0) return null;
726
+ 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
+ ];
734
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
735
+ /* @__PURE__ */ jsx3("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx3(
736
+ "div",
737
+ {
738
+ className: clsx2(
739
+ "flex-1 h-0.5 rounded-full transition-all duration-300",
740
+ i <= score ? colors[score] : "bg-white/10"
741
+ )
742
+ },
743
+ i
744
+ )) }),
745
+ /* @__PURE__ */ jsx3("p", { className: "text-[10px] text-[#475569]", children: labels[score] })
746
+ ] });
747
+ }
748
+ function Spinner2() {
749
+ return /* @__PURE__ */ jsxs2(
750
+ "svg",
751
+ {
752
+ className: "animate-spin h-4 w-4 text-white/80",
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
+ );
769
+ }
770
+ function SparkleIcon() {
771
+ 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
+ }
773
+
774
+ // src/components/UserButton.tsx
775
+ import { clsx as clsx3 } from "clsx";
776
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
777
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
778
+ function UserButton({
779
+ showName = false,
780
+ afterSignOutUrl,
781
+ className
782
+ }) {
783
+ const { user, session, isLoaded, isSignedIn, signOut } = useVaultixContext();
784
+ const [open, setOpen] = useState4(false);
785
+ const [signingOut, setSigningOut] = useState4(false);
786
+ const containerRef = useRef3(null);
787
+ useEffect2(() => {
788
+ function onPointerDown(e) {
789
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
790
+ setOpen(false);
791
+ }
792
+ }
793
+ document.addEventListener("pointerdown", onPointerDown);
794
+ return () => document.removeEventListener("pointerdown", onPointerDown);
795
+ }, []);
796
+ if (!isLoaded) return /* @__PURE__ */ jsx4(AvatarSkeleton, {});
797
+ if (!isSignedIn || !user) return null;
798
+ const initials = [user.firstName, user.lastName].filter(Boolean).map((s) => s.charAt(0)).join("").toUpperCase() || user.email.charAt(0).toUpperCase();
799
+ async function handleSignOut() {
800
+ setSigningOut(true);
801
+ await signOut();
802
+ if (afterSignOutUrl) window.location.href = afterSignOutUrl;
803
+ }
804
+ return /* @__PURE__ */ jsxs3("div", { ref: containerRef, className: clsx3("relative", className), children: [
805
+ /* @__PURE__ */ jsxs3(
806
+ "button",
807
+ {
808
+ onClick: () => setOpen((o) => !o),
809
+ className: "flex items-center gap-2 rounded-xl p-1 hover:bg-white/8 transition-colors",
810
+ children: [
811
+ /* @__PURE__ */ jsx4(Avatar, { initials, imageUrl: user.imageUrl }),
812
+ showName && /* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-white/90 pr-1", children: user.firstName ?? user.email })
813
+ ]
814
+ }
815
+ ),
816
+ open && /* @__PURE__ */ jsxs3(
817
+ "div",
818
+ {
819
+ className: clsx3(
820
+ "absolute right-0 mt-2 w-64 rounded-2xl border border-white/8",
821
+ "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"
822
+ ),
823
+ style: { background: "rgba(22,27,45,0.96)" },
824
+ children: [
825
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-white/8", children: [
826
+ /* @__PURE__ */ jsx4(Avatar, { initials, imageUrl: user.imageUrl, size: "lg" }),
827
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
828
+ (user.firstName || user.lastName) && /* @__PURE__ */ jsx4("p", { className: "text-sm font-semibold text-white/90 truncate", children: [user.firstName, user.lastName].filter(Boolean).join(" ") }),
829
+ /* @__PURE__ */ jsx4("p", { className: "text-xs text-[#475569] truncate", children: user.email })
830
+ ] })
831
+ ] }),
832
+ session && /* @__PURE__ */ jsx4("div", { className: "px-4 py-2 border-b border-white/8", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
833
+ /* @__PURE__ */ jsx4("span", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Risk level" }),
834
+ /* @__PURE__ */ jsx4(RiskBadge, { level: session.riskLevel })
835
+ ] }) }),
836
+ /* @__PURE__ */ jsxs3("div", { className: "p-2", children: [
837
+ /* @__PURE__ */ jsx4(
838
+ DropdownItem,
839
+ {
840
+ label: "Manage account",
841
+ icon: /* @__PURE__ */ jsx4(UserIcon, {}),
842
+ onClick: () => setOpen(false)
843
+ }
844
+ ),
845
+ /* @__PURE__ */ jsx4(
846
+ DropdownItem,
847
+ {
848
+ label: "Security settings",
849
+ icon: /* @__PURE__ */ jsx4(ShieldIcon, {}),
850
+ onClick: () => setOpen(false)
851
+ }
852
+ ),
853
+ /* @__PURE__ */ jsx4("div", { className: "h-px bg-white/8 my-1" }),
854
+ /* @__PURE__ */ jsx4(
855
+ DropdownItem,
856
+ {
857
+ label: signingOut ? "Signing out\u2026" : "Sign out",
858
+ icon: /* @__PURE__ */ jsx4(SignOutIcon, {}),
859
+ onClick: handleSignOut,
860
+ destructive: true
861
+ }
862
+ )
863
+ ] })
864
+ ]
865
+ }
866
+ )
867
+ ] });
868
+ }
869
+ function Avatar({ initials, imageUrl, size = "sm" }) {
870
+ const dim = size === "lg" ? "w-9 h-9 text-sm" : "w-8 h-8 text-xs";
871
+ if (imageUrl) {
872
+ return (
873
+ // eslint-disable-next-line @next/next/no-img-element
874
+ /* @__PURE__ */ jsx4(
875
+ "img",
876
+ {
877
+ src: imageUrl,
878
+ alt: "",
879
+ className: clsx3(dim, "rounded-full object-cover ring-2 ring-white/10")
880
+ }
881
+ )
882
+ );
883
+ }
884
+ return /* @__PURE__ */ jsx4(
885
+ "div",
886
+ {
887
+ className: clsx3(
888
+ dim,
889
+ "rounded-full flex items-center justify-center font-semibold text-white",
890
+ "bg-gradient-to-br from-purple-600 to-blue-600 ring-2 ring-white/10"
891
+ ),
892
+ children: initials
893
+ }
894
+ );
895
+ }
896
+ function AvatarSkeleton() {
897
+ return /* @__PURE__ */ jsx4("div", { className: "w-8 h-8 rounded-full bg-white/5 animate-pulse" });
898
+ }
899
+ function RiskBadge({ level }) {
900
+ const styles = {
901
+ low: "bg-emerald-500/15 text-emerald-400 border-emerald-500/30",
902
+ medium: "bg-amber-500/15 text-amber-400 border-amber-500/30",
903
+ high: "bg-orange-500/15 text-orange-400 border-orange-500/30",
904
+ critical: "bg-red-500/15 text-red-400 border-red-500/30"
905
+ };
906
+ return /* @__PURE__ */ jsx4(
907
+ "span",
908
+ {
909
+ className: clsx3(
910
+ "inline-flex items-center text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full border",
911
+ styles[level] ?? styles.low
912
+ ),
913
+ children: level
914
+ }
915
+ );
916
+ }
917
+ function DropdownItem({ label, icon, onClick, destructive }) {
918
+ return /* @__PURE__ */ jsxs3(
919
+ "button",
920
+ {
921
+ onClick,
922
+ className: clsx3(
923
+ "w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-colors",
924
+ destructive ? "text-red-400 hover:bg-red-500/10" : "text-white/70 hover:text-white hover:bg-white/8"
925
+ ),
926
+ children: [
927
+ /* @__PURE__ */ jsx4("span", { className: "opacity-70", children: icon }),
928
+ label
929
+ ]
930
+ }
931
+ );
932
+ }
933
+ function UserIcon() {
934
+ 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__ */ jsx4("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
936
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "7", r: "4" })
937
+ ] });
938
+ }
939
+ function ShieldIcon() {
940
+ return /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) });
941
+ }
942
+ function SignOutIcon() {
943
+ 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__ */ jsx4("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
945
+ /* @__PURE__ */ jsx4("polyline", { points: "16 17 21 12 16 7" }),
946
+ /* @__PURE__ */ jsx4("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
947
+ ] });
948
+ }
949
+
950
+ // src/components/OrganizationSwitcher.tsx
951
+ import { clsx as clsx4 } from "clsx";
952
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState5 } from "react";
953
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
954
+ function resolveApiOrigin4() {
955
+ if (typeof document !== "undefined") {
956
+ const el = document.querySelector("[data-vaultix-api]");
957
+ if (el) return el.getAttribute("data-vaultix-api") ?? "";
958
+ }
959
+ return "";
960
+ }
961
+ function OrganizationSwitcher({
962
+ afterSwitchUrl,
963
+ className
964
+ }) {
965
+ const { organization, isLoaded, isSignedIn } = useVaultixContext();
966
+ const [open, setOpen] = useState5(false);
967
+ const [orgs, setOrgs] = useState5([]);
968
+ const [switching, setSwitching] = useState5(null);
969
+ const containerRef = useRef4(null);
970
+ useEffect3(() => {
971
+ function onPointerDown(e) {
972
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
973
+ setOpen(false);
974
+ }
975
+ }
976
+ document.addEventListener("pointerdown", onPointerDown);
977
+ return () => document.removeEventListener("pointerdown", onPointerDown);
978
+ }, []);
979
+ useEffect3(() => {
980
+ if (!open) return;
981
+ const api = resolveApiOrigin4();
982
+ fetch(`${api}/v1/me/organizations`, { credentials: "include" }).then((r) => r.json()).then(
983
+ (data) => setOrgs(data.organizations ?? [])
984
+ ).catch(() => {
985
+ });
986
+ }, [open]);
987
+ if (!isLoaded || !isSignedIn) return null;
988
+ async function switchOrg(orgId) {
989
+ if (switching) return;
990
+ setSwitching(orgId);
991
+ try {
992
+ const api = resolveApiOrigin4();
993
+ const res = await fetch(`${api}/v1/session/org`, {
994
+ method: "POST",
995
+ credentials: "include",
996
+ headers: { "Content-Type": "application/json" },
997
+ body: JSON.stringify({ org_id: orgId })
998
+ });
999
+ if (res.ok) {
1000
+ setOpen(false);
1001
+ if (afterSwitchUrl) {
1002
+ window.location.href = afterSwitchUrl;
1003
+ } else {
1004
+ window.location.reload();
1005
+ }
1006
+ }
1007
+ } finally {
1008
+ setSwitching(null);
1009
+ }
1010
+ }
1011
+ const displayName = organization?.name ?? "Personal";
1012
+ const initials = displayName.split(/\s+/).slice(0, 2).map((w) => w[0]).join("").toUpperCase();
1013
+ return /* @__PURE__ */ jsxs4("div", { ref: containerRef, className: clsx4("relative", className), children: [
1014
+ /* @__PURE__ */ jsxs4(
1015
+ "button",
1016
+ {
1017
+ onClick: () => setOpen((o) => !o),
1018
+ className: clsx4(
1019
+ "flex items-center gap-2 px-2 py-1.5 rounded-xl",
1020
+ "border border-white/8 hover:border-white/16 hover:bg-white/5",
1021
+ "transition-all duration-150"
1022
+ ),
1023
+ children: [
1024
+ /* @__PURE__ */ jsx5(OrgAvatar, { initials }),
1025
+ /* @__PURE__ */ jsxs4("div", { className: "text-left min-w-0", children: [
1026
+ /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-white/90 truncate max-w-[120px]", children: displayName }),
1027
+ organization && /* @__PURE__ */ jsx5("p", { className: "text-[10px] text-[#475569] capitalize", children: organization.role })
1028
+ ] }),
1029
+ /* @__PURE__ */ jsx5(ChevronIcon, {})
1030
+ ]
1031
+ }
1032
+ ),
1033
+ open && /* @__PURE__ */ jsxs4(
1034
+ "div",
1035
+ {
1036
+ className: clsx4(
1037
+ "absolute left-0 mt-2 w-72 rounded-2xl border border-white/8",
1038
+ "backdrop-blur-[16px] shadow-2xl shadow-black/40 z-50"
1039
+ ),
1040
+ style: { background: "rgba(22,27,45,0.96)" },
1041
+ children: [
1042
+ /* @__PURE__ */ jsx5("div", { className: "px-4 py-3 border-b border-white/8", children: /* @__PURE__ */ jsx5("p", { className: "text-[10px] uppercase tracking-widest text-[#475569]", children: "Switch organization" }) }),
1043
+ /* @__PURE__ */ jsxs4("div", { className: "p-2 max-h-56 overflow-y-auto", children: [
1044
+ orgs.length === 0 && /* @__PURE__ */ jsx5("p", { className: "text-xs text-[#475569] text-center py-4", children: "Loading\u2026" }),
1045
+ orgs.map((org) => {
1046
+ const isActive = org.id === organization?.id;
1047
+ const orgInitials = org.name.split(/\s+/).slice(0, 2).map((w) => w[0]).join("").toUpperCase();
1048
+ return /* @__PURE__ */ jsxs4(
1049
+ "button",
1050
+ {
1051
+ onClick: () => !isActive && switchOrg(org.id),
1052
+ disabled: isActive || switching === org.id,
1053
+ className: clsx4(
1054
+ "w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors text-left",
1055
+ isActive ? "bg-purple-500/10 cursor-default" : "hover:bg-white/8 cursor-pointer"
1056
+ ),
1057
+ children: [
1058
+ /* @__PURE__ */ jsx5(OrgAvatar, { initials: orgInitials, active: isActive }),
1059
+ /* @__PURE__ */ jsxs4("div", { className: "flex-1 min-w-0", children: [
1060
+ /* @__PURE__ */ jsx5(
1061
+ "p",
1062
+ {
1063
+ className: clsx4(
1064
+ "text-sm font-medium truncate",
1065
+ isActive ? "text-purple-300" : "text-white/80"
1066
+ ),
1067
+ children: org.name
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ jsx5("p", { className: "text-[10px] text-[#475569] capitalize", children: org.role })
1071
+ ] }),
1072
+ isActive && /* @__PURE__ */ jsx5("span", { className: "w-1.5 h-1.5 rounded-full bg-purple-400 shrink-0" }),
1073
+ switching === org.id && /* @__PURE__ */ jsx5(MiniSpinner, {})
1074
+ ]
1075
+ },
1076
+ org.id
1077
+ );
1078
+ })
1079
+ ] }),
1080
+ /* @__PURE__ */ jsx5("div", { className: "p-2 border-t border-white/8", children: /* @__PURE__ */ jsxs4(
1081
+ "button",
1082
+ {
1083
+ onClick: () => {
1084
+ setOpen(false);
1085
+ },
1086
+ 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
+ children: [
1088
+ /* @__PURE__ */ jsx5(PlusIcon, {}),
1089
+ "Create organization"
1090
+ ]
1091
+ }
1092
+ ) })
1093
+ ]
1094
+ }
1095
+ )
1096
+ ] });
1097
+ }
1098
+ function OrgAvatar({
1099
+ initials,
1100
+ active = false
1101
+ }) {
1102
+ return /* @__PURE__ */ jsx5(
1103
+ "div",
1104
+ {
1105
+ className: clsx4(
1106
+ "w-8 h-8 rounded-lg flex items-center justify-center text-xs font-bold text-white shrink-0",
1107
+ active ? "bg-gradient-to-br from-purple-600 to-blue-600" : "bg-gradient-to-br from-[#1E2538] to-[#2D3548] border border-white/8"
1108
+ ),
1109
+ children: initials
1110
+ }
1111
+ );
1112
+ }
1113
+ function ChevronIcon() {
1114
+ return /* @__PURE__ */ jsx5(
1115
+ "svg",
1116
+ {
1117
+ width: "12",
1118
+ height: "12",
1119
+ viewBox: "0 0 24 24",
1120
+ fill: "none",
1121
+ stroke: "currentColor",
1122
+ strokeWidth: "2",
1123
+ strokeLinecap: "round",
1124
+ strokeLinejoin: "round",
1125
+ className: "text-[#475569] shrink-0",
1126
+ children: /* @__PURE__ */ jsx5("polyline", { points: "6 9 12 15 18 9" })
1127
+ }
1128
+ );
1129
+ }
1130
+ function PlusIcon() {
1131
+ 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__ */ jsx5("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1133
+ /* @__PURE__ */ jsx5("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1134
+ ] });
1135
+ }
1136
+ function MiniSpinner() {
1137
+ 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__ */ jsx5("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
1139
+ /* @__PURE__ */ jsx5("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })
1140
+ ] });
1141
+ }
1142
+ export {
1143
+ OrganizationSwitcher,
1144
+ SignIn,
1145
+ SignUp,
1146
+ UserButton,
1147
+ VaultixProvider,
1148
+ useOrganization,
1149
+ useSession,
1150
+ useUser,
1151
+ useVaultix
1152
+ };