@kaappu/react 0.2.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,2183 @@
1
+ import { createContext, useContext, useState, useRef, useEffect, useCallback } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/context/KaappuProvider.tsx
5
+ var KaappuContext = createContext(null);
6
+ function useKaappu() {
7
+ const ctx = useContext(KaappuContext);
8
+ if (!ctx) {
9
+ throw new Error(
10
+ '[Kaappu] useKaappu() must be used inside <KaappuProvider>. Wrap your app: <KaappuProvider publishableKey="pk_live_..."><App /></KaappuProvider>'
11
+ );
12
+ }
13
+ return ctx;
14
+ }
15
+ var TOKEN_KEY = "kaappu_token";
16
+ var REFRESH_KEY = "kaappu_refresh";
17
+ var USER_KEY = "kaappu_user";
18
+ var REFRESH_BEFORE_EXPIRY_MS = 6e4;
19
+ function getStoredToken() {
20
+ if (typeof window === "undefined") return null;
21
+ return localStorage.getItem(TOKEN_KEY);
22
+ }
23
+ function parseJwtPayload(token) {
24
+ try {
25
+ return JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function isTokenExpired(token) {
31
+ const payload = parseJwtPayload(token);
32
+ if (!payload || typeof payload.exp !== "number") return true;
33
+ return Date.now() >= payload.exp * 1e3;
34
+ }
35
+ function getTokenExpiryMs(token) {
36
+ const payload = parseJwtPayload(token);
37
+ if (!payload || typeof payload.exp !== "number") return 0;
38
+ return payload.exp * 1e3 - Date.now();
39
+ }
40
+ function KaappuProvider({
41
+ publishableKey,
42
+ baseUrl = "http://localhost:9091",
43
+ signInUrl = "/sign-in",
44
+ signUpUrl = "/sign-up",
45
+ children
46
+ }) {
47
+ const [isLoaded, setIsLoaded] = useState(false);
48
+ const [isSignedIn, setIsSignedIn] = useState(false);
49
+ const [user, setUser] = useState(null);
50
+ const [accessToken, setAccessToken] = useState(null);
51
+ const [tenantConfig, setTenantConfig] = useState(null);
52
+ const refreshTimerRef = useRef(null);
53
+ const [configLoaded, setConfigLoaded] = useState(false);
54
+ const [sessionReady, setSessionReady] = useState(false);
55
+ useEffect(() => {
56
+ if (configLoaded && sessionReady) setIsLoaded(true);
57
+ }, [configLoaded, sessionReady]);
58
+ useEffect(() => {
59
+ let cancelled = false;
60
+ async function loadConfig(attempt = 0) {
61
+ try {
62
+ const r = await fetch(`${baseUrl}/api/v1/accounts/config?pk=${publishableKey}`);
63
+ if (r.ok) {
64
+ const data = await r.json();
65
+ if (!cancelled && data?.data) setTenantConfig(data.data);
66
+ if (!cancelled) setConfigLoaded(true);
67
+ return;
68
+ }
69
+ } catch {
70
+ }
71
+ if (!cancelled && attempt < 3) {
72
+ setTimeout(() => loadConfig(attempt + 1), 1e3 * Math.pow(2, attempt));
73
+ } else if (!cancelled) {
74
+ setConfigLoaded(true);
75
+ }
76
+ }
77
+ loadConfig();
78
+ return () => {
79
+ cancelled = true;
80
+ };
81
+ }, [publishableKey, baseUrl]);
82
+ useEffect(() => {
83
+ const cookieToken = document.cookie.split("; ").find((c) => c.startsWith("kaappu_token="));
84
+ if (cookieToken) {
85
+ const token = decodeURIComponent(cookieToken.split("=")[1]);
86
+ if (token && !isTokenExpired(token)) {
87
+ localStorage.setItem(TOKEN_KEY, token);
88
+ document.cookie = "kaappu_token=; path=/; max-age=0";
89
+ const payload = parseJwtPayload(token);
90
+ if (payload) {
91
+ const userData = {
92
+ id: payload.sub,
93
+ email: payload.email,
94
+ accountId: payload.tid || "default",
95
+ sessionId: payload.sid || "",
96
+ permissions: Array.isArray(payload.permissions) ? payload.permissions : []
97
+ };
98
+ localStorage.setItem(USER_KEY, JSON.stringify(userData));
99
+ setAccessToken(token);
100
+ setUser(userData);
101
+ setIsSignedIn(true);
102
+ scheduleRefresh(token);
103
+ setSessionReady(true);
104
+ return;
105
+ }
106
+ }
107
+ }
108
+ const storedToken = getStoredToken();
109
+ const storedUser = localStorage.getItem(USER_KEY);
110
+ if (storedToken && !isTokenExpired(storedToken)) {
111
+ setAccessToken(storedToken);
112
+ if (storedUser) {
113
+ try {
114
+ setUser(JSON.parse(storedUser));
115
+ } catch {
116
+ }
117
+ }
118
+ setIsSignedIn(true);
119
+ scheduleRefresh(storedToken);
120
+ } else if (storedToken && isTokenExpired(storedToken)) {
121
+ silentRefresh().finally(() => setSessionReady(true));
122
+ return;
123
+ }
124
+ setSessionReady(true);
125
+ }, []);
126
+ function scheduleRefresh(token) {
127
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
128
+ const msUntilRefresh = Math.max(getTokenExpiryMs(token) - REFRESH_BEFORE_EXPIRY_MS, 0);
129
+ refreshTimerRef.current = setTimeout(() => silentRefresh(), msUntilRefresh);
130
+ }
131
+ async function silentRefresh() {
132
+ const refreshToken = localStorage.getItem(REFRESH_KEY);
133
+ if (!refreshToken) {
134
+ clearSession();
135
+ return null;
136
+ }
137
+ try {
138
+ const res = await fetch(`${baseUrl}/api/v1/idm/auth/refresh`, {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/json" },
141
+ body: JSON.stringify({ refreshToken })
142
+ });
143
+ if (!res.ok) {
144
+ clearSession();
145
+ return null;
146
+ }
147
+ const data = await res.json();
148
+ const newToken = data.data?.accessToken;
149
+ if (newToken) {
150
+ localStorage.setItem(TOKEN_KEY, newToken);
151
+ document.cookie = `kaappu_token=${encodeURIComponent(newToken)}; path=/; max-age=${15 * 60}; samesite=lax`;
152
+ setAccessToken(newToken);
153
+ scheduleRefresh(newToken);
154
+ return newToken;
155
+ }
156
+ clearSession();
157
+ return null;
158
+ } catch {
159
+ clearSession();
160
+ return null;
161
+ }
162
+ }
163
+ function clearSession() {
164
+ localStorage.removeItem(TOKEN_KEY);
165
+ localStorage.removeItem(REFRESH_KEY);
166
+ localStorage.removeItem(USER_KEY);
167
+ document.cookie = "kaappu_token=; path=/; max-age=0";
168
+ setAccessToken(null);
169
+ setUser(null);
170
+ setIsSignedIn(false);
171
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
172
+ }
173
+ const signOut = useCallback(async () => {
174
+ const token = getStoredToken();
175
+ if (token) {
176
+ await fetch(`${baseUrl}/api/v1/idm/auth/sign-out`, {
177
+ method: "POST",
178
+ headers: { Authorization: `Bearer ${token}` }
179
+ }).catch(() => {
180
+ });
181
+ }
182
+ clearSession();
183
+ }, [baseUrl]);
184
+ const getToken = useCallback(async () => {
185
+ const stored = getStoredToken();
186
+ if (stored && !isTokenExpired(stored)) return stored;
187
+ return silentRefresh();
188
+ }, [baseUrl]);
189
+ const hasPermission = useCallback((permission) => {
190
+ if (!user?.permissions) return false;
191
+ return user.permissions.includes("*") || user.permissions.includes(permission);
192
+ }, [user]);
193
+ function onAuthSuccess(token, refreshToken, userData) {
194
+ localStorage.setItem(TOKEN_KEY, token);
195
+ localStorage.setItem(REFRESH_KEY, refreshToken);
196
+ localStorage.setItem(USER_KEY, JSON.stringify(userData));
197
+ document.cookie = `kaappu_token=${encodeURIComponent(token)}; path=/; max-age=${15 * 60}; samesite=lax`;
198
+ setAccessToken(token);
199
+ setUser(userData);
200
+ setIsSignedIn(true);
201
+ scheduleRefresh(token);
202
+ }
203
+ const contextValue = {
204
+ isLoaded,
205
+ isSignedIn,
206
+ user,
207
+ accessToken,
208
+ tenantConfig,
209
+ signOut,
210
+ getToken,
211
+ hasPermission,
212
+ // Internal — used by LoginPanel/RegisterPanel without exposing in public types
213
+ _onAuthSuccess: onAuthSuccess,
214
+ _baseUrl: baseUrl
215
+ };
216
+ return /* @__PURE__ */ jsx(KaappuContext.Provider, { value: contextValue, children });
217
+ }
218
+
219
+ // src/theme/index.ts
220
+ var DARK_VARS = {
221
+ "--k-primary": "#6366f1",
222
+ "--k-primary-fg": "#ffffff",
223
+ "--k-bg": "#0a0a0f",
224
+ "--k-card-bg": "#111118",
225
+ "--k-text": "#f0f6fc",
226
+ "--k-muted": "#8b949e",
227
+ "--k-border": "rgba(240,246,252,0.12)",
228
+ "--k-hover": "rgba(240,246,252,0.06)",
229
+ "--k-radius": "0.75rem",
230
+ "--k-font": '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
231
+ "--k-shadow": "0 8px 32px rgba(0,0,0,0.4)"
232
+ };
233
+ var LIGHT_VARS = {
234
+ "--k-primary": "#6366f1",
235
+ "--k-primary-fg": "#ffffff",
236
+ "--k-bg": "#f5f5fa",
237
+ "--k-card-bg": "#ffffff",
238
+ "--k-text": "#111118",
239
+ "--k-muted": "#6b7280",
240
+ "--k-border": "rgba(0,0,0,0.10)",
241
+ "--k-hover": "rgba(0,0,0,0.04)",
242
+ "--k-radius": "0.75rem",
243
+ "--k-font": '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
244
+ "--k-shadow": "0 8px 32px rgba(0,0,0,0.12)"
245
+ };
246
+ function resolveColorScheme(scheme = "dark") {
247
+ if (scheme !== "auto") return scheme;
248
+ if (typeof window === "undefined") return "dark";
249
+ return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
250
+ }
251
+ function buildThemeVars(appearance, brandingColor) {
252
+ const scheme = resolveColorScheme(appearance?.colorScheme ?? "dark");
253
+ const base = scheme === "light" ? { ...LIGHT_VARS } : { ...DARK_VARS };
254
+ if (brandingColor) base["--k-primary"] = brandingColor;
255
+ const v = appearance?.variables ?? {};
256
+ if (v.primaryColor) base["--k-primary"] = v.primaryColor;
257
+ if (v.primaryForeground) base["--k-primary-fg"] = v.primaryForeground;
258
+ if (v.backgroundColor) base["--k-bg"] = v.backgroundColor;
259
+ if (v.cardBackground) base["--k-card-bg"] = v.cardBackground;
260
+ if (v.textColor) base["--k-text"] = v.textColor;
261
+ if (v.mutedColor) base["--k-muted"] = v.mutedColor;
262
+ if (v.borderColor) base["--k-border"] = v.borderColor;
263
+ if (v.borderRadius) base["--k-radius"] = v.borderRadius;
264
+ if (v.fontFamily) base["--k-font"] = v.fontFamily;
265
+ return base;
266
+ }
267
+ function createTheme(appearance) {
268
+ return appearance;
269
+ }
270
+ var inputStyle = {
271
+ width: "100%",
272
+ padding: "0.625rem 0.875rem",
273
+ background: "var(--k-hover)",
274
+ border: "1px solid var(--k-border)",
275
+ borderRadius: "calc(var(--k-radius) * 0.6)",
276
+ color: "var(--k-text)",
277
+ fontSize: "0.875rem",
278
+ outline: "none",
279
+ boxSizing: "border-box",
280
+ fontFamily: "var(--k-font)"
281
+ };
282
+ var primaryButtonStyle = {
283
+ width: "100%",
284
+ padding: "0.625rem 1rem",
285
+ background: "var(--k-primary)",
286
+ color: "var(--k-primary-fg)",
287
+ border: "none",
288
+ borderRadius: "calc(var(--k-radius) * 0.6)",
289
+ fontSize: "0.875rem",
290
+ fontWeight: 600,
291
+ cursor: "pointer",
292
+ fontFamily: "var(--k-font)"
293
+ };
294
+ var ghostButtonStyle = {
295
+ width: "100%",
296
+ padding: "0.625rem 1rem",
297
+ background: "var(--k-hover)",
298
+ color: "var(--k-text)",
299
+ border: "1px solid var(--k-border)",
300
+ borderRadius: "calc(var(--k-radius) * 0.6)",
301
+ fontSize: "0.875rem",
302
+ fontWeight: 500,
303
+ cursor: "pointer",
304
+ fontFamily: "var(--k-font)"
305
+ };
306
+ var labelStyle = {
307
+ display: "block",
308
+ fontSize: "0.8125rem",
309
+ fontWeight: 500,
310
+ color: "var(--k-muted)",
311
+ marginBottom: "0.375rem"
312
+ };
313
+ var errorStyle = {
314
+ fontSize: "0.8125rem",
315
+ color: "#ef4444",
316
+ marginTop: "0.25rem"
317
+ };
318
+ function LoginPanel({
319
+ onSuccess,
320
+ redirectUrl,
321
+ logoUrl,
322
+ appearance,
323
+ className,
324
+ oauthProxyUrl,
325
+ allowedProviders,
326
+ // New props for standalone usage (without KaappuProvider)
327
+ authUrl: authUrlProp,
328
+ accountId: accountIdProp,
329
+ signUpPath = "/sign-up",
330
+ signUpLabel = "Enroll Identity",
331
+ signUpPrompt = "New operator?"
332
+ }) {
333
+ const ctx = useContext(KaappuContext);
334
+ const config = ctx?.tenantConfig;
335
+ const rawBase = authUrlProp ?? ctx?._baseUrl ?? "/api/auth";
336
+ const baseUrl = rawBase.includes("/api/auth") || rawBase.includes("/idm/auth") ? rawBase : rawBase.endsWith("/igai") || rawBase.includes(":9091") ? `${rawBase}/api/v1/idm/auth` : rawBase;
337
+ const accountId = accountIdProp ?? config?.accountId ?? "default";
338
+ const [tab, setTab] = useState("password");
339
+ const [email, setEmail] = useState("");
340
+ const [password, setPassword] = useState("");
341
+ const [showPassword, setShowPassword] = useState(false);
342
+ const [loading, setLoading] = useState(false);
343
+ const [error, setError] = useState(null);
344
+ const [info, setInfo] = useState(null);
345
+ async function handlePasswordSignIn(e) {
346
+ e.preventDefault();
347
+ setError(null);
348
+ setLoading(true);
349
+ try {
350
+ const res = await fetch(`${baseUrl}/sign-in`, {
351
+ method: "POST",
352
+ headers: { "Content-Type": "application/json" },
353
+ body: JSON.stringify({ email, password, accountId })
354
+ });
355
+ const data = await res.json();
356
+ if (!data.success && !data.data) {
357
+ setError(data.error ?? "Sign in failed");
358
+ return;
359
+ }
360
+ const { accessToken, refreshToken, user } = data.data ?? data;
361
+ const mapped = mapUser(user, accountId);
362
+ ctx?._onAuthSuccess?.(accessToken, refreshToken, mapped);
363
+ onSuccess?.(accessToken, mapped);
364
+ if (redirectUrl) window.location.href = redirectUrl;
365
+ } catch {
366
+ setError("Unable to connect. Please try again.");
367
+ } finally {
368
+ setLoading(false);
369
+ }
370
+ }
371
+ async function handleMagicLink(e) {
372
+ e.preventDefault();
373
+ setError(null);
374
+ setLoading(true);
375
+ try {
376
+ const res = await fetch(`${baseUrl}/magic-link`, {
377
+ method: "POST",
378
+ headers: { "Content-Type": "application/json" },
379
+ body: JSON.stringify({ email, accountId })
380
+ });
381
+ if (res.ok) {
382
+ setInfo(`Magic link sent to ${email}. Check your inbox.`);
383
+ } else {
384
+ const d = await res.json().catch(() => ({}));
385
+ setError(d.error ?? "Could not send magic link");
386
+ }
387
+ } catch {
388
+ setError("Unable to connect.");
389
+ } finally {
390
+ setLoading(false);
391
+ }
392
+ }
393
+ async function handleOtpRequest(e) {
394
+ e.preventDefault();
395
+ setError(null);
396
+ setLoading(true);
397
+ try {
398
+ const res = await fetch(`${baseUrl}/email-otp`, {
399
+ method: "POST",
400
+ headers: { "Content-Type": "application/json" },
401
+ body: JSON.stringify({ email, accountId })
402
+ });
403
+ if (res.ok) {
404
+ setInfo(`A 6-digit code was sent to ${email}`);
405
+ } else {
406
+ const d = await res.json().catch(() => ({}));
407
+ setError(d.error ?? "Could not send code");
408
+ }
409
+ } catch {
410
+ setError("Unable to connect.");
411
+ } finally {
412
+ setLoading(false);
413
+ }
414
+ }
415
+ function handleOAuth(provider) {
416
+ if (oauthProxyUrl) {
417
+ window.location.href = `${oauthProxyUrl}/${provider}`;
418
+ } else {
419
+ const redirect = redirectUrl ?? window.location.href;
420
+ window.location.href = `${baseUrl}/oauth/${provider}?redirect=${encodeURIComponent(redirect)}`;
421
+ }
422
+ }
423
+ const resolvedLogo = logoUrl ?? config?.branding?.logoUrl ?? "/kaappu-logo.png";
424
+ const cssVars = buildThemeVars(appearance, config?.branding?.primaryColor);
425
+ const isDark = resolveColorScheme(appearance?.colorScheme) === "dark";
426
+ return /* @__PURE__ */ jsxs(
427
+ "div",
428
+ {
429
+ style: {
430
+ ...cssVars,
431
+ background: isDark ? "#030712" : "var(--k-bg, #f5f5fa)",
432
+ minHeight: isDark ? "100vh" : void 0,
433
+ display: "flex",
434
+ alignItems: "center",
435
+ justifyContent: "center",
436
+ position: "relative",
437
+ overflow: "hidden",
438
+ padding: isDark ? void 0 : "1rem 0"
439
+ },
440
+ className,
441
+ children: [
442
+ isDark && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: "radial-gradient(circle at 20% 30%, rgba(99,102,241,0.12) 0%, transparent 60%), radial-gradient(circle at 80% 70%, rgba(139,92,246,0.08) 0%, transparent 60%)", zIndex: 0 } }),
443
+ isDark && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, backgroundImage: "linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px)", backgroundSize: "40px 40px", zIndex: 1 } }),
444
+ /* @__PURE__ */ jsxs("div", { style: CARD, children: [
445
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", marginBottom: 32 }, children: [
446
+ /* @__PURE__ */ jsx("img", { src: resolvedLogo, alt: "Logo", style: { height: isDark ? 120 : 52, marginBottom: 4, filter: isDark ? "drop-shadow(0 0 20px rgba(99,102,241,0.3))" : "none" } }),
447
+ /* @__PURE__ */ jsx("p", { style: { color: "var(--k-muted, #8b949e)", fontSize: 13, marginTop: 12, textAlign: "center" }, children: "Welcome back! Please sign in to continue." })
448
+ ] }),
449
+ (() => {
450
+ const allProviders = [
451
+ { id: "google", label: "Google", icon: /* @__PURE__ */ jsx(GoogleIcon, {}) },
452
+ { id: "github", label: "GitHub", icon: /* @__PURE__ */ jsx(GithubIcon, {}) },
453
+ { id: "microsoft", label: "Microsoft", icon: /* @__PURE__ */ jsx(MicrosoftIcon, {}) },
454
+ { id: "apple", label: "Apple", icon: /* @__PURE__ */ jsx(AppleIcon, {}) }
455
+ ];
456
+ const visible = allProviders.filter((p) => {
457
+ if (allowedProviders && allowedProviders.length > 0) return allowedProviders.includes(p.id);
458
+ return config?.authMethods?.[p.id] !== false;
459
+ });
460
+ if (visible.length === 0) return null;
461
+ if (visible.length === 1) {
462
+ const p = visible[0];
463
+ return /* @__PURE__ */ jsxs(
464
+ "button",
465
+ {
466
+ onClick: () => handleOAuth(p.id),
467
+ style: {
468
+ ...OAUTH_BTN,
469
+ display: "flex",
470
+ gap: 10,
471
+ padding: "12px 16px",
472
+ width: "100%",
473
+ marginBottom: 12,
474
+ fontSize: 14,
475
+ fontWeight: 500
476
+ },
477
+ onMouseEnter: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.08))",
478
+ onMouseLeave: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.04))",
479
+ children: [
480
+ p.icon,
481
+ " ",
482
+ /* @__PURE__ */ jsxs("span", { children: [
483
+ "Continue with ",
484
+ p.label
485
+ ] })
486
+ ]
487
+ }
488
+ );
489
+ }
490
+ const cols = Math.min(visible.length, 4);
491
+ return /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: 10, marginBottom: 12 }, children: visible.map((p) => /* @__PURE__ */ jsx(
492
+ "button",
493
+ {
494
+ onClick: () => handleOAuth(p.id),
495
+ style: OAUTH_BTN,
496
+ onMouseEnter: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.08))",
497
+ onMouseLeave: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.04))",
498
+ children: p.icon
499
+ },
500
+ p.id
501
+ )) });
502
+ })(),
503
+ /* @__PURE__ */ jsxs(
504
+ "button",
505
+ {
506
+ style: PASSKEY_BTN,
507
+ onMouseEnter: (e) => e.currentTarget.style.background = "rgba(99,102,241,0.15)",
508
+ onMouseLeave: (e) => e.currentTarget.style.background = "rgba(99,102,241,0.08)",
509
+ children: [
510
+ /* @__PURE__ */ jsx(FingerprintIcon, {}),
511
+ " Sign in with Passkey"
512
+ ]
513
+ }
514
+ ),
515
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, marginBottom: 24, color: "var(--k-muted, #484f58)", fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.1em" }, children: [
516
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "var(--k-border, rgba(255,255,255,0.06))" } }),
517
+ "or use email",
518
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "var(--k-border, rgba(255,255,255,0.06))" } })
519
+ ] }),
520
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", background: "var(--k-hover, rgba(255,255,255,0.04))", borderRadius: 10, padding: 3, marginBottom: 24 }, children: [
521
+ { key: "password", label: "\u{1F511} Password" },
522
+ { key: "magic", label: "\u2728 Magic Link" },
523
+ { key: "otp", label: "\u{1F4E7} Email OTP" },
524
+ { key: "phone", label: "\u{1F4F1} Phone" }
525
+ ].map(({ key, label }) => /* @__PURE__ */ jsx(
526
+ "button",
527
+ {
528
+ onClick: () => {
529
+ setTab(key);
530
+ setError(null);
531
+ setInfo(null);
532
+ },
533
+ style: {
534
+ flex: 1,
535
+ padding: "7px 2px",
536
+ borderRadius: 8,
537
+ fontSize: 10,
538
+ fontWeight: 700,
539
+ cursor: "pointer",
540
+ background: tab === key ? "rgba(99,102,241,0.2)" : "transparent",
541
+ color: tab === key ? "#818cf8" : "#8b949e",
542
+ border: "none",
543
+ transition: "all 0.15s ease"
544
+ },
545
+ children: label
546
+ },
547
+ key
548
+ )) }),
549
+ error && /* @__PURE__ */ jsx("div", { style: ERROR_BANNER, children: error }),
550
+ info && /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: "#34d399", marginBottom: 12, textAlign: "center" }, children: info }),
551
+ /* @__PURE__ */ jsxs("form", { onSubmit: tab === "magic" ? handleMagicLink : tab === "otp" ? handleOtpRequest : handlePasswordSignIn, children: [
552
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
553
+ /* @__PURE__ */ jsx("label", { style: LABEL, children: "Work Email" }),
554
+ /* @__PURE__ */ jsxs("div", { style: INPUT_ROW, children: [
555
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX, children: /* @__PURE__ */ jsx(MailIcon, {}) }),
556
+ /* @__PURE__ */ jsx(
557
+ "input",
558
+ {
559
+ type: "email",
560
+ value: email,
561
+ onChange: (e) => setEmail(e.target.value),
562
+ placeholder: "name@company.com",
563
+ required: true,
564
+ style: INPUT
565
+ }
566
+ )
567
+ ] })
568
+ ] }),
569
+ tab === "password" && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
570
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6, marginLeft: 4 }, children: [
571
+ /* @__PURE__ */ jsx("label", { style: { ...LABEL, marginBottom: 0 }, children: "Access Key" }),
572
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "var(--k-primary, #818cf8)", fontWeight: 700, cursor: "pointer" }, children: "Recover" })
573
+ ] }),
574
+ /* @__PURE__ */ jsxs("div", { style: INPUT_ROW, children: [
575
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX, children: /* @__PURE__ */ jsx(LockIcon, {}) }),
576
+ /* @__PURE__ */ jsx(
577
+ "input",
578
+ {
579
+ type: showPassword ? "text" : "password",
580
+ value: password,
581
+ onChange: (e) => setPassword(e.target.value),
582
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
583
+ required: true,
584
+ style: { ...INPUT, paddingRight: 0 }
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsx(
588
+ "button",
589
+ {
590
+ type: "button",
591
+ onClick: () => setShowPassword(!showPassword),
592
+ style: { width: 48, height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "none", border: "none", cursor: "pointer", color: "var(--k-muted, #484f58)" },
593
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
594
+ }
595
+ )
596
+ ] })
597
+ ] }),
598
+ tab === "magic" && /* @__PURE__ */ jsx("p", { style: HINT, children: "We'll send a one-click sign-in link to your email." }),
599
+ tab === "otp" && /* @__PURE__ */ jsx("p", { style: HINT, children: "We'll send a 6-digit code to your email. No password needed." }),
600
+ tab === "phone" && /* @__PURE__ */ jsx("p", { style: HINT, children: "We'll send a 6-digit code via SMS. No password needed." }),
601
+ /* @__PURE__ */ jsxs("button", { type: "submit", disabled: loading, style: {
602
+ ...PRIMARY_BTN,
603
+ opacity: loading ? 0.6 : 1,
604
+ cursor: loading ? "not-allowed" : "pointer"
605
+ }, children: [
606
+ loading ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : null,
607
+ tab === "password" && "Authenticate",
608
+ tab === "magic" && "Send Magic Link",
609
+ tab === "otp" && "Send Code",
610
+ tab === "phone" && "Send Code",
611
+ !loading && /* @__PURE__ */ jsx(ArrowIcon, {})
612
+ ] })
613
+ ] }),
614
+ /* @__PURE__ */ jsxs("p", { style: { textAlign: "center", fontSize: 14, color: "var(--k-muted, #8b949e)", marginTop: 28 }, children: [
615
+ signUpPrompt,
616
+ " ",
617
+ /* @__PURE__ */ jsx("a", { href: signUpPath, style: { color: "var(--k-primary, #818cf8)", textDecoration: "none", fontWeight: 700 }, children: signUpLabel })
618
+ ] }),
619
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 32, borderTop: "1px solid var(--k-border, rgba(255,255,255,0.06))", paddingTop: 20, textAlign: "center" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "inline-flex", alignItems: "center", gap: 8, opacity: 0.4 }, children: [
620
+ /* @__PURE__ */ jsx(ShieldIcon, {}),
621
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.3em", color: "var(--k-muted, #8b949e)" }, children: "Protected by Kaappu" })
622
+ ] }) })
623
+ ] })
624
+ ]
625
+ }
626
+ );
627
+ }
628
+ var CARD = {
629
+ width: "100%",
630
+ maxWidth: 420,
631
+ background: "var(--k-card-bg, rgba(13, 17, 23, 0.9))",
632
+ border: "1px solid var(--k-border, rgba(255,255,255,0.1))",
633
+ borderRadius: 24,
634
+ padding: 40,
635
+ position: "relative",
636
+ zIndex: 10,
637
+ backdropFilter: "blur(20px)",
638
+ boxShadow: "var(--k-shadow, 0 25px 80px rgba(0,0,0,0.5))"
639
+ };
640
+ var INPUT_ROW = {
641
+ display: "flex",
642
+ alignItems: "center",
643
+ background: "var(--k-hover, rgba(255,255,255,0.03))",
644
+ border: "1px solid var(--k-border, rgba(255,255,255,0.08))",
645
+ borderRadius: 12,
646
+ height: 52,
647
+ overflow: "hidden"
648
+ };
649
+ var INPUT = {
650
+ flex: 1,
651
+ background: "transparent",
652
+ border: "none",
653
+ outline: "none",
654
+ color: "var(--k-text, #f0f6fc)",
655
+ fontSize: 15,
656
+ paddingRight: 16
657
+ };
658
+ var ICON_BOX = {
659
+ width: 48,
660
+ display: "flex",
661
+ alignItems: "center",
662
+ justifyContent: "center",
663
+ color: "var(--k-muted, #484f58)"
664
+ };
665
+ var LABEL = {
666
+ display: "block",
667
+ fontSize: 11,
668
+ fontWeight: 800,
669
+ color: "var(--k-muted, #484f58)",
670
+ textTransform: "uppercase",
671
+ letterSpacing: "0.1em",
672
+ marginBottom: 6,
673
+ marginLeft: 4
674
+ };
675
+ var HINT = {
676
+ fontSize: 12,
677
+ color: "var(--k-muted, #8b949e)",
678
+ marginBottom: 12,
679
+ marginLeft: 4
680
+ };
681
+ var PRIMARY_BTN = {
682
+ width: "100%",
683
+ height: 56,
684
+ marginTop: 8,
685
+ background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
686
+ color: "white",
687
+ border: "none",
688
+ borderRadius: 14,
689
+ fontSize: 15,
690
+ fontWeight: 700,
691
+ boxShadow: "0 10px 25px rgba(99,102,241,0.3)",
692
+ display: "flex",
693
+ alignItems: "center",
694
+ justifyContent: "center",
695
+ gap: 10
696
+ };
697
+ var OAUTH_BTN = {
698
+ padding: 10,
699
+ display: "flex",
700
+ alignItems: "center",
701
+ justifyContent: "center",
702
+ background: "var(--k-hover, rgba(255,255,255,0.04))",
703
+ border: "1px solid var(--k-border, rgba(255,255,255,0.08))",
704
+ borderRadius: 12,
705
+ cursor: "pointer",
706
+ color: "var(--k-text, #f0f6fc)"
707
+ };
708
+ var PASSKEY_BTN = {
709
+ width: "100%",
710
+ marginBottom: 12,
711
+ padding: "10px 16px",
712
+ display: "flex",
713
+ alignItems: "center",
714
+ justifyContent: "center",
715
+ gap: 8,
716
+ background: "rgba(99,102,241,0.08)",
717
+ border: "1px solid rgba(99,102,241,0.2)",
718
+ borderRadius: 12,
719
+ cursor: "pointer",
720
+ color: "var(--k-primary, #818cf8)",
721
+ fontSize: 13,
722
+ fontWeight: 600
723
+ };
724
+ var ERROR_BANNER = {
725
+ background: "rgba(248,81,73,0.1)",
726
+ border: "1px solid rgba(248,81,73,0.2)",
727
+ borderRadius: 10,
728
+ padding: "10px 14px",
729
+ fontSize: 13,
730
+ color: "#f85149",
731
+ textAlign: "center",
732
+ fontWeight: 600,
733
+ marginBottom: 16
734
+ };
735
+ var GoogleIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
736
+ /* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
737
+ /* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
738
+ /* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z", fill: "#FBBC05" }),
739
+ /* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.66l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
740
+ ] });
741
+ var GithubIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M12 2C6.477 2 2 6.477 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0012 2z" }) });
742
+ var MicrosoftIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 23 23", width: "16", height: "16", children: /* @__PURE__ */ jsx("path", { fill: "#f3f3f3", d: "M0 0h11v11H0zM12 0h11v11H12zM0 12h11v11H0zM12 12h11v11H12z" }) });
743
+ var AppleIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 384 512", width: "20", height: "20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" }) });
744
+ var FingerprintIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
745
+ /* @__PURE__ */ jsx("path", { d: "M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4" }),
746
+ /* @__PURE__ */ jsx("path", { d: "M5 19.5C5.5 18 6 15 6 12c0-3.5 2.5-6 6-6 3 0 5.5 2 5.8 5" }),
747
+ /* @__PURE__ */ jsx("path", { d: "M12 12v4s.9 3 3.3 5" }),
748
+ /* @__PURE__ */ jsx("path", { d: "M8.5 18c.5-2 .5-4 .5-6 0-1.7 1.3-3 3-3s3 1.3 3 3c0 2-.5 4.5-1.5 6.5" }),
749
+ /* @__PURE__ */ jsx("path", { d: "M14 21c.5-2 .5-4.5 0-7" })
750
+ ] });
751
+ var MailIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
752
+ /* @__PURE__ */ jsx("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }),
753
+ /* @__PURE__ */ jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
754
+ ] });
755
+ var LockIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
756
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }),
757
+ /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
758
+ ] });
759
+ var EyeIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
760
+ /* @__PURE__ */ jsx("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
761
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
762
+ ] });
763
+ var EyeOffIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
764
+ /* @__PURE__ */ jsx("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
765
+ /* @__PURE__ */ jsx("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
766
+ /* @__PURE__ */ jsx("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
767
+ /* @__PURE__ */ jsx("path", { d: "m2 2 20 20" })
768
+ ] });
769
+ var ArrowIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
770
+ /* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
771
+ /* @__PURE__ */ jsx("path", { d: "m12 5 7 7-7 7" })
772
+ ] });
773
+ var ShieldIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "#8b949e", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }) });
774
+ var SpinnerIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { animation: "kaappu-spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
775
+ function mapUser(raw, accountId) {
776
+ return {
777
+ id: raw.id ?? raw.userId,
778
+ email: raw.email,
779
+ firstName: raw.firstName,
780
+ lastName: raw.lastName,
781
+ avatarUrl: raw.avatarUrl,
782
+ emailVerified: raw.emailVerified,
783
+ mfaEnabled: raw.mfaEnabled,
784
+ accountId: raw.accountId ?? accountId,
785
+ sessionId: raw.sessionId ?? "",
786
+ roles: raw.roles ?? [],
787
+ permissions: raw.permissions ?? []
788
+ };
789
+ }
790
+ function RegisterPanel({
791
+ onSuccess,
792
+ redirectUrl,
793
+ logoUrl,
794
+ appearance,
795
+ className,
796
+ // Standalone-mode props (no KaappuProvider required)
797
+ authUrl: authUrlProp,
798
+ accountId: accountIdProp,
799
+ signInPath = "/sign-in",
800
+ signInLabel = "Sign in",
801
+ signInPrompt = "Already have an account?",
802
+ // OAuth provider gating (mirrors LoginPanel)
803
+ allowedProviders,
804
+ oauthProxyUrl
805
+ }) {
806
+ const ctx = useContext(KaappuContext);
807
+ const config = ctx?.tenantConfig;
808
+ const rawBase = authUrlProp ?? ctx?._baseUrl ?? "/api/auth";
809
+ const baseUrl = rawBase.includes("/api/auth") || rawBase.includes("/idm/auth") ? rawBase : rawBase.endsWith("/igai") || rawBase.includes(":9091") ? `${rawBase}/api/v1/idm/auth` : rawBase;
810
+ const accountId = accountIdProp ?? config?.accountId ?? "default";
811
+ const [firstName, setFirstName] = useState("");
812
+ const [lastName, setLastName] = useState("");
813
+ const [email, setEmail] = useState("");
814
+ const [password, setPassword] = useState("");
815
+ const [confirm, setConfirm] = useState("");
816
+ const [showPassword, setShowPassword] = useState(false);
817
+ const [loading, setLoading] = useState(false);
818
+ const [error, setError] = useState(null);
819
+ const [step, setStep] = useState("form");
820
+ const rules = {
821
+ length: password.length >= 8,
822
+ letter: /[A-Za-z]/.test(password),
823
+ number: /\d/.test(password),
824
+ symbol: /[^A-Za-z0-9]/.test(password)
825
+ };
826
+ const passwordsMatch = confirm.length > 0 && password === confirm;
827
+ const passwordStrong = rules.length && rules.letter && rules.number;
828
+ const canSubmit = firstName && lastName && email && passwordStrong && passwordsMatch && !loading;
829
+ async function handleSignUp(e) {
830
+ e.preventDefault();
831
+ setError(null);
832
+ if (!passwordStrong) {
833
+ setError("Password must be at least 8 characters and include letters and numbers.");
834
+ return;
835
+ }
836
+ if (!passwordsMatch) {
837
+ setError("Passwords do not match.");
838
+ return;
839
+ }
840
+ setLoading(true);
841
+ try {
842
+ const res = await fetch(`${baseUrl}/sign-up`, {
843
+ method: "POST",
844
+ headers: { "Content-Type": "application/json" },
845
+ body: JSON.stringify({ email, password, firstName, lastName, accountId })
846
+ });
847
+ const data = await res.json();
848
+ if (!res.ok && !data.success) {
849
+ setError(friendlyError(data.error ?? data.code));
850
+ return;
851
+ }
852
+ const payload = data.data ?? data;
853
+ const { accessToken, refreshToken, user } = payload;
854
+ if (accessToken) {
855
+ const mapped = mapUser2(user, accountId);
856
+ ctx?._onAuthSuccess?.(accessToken, refreshToken, mapped);
857
+ onSuccess?.(accessToken, mapped);
858
+ if (redirectUrl) window.location.href = redirectUrl;
859
+ } else {
860
+ setStep("verify");
861
+ }
862
+ } catch {
863
+ setError("Unable to connect. Please try again.");
864
+ } finally {
865
+ setLoading(false);
866
+ }
867
+ }
868
+ function handleOAuth(provider) {
869
+ if (oauthProxyUrl) {
870
+ window.location.href = `${oauthProxyUrl}/${provider}`;
871
+ } else {
872
+ const redirect = redirectUrl ?? window.location.href;
873
+ window.location.href = `${baseUrl}/oauth/${provider}?redirect=${encodeURIComponent(redirect)}`;
874
+ }
875
+ }
876
+ const resolvedLogo = logoUrl ?? config?.branding?.logoUrl ?? "/kaappu-logo.png";
877
+ const cssVars = buildThemeVars(appearance, config?.branding?.primaryColor);
878
+ const isDark = resolveColorScheme(appearance?.colorScheme) === "dark";
879
+ if (step === "verify") {
880
+ return /* @__PURE__ */ jsx(
881
+ "div",
882
+ {
883
+ style: {
884
+ ...cssVars,
885
+ background: isDark ? "#030712" : "var(--k-bg, #f5f5fa)",
886
+ minHeight: isDark ? "100vh" : void 0,
887
+ display: "flex",
888
+ alignItems: "center",
889
+ justifyContent: "center",
890
+ padding: isDark ? void 0 : "1rem 0"
891
+ },
892
+ className,
893
+ children: /* @__PURE__ */ jsxs("div", { style: CARD2, children: [
894
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
895
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 40, marginBottom: 12 }, children: "\u2709\uFE0F" }),
896
+ /* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: 22, fontWeight: 700, color: "var(--k-text, #f0f6fc)" }, children: "Check your inbox" }),
897
+ /* @__PURE__ */ jsxs("p", { style: { margin: 0, fontSize: 14, color: "var(--k-muted, #8b949e)", lineHeight: 1.6 }, children: [
898
+ "We sent a verification link to",
899
+ " ",
900
+ /* @__PURE__ */ jsx("strong", { style: { color: "var(--k-text, #f0f6fc)" }, children: email }),
901
+ ". Click the link to activate your account."
902
+ ] })
903
+ ] }),
904
+ /* @__PURE__ */ jsx(ProtectedFooter, {})
905
+ ] })
906
+ }
907
+ );
908
+ }
909
+ return /* @__PURE__ */ jsxs(
910
+ "div",
911
+ {
912
+ style: {
913
+ ...cssVars,
914
+ background: isDark ? "#030712" : "var(--k-bg, #f5f5fa)",
915
+ minHeight: isDark ? "100vh" : void 0,
916
+ display: "flex",
917
+ alignItems: "center",
918
+ justifyContent: "center",
919
+ position: "relative",
920
+ overflow: "hidden",
921
+ padding: isDark ? "40px 16px" : "1rem 0"
922
+ },
923
+ className,
924
+ children: [
925
+ isDark && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: "radial-gradient(circle at 20% 30%, rgba(99,102,241,0.12) 0%, transparent 60%), radial-gradient(circle at 80% 70%, rgba(139,92,246,0.08) 0%, transparent 60%)", zIndex: 0 } }),
926
+ isDark && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, backgroundImage: "linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px)", backgroundSize: "40px 40px", zIndex: 1 } }),
927
+ /* @__PURE__ */ jsxs("div", { style: CARD2, children: [
928
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", marginBottom: 24 }, children: [
929
+ /* @__PURE__ */ jsx(
930
+ "img",
931
+ {
932
+ src: resolvedLogo,
933
+ alt: "Logo",
934
+ style: { height: isDark ? 96 : 52, marginBottom: 6, filter: isDark ? "drop-shadow(0 0 20px rgba(99,102,241,0.30))" : "none" }
935
+ }
936
+ ),
937
+ /* @__PURE__ */ jsx("h2", { style: { color: "var(--k-text, #f0f6fc)", fontSize: 22, fontWeight: 700, margin: "4px 0 6px 0" }, children: "Create your account" }),
938
+ /* @__PURE__ */ jsxs("p", { style: { color: "var(--k-muted, #8b949e)", fontSize: 13, margin: 0, textAlign: "center" }, children: [
939
+ "Join ",
940
+ config?.branding?.name ?? "Kaappu",
941
+ " to get started."
942
+ ] })
943
+ ] }),
944
+ (() => {
945
+ const allProviders = [
946
+ { id: "google", label: "Google", icon: /* @__PURE__ */ jsx(GoogleIcon2, {}) },
947
+ { id: "github", label: "GitHub", icon: /* @__PURE__ */ jsx(GithubIcon2, {}) },
948
+ { id: "microsoft", label: "Microsoft", icon: /* @__PURE__ */ jsx(MicrosoftIcon2, {}) },
949
+ { id: "apple", label: "Apple", icon: /* @__PURE__ */ jsx(AppleIcon2, {}) }
950
+ ];
951
+ const visible = allProviders.filter((p) => {
952
+ if (allowedProviders && allowedProviders.length > 0) return allowedProviders.includes(p.id);
953
+ return config?.authMethods?.[p.id] !== false;
954
+ });
955
+ if (visible.length === 0) return null;
956
+ if (visible.length === 1) {
957
+ const p = visible[0];
958
+ return /* @__PURE__ */ jsxs(
959
+ "button",
960
+ {
961
+ type: "button",
962
+ onClick: () => handleOAuth(p.id),
963
+ style: {
964
+ ...OAUTH_BTN2,
965
+ gap: 10,
966
+ padding: "12px 16px",
967
+ width: "100%",
968
+ marginBottom: 14,
969
+ fontSize: 14,
970
+ fontWeight: 500
971
+ },
972
+ onMouseEnter: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.08))",
973
+ onMouseLeave: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.04))",
974
+ children: [
975
+ p.icon,
976
+ /* @__PURE__ */ jsxs("span", { children: [
977
+ "Continue with ",
978
+ p.label
979
+ ] })
980
+ ]
981
+ }
982
+ );
983
+ }
984
+ const cols = Math.min(visible.length, 4);
985
+ return /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: 10, marginBottom: 14 }, children: visible.map((p) => /* @__PURE__ */ jsx(
986
+ "button",
987
+ {
988
+ type: "button",
989
+ onClick: () => handleOAuth(p.id),
990
+ style: OAUTH_BTN2,
991
+ onMouseEnter: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.08))",
992
+ onMouseLeave: (e) => e.currentTarget.style.background = "var(--k-hover, rgba(255,255,255,0.04))",
993
+ children: p.icon
994
+ },
995
+ p.id
996
+ )) });
997
+ })(),
998
+ /* @__PURE__ */ jsxs("div", { style: {
999
+ display: "flex",
1000
+ alignItems: "center",
1001
+ gap: 12,
1002
+ marginTop: 4,
1003
+ marginBottom: 18,
1004
+ color: "var(--k-muted, #484f58)",
1005
+ fontSize: 11,
1006
+ fontWeight: 700,
1007
+ textTransform: "uppercase",
1008
+ letterSpacing: "0.1em"
1009
+ }, children: [
1010
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "var(--k-border, rgba(255,255,255,0.06))" } }),
1011
+ "or with email",
1012
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: "var(--k-border, rgba(255,255,255,0.06))" } })
1013
+ ] }),
1014
+ error && /* @__PURE__ */ jsxs("div", { style: ERROR_BANNER2, children: [
1015
+ /* @__PURE__ */ jsx(AlertIcon, {}),
1016
+ /* @__PURE__ */ jsx("span", { children: error })
1017
+ ] }),
1018
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSignUp, children: [
1019
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 14 }, children: [
1020
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
1021
+ /* @__PURE__ */ jsx("label", { style: LABEL2, children: "First name" }),
1022
+ /* @__PURE__ */ jsxs("div", { style: INPUT_ROW2, children: [
1023
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX2, children: /* @__PURE__ */ jsx(UserIcon, {}) }),
1024
+ /* @__PURE__ */ jsx(
1025
+ "input",
1026
+ {
1027
+ type: "text",
1028
+ value: firstName,
1029
+ onChange: (e) => setFirstName(e.target.value),
1030
+ placeholder: "Jane",
1031
+ required: true,
1032
+ autoComplete: "given-name",
1033
+ style: { ...INPUT2, minWidth: 0, width: "100%" }
1034
+ }
1035
+ )
1036
+ ] })
1037
+ ] }),
1038
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
1039
+ /* @__PURE__ */ jsx("label", { style: LABEL2, children: "Last name" }),
1040
+ /* @__PURE__ */ jsxs("div", { style: INPUT_ROW2, children: [
1041
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX2, children: /* @__PURE__ */ jsx(UserIcon, {}) }),
1042
+ /* @__PURE__ */ jsx(
1043
+ "input",
1044
+ {
1045
+ type: "text",
1046
+ value: lastName,
1047
+ onChange: (e) => setLastName(e.target.value),
1048
+ placeholder: "Doe",
1049
+ required: true,
1050
+ autoComplete: "family-name",
1051
+ style: { ...INPUT2, minWidth: 0, width: "100%" }
1052
+ }
1053
+ )
1054
+ ] })
1055
+ ] })
1056
+ ] }),
1057
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 14 }, children: [
1058
+ /* @__PURE__ */ jsx("label", { style: LABEL2, children: "Work email" }),
1059
+ /* @__PURE__ */ jsxs("div", { style: INPUT_ROW2, children: [
1060
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX2, children: /* @__PURE__ */ jsx(MailIcon2, {}) }),
1061
+ /* @__PURE__ */ jsx(
1062
+ "input",
1063
+ {
1064
+ type: "email",
1065
+ value: email,
1066
+ onChange: (e) => setEmail(e.target.value),
1067
+ placeholder: "you@company.com",
1068
+ required: true,
1069
+ autoComplete: "email",
1070
+ style: INPUT2
1071
+ }
1072
+ )
1073
+ ] })
1074
+ ] }),
1075
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
1076
+ /* @__PURE__ */ jsx("label", { style: LABEL2, children: "Password" }),
1077
+ /* @__PURE__ */ jsxs("div", { style: INPUT_ROW2, children: [
1078
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX2, children: /* @__PURE__ */ jsx(LockIcon2, {}) }),
1079
+ /* @__PURE__ */ jsx(
1080
+ "input",
1081
+ {
1082
+ type: showPassword ? "text" : "password",
1083
+ value: password,
1084
+ onChange: (e) => setPassword(e.target.value),
1085
+ placeholder: "At least 8 characters",
1086
+ required: true,
1087
+ minLength: 8,
1088
+ autoComplete: "new-password",
1089
+ style: { ...INPUT2, paddingRight: 0 }
1090
+ }
1091
+ ),
1092
+ /* @__PURE__ */ jsx(
1093
+ "button",
1094
+ {
1095
+ type: "button",
1096
+ onClick: () => setShowPassword(!showPassword),
1097
+ "aria-label": showPassword ? "Hide password" : "Show password",
1098
+ style: { width: 44, height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "none", border: "none", cursor: "pointer", color: "var(--k-muted, #6b7280)" },
1099
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon2, {}) : /* @__PURE__ */ jsx(EyeIcon2, {})
1100
+ }
1101
+ )
1102
+ ] }),
1103
+ password.length > 0 && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px 14px", marginTop: 8, marginLeft: 4, fontSize: 11 }, children: [
1104
+ /* @__PURE__ */ jsx(RuleChip, { ok: rules.length, label: "8+ characters" }),
1105
+ /* @__PURE__ */ jsx(RuleChip, { ok: rules.letter, label: "letter" }),
1106
+ /* @__PURE__ */ jsx(RuleChip, { ok: rules.number, label: "number" }),
1107
+ /* @__PURE__ */ jsx(RuleChip, { ok: rules.symbol, label: "symbol (recommended)", optional: true })
1108
+ ] })
1109
+ ] }),
1110
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
1111
+ /* @__PURE__ */ jsx("label", { style: LABEL2, children: "Confirm password" }),
1112
+ /* @__PURE__ */ jsxs("div", { style: {
1113
+ ...INPUT_ROW2,
1114
+ ...confirm.length > 0 && {
1115
+ borderColor: passwordsMatch ? "rgba(52,211,153,0.40)" : "rgba(248,81,73,0.40)"
1116
+ }
1117
+ }, children: [
1118
+ /* @__PURE__ */ jsx("div", { style: ICON_BOX2, children: /* @__PURE__ */ jsx(LockIcon2, {}) }),
1119
+ /* @__PURE__ */ jsx(
1120
+ "input",
1121
+ {
1122
+ type: showPassword ? "text" : "password",
1123
+ value: confirm,
1124
+ onChange: (e) => setConfirm(e.target.value),
1125
+ placeholder: "Re-enter password",
1126
+ required: true,
1127
+ autoComplete: "new-password",
1128
+ style: INPUT2
1129
+ }
1130
+ ),
1131
+ confirm.length > 0 && /* @__PURE__ */ jsx("div", { style: { width: 44, display: "flex", alignItems: "center", justifyContent: "center" }, children: passwordsMatch ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(XIcon, {}) })
1132
+ ] })
1133
+ ] }),
1134
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: !canSubmit, style: {
1135
+ ...PRIMARY_BTN2,
1136
+ background: canSubmit ? PRIMARY_BTN2.background : "rgba(99,102,241,0.30)",
1137
+ boxShadow: canSubmit ? PRIMARY_BTN2.boxShadow : "none",
1138
+ cursor: canSubmit ? "pointer" : "not-allowed"
1139
+ }, children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
1140
+ /* @__PURE__ */ jsx(SpinnerIcon2, {}),
1141
+ " Creating account\u2026"
1142
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1143
+ "Create account ",
1144
+ /* @__PURE__ */ jsx(ArrowIcon2, {})
1145
+ ] }) }),
1146
+ /* @__PURE__ */ jsx("p", { style: { margin: "14px 0 0", textAlign: "center", fontSize: 12, color: "var(--k-muted, #8b949e)" }, children: "By continuing, you agree to the Terms of Service and Privacy Policy." })
1147
+ ] }),
1148
+ /* @__PURE__ */ jsxs("p", { style: { textAlign: "center", fontSize: 13, color: "var(--k-muted, #8b949e)", marginTop: 22 }, children: [
1149
+ signInPrompt,
1150
+ " ",
1151
+ /* @__PURE__ */ jsx("a", { href: signInPath, style: { color: "var(--k-primary, #818cf8)", textDecoration: "none", fontWeight: 600 }, children: signInLabel })
1152
+ ] }),
1153
+ /* @__PURE__ */ jsx(ProtectedFooter, {})
1154
+ ] }),
1155
+ /* @__PURE__ */ jsx("style", { children: `@keyframes kaappu-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` })
1156
+ ]
1157
+ }
1158
+ );
1159
+ }
1160
+ function RuleChip({ ok, label, optional }) {
1161
+ const color = ok ? "#34d399" : optional ? "#6b7280" : "#9ca3af";
1162
+ return /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 4, color, fontWeight: 500 }, children: [
1163
+ ok ? /* @__PURE__ */ jsx(CheckIcon, { size: 12 }) : /* @__PURE__ */ jsx("span", { style: { width: 8, height: 8, borderRadius: "50%", border: `1.5px solid ${color}`, display: "inline-block" } }),
1164
+ label
1165
+ ] });
1166
+ }
1167
+ function ProtectedFooter() {
1168
+ return /* @__PURE__ */ jsx("div", { style: { marginTop: 26, borderTop: "1px solid var(--k-border, rgba(255,255,255,0.06))", paddingTop: 18, textAlign: "center" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "inline-flex", alignItems: "center", gap: 8, opacity: 0.5 }, children: [
1169
+ /* @__PURE__ */ jsx(ShieldIcon2, {}),
1170
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 9, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.28em", color: "var(--k-muted, #8b949e)" }, children: "Protected by Kaappu" })
1171
+ ] }) });
1172
+ }
1173
+ function friendlyError(code) {
1174
+ const map = {
1175
+ email_already_exists: "An account with this email already exists. Try signing in instead.",
1176
+ email_exists: "An account with this email already exists. Try signing in instead.",
1177
+ password_breached: "This password has appeared in a known data breach. Please choose a different one.",
1178
+ weak_password: "Password is too weak. Use at least 8 characters with letters and numbers.",
1179
+ password_too_weak: "Password is too weak. Use at least 8 characters with letters and numbers.",
1180
+ password_too_short: "Password must be at least 8 characters.",
1181
+ invalid_email: "Please enter a valid email address.",
1182
+ invalid_password: "Password does not meet the requirements.",
1183
+ email_domain_not_allowed: "Sign-ups from this email domain are not allowed.",
1184
+ email_blocked: "This email address cannot be used.",
1185
+ bot_protection_failed: "Bot protection check failed. Please try again.",
1186
+ rate_limited: "Too many attempts. Please wait a minute and try again."
1187
+ };
1188
+ return map[code] ?? code ?? "Registration failed. Please try again.";
1189
+ }
1190
+ function mapUser2(raw, accountId) {
1191
+ return {
1192
+ id: raw.id ?? raw.userId,
1193
+ email: raw.email,
1194
+ firstName: raw.firstName,
1195
+ lastName: raw.lastName,
1196
+ avatarUrl: raw.avatarUrl,
1197
+ emailVerified: raw.emailVerified,
1198
+ mfaEnabled: raw.mfaEnabled,
1199
+ accountId: raw.accountId ?? accountId,
1200
+ sessionId: raw.sessionId ?? "",
1201
+ roles: raw.roles ?? [],
1202
+ permissions: raw.permissions ?? []
1203
+ };
1204
+ }
1205
+ var CARD2 = {
1206
+ width: "100%",
1207
+ maxWidth: 460,
1208
+ background: "var(--k-card-bg, rgba(13, 17, 23, 0.92))",
1209
+ border: "1px solid var(--k-border, rgba(255,255,255,0.10))",
1210
+ borderRadius: 24,
1211
+ padding: "40px 38px",
1212
+ position: "relative",
1213
+ zIndex: 10,
1214
+ backdropFilter: "blur(20px)",
1215
+ boxShadow: "var(--k-shadow, 0 25px 80px rgba(0,0,0,0.5))"
1216
+ };
1217
+ var INPUT_ROW2 = {
1218
+ display: "flex",
1219
+ alignItems: "center",
1220
+ background: "var(--k-hover, rgba(255,255,255,0.03))",
1221
+ border: "1px solid var(--k-border, rgba(255,255,255,0.10))",
1222
+ borderRadius: 12,
1223
+ height: 48,
1224
+ overflow: "hidden",
1225
+ transition: "border-color 0.15s ease"
1226
+ };
1227
+ var INPUT2 = {
1228
+ flex: 1,
1229
+ background: "transparent",
1230
+ border: "none",
1231
+ outline: "none",
1232
+ color: "var(--k-text, #f0f6fc)",
1233
+ fontSize: 14,
1234
+ paddingRight: 14,
1235
+ WebkitTextFillColor: "var(--k-text, #f0f6fc)",
1236
+ minWidth: 0
1237
+ };
1238
+ var ICON_BOX2 = {
1239
+ width: 44,
1240
+ display: "flex",
1241
+ alignItems: "center",
1242
+ justifyContent: "center",
1243
+ color: "var(--k-muted, #6b7280)"
1244
+ };
1245
+ var LABEL2 = {
1246
+ display: "block",
1247
+ fontSize: 11,
1248
+ fontWeight: 700,
1249
+ color: "var(--k-muted, #9ca3af)",
1250
+ textTransform: "uppercase",
1251
+ letterSpacing: "0.08em",
1252
+ marginBottom: 6,
1253
+ marginLeft: 2
1254
+ };
1255
+ var PRIMARY_BTN2 = {
1256
+ width: "100%",
1257
+ height: 50,
1258
+ marginTop: 4,
1259
+ background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
1260
+ color: "white",
1261
+ border: "none",
1262
+ borderRadius: 12,
1263
+ fontSize: 14,
1264
+ fontWeight: 700,
1265
+ boxShadow: "0 10px 25px rgba(99,102,241,0.30)",
1266
+ display: "flex",
1267
+ alignItems: "center",
1268
+ justifyContent: "center",
1269
+ gap: 10,
1270
+ transition: "all 0.15s ease"
1271
+ };
1272
+ var OAUTH_BTN2 = {
1273
+ padding: 10,
1274
+ display: "flex",
1275
+ alignItems: "center",
1276
+ justifyContent: "center",
1277
+ background: "var(--k-hover, rgba(255,255,255,0.04))",
1278
+ border: "1px solid var(--k-border, rgba(255,255,255,0.08))",
1279
+ borderRadius: 12,
1280
+ cursor: "pointer",
1281
+ color: "var(--k-text, #f0f6fc)",
1282
+ height: 46
1283
+ };
1284
+ var ERROR_BANNER2 = {
1285
+ background: "rgba(248,81,73,0.10)",
1286
+ border: "1px solid rgba(248,81,73,0.25)",
1287
+ borderRadius: 10,
1288
+ padding: "10px 14px",
1289
+ fontSize: 13,
1290
+ color: "#f87171",
1291
+ display: "flex",
1292
+ alignItems: "flex-start",
1293
+ gap: 8,
1294
+ marginBottom: 14
1295
+ };
1296
+ var GoogleIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", children: [
1297
+ /* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
1298
+ /* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
1299
+ /* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z", fill: "#FBBC05" }),
1300
+ /* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.66l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
1301
+ ] });
1302
+ var GithubIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M12 2C6.477 2 2 6.477 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0012 2z" }) });
1303
+ var MicrosoftIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 23 23", width: "18", height: "18", children: /* @__PURE__ */ jsx("path", { fill: "#f3f3f3", d: "M0 0h11v11H0zM12 0h11v11H12zM0 12h11v11H0zM12 12h11v11H12z" }) });
1304
+ var AppleIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 384 512", width: "20", height: "20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z" }) });
1305
+ var UserIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1306
+ /* @__PURE__ */ jsx("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }),
1307
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "7", r: "4" })
1308
+ ] });
1309
+ var MailIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1310
+ /* @__PURE__ */ jsx("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }),
1311
+ /* @__PURE__ */ jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
1312
+ ] });
1313
+ var LockIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1314
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }),
1315
+ /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
1316
+ ] });
1317
+ var EyeIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1318
+ /* @__PURE__ */ jsx("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
1319
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
1320
+ ] });
1321
+ var EyeOffIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1322
+ /* @__PURE__ */ jsx("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
1323
+ /* @__PURE__ */ jsx("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
1324
+ /* @__PURE__ */ jsx("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
1325
+ /* @__PURE__ */ jsx("path", { d: "m2 2 20 20" })
1326
+ ] });
1327
+ var ArrowIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1328
+ /* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
1329
+ /* @__PURE__ */ jsx("path", { d: "m12 5 7 7-7 7" })
1330
+ ] });
1331
+ var ShieldIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "12", height: "12", fill: "none", stroke: "#8b949e", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }) });
1332
+ var SpinnerIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { animation: "kaappu-spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1333
+ var AlertIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0, marginTop: 1 }, children: [
1334
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1335
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "8", y2: "12" }),
1336
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" })
1337
+ ] });
1338
+ var CheckIcon = ({ size = 16 }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: "#34d399", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
1339
+ var XIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "#f87171", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: [
1340
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1341
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1342
+ ] });
1343
+ function ProfileBadge({
1344
+ afterSignOutUrl = "/sign-in",
1345
+ userProfileUrl = "/account",
1346
+ appearance,
1347
+ className
1348
+ }) {
1349
+ const ctx = useContext(KaappuContext);
1350
+ if (!ctx) throw new Error("[Kaappu] <ProfileBadge> must be inside <KaappuProvider>");
1351
+ const { user, isLoaded, isSignedIn, signOut, tenantConfig } = ctx;
1352
+ const [open, setOpen] = useState(false);
1353
+ const wrapperRef = useRef(null);
1354
+ useEffect(() => {
1355
+ function handleClick(e) {
1356
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
1357
+ setOpen(false);
1358
+ }
1359
+ }
1360
+ if (open) document.addEventListener("mousedown", handleClick);
1361
+ return () => document.removeEventListener("mousedown", handleClick);
1362
+ }, [open]);
1363
+ if (!isLoaded || !isSignedIn || !user) return null;
1364
+ const themeVars = buildThemeVars(appearance, tenantConfig?.branding?.primaryColor);
1365
+ const initials = [user.firstName, user.lastName].filter(Boolean).map((n) => n[0].toUpperCase()).join("") || user.email[0].toUpperCase();
1366
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
1367
+ async function handleSignOut() {
1368
+ setOpen(false);
1369
+ await signOut();
1370
+ window.location.href = afterSignOutUrl;
1371
+ }
1372
+ return /* @__PURE__ */ jsxs(
1373
+ "div",
1374
+ {
1375
+ ref: wrapperRef,
1376
+ style: { position: "relative", display: "inline-block", ...themeVars, fontFamily: "var(--k-font)" },
1377
+ children: [
1378
+ /* @__PURE__ */ jsxs(
1379
+ "button",
1380
+ {
1381
+ onClick: () => setOpen((o) => !o),
1382
+ className,
1383
+ "aria-haspopup": "true",
1384
+ "aria-expanded": open,
1385
+ style: {
1386
+ display: "flex",
1387
+ alignItems: "center",
1388
+ gap: "0.5rem",
1389
+ padding: "0.25rem 0.5rem 0.25rem 0.25rem",
1390
+ background: open ? "var(--k-hover)" : "transparent",
1391
+ border: "1px solid transparent",
1392
+ borderRadius: "2rem",
1393
+ cursor: "pointer",
1394
+ color: "var(--k-text)",
1395
+ fontFamily: "var(--k-font)",
1396
+ transition: "background 0.15s ease, border-color 0.15s ease"
1397
+ },
1398
+ onMouseEnter: (e) => {
1399
+ const el = e.currentTarget;
1400
+ el.style.background = "var(--k-hover)";
1401
+ el.style.borderColor = "var(--k-border)";
1402
+ },
1403
+ onMouseLeave: (e) => {
1404
+ if (!open) {
1405
+ const el = e.currentTarget;
1406
+ el.style.background = "transparent";
1407
+ el.style.borderColor = "transparent";
1408
+ }
1409
+ },
1410
+ children: [
1411
+ /* @__PURE__ */ jsx(
1412
+ "div",
1413
+ {
1414
+ style: {
1415
+ width: "32px",
1416
+ height: "32px",
1417
+ borderRadius: "50%",
1418
+ background: user.avatarUrl ? "transparent" : "var(--k-primary)",
1419
+ color: "var(--k-primary-fg)",
1420
+ display: "flex",
1421
+ alignItems: "center",
1422
+ justifyContent: "center",
1423
+ fontSize: "0.8125rem",
1424
+ fontWeight: 700,
1425
+ flexShrink: 0,
1426
+ overflow: "hidden"
1427
+ },
1428
+ children: user.avatarUrl ? /* @__PURE__ */ jsx("img", { src: user.avatarUrl, alt: displayName, style: { width: "100%", height: "100%", objectFit: "cover" } }) : initials
1429
+ }
1430
+ ),
1431
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", fontWeight: 500, maxWidth: "120px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: displayName }),
1432
+ /* @__PURE__ */ jsx(
1433
+ "svg",
1434
+ {
1435
+ width: "12",
1436
+ height: "12",
1437
+ viewBox: "0 0 12 12",
1438
+ fill: "none",
1439
+ style: { color: "var(--k-muted)", transform: open ? "rotate(180deg)" : "none", transition: "transform 0.15s ease", flexShrink: 0 },
1440
+ children: /* @__PURE__ */ jsx("path", { d: "M2 4L6 8L10 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
1441
+ }
1442
+ )
1443
+ ]
1444
+ }
1445
+ ),
1446
+ open && /* @__PURE__ */ jsxs(
1447
+ "div",
1448
+ {
1449
+ role: "menu",
1450
+ style: {
1451
+ position: "absolute",
1452
+ top: "calc(100% + 0.5rem)",
1453
+ right: 0,
1454
+ minWidth: "220px",
1455
+ background: "var(--k-card-bg)",
1456
+ border: "1px solid var(--k-border)",
1457
+ borderRadius: "var(--k-radius)",
1458
+ boxShadow: "var(--k-shadow)",
1459
+ padding: "0.5rem",
1460
+ zIndex: 9999
1461
+ },
1462
+ children: [
1463
+ /* @__PURE__ */ jsxs("div", { style: {
1464
+ padding: "0.5rem 0.75rem 0.75rem",
1465
+ borderBottom: "1px solid var(--k-border)",
1466
+ marginBottom: "0.375rem"
1467
+ }, children: [
1468
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", fontWeight: 600, color: "var(--k-text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: displayName }),
1469
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: "var(--k-muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: "0.125rem" }, children: user.email })
1470
+ ] }),
1471
+ /* @__PURE__ */ jsx(
1472
+ DropdownItem,
1473
+ {
1474
+ href: userProfileUrl,
1475
+ icon: "\u2699\uFE0F",
1476
+ label: "Manage account",
1477
+ onClick: () => setOpen(false)
1478
+ }
1479
+ ),
1480
+ /* @__PURE__ */ jsx("div", { style: { height: "1px", background: "var(--k-border)", margin: "0.375rem 0" } }),
1481
+ /* @__PURE__ */ jsx(
1482
+ DropdownItem,
1483
+ {
1484
+ icon: "\u2192",
1485
+ label: "Sign out",
1486
+ onClick: handleSignOut,
1487
+ danger: true
1488
+ }
1489
+ )
1490
+ ]
1491
+ }
1492
+ )
1493
+ ]
1494
+ }
1495
+ );
1496
+ }
1497
+ function DropdownItem({
1498
+ href,
1499
+ icon,
1500
+ label,
1501
+ onClick,
1502
+ danger = false
1503
+ }) {
1504
+ const baseStyle = {
1505
+ display: "flex",
1506
+ alignItems: "center",
1507
+ gap: "0.625rem",
1508
+ width: "100%",
1509
+ padding: "0.5rem 0.75rem",
1510
+ borderRadius: "calc(var(--k-radius) * 0.5)",
1511
+ fontSize: "0.875rem",
1512
+ fontWeight: 500,
1513
+ color: danger ? "#ef4444" : "var(--k-text)",
1514
+ background: "transparent",
1515
+ border: "none",
1516
+ cursor: "pointer",
1517
+ textDecoration: "none",
1518
+ fontFamily: "var(--k-font)",
1519
+ textAlign: "left",
1520
+ boxSizing: "border-box"
1521
+ };
1522
+ const hoverStyle = { background: "var(--k-hover)" };
1523
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1524
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem" }, children: icon }),
1525
+ label
1526
+ ] });
1527
+ if (href) {
1528
+ return /* @__PURE__ */ jsx(
1529
+ "a",
1530
+ {
1531
+ href,
1532
+ style: baseStyle,
1533
+ role: "menuitem",
1534
+ onClick,
1535
+ onMouseEnter: (e) => Object.assign(e.currentTarget.style, hoverStyle),
1536
+ onMouseLeave: (e) => {
1537
+ e.currentTarget.style.background = "transparent";
1538
+ },
1539
+ children: content
1540
+ }
1541
+ );
1542
+ }
1543
+ return /* @__PURE__ */ jsx(
1544
+ "button",
1545
+ {
1546
+ style: baseStyle,
1547
+ role: "menuitem",
1548
+ onClick,
1549
+ onMouseEnter: (e) => Object.assign(e.currentTarget.style, hoverStyle),
1550
+ onMouseLeave: (e) => {
1551
+ e.currentTarget.style.background = "transparent";
1552
+ },
1553
+ children: content
1554
+ }
1555
+ );
1556
+ }
1557
+ function AccountView({ appearance, className }) {
1558
+ const ctx = useContext(KaappuContext);
1559
+ if (!ctx) throw new Error("[Kaappu] <AccountView> must be inside <KaappuProvider>");
1560
+ const { user, isLoaded, isSignedIn, tenantConfig, getToken } = ctx;
1561
+ const baseUrl = ctx._baseUrl ?? "http://localhost:9091";
1562
+ const [tab, setTab] = useState("profile");
1563
+ if (!isLoaded) return /* @__PURE__ */ jsx(Skeleton, {});
1564
+ if (!isSignedIn || !user) return null;
1565
+ const themeVars = buildThemeVars(appearance, tenantConfig?.branding?.primaryColor);
1566
+ const accountId = user.accountId ?? "default";
1567
+ return /* @__PURE__ */ jsxs("div", { style: { ...themeVars, fontFamily: "var(--k-font)", color: "var(--k-text)", width: "100%" }, className, children: [
1568
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "0", borderBottom: "1px solid var(--k-border)", marginBottom: "1.5rem", overflowX: "auto" }, children: ["profile", "security", "sessions", "passkeys"].map((t) => /* @__PURE__ */ jsx(
1569
+ "button",
1570
+ {
1571
+ onClick: () => setTab(t),
1572
+ style: {
1573
+ padding: "0.625rem 1rem",
1574
+ background: "none",
1575
+ border: "none",
1576
+ borderBottom: tab === t ? "2px solid var(--k-primary)" : "2px solid transparent",
1577
+ color: tab === t ? "var(--k-text)" : "var(--k-muted)",
1578
+ fontWeight: tab === t ? 600 : 400,
1579
+ cursor: "pointer",
1580
+ fontSize: "0.875rem",
1581
+ fontFamily: "var(--k-font)",
1582
+ whiteSpace: "nowrap",
1583
+ textTransform: "capitalize"
1584
+ },
1585
+ children: t
1586
+ },
1587
+ t
1588
+ )) }),
1589
+ tab === "profile" && /* @__PURE__ */ jsx(ProfileTab, { user, baseUrl, accountId, getToken }),
1590
+ tab === "security" && /* @__PURE__ */ jsx(SecurityTab, { user, baseUrl, accountId, getToken }),
1591
+ tab === "sessions" && /* @__PURE__ */ jsx(SessionsTab, { baseUrl, accountId, getToken, currentSessionId: user.sessionId }),
1592
+ tab === "passkeys" && /* @__PURE__ */ jsx(PasskeysTab, { baseUrl, accountId, getToken })
1593
+ ] });
1594
+ }
1595
+ function ProfileTab({ user, baseUrl, accountId, getToken }) {
1596
+ const [firstName, setFirstName] = useState(user.firstName ?? "");
1597
+ const [lastName, setLastName] = useState(user.lastName ?? "");
1598
+ const [phoneNumber, setPhoneNumber] = useState(user.phoneNumber ?? "");
1599
+ const [saving, setSaving] = useState(false);
1600
+ const [saved, setSaved] = useState(false);
1601
+ const [error, setError] = useState(null);
1602
+ async function handleSave(e) {
1603
+ e.preventDefault();
1604
+ setError(null);
1605
+ setSaving(true);
1606
+ try {
1607
+ const token = await getToken();
1608
+ const res = await fetch(`${baseUrl}/api/v1/idm/users/me`, {
1609
+ method: "PATCH",
1610
+ headers: {
1611
+ "Content-Type": "application/json",
1612
+ ...token ? { Authorization: `Bearer ${token}` } : {}
1613
+ },
1614
+ body: JSON.stringify({ firstName, lastName })
1615
+ });
1616
+ if (!res.ok) {
1617
+ const d = await res.json().catch(() => ({}));
1618
+ setError(d.error ?? "Failed to save changes");
1619
+ } else {
1620
+ setSaved(true);
1621
+ setTimeout(() => setSaved(false), 2500);
1622
+ }
1623
+ } catch {
1624
+ setError("Unable to connect.");
1625
+ } finally {
1626
+ setSaving(false);
1627
+ }
1628
+ }
1629
+ return /* @__PURE__ */ jsxs("div", { style: { maxWidth: "480px" }, children: [
1630
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Personal information" }),
1631
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "1.25rem" }, children: [
1632
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Email address" }),
1633
+ /* @__PURE__ */ jsxs("div", { style: { ...inputStyle, color: "var(--k-muted)", cursor: "default", userSelect: "text" }, children: [
1634
+ user.email,
1635
+ user.emailVerified && /* @__PURE__ */ jsx("span", { style: { marginLeft: "0.5rem", fontSize: "0.75rem", color: "#34d399" }, children: "\u2713 Verified" })
1636
+ ] })
1637
+ ] }),
1638
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSave, style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
1639
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.75rem" }, children: [
1640
+ /* @__PURE__ */ jsxs("div", { children: [
1641
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "First name" }),
1642
+ /* @__PURE__ */ jsx("input", { style: inputStyle, value: firstName, onChange: (e) => setFirstName(e.target.value), placeholder: "Jane" })
1643
+ ] }),
1644
+ /* @__PURE__ */ jsxs("div", { children: [
1645
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Last name" }),
1646
+ /* @__PURE__ */ jsx("input", { style: inputStyle, value: lastName, onChange: (e) => setLastName(e.target.value), placeholder: "Smith" })
1647
+ ] })
1648
+ ] }),
1649
+ /* @__PURE__ */ jsxs("div", { children: [
1650
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Phone number" }),
1651
+ /* @__PURE__ */ jsx("input", { style: inputStyle, value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "+1 (555) 000-0000", type: "tel" })
1652
+ ] }),
1653
+ error && /* @__PURE__ */ jsx("p", { style: errorStyle, children: error }),
1654
+ /* @__PURE__ */ jsx("button", { style: { ...primaryButtonStyle, maxWidth: "160px" }, type: "submit", disabled: saving, children: saving ? "Saving\u2026" : saved ? "Saved \u2713" : "Save changes" })
1655
+ ] })
1656
+ ] });
1657
+ }
1658
+ function SecurityTab({ user, baseUrl, accountId, getToken }) {
1659
+ const [pwOld, setPwOld] = useState("");
1660
+ const [pwNew, setPwNew] = useState("");
1661
+ const [pwSaving, setPwSaving] = useState(false);
1662
+ const [pwError, setPwError] = useState(null);
1663
+ const [pwDone, setPwDone] = useState(false);
1664
+ const [totpUri, setTotpUri] = useState(null);
1665
+ const [totpCode, setTotpCode] = useState("");
1666
+ const [totpStep, setTotpStep] = useState("idle");
1667
+ const [totpError, setTotpError] = useState(null);
1668
+ async function handleChangePassword(e) {
1669
+ e.preventDefault();
1670
+ setPwError(null);
1671
+ setPwSaving(true);
1672
+ try {
1673
+ const token = await getToken();
1674
+ const res = await fetch(`${baseUrl}/api/v1/idm/auth/change-password`, {
1675
+ method: "POST",
1676
+ headers: {
1677
+ "Content-Type": "application/json",
1678
+ ...token ? { Authorization: `Bearer ${token}` } : {}
1679
+ },
1680
+ body: JSON.stringify({ currentPassword: pwOld, newPassword: pwNew, accountId })
1681
+ });
1682
+ const d = await res.json().catch(() => ({}));
1683
+ if (!res.ok) {
1684
+ setPwError(d.error ?? "Failed to change password");
1685
+ } else {
1686
+ setPwDone(true);
1687
+ setPwOld("");
1688
+ setPwNew("");
1689
+ }
1690
+ } catch {
1691
+ setPwError("Unable to connect.");
1692
+ } finally {
1693
+ setPwSaving(false);
1694
+ }
1695
+ }
1696
+ async function startTotpEnroll() {
1697
+ setTotpError(null);
1698
+ const token = await getToken();
1699
+ const res = await fetch(`${baseUrl}/api/v1/idm/auth/totp/enroll`, {
1700
+ method: "POST",
1701
+ headers: {
1702
+ "Content-Type": "application/json",
1703
+ ...token ? { Authorization: `Bearer ${token}` } : {}
1704
+ },
1705
+ body: JSON.stringify({ accountId })
1706
+ });
1707
+ const d = await res.json().catch(() => ({}));
1708
+ if (res.ok && d.data?.uri) {
1709
+ setTotpUri(d.data.uri);
1710
+ setTotpStep("enroll");
1711
+ } else {
1712
+ setTotpError(d.error ?? "Failed to start TOTP enrollment");
1713
+ }
1714
+ }
1715
+ async function confirmTotp(e) {
1716
+ e.preventDefault();
1717
+ setTotpError(null);
1718
+ const token = await getToken();
1719
+ const res = await fetch(`${baseUrl}/api/v1/idm/auth/totp/confirm`, {
1720
+ method: "POST",
1721
+ headers: {
1722
+ "Content-Type": "application/json",
1723
+ ...token ? { Authorization: `Bearer ${token}` } : {}
1724
+ },
1725
+ body: JSON.stringify({ code: totpCode, accountId })
1726
+ });
1727
+ const d = await res.json().catch(() => ({}));
1728
+ if (res.ok) {
1729
+ setTotpStep("done");
1730
+ } else {
1731
+ setTotpError(d.error ?? "Invalid code");
1732
+ }
1733
+ }
1734
+ return /* @__PURE__ */ jsxs("div", { style: { maxWidth: "480px", display: "flex", flexDirection: "column", gap: "2rem" }, children: [
1735
+ /* @__PURE__ */ jsxs("section", { children: [
1736
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Change password" }),
1737
+ pwDone ? /* @__PURE__ */ jsx("p", { style: { color: "#34d399", fontSize: "0.875rem" }, children: "Password updated successfully." }) : /* @__PURE__ */ jsxs("form", { onSubmit: handleChangePassword, style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
1738
+ /* @__PURE__ */ jsxs("div", { children: [
1739
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Current password" }),
1740
+ /* @__PURE__ */ jsx("input", { style: inputStyle, type: "password", value: pwOld, onChange: (e) => setPwOld(e.target.value), required: true, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: "current-password" })
1741
+ ] }),
1742
+ /* @__PURE__ */ jsxs("div", { children: [
1743
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "New password" }),
1744
+ /* @__PURE__ */ jsx("input", { style: inputStyle, type: "password", value: pwNew, onChange: (e) => setPwNew(e.target.value), required: true, minLength: 8, placeholder: "Min. 8 characters", autoComplete: "new-password" })
1745
+ ] }),
1746
+ pwError && /* @__PURE__ */ jsx("p", { style: errorStyle, children: pwError }),
1747
+ /* @__PURE__ */ jsx("button", { style: { ...primaryButtonStyle, maxWidth: "180px" }, type: "submit", disabled: pwSaving, children: pwSaving ? "Updating\u2026" : "Update password" })
1748
+ ] })
1749
+ ] }),
1750
+ /* @__PURE__ */ jsxs("section", { children: [
1751
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Authenticator app (TOTP)" }),
1752
+ user.mfaEnabled && totpStep !== "done" && /* @__PURE__ */ jsx(StatusBadge, { color: "#34d399", children: "MFA enabled" }),
1753
+ !user.mfaEnabled && totpStep === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
1754
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "var(--k-muted)", marginBottom: "1rem", lineHeight: 1.6 }, children: "Add an extra layer of security by requiring a code from your authenticator app at sign-in." }),
1755
+ totpError && /* @__PURE__ */ jsx("p", { style: errorStyle, children: totpError }),
1756
+ /* @__PURE__ */ jsx("button", { style: { ...ghostButtonStyle, maxWidth: "200px" }, onClick: startTotpEnroll, children: "Set up authenticator" })
1757
+ ] }),
1758
+ totpStep === "enroll" && totpUri && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
1759
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "var(--k-muted)", lineHeight: 1.6 }, children: "Scan this QR code with your authenticator app (Google Authenticator, Authy, etc.), then enter the 6-digit code to confirm." }),
1760
+ /* @__PURE__ */ jsx(QrCode, { uri: totpUri }),
1761
+ /* @__PURE__ */ jsxs("form", { onSubmit: confirmTotp, style: { display: "flex", flexDirection: "column", gap: "0.75rem", maxWidth: "240px" }, children: [
1762
+ /* @__PURE__ */ jsxs("div", { children: [
1763
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Verification code" }),
1764
+ /* @__PURE__ */ jsx(
1765
+ "input",
1766
+ {
1767
+ style: { ...inputStyle, letterSpacing: "0.25em", textAlign: "center", fontSize: "1.125rem" },
1768
+ type: "text",
1769
+ inputMode: "numeric",
1770
+ maxLength: 6,
1771
+ value: totpCode,
1772
+ onChange: (e) => setTotpCode(e.target.value.replace(/\D/g, "")),
1773
+ placeholder: "000000",
1774
+ required: true
1775
+ }
1776
+ )
1777
+ ] }),
1778
+ totpError && /* @__PURE__ */ jsx("p", { style: errorStyle, children: totpError }),
1779
+ /* @__PURE__ */ jsx("button", { style: primaryButtonStyle, type: "submit", disabled: totpCode.length < 6, children: "Verify & enable" })
1780
+ ] })
1781
+ ] }),
1782
+ totpStep === "done" && /* @__PURE__ */ jsx(StatusBadge, { color: "#34d399", children: "Authenticator app enabled \u2713" })
1783
+ ] })
1784
+ ] });
1785
+ }
1786
+ function SessionsTab({ baseUrl, accountId, getToken, currentSessionId }) {
1787
+ const [sessions, setSessions] = useState([]);
1788
+ const [loading, setLoading] = useState(true);
1789
+ const [revoking, setRevoking] = useState(null);
1790
+ const loadSessions = useCallback(async () => {
1791
+ setLoading(true);
1792
+ try {
1793
+ const token = await getToken();
1794
+ const res = await fetch(`${baseUrl}/api/v1/idm/sessions?accountId=${accountId}`, {
1795
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
1796
+ });
1797
+ const d = await res.json().catch(() => ({}));
1798
+ setSessions(Array.isArray(d.data) ? d.data : []);
1799
+ } catch {
1800
+ setSessions([]);
1801
+ } finally {
1802
+ setLoading(false);
1803
+ }
1804
+ }, [baseUrl, accountId, getToken]);
1805
+ useEffect(() => {
1806
+ loadSessions();
1807
+ }, [loadSessions]);
1808
+ async function handleRevoke(sessionId) {
1809
+ setRevoking(sessionId);
1810
+ try {
1811
+ const token = await getToken();
1812
+ await fetch(`${baseUrl}/api/v1/idm/sessions/${sessionId}`, {
1813
+ method: "DELETE",
1814
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
1815
+ });
1816
+ setSessions((s) => s.filter((x) => x.id !== sessionId));
1817
+ } finally {
1818
+ setRevoking(null);
1819
+ }
1820
+ }
1821
+ if (loading) return /* @__PURE__ */ jsx(Skeleton, { rows: 3 });
1822
+ return /* @__PURE__ */ jsxs("div", { style: { maxWidth: "560px" }, children: [
1823
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Active sessions" }),
1824
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "var(--k-muted)", marginBottom: "1.25rem", lineHeight: 1.6 }, children: "These devices are currently signed into your account." }),
1825
+ sessions.length === 0 && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "var(--k-muted)" }, children: "No active sessions found." }),
1826
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.625rem" }, children: sessions.map((s) => {
1827
+ const isCurrent = s.id === currentSessionId;
1828
+ return /* @__PURE__ */ jsxs(
1829
+ "div",
1830
+ {
1831
+ style: {
1832
+ display: "flex",
1833
+ alignItems: "center",
1834
+ justifyContent: "space-between",
1835
+ gap: "1rem",
1836
+ padding: "0.875rem 1rem",
1837
+ background: "var(--k-hover)",
1838
+ border: `1px solid ${isCurrent ? "var(--k-primary)" : "var(--k-border)"}`,
1839
+ borderRadius: "calc(var(--k-radius) * 0.6)"
1840
+ },
1841
+ children: [
1842
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1843
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "0.875rem", fontWeight: 500, color: "var(--k-text)", display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1844
+ /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: s.ipAddress ?? "Unknown IP" }),
1845
+ isCurrent && /* @__PURE__ */ jsx(StatusBadge, { color: "var(--k-primary)", inline: true, children: "Current" })
1846
+ ] }),
1847
+ s.userAgent && /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: "var(--k-muted)", marginTop: "0.25rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: parseUserAgent(s.userAgent) }),
1848
+ s.createdAt && /* @__PURE__ */ jsxs("div", { style: { fontSize: "0.75rem", color: "var(--k-muted)", marginTop: "0.125rem" }, children: [
1849
+ "Started ",
1850
+ formatDate(s.createdAt)
1851
+ ] })
1852
+ ] }),
1853
+ !isCurrent && /* @__PURE__ */ jsx(
1854
+ "button",
1855
+ {
1856
+ style: {
1857
+ padding: "0.375rem 0.75rem",
1858
+ background: "transparent",
1859
+ border: "1px solid rgba(239,68,68,0.35)",
1860
+ borderRadius: "calc(var(--k-radius) * 0.5)",
1861
+ color: "#ef4444",
1862
+ fontSize: "0.8125rem",
1863
+ cursor: "pointer",
1864
+ fontFamily: "var(--k-font)",
1865
+ flexShrink: 0
1866
+ },
1867
+ disabled: revoking === s.id,
1868
+ onClick: () => handleRevoke(s.id),
1869
+ children: revoking === s.id ? "Revoking\u2026" : "Revoke"
1870
+ }
1871
+ )
1872
+ ]
1873
+ },
1874
+ s.id
1875
+ );
1876
+ }) })
1877
+ ] });
1878
+ }
1879
+ function PasskeysTab({ baseUrl, accountId, getToken }) {
1880
+ const [passkeys, setPasskeys] = useState([]);
1881
+ const [loading, setLoading] = useState(true);
1882
+ const [deleting, setDeleting] = useState(null);
1883
+ const loadPasskeys = useCallback(async () => {
1884
+ setLoading(true);
1885
+ try {
1886
+ const token = await getToken();
1887
+ const res = await fetch(`${baseUrl}/api/v1/idm/auth/passkey/credentials?accountId=${accountId}`, {
1888
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
1889
+ });
1890
+ const d = await res.json().catch(() => ({}));
1891
+ setPasskeys(Array.isArray(d.data) ? d.data : []);
1892
+ } catch {
1893
+ setPasskeys([]);
1894
+ } finally {
1895
+ setLoading(false);
1896
+ }
1897
+ }, [baseUrl, accountId, getToken]);
1898
+ useEffect(() => {
1899
+ loadPasskeys();
1900
+ }, [loadPasskeys]);
1901
+ async function handleDelete(passkeyId) {
1902
+ setDeleting(passkeyId);
1903
+ try {
1904
+ const token = await getToken();
1905
+ await fetch(`${baseUrl}/api/v1/idm/auth/passkey/credentials/${passkeyId}`, {
1906
+ method: "DELETE",
1907
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
1908
+ });
1909
+ setPasskeys((p) => p.filter((x) => x.id !== passkeyId));
1910
+ } finally {
1911
+ setDeleting(null);
1912
+ }
1913
+ }
1914
+ if (loading) return /* @__PURE__ */ jsx(Skeleton, { rows: 2 });
1915
+ return /* @__PURE__ */ jsxs("div", { style: { maxWidth: "560px" }, children: [
1916
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Passkeys" }),
1917
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "var(--k-muted)", marginBottom: "1.25rem", lineHeight: 1.6 }, children: "Passkeys let you sign in with your device's biometrics or PIN \u2014 no password needed." }),
1918
+ passkeys.length === 0 && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "var(--k-muted)" }, children: "No passkeys registered." }),
1919
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.625rem" }, children: passkeys.map((pk) => /* @__PURE__ */ jsxs(
1920
+ "div",
1921
+ {
1922
+ style: {
1923
+ display: "flex",
1924
+ alignItems: "center",
1925
+ justifyContent: "space-between",
1926
+ gap: "1rem",
1927
+ padding: "0.875rem 1rem",
1928
+ background: "var(--k-hover)",
1929
+ border: "1px solid var(--k-border)",
1930
+ borderRadius: "calc(var(--k-radius) * 0.6)"
1931
+ },
1932
+ children: [
1933
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", flex: 1, minWidth: 0 }, children: [
1934
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "1.25rem" }, children: "\u{1F511}" }),
1935
+ /* @__PURE__ */ jsxs("div", { children: [
1936
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", fontWeight: 500, color: "var(--k-text)" }, children: pk.name ?? "Passkey" }),
1937
+ pk.createdAt && /* @__PURE__ */ jsxs("div", { style: { fontSize: "0.75rem", color: "var(--k-muted)", marginTop: "0.125rem" }, children: [
1938
+ "Added ",
1939
+ formatDate(pk.createdAt)
1940
+ ] })
1941
+ ] })
1942
+ ] }),
1943
+ /* @__PURE__ */ jsx(
1944
+ "button",
1945
+ {
1946
+ style: {
1947
+ padding: "0.375rem 0.75rem",
1948
+ background: "transparent",
1949
+ border: "1px solid rgba(239,68,68,0.35)",
1950
+ borderRadius: "calc(var(--k-radius) * 0.5)",
1951
+ color: "#ef4444",
1952
+ fontSize: "0.8125rem",
1953
+ cursor: "pointer",
1954
+ fontFamily: "var(--k-font)",
1955
+ flexShrink: 0
1956
+ },
1957
+ disabled: deleting === pk.id,
1958
+ onClick: () => handleDelete(pk.id),
1959
+ children: deleting === pk.id ? "Removing\u2026" : "Remove"
1960
+ }
1961
+ )
1962
+ ]
1963
+ },
1964
+ pk.id
1965
+ )) })
1966
+ ] });
1967
+ }
1968
+ function SectionTitle({ children }) {
1969
+ return /* @__PURE__ */ jsx("h3", { style: { margin: "0 0 1rem", fontSize: "0.9375rem", fontWeight: 600, color: "var(--k-text)" }, children });
1970
+ }
1971
+ function StatusBadge({ children, color, inline = false }) {
1972
+ return /* @__PURE__ */ jsx("span", { style: {
1973
+ display: inline ? "inline-flex" : "inline-flex",
1974
+ alignItems: "center",
1975
+ padding: "0.125rem 0.5rem",
1976
+ borderRadius: "1rem",
1977
+ fontSize: "0.75rem",
1978
+ fontWeight: 600,
1979
+ color,
1980
+ background: `${color}22`,
1981
+ border: `1px solid ${color}44`,
1982
+ marginBottom: inline ? 0 : "0.75rem"
1983
+ }, children });
1984
+ }
1985
+ function Skeleton({ rows = 1 }) {
1986
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx("div", { style: { height: "52px", background: "var(--k-hover)", borderRadius: "calc(var(--k-radius) * 0.6)", border: "1px solid var(--k-border)" } }, i)) });
1987
+ }
1988
+ function QrCode({ uri }) {
1989
+ const encoded = encodeURIComponent(uri);
1990
+ return /* @__PURE__ */ jsx("div", { style: {
1991
+ padding: "0.75rem",
1992
+ background: "#ffffff",
1993
+ borderRadius: "calc(var(--k-radius) * 0.6)",
1994
+ display: "inline-block",
1995
+ lineHeight: 0
1996
+ }, children: /* @__PURE__ */ jsx(
1997
+ "img",
1998
+ {
1999
+ src: `https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${encoded}`,
2000
+ alt: "TOTP QR code",
2001
+ width: 160,
2002
+ height: 160,
2003
+ style: { display: "block" }
2004
+ }
2005
+ ) });
2006
+ }
2007
+ function parseUserAgent(ua) {
2008
+ if (ua.includes("Chrome")) return `Chrome \xB7 ${extractOS(ua)}`;
2009
+ if (ua.includes("Firefox")) return `Firefox \xB7 ${extractOS(ua)}`;
2010
+ if (ua.includes("Safari")) return `Safari \xB7 ${extractOS(ua)}`;
2011
+ return ua.slice(0, 60);
2012
+ }
2013
+ function extractOS(ua) {
2014
+ if (ua.includes("Windows")) return "Windows";
2015
+ if (ua.includes("Mac")) return "macOS";
2016
+ if (ua.includes("Linux")) return "Linux";
2017
+ if (ua.includes("Android")) return "Android";
2018
+ if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
2019
+ return "Unknown OS";
2020
+ }
2021
+ function formatDate(iso) {
2022
+ try {
2023
+ return new Intl.DateTimeFormat(void 0, { dateStyle: "medium", timeStyle: "short" }).format(new Date(iso));
2024
+ } catch {
2025
+ return iso;
2026
+ }
2027
+ }
2028
+ function LoggedIn({ children, fallback = null }) {
2029
+ const { isLoaded, isSignedIn } = useKaappu();
2030
+ if (!isLoaded) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
2031
+ if (!isSignedIn) return null;
2032
+ return /* @__PURE__ */ jsx(Fragment, { children });
2033
+ }
2034
+ function LoggedOut({ children, fallback = null }) {
2035
+ const { isLoaded, isSignedIn } = useKaappu();
2036
+ if (!isLoaded) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
2037
+ if (isSignedIn) return null;
2038
+ return /* @__PURE__ */ jsx(Fragment, { children });
2039
+ }
2040
+ function Authorize({
2041
+ children,
2042
+ permission,
2043
+ role,
2044
+ fallback = null,
2045
+ loading = null
2046
+ }) {
2047
+ const { isLoaded, isSignedIn, user, hasPermission } = useKaappu();
2048
+ if (!isLoaded) return /* @__PURE__ */ jsx(Fragment, { children: loading });
2049
+ if (!isSignedIn || !user) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
2050
+ if (permission && !hasPermission(permission)) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
2051
+ if (role) {
2052
+ const userRoles = user.roles ?? [];
2053
+ if (!userRoles.includes(role) && !userRoles.includes("super_admin")) {
2054
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
2055
+ }
2056
+ }
2057
+ return /* @__PURE__ */ jsx(Fragment, { children });
2058
+ }
2059
+
2060
+ // ../core/dist/index.mjs
2061
+ function checkPermission(userPermissions, required) {
2062
+ if (!required) return true;
2063
+ if (!userPermissions || userPermissions.length === 0) return false;
2064
+ const [requiredResource] = required.split(":");
2065
+ return userPermissions.some(
2066
+ (p) => p === "*" || p === required || p === `${requiredResource}:*`
2067
+ );
2068
+ }
2069
+ function checkAllPermissions(userPermissions, required) {
2070
+ return required.every((perm) => checkPermission(userPermissions, perm));
2071
+ }
2072
+ function checkAnyPermission(userPermissions, required) {
2073
+ return required.some((perm) => checkPermission(userPermissions, perm));
2074
+ }
2075
+ function isPermission(val) {
2076
+ return val === "*" || /^[a-z_]+:[a-z_*]+$/.test(val);
2077
+ }
2078
+ var KaappuApiClient = class {
2079
+ constructor(config) {
2080
+ this.baseUrl = config.baseUrl || "http://localhost:9091";
2081
+ this.publishableKey = config.publishableKey;
2082
+ }
2083
+ /** Fetch tenant config (auth methods, branding, bot protection) */
2084
+ async getTenantConfig() {
2085
+ try {
2086
+ const res = await fetch(`${this.baseUrl}/api/v1/accounts/config?pk=${this.publishableKey}`);
2087
+ if (!res.ok) return null;
2088
+ const data = await res.json();
2089
+ return data?.data ?? null;
2090
+ } catch {
2091
+ return null;
2092
+ }
2093
+ }
2094
+ /** Sign in with email + password */
2095
+ async signIn(email, password, accountId) {
2096
+ const res = await fetch(`${this.baseUrl}/api/v1/idm/auth/sign-in`, {
2097
+ method: "POST",
2098
+ headers: { "Content-Type": "application/json" },
2099
+ body: JSON.stringify({ email, password, accountId: accountId || "default" })
2100
+ });
2101
+ const data = await res.json();
2102
+ if (!data.success) throw new Error(data.error || "Sign in failed");
2103
+ return data.data;
2104
+ }
2105
+ /** Sign up with email + password */
2106
+ async signUp(params) {
2107
+ const res = await fetch(`${this.baseUrl}/api/v1/idm/auth/sign-up`, {
2108
+ method: "POST",
2109
+ headers: { "Content-Type": "application/json" },
2110
+ body: JSON.stringify({ ...params, accountId: params.accountId || "default" })
2111
+ });
2112
+ const data = await res.json();
2113
+ if (!data.success) throw new Error(data.error || "Sign up failed");
2114
+ return data.data;
2115
+ }
2116
+ /** Refresh an access token using a refresh token */
2117
+ async refreshToken(refreshToken) {
2118
+ try {
2119
+ const res = await fetch(`${this.baseUrl}/api/v1/idm/auth/refresh`, {
2120
+ method: "POST",
2121
+ headers: { "Content-Type": "application/json" },
2122
+ body: JSON.stringify({ refreshToken })
2123
+ });
2124
+ if (!res.ok) return null;
2125
+ const data = await res.json();
2126
+ return data?.data ?? null;
2127
+ } catch {
2128
+ return null;
2129
+ }
2130
+ }
2131
+ /** Sign out — invalidate session on the server */
2132
+ async signOut(accessToken) {
2133
+ await fetch(`${this.baseUrl}/api/v1/idm/auth/sign-out`, {
2134
+ method: "POST",
2135
+ headers: { Authorization: `Bearer ${accessToken}` }
2136
+ }).catch(() => {
2137
+ });
2138
+ }
2139
+ /** Get current user profile */
2140
+ async getMe(accessToken) {
2141
+ try {
2142
+ const res = await fetch(`${this.baseUrl}/api/v1/idm/auth/me`, {
2143
+ headers: { Authorization: `Bearer ${accessToken}` }
2144
+ });
2145
+ if (!res.ok) return null;
2146
+ const data = await res.json();
2147
+ return data?.data?.user ?? null;
2148
+ } catch {
2149
+ return null;
2150
+ }
2151
+ }
2152
+ /** Verify MFA challenge */
2153
+ async verifyMfa(challengeId, code) {
2154
+ const res = await fetch(`${this.baseUrl}/api/v1/idm/auth/mfa/verify`, {
2155
+ method: "POST",
2156
+ headers: { "Content-Type": "application/json" },
2157
+ body: JSON.stringify({ challengeId, code })
2158
+ });
2159
+ const data = await res.json();
2160
+ if (!data.success) throw new Error(data.error || "MFA verification failed");
2161
+ return data.data;
2162
+ }
2163
+ /** Request magic link */
2164
+ async requestMagicLink(email, accountId) {
2165
+ await fetch(`${this.baseUrl}/api/v1/idm/auth/magic-link`, {
2166
+ method: "POST",
2167
+ headers: { "Content-Type": "application/json" },
2168
+ body: JSON.stringify({ email, accountId: accountId || "default" })
2169
+ });
2170
+ }
2171
+ /** Get OAuth authorization URL */
2172
+ async getOAuthUrl(provider, redirectUri, accountId) {
2173
+ const res = await fetch(
2174
+ `${this.baseUrl}/api/v1/idm/auth/oauth/${provider}?accountId=${accountId || "default"}&redirectUri=${encodeURIComponent(redirectUri)}`
2175
+ );
2176
+ const data = await res.json();
2177
+ return data?.data?.url || data?.url || "";
2178
+ }
2179
+ };
2180
+
2181
+ export { AccountView, Authorize, KaappuApiClient, KaappuProvider, LoggedIn, LoggedOut, LoginPanel, ProfileBadge, RegisterPanel, buildThemeVars, checkAllPermissions, checkAnyPermission, checkPermission, createTheme, isPermission, resolveColorScheme, useKaappu };
2182
+ //# sourceMappingURL=index.mjs.map
2183
+ //# sourceMappingURL=index.mjs.map