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