@iqauth/sdk 2.3.0 → 2.5.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.
Files changed (87) hide show
  1. package/README.md +110 -0
  2. package/dist/browser-session.d.mts +3 -2
  3. package/dist/browser-session.d.ts +3 -2
  4. package/dist/browser.d.mts +64 -29
  5. package/dist/browser.d.ts +64 -29
  6. package/dist/browser.js +782 -38
  7. package/dist/browser.mjs +43 -3
  8. package/dist/bundle-LUKDQYVQ.mjs +374 -0
  9. package/dist/chunk-3JULWS6F.mjs +106 -0
  10. package/dist/chunk-5T7GHBX6.mjs +1165 -0
  11. package/dist/{chunk-KGEPDXHU.mjs → chunk-6TDJJER7.mjs} +2 -2
  12. package/dist/{chunk-RACIPVLD.mjs → chunk-76W5TLQQ.mjs} +262 -220
  13. package/dist/{chunk-EKTNEZIH.mjs → chunk-BVV54LPI.mjs} +37 -5
  14. package/dist/chunk-LIZYFXH7.mjs +90 -0
  15. package/dist/chunk-MKKZULZR.mjs +241 -0
  16. package/dist/chunk-SL3KRS4W.mjs +54 -0
  17. package/dist/chunk-TKZTCPEK.mjs +232 -0
  18. package/dist/chunk-UKZLOHZG.mjs +83 -0
  19. package/dist/cli/index.js +144 -36
  20. package/dist/cli/index.mjs +1 -1
  21. package/dist/{client-DTX4hNdS.d.ts → client-BNQe3AgF.d.ts} +3 -62
  22. package/dist/{client-vdh2a9fJ.d.mts → client-kYlJFgPv.d.mts} +3 -62
  23. package/dist/doctor-YYNHNMLD.mjs +198 -0
  24. package/dist/{express-A0-dWEMy.d.mts → express-B6_1vBYZ.d.mts} +23 -2
  25. package/dist/{express-Bo_pJKHN.d.ts → express-CHpfa7D_.d.ts} +23 -2
  26. package/dist/express.d.mts +5 -4
  27. package/dist/express.d.ts +5 -4
  28. package/dist/express.js +36 -4
  29. package/dist/express.mjs +8 -8
  30. package/dist/fastify.js +2 -2
  31. package/dist/fastify.mjs +4 -4
  32. package/dist/hono.js +2 -2
  33. package/dist/hono.mjs +4 -4
  34. package/dist/index.d.mts +8 -3
  35. package/dist/index.d.ts +8 -3
  36. package/dist/index.js +500 -4
  37. package/dist/index.mjs +29 -9
  38. package/dist/locales.d.mts +53 -0
  39. package/dist/locales.d.ts +53 -0
  40. package/dist/locales.js +1202 -0
  41. package/dist/locales.mjs +29 -0
  42. package/dist/mobile.d.mts +3 -2
  43. package/dist/mobile.d.ts +3 -2
  44. package/dist/next.d.mts +1 -1
  45. package/dist/next.d.ts +1 -1
  46. package/dist/next.js +2 -2
  47. package/dist/next.mjs +1 -1
  48. package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
  49. package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
  50. package/dist/react.d.mts +1349 -10
  51. package/dist/react.d.ts +1349 -10
  52. package/dist/react.js +2985 -567
  53. package/dist/react.mjs +1517 -94
  54. package/dist/reverify-4UEJXUS6.mjs +16 -0
  55. package/dist/server/handlers.d.mts +10 -1
  56. package/dist/server/handlers.d.ts +10 -1
  57. package/dist/server/handlers.js +2 -2
  58. package/dist/server/handlers.mjs +1 -1
  59. package/dist/server.d.mts +5 -3
  60. package/dist/server.d.ts +5 -3
  61. package/dist/server.js +89 -4
  62. package/dist/server.mjs +12 -8
  63. package/dist/service.d.mts +3 -2
  64. package/dist/service.d.ts +3 -2
  65. package/dist/signIn-CCY4JE5G.mjs +15 -0
  66. package/dist/{signIn-Cd0P4y9d.d.mts → signIn-CiIBTJIh.d.mts} +224 -4
  67. package/dist/{signIn-DKakyzeu.d.ts → signIn-OCr88Zf8.d.ts} +224 -4
  68. package/dist/test.d.mts +86 -0
  69. package/dist/test.d.ts +86 -0
  70. package/dist/test.js +289 -0
  71. package/dist/test.mjs +9 -0
  72. package/dist/tokens-DCyzzn8L.d.mts +63 -0
  73. package/dist/tokens-aHiGFr_E.d.ts +63 -0
  74. package/dist/types-6bNdxesb.d.mts +196 -0
  75. package/dist/types-6bNdxesb.d.ts +196 -0
  76. package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.mts} +6 -0
  77. package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.ts} +6 -0
  78. package/dist/webhooks.d.mts +61 -0
  79. package/dist/webhooks.d.ts +61 -0
  80. package/dist/webhooks.js +119 -0
  81. package/dist/webhooks.mjs +11 -0
  82. package/dist/ws.d.mts +73 -0
  83. package/dist/ws.d.ts +73 -0
  84. package/dist/ws.js +397 -0
  85. package/dist/ws.mjs +12 -0
  86. package/package.json +22 -2
  87. package/dist/doctor-A5E7LSFW.mjs +0 -90
package/dist/react.mjs CHANGED
@@ -1,11 +1,28 @@
1
1
  import {
2
+ AccountRegistry,
3
+ MultiAccountTokenStore,
2
4
  SessionManager,
5
+ defaultCookieStore,
6
+ enrollPasskey,
7
+ linkProvider,
8
+ listLinkedIdentities,
9
+ requestMagicLink,
10
+ signInWithPasskey,
11
+ unlinkProvider
12
+ } from "./chunk-76W5TLQQ.mjs";
13
+ import {
3
14
  handleAuthCallback,
4
15
  redirectToSignIn,
5
16
  signIn,
6
17
  signOut
7
- } from "./chunk-RACIPVLD.mjs";
18
+ } from "./chunk-TKZTCPEK.mjs";
8
19
  import "./chunk-WQWBJSSS.mjs";
20
+ import {
21
+ defaultBundle,
22
+ localizeErrorCode,
23
+ resolveBundle,
24
+ t
25
+ } from "./chunk-5T7GHBX6.mjs";
9
26
  import "./chunk-6I6RM4MN.mjs";
10
27
  import "./chunk-Y6FXYEAI.mjs";
11
28
 
@@ -22,7 +39,57 @@ import {
22
39
  useState,
23
40
  useSyncExternalStore
24
41
  } from "react";
42
+
43
+ // src/browser/returnTo.ts
44
+ function normalizeOrigin(o) {
45
+ try {
46
+ return new URL(o).origin;
47
+ } catch {
48
+ return o.replace(/\/+$/, "");
49
+ }
50
+ }
51
+ function sanitizeReturnTo(input, options = {}) {
52
+ const fallback = options.fallback ?? "/";
53
+ if (!input || typeof input !== "string") return fallback;
54
+ const trimmed = input.trim();
55
+ if (!trimmed) return fallback;
56
+ if (trimmed.startsWith("//")) return fallback;
57
+ if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?")) {
58
+ return trimmed;
59
+ }
60
+ if (!/^[a-z][a-z0-9+\-.]*:/i.test(trimmed)) {
61
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
62
+ }
63
+ let parsed;
64
+ try {
65
+ parsed = new URL(trimmed);
66
+ } catch {
67
+ return fallback;
68
+ }
69
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return fallback;
70
+ const currentOrigin = options.currentOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
71
+ const allowed = /* @__PURE__ */ new Set();
72
+ if (currentOrigin) allowed.add(normalizeOrigin(currentOrigin));
73
+ for (const o of options.allowedOrigins ?? []) allowed.add(normalizeOrigin(o));
74
+ if (allowed.has(parsed.origin)) return parsed.toString();
75
+ return fallback;
76
+ }
77
+ function isReturnToAllowed(input, options = {}) {
78
+ const fallback = options.fallback ?? "/";
79
+ const out = sanitizeReturnTo(input, options);
80
+ if (!input) return false;
81
+ return out !== fallback || input === fallback;
82
+ }
83
+
84
+ // src/react/index.tsx
25
85
  import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
86
+ var globalManager = null;
87
+ function setGlobalManager(m) {
88
+ globalManager = m;
89
+ }
90
+ function getGlobalManager() {
91
+ return globalManager;
92
+ }
26
93
  var IQAuthContext = createContext(null);
27
94
  function IQAuthProvider({
28
95
  publishableKey,
@@ -30,6 +97,11 @@ function IQAuthProvider({
30
97
  channelName,
31
98
  proactiveRefresh,
32
99
  manager: externalManager,
100
+ allowedReturnOrigins,
101
+ appearance,
102
+ roleMapper,
103
+ cookieNames,
104
+ localization,
33
105
  children
34
106
  }) {
35
107
  const managerRef = useRef(null);
@@ -38,8 +110,10 @@ function IQAuthProvider({
38
110
  publishableKey,
39
111
  issuer,
40
112
  channelName,
41
- proactiveRefresh
113
+ proactiveRefresh,
114
+ cookieNames
42
115
  });
116
+ setGlobalManager(managerRef.current);
43
117
  }
44
118
  const manager = managerRef.current;
45
119
  const subscribe = useCallback(
@@ -65,7 +139,21 @@ function IQAuthProvider({
65
139
  }
66
140
  };
67
141
  }, [manager]);
68
- const value = useMemo(() => ({ manager, snapshot }), [manager, snapshot]);
142
+ const resolvedLocalization = useMemo(
143
+ () => resolveBundle(localization),
144
+ [localization]
145
+ );
146
+ const value = useMemo(
147
+ () => ({
148
+ manager,
149
+ snapshot,
150
+ allowedReturnOrigins: allowedReturnOrigins ?? [],
151
+ appearance: appearance ?? null,
152
+ roleMapper: roleMapper ?? null,
153
+ localization: resolvedLocalization
154
+ }),
155
+ [manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization]
156
+ );
69
157
  return createElement(IQAuthContext.Provider, { value }, children);
70
158
  }
71
159
  function useCtx() {
@@ -73,16 +161,56 @@ function useCtx() {
73
161
  if (!ctx) throw new Error("IQAuth hooks must be used inside <IQAuthProvider>");
74
162
  return ctx;
75
163
  }
164
+ function useLocale() {
165
+ const ctx = useContext(IQAuthContext);
166
+ return ctx?.localization ?? defaultBundle;
167
+ }
168
+ function useT() {
169
+ const bundle = useLocale();
170
+ return useMemo(
171
+ () => (key, vars) => t(bundle, key, vars),
172
+ [bundle]
173
+ );
174
+ }
175
+ function localizeError(bundle, raw) {
176
+ if (!raw) return t(bundle, "errors.generic");
177
+ if (typeof raw === "string") {
178
+ if (/^[A-Z][A-Z0-9_]+$/.test(raw)) return localizeErrorCode(bundle, raw);
179
+ return raw;
180
+ }
181
+ if (raw.code) return localizeErrorCode(bundle, raw.code);
182
+ return raw.message || t(bundle, "errors.generic");
183
+ }
76
184
  function useUser() {
77
- const { snapshot } = useCtx();
185
+ const { snapshot, roleMapper } = useCtx();
78
186
  return useMemo(
79
- () => ({
80
- isLoaded: snapshot.status !== "loading",
81
- isSignedIn: snapshot.status === "authenticated" && !!snapshot.user,
82
- user: snapshot.user,
83
- error: snapshot.error
84
- }),
85
- [snapshot.status, snapshot.user, snapshot.error, snapshot.version]
187
+ () => {
188
+ const user = snapshot.user ? {
189
+ ...snapshot.user,
190
+ // F13 — derive `role` via roleMapper, falling back to a sensible default
191
+ role: (() => {
192
+ if (roleMapper) {
193
+ try {
194
+ return roleMapper(snapshot.claims);
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+ const c = snapshot.claims;
200
+ if (!c) return null;
201
+ if (typeof c.role === "string" && c.role) return c.role;
202
+ if (Array.isArray(c.roles) && c.roles.length > 0 && typeof c.roles[0] === "string") return c.roles[0];
203
+ return null;
204
+ })()
205
+ } : null;
206
+ return {
207
+ isLoaded: snapshot.status !== "loading",
208
+ isSignedIn: snapshot.status === "authenticated" && !!snapshot.user,
209
+ user,
210
+ error: snapshot.error
211
+ };
212
+ },
213
+ [snapshot.status, snapshot.user, snapshot.claims, snapshot.error, snapshot.version, roleMapper]
86
214
  );
87
215
  }
88
216
  function useSession() {
@@ -134,6 +262,148 @@ function useAuthFetch() {
134
262
  [manager]
135
263
  );
136
264
  }
265
+ function useSessionList() {
266
+ const { manager } = useCtx();
267
+ const [sessions, setSessions] = useState([]);
268
+ const [loading, setLoading] = useState(true);
269
+ const [error, setError] = useState(null);
270
+ const base = manager.issuerUrl.replace(/\/$/, "");
271
+ const refresh = useCallback(async () => {
272
+ setLoading(true);
273
+ setError(null);
274
+ try {
275
+ const res = await manager.fetch(`${base}/api/v1/users/me/sessions`);
276
+ const json = await res.json().catch(() => ({}));
277
+ if (!res.ok) throw new Error(json?.error?.message || `HTTP ${res.status}`);
278
+ setSessions(json?.data?.sessions || []);
279
+ } catch (err) {
280
+ setError(err.message);
281
+ } finally {
282
+ setLoading(false);
283
+ }
284
+ }, [manager, base]);
285
+ useEffect(() => {
286
+ void refresh();
287
+ }, [refresh]);
288
+ const revoke = useCallback(async (sessionId) => {
289
+ const res = await manager.fetch(`${base}/api/v1/users/me/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
290
+ if (!res.ok) {
291
+ const j = await res.json().catch(() => ({}));
292
+ throw new Error(j?.error?.message || `HTTP ${res.status}`);
293
+ }
294
+ setSessions((prev) => prev.filter((s) => s.id !== sessionId));
295
+ }, [manager, base]);
296
+ const revokeAllOthers = useCallback(async () => {
297
+ const res = await manager.fetch(`${base}/api/v1/users/me/sessions/revoke-all-others`, { method: "POST" });
298
+ const json = await res.json().catch(() => ({}));
299
+ if (!res.ok) throw new Error(json?.error?.message || `HTTP ${res.status}`);
300
+ setSessions((prev) => prev.filter((s) => s.isCurrent));
301
+ return { terminatedCount: json?.data?.terminatedCount ?? 0 };
302
+ }, [manager, base]);
303
+ return { sessions, loading, error, refresh, revoke, revokeAllOthers };
304
+ }
305
+ async function revokeSession(sessionId) {
306
+ const mgr = getGlobalManager();
307
+ if (!mgr) throw new Error("revokeSession() requires <IQAuthProvider> mounted");
308
+ const base = mgr.issuerUrl.replace(/\/$/, "");
309
+ const res = await mgr.fetch(`${base}/api/v1/users/me/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
310
+ if (!res.ok) {
311
+ const j = await res.json().catch(() => ({}));
312
+ throw new Error(j?.error?.message || `HTTP ${res.status}`);
313
+ }
314
+ }
315
+ var MultisessionContext = createContext(null);
316
+ function MultisessionAppSupport({ children }) {
317
+ const { manager, snapshot } = useCtx();
318
+ const registryRef = useRef(null);
319
+ if (!registryRef.current) {
320
+ registryRef.current = new AccountRegistry(manager.appKey);
321
+ }
322
+ const registry = registryRef.current;
323
+ const [version, setVersion] = useState(0);
324
+ useEffect(() => {
325
+ const store = new MultiAccountTokenStore(registry, defaultCookieStore());
326
+ manager.setTokenStore(store);
327
+ const unsub = registry.subscribe(() => setVersion((v) => v + 1));
328
+ return () => {
329
+ unsub();
330
+ registry.destroy();
331
+ };
332
+ }, [manager]);
333
+ useEffect(() => {
334
+ if (snapshot.status !== "authenticated" || !snapshot.user) return;
335
+ const claims = snapshot.claims;
336
+ const rec = {
337
+ accountId: snapshot.user.sub,
338
+ userId: snapshot.user.sub,
339
+ email: snapshot.user.email,
340
+ name: snapshot.user.name,
341
+ tenantId: snapshot.tenantId ?? claims?.tenantId ?? null,
342
+ addedAt: Date.now()
343
+ };
344
+ registry.upsert(rec);
345
+ if (registry.active() !== rec.accountId) {
346
+ registry.setActive(rec.accountId);
347
+ }
348
+ }, [snapshot.user?.sub, snapshot.tenantId, snapshot.status]);
349
+ const value = useMemo(
350
+ () => ({ registry, version }),
351
+ [registry, version]
352
+ );
353
+ return createElement(MultisessionContext.Provider, { value }, children);
354
+ }
355
+ function useMultiCtx() {
356
+ const ctx = useContext(MultisessionContext);
357
+ if (!ctx) throw new Error("F22 hooks must be used inside <MultisessionAppSupport>");
358
+ return ctx;
359
+ }
360
+ function useAccountList() {
361
+ const { registry, version } = useMultiCtx();
362
+ const { isLoaded } = useUser();
363
+ const accounts = useMemo(() => {
364
+ const active = registry.active();
365
+ return registry.list().map((a) => ({
366
+ accountId: a.accountId,
367
+ userId: a.userId,
368
+ email: a.email,
369
+ name: a.name,
370
+ tenantId: a.tenantId,
371
+ isActive: a.accountId === active
372
+ }));
373
+ }, [registry, version]);
374
+ return { accounts, loading: !isLoaded };
375
+ }
376
+ function useAccountSwitcher() {
377
+ const { manager } = useCtx();
378
+ const { registry } = useMultiCtx();
379
+ const addAccount = useCallback(
380
+ async (opts = {}) => {
381
+ registry.setActive(null);
382
+ await signIn(manager, { ...opts, prompt: "login" });
383
+ },
384
+ [manager, registry]
385
+ );
386
+ const switchTo = useCallback(
387
+ async (accountId) => {
388
+ const target = registry.get(accountId);
389
+ if (!target) throw new Error(`Unknown accountId: ${accountId}`);
390
+ registry.setActive(accountId);
391
+ const ok = await manager.refresh();
392
+ if (!ok) {
393
+ registry.remove(accountId);
394
+ throw new Error("Could not switch account; session may have been revoked");
395
+ }
396
+ },
397
+ [manager, registry]
398
+ );
399
+ const removeAccount = useCallback(
400
+ (accountId) => {
401
+ registry.remove(accountId);
402
+ },
403
+ [registry]
404
+ );
405
+ return { addAccount, switchTo, removeAccount };
406
+ }
137
407
  function SignedIn({ children }) {
138
408
  const { isSignedIn, isLoaded } = useUser();
139
409
  if (!isLoaded || !isSignedIn) return null;
@@ -197,6 +467,113 @@ function RedirectToSignIn(props = {}) {
197
467
  }
198
468
  return null;
199
469
  }
470
+ function asArray(v) {
471
+ if (v == null) return [];
472
+ return Array.isArray(v) ? v : [v];
473
+ }
474
+ function claimRoles(c) {
475
+ if (!c) return [];
476
+ const x = c;
477
+ if (Array.isArray(x.roles)) return x.roles.filter((r) => typeof r === "string");
478
+ if (typeof x.role === "string") return [x.role];
479
+ return [];
480
+ }
481
+ function claimPermissions(c) {
482
+ if (!c) return [];
483
+ const x = c;
484
+ const out = /* @__PURE__ */ new Set();
485
+ if (Array.isArray(x.permissions)) {
486
+ for (const p of x.permissions) if (typeof p === "string") out.add(p);
487
+ }
488
+ if (Array.isArray(x.entitlements)) {
489
+ for (const p of x.entitlements) if (typeof p === "string") out.add(p);
490
+ }
491
+ if (typeof x.scope === "string") {
492
+ for (const s of x.scope.split(/\s+/)) if (s) out.add(s);
493
+ }
494
+ return Array.from(out);
495
+ }
496
+ function Protect({ role, permission, condition, fallback = null, children }) {
497
+ const { snapshot } = useCtx();
498
+ if (snapshot.status !== "authenticated") return createElement(Fragment, null, fallback);
499
+ const wantedRoles = asArray(role);
500
+ const wantedPerms = asArray(permission);
501
+ if (wantedRoles.length) {
502
+ const have = new Set(claimRoles(snapshot.claims));
503
+ if (!wantedRoles.some((r) => have.has(r))) return createElement(Fragment, null, fallback);
504
+ }
505
+ if (wantedPerms.length) {
506
+ const have = new Set(claimPermissions(snapshot.claims));
507
+ if (!wantedPerms.some((p) => have.has(p))) return createElement(Fragment, null, fallback);
508
+ }
509
+ if (condition && !condition(snapshot.claims)) return createElement(Fragment, null, fallback);
510
+ return createElement(Fragment, null, children);
511
+ }
512
+ function RedirectToSignedIn({ to = "/", replace = true } = {}) {
513
+ const { snapshot, allowedReturnOrigins } = useCtx();
514
+ useEffect(() => {
515
+ if (snapshot.status !== "authenticated") return;
516
+ if (typeof window === "undefined") return;
517
+ const safe = sanitizeReturnTo(to, { allowedOrigins: allowedReturnOrigins, fallback: "/" });
518
+ if (replace) window.location.replace(safe);
519
+ else window.location.assign(safe);
520
+ }, [snapshot.status, to, replace, allowedReturnOrigins]);
521
+ return null;
522
+ }
523
+ function useReturnTo(options = {}) {
524
+ const { allowedReturnOrigins } = useCtx();
525
+ const paramName = options.paramName ?? "return_to";
526
+ const storageKey = options.storageKey ?? "iqauth_return_to";
527
+ const fallback = options.fallback ?? "/";
528
+ return useMemo(() => {
529
+ if (typeof window === "undefined") return fallback;
530
+ let raw = null;
531
+ try {
532
+ const params = new URLSearchParams(window.location.search);
533
+ raw = params.get(paramName) || params.get("next");
534
+ } catch {
535
+ }
536
+ if (!raw) {
537
+ try {
538
+ raw = window.sessionStorage.getItem(storageKey);
539
+ } catch {
540
+ }
541
+ }
542
+ const safe = sanitizeReturnTo(raw, { allowedOrigins: allowedReturnOrigins, fallback });
543
+ if (safe !== fallback) {
544
+ try {
545
+ window.sessionStorage.setItem(storageKey, safe);
546
+ } catch {
547
+ }
548
+ }
549
+ return safe;
550
+ }, [paramName, storageKey, fallback, allowedReturnOrigins]);
551
+ }
552
+ function IQAuthReturnToBouncer({ children, ...opts }) {
553
+ const { snapshot } = useCtx();
554
+ const returnTo = useReturnTo(opts);
555
+ useEffect(() => {
556
+ if (snapshot.status !== "authenticated") return;
557
+ if (typeof window === "undefined") return;
558
+ window.location.replace(returnTo);
559
+ }, [snapshot.status, returnTo]);
560
+ if (snapshot.status === "authenticated") return null;
561
+ return createElement(Fragment, null, children);
562
+ }
563
+ async function preflightReturnTo(args) {
564
+ const f = args.fetchImpl ?? fetch;
565
+ const url = `${args.iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(args.appKey)}/sign-in-context?return_to=${encodeURIComponent(args.returnTo)}`;
566
+ try {
567
+ const r = await f(url, { credentials: "include" });
568
+ const body = await r.json().catch(() => null);
569
+ if (!r.ok || !body?.success || !body.data) {
570
+ return { ok: false, allowedOrigins: [], reason: body?.error?.message || `HTTP ${r.status}` };
571
+ }
572
+ return { ok: !!body.data.returnAllowed, allowedOrigins: body.data.allowedOrigins ?? [], reason: body.data.returnAllowed ? void 0 : "returnTo not in app allowedOrigins" };
573
+ } catch (err) {
574
+ return { ok: false, allowedOrigins: [], reason: err.message };
575
+ }
576
+ }
200
577
  function AuthCallback({ onComplete, fallback } = {}) {
201
578
  const { manager } = useCtx();
202
579
  useEffect(() => {
@@ -482,10 +859,12 @@ function Shell({
482
859
  className,
483
860
  children,
484
861
  title,
485
- subtitle
862
+ subtitle,
863
+ appearance
486
864
  }) {
487
865
  ensureSdkShellStyles();
488
- useDocumentBranding(branding, title || "Sign in");
866
+ const t2 = useT();
867
+ useDocumentBranding(branding, title || t2("signIn.title"));
489
868
  const brandVars = brandStyle(branding);
490
869
  const brandName = branding?.brandName || "IQAuth";
491
870
  const heroImage = branding?.heroImageUrl || null;
@@ -496,14 +875,16 @@ function Shell({
496
875
  const heroStyle = heroImage ? { ["--iqauth-sdk-hero-image"]: `url("${heroImage.replace(/"/g, '\\"')}")` } : {};
497
876
  const shellStyle = {
498
877
  ...brandVars,
499
- ...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {}
878
+ ...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {},
879
+ ...appearance?.elements?.rootBox?.style || {}
500
880
  };
881
+ const ap = appearance?.elements;
501
882
  const supportLink = branding?.supportUrl ? branding.supportUrl : branding?.supportEmail ? `mailto:${branding.supportEmail}` : null;
502
883
  const hasFooterLinks = !!(branding?.termsUrl || branding?.privacyUrl || supportLink);
503
884
  return /* @__PURE__ */ jsxs(
504
885
  "div",
505
886
  {
506
- className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`,
887
+ className: `iqauth-sdk-shell${className ? ` ${className}` : ""}${ap?.rootBox?.className ? ` ${ap.rootBox.className}` : ""}`,
507
888
  "data-layout": layout,
508
889
  "data-social-style": socialStyle || void 0,
509
890
  style: shellStyle,
@@ -524,13 +905,34 @@ function Shell({
524
905
  ] }),
525
906
  /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ jsxs("main", { style: { width: "100%", maxWidth: 420 }, children: [
526
907
  /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-mobile-brand", children: /* @__PURE__ */ jsx(SdkBrandLogo, { branding, alt: `${brandName} logo`, fallback: /* @__PURE__ */ jsx("span", { children: brandName }) }) }),
527
- /* @__PURE__ */ jsxs("section", { className: "iqauth-sdk-card", children: [
528
- title || subtitle ? /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-card-header", children: [
529
- title ? /* @__PURE__ */ jsx("h1", { children: title }) : null,
530
- subtitle ? /* @__PURE__ */ jsx("p", { children: subtitle }) : null
531
- ] }) : null,
532
- /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-card-body", children })
533
- ] }),
908
+ /* @__PURE__ */ jsxs(
909
+ "section",
910
+ {
911
+ className: `iqauth-sdk-card${ap?.card?.className ? ` ${ap.card.className}` : ""}`,
912
+ style: ap?.card?.style,
913
+ children: [
914
+ title || subtitle ? /* @__PURE__ */ jsxs(
915
+ "div",
916
+ {
917
+ className: `iqauth-sdk-card-header${ap?.cardHeader?.className ? ` ${ap.cardHeader.className}` : ""}`,
918
+ style: ap?.cardHeader?.style,
919
+ children: [
920
+ title ? /* @__PURE__ */ jsx("h1", { className: ap?.headerTitle?.className, style: ap?.headerTitle?.style, children: title }) : null,
921
+ subtitle ? /* @__PURE__ */ jsx("p", { className: ap?.headerSubtitle?.className, style: ap?.headerSubtitle?.style, children: subtitle }) : null
922
+ ]
923
+ }
924
+ ) : null,
925
+ /* @__PURE__ */ jsx(
926
+ "div",
927
+ {
928
+ className: `iqauth-sdk-card-body${ap?.cardBody?.className ? ` ${ap.cardBody.className}` : ""}`,
929
+ style: ap?.cardBody?.style,
930
+ children
931
+ }
932
+ )
933
+ ]
934
+ }
935
+ ),
534
936
  hasFooterLinks || branding?.footerText ? /* @__PURE__ */ jsxs("footer", { className: "iqauth-sdk-footer", children: [
535
937
  branding?.footerText ? /* @__PURE__ */ jsx("div", { "data-testid": "text-brand-footer-sdk", style: { fontSize: 12, opacity: 0.75 }, children: branding.footerText }) : null,
536
938
  hasFooterLinks ? /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-footer-links", children: [
@@ -621,8 +1023,30 @@ function isSilentSsoEligible(ctx, effectivePrompt) {
621
1023
  if (!ctx.returnAllowed) return false;
622
1024
  return true;
623
1025
  }
624
- function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt }) {
1026
+ function SignIn(props) {
1027
+ const providerCtx = useContext(IQAuthContext);
1028
+ const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
1029
+ const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
1030
+ const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
1031
+ const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
1032
+ const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
1033
+ if (!iqAuthBaseUrl || !appKey) {
1034
+ console.error(
1035
+ "[IQAuth] <SignIn /> could not determine iqAuthBaseUrl/appKey. Either pass them explicitly OR wrap the component in <IQAuthProvider publishableKey=\u2026/>."
1036
+ );
1037
+ }
1038
+ const t2 = useT();
1039
+ const localeBundle = useLocale();
625
1040
  const { ctx, loading, error } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo);
1041
+ const preflightLoggedRef = useRef(false);
1042
+ useEffect(() => {
1043
+ if (!ctx || preflightLoggedRef.current) return;
1044
+ if (ctx.returnAllowed) return;
1045
+ preflightLoggedRef.current = true;
1046
+ console.error(
1047
+ `[IQAuth] returnTo "${returnTo}" is NOT in the app's allowed origins. Add it via the IQAuth admin console: Apps \u2192 ${ctx.app.key} \u2192 Allowed Origins. Currently allowed: [${ctx.allowedOrigins.join(", ") || "\u2014"}].`
1048
+ );
1049
+ }, [ctx, returnTo]);
626
1050
  const [email, setEmail] = useState("");
627
1051
  const [password, setPassword] = useState("");
628
1052
  const [submitting, setSubmitting] = useState(false);
@@ -681,9 +1105,9 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
681
1105
  body: JSON.stringify({ email, password, ...oidcPayload() })
682
1106
  });
683
1107
  const payload = await r.json().catch(() => ({}));
684
- if (!handlePayload(payload)) setFormError(payload.error_description || payload.error || "Sign-in failed");
1108
+ if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
685
1109
  } catch (err) {
686
- setFormError(err.message || "Network error");
1110
+ setFormError(err.message || t(localeBundle, "errors.network"));
687
1111
  }
688
1112
  setSubmitting(false);
689
1113
  };
@@ -705,7 +1129,7 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
705
1129
  })
706
1130
  });
707
1131
  const payload = await r.json().catch(() => ({}));
708
- if (!handlePayload(payload)) setFormError(payload.error_description || payload.error || "MFA verification failed");
1132
+ if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
709
1133
  setSubmitting(false);
710
1134
  };
711
1135
  const submitTenant = async (tenantId) => {
@@ -719,7 +1143,7 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
719
1143
  body: JSON.stringify({ tenantSelectionToken: tenantSel.token, tenantId, ...oidcPayload() })
720
1144
  });
721
1145
  const payload = await r.json().catch(() => ({}));
722
- if (!handlePayload(payload)) setFormError(payload.error_description || payload.error || "Tenant selection failed");
1146
+ if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
723
1147
  setSubmitting(false);
724
1148
  };
725
1149
  const startGoogleLogin = () => {
@@ -817,36 +1241,36 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
817
1241
  setOauthExchanging(false);
818
1242
  })();
819
1243
  }, [ctx?.app.defaultClientId]);
820
- if (loading || oauthExchanging) return /* @__PURE__ */ jsx(Shell, { branding: ctx?.branding || null, className, title: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026", children: /* @__PURE__ */ jsx("p", { children: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026" }) });
821
- if (error || !ctx) return /* @__PURE__ */ jsx(Shell, { branding: null, className, title: "Application unavailable", children: /* @__PURE__ */ jsx(ErrorBanner, { message: error || "Failed to load app context" }) });
822
- if (!ctx.returnAllowed) return /* @__PURE__ */ jsx(Shell, { branding: ctx.branding, className, title: "Invalid redirect", children: /* @__PURE__ */ jsx(ErrorBanner, { message: `returnTo "${returnTo}" is not in this app's allowed origins.` }) });
1244
+ if (loading || oauthExchanging) return /* @__PURE__ */ jsx(Shell, { appearance, branding: ctx?.branding || null, className, title: oauthExchanging ? t2("signIn.submitting") : t2("common.loading"), children: /* @__PURE__ */ jsx("p", { children: oauthExchanging ? t2("signIn.submitting") : t2("common.loading") }) });
1245
+ if (error || !ctx) return /* @__PURE__ */ jsx(Shell, { appearance, branding: null, className, title: t2("errors.serverError"), children: /* @__PURE__ */ jsx(ErrorBanner, { message: error || t2("errors.serverError") }) });
1246
+ if (!ctx.returnAllowed) return /* @__PURE__ */ jsx(Shell, { appearance, branding: ctx.branding, className, title: t2("errors.generic"), children: /* @__PURE__ */ jsx(ErrorBanner, { message: `returnTo "${returnTo}" is not in this app's allowed origins.` }) });
823
1247
  const silentEligible = isSilentSsoEligible(ctx, effectivePrompt);
824
1248
  if (silentEligible && silent !== "failed") {
825
- return /* @__PURE__ */ jsxs(Shell, { branding: ctx.branding, className, title: "Signing you in\u2026", subtitle: ctx.session ? `Welcome back, ${ctx.session.name || ctx.session.email}.` : void 0, children: [
826
- /* @__PURE__ */ jsx("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: "Resuming your session." }),
827
- /* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: "Not you? Use a different account" })
1249
+ return /* @__PURE__ */ jsxs(Shell, { appearance, branding: ctx.branding, className, title: t2("signIn.resumingSession"), subtitle: ctx.session ? `${t2("signIn.subtitle")}, ${ctx.session.name || ctx.session.email}.` : void 0, children: [
1250
+ /* @__PURE__ */ jsx("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: t2("signIn.resumingSession") }),
1251
+ /* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: t2("signIn.useDifferentAccount") })
828
1252
  ] });
829
1253
  }
830
- const cardTitle = ctx.branding?.loginHeadline || `Sign in to ${ctx.app.name}`;
1254
+ const cardTitle = ctx.branding?.loginHeadline || t2("signIn.titleWithApp", { appName: ctx.app.name });
831
1255
  const cardSubtitle = ctx.branding?.loginSubheadline || void 0;
832
- return /* @__PURE__ */ jsxs(Shell, { branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
1256
+ return /* @__PURE__ */ jsxs(Shell, { appearance, branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
833
1257
  formError ? /* @__PURE__ */ jsx(ErrorBanner, { message: formError }) : null,
834
- tenantSel ? /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": "Choose tenant", style: { display: "flex", flexDirection: "column", gap: 8 }, children: tenantSel.tenants.map((t) => /* @__PURE__ */ jsxs(
1258
+ tenantSel ? /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": t2("signIn.selectTenant"), style: { display: "flex", flexDirection: "column", gap: 8 }, children: tenantSel.tenants.map((tn) => /* @__PURE__ */ jsxs(
835
1259
  "button",
836
1260
  {
837
1261
  type: "button",
838
- "data-iqauth-tenant": t.tenantId,
839
- onClick: () => submitTenant(t.tenantId),
1262
+ "data-iqauth-tenant": tn.tenantId,
1263
+ onClick: () => submitTenant(tn.tenantId),
840
1264
  style: { textAlign: "left", padding: "10px 14px", border: "1px solid rgba(15,23,42,0.15)", borderRadius: 8, background: "transparent", color: "inherit", cursor: "pointer" },
841
1265
  children: [
842
- /* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 500 }, children: t.tenantName || t.tenantSlug || t.tenantId }),
843
- /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 12, opacity: 0.6 }, children: t.roles.join(", ") })
1266
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 500 }, children: tn.tenantName || tn.tenantSlug || tn.tenantId }),
1267
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 12, opacity: 0.6 }, children: tn.roles.join(", ") })
844
1268
  ]
845
1269
  },
846
- t.tenantId
847
- )) }) : mfa ? /* @__PURE__ */ jsxs("form", { onSubmit: submitMfa, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": "MFA verification", children: [
848
- !mfa.backup && mfa.methods.length > 1 ? /* @__PURE__ */ jsx(Field, { label: "Method", children: /* @__PURE__ */ jsx("select", { style: inputStyle(), value: mfa.selected, onChange: (e) => setMfa({ ...mfa, selected: e.target.value }), children: mfa.methods.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m.toUpperCase() }, m)) }) }) : null,
849
- /* @__PURE__ */ jsx(Field, { label: mfa.backup ? "Backup code" : "Verification code", children: /* @__PURE__ */ jsx(
1270
+ tn.tenantId
1271
+ )) }) : mfa ? /* @__PURE__ */ jsxs("form", { onSubmit: submitMfa, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": t2("mfa.title"), children: [
1272
+ !mfa.backup && mfa.methods.length > 1 ? /* @__PURE__ */ jsx(Field, { label: t2("mfa.title"), children: /* @__PURE__ */ jsx("select", { style: inputStyle(), value: mfa.selected, onChange: (e) => setMfa({ ...mfa, selected: e.target.value }), children: mfa.methods.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m.toUpperCase() }, m)) }) }) : null,
1273
+ /* @__PURE__ */ jsx(Field, { label: mfa.backup ? t2("mfa.backupCodeLabel") : t2("mfa.totpLabel"), children: /* @__PURE__ */ jsx(
850
1274
  "input",
851
1275
  {
852
1276
  style: { ...inputStyle(), fontFamily: "monospace", textAlign: mfa.backup ? "left" : "center", letterSpacing: mfa.backup ? "0.04em" : "0.3em" },
@@ -856,34 +1280,33 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
856
1280
  inputMode: mfa.backup ? "text" : "numeric"
857
1281
  }
858
1282
  ) }),
859
- /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !mfa.code, children: submitting ? "Verifying\u2026" : "Verify" }),
860
- /* @__PURE__ */ jsx(GhostButton, { type: "button", onClick: () => setMfa({ ...mfa, backup: !mfa.backup, code: "" }), children: mfa.backup ? "Use verification code" : "Use backup code" })
1283
+ /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !mfa.code, children: submitting ? t2("mfa.submitting") : t2("mfa.submit") }),
1284
+ /* @__PURE__ */ jsx(GhostButton, { type: "button", onClick: () => setMfa({ ...mfa, backup: !mfa.backup, code: "" }), children: mfa.backup ? t2("mfa.useAuthenticator") : t2("mfa.useBackupCode") })
861
1285
  ] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
862
1286
  ctx.providers?.google ? /* @__PURE__ */ jsxs(Fragment2, { children: [
863
- /* @__PURE__ */ jsxs("button", { type: "button", className: "iqauth-sdk-google-btn", onClick: startGoogleLogin, disabled: submitting, "aria-label": ctx.branding?.googleButtonLabel || "Continue with Google", children: [
1287
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "iqauth-sdk-google-btn", onClick: startGoogleLogin, disabled: submitting, "aria-label": ctx.branding?.googleButtonLabel || t2("signIn.continueWithGoogle"), children: [
864
1288
  /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
865
1289
  /* @__PURE__ */ jsx("path", { fill: "#4285F4", d: "M17.64 9.2c0-.64-.06-1.25-.17-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.71v2.26h2.92a8.78 8.78 0 0 0 2.68-6.61z" }),
866
1290
  /* @__PURE__ */ jsx("path", { fill: "#34A853", d: "M9 18c2.43 0 4.47-.81 5.96-2.18l-2.92-2.26a5.4 5.4 0 0 1-8.04-2.83H.96v2.33A9 9 0 0 0 9 18z" }),
867
1291
  /* @__PURE__ */ jsx("path", { fill: "#FBBC05", d: "M3.96 10.71A5.41 5.41 0 0 1 3.68 9c0-.59.1-1.17.29-1.71V4.96H.96a9 9 0 0 0 0 8.08l3-2.33z" }),
868
1292
  /* @__PURE__ */ jsx("path", { fill: "#EA4335", d: "M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.59A9 9 0 0 0 .96 4.96l3 2.33A5.4 5.4 0 0 1 9 3.58z" })
869
1293
  ] }),
870
- ctx.branding?.googleButtonLabel || "Continue with Google"
1294
+ ctx.branding?.googleButtonLabel || t2("signIn.continueWithGoogle")
871
1295
  ] }),
872
- /* @__PURE__ */ jsx("div", { role: "separator", "aria-label": "or", className: "iqauth-sdk-divider", children: "OR" })
1296
+ /* @__PURE__ */ jsx("div", { role: "separator", "aria-label": t2("common.or"), className: "iqauth-sdk-divider", children: t2("signIn.dividerOr").toUpperCase() })
873
1297
  ] }) : null,
874
- /* @__PURE__ */ jsxs("form", { onSubmit: submitLogin, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": `Sign in to ${ctx.app.name}`, children: [
875
- /* @__PURE__ */ jsx(Field, { label: "Email", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "email", autoComplete: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) }) }),
876
- /* @__PURE__ */ jsx(Field, { label: "Password", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value) }) }),
877
- /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? "Signing in\u2026" : "Sign in" })
1298
+ /* @__PURE__ */ jsxs("form", { onSubmit: submitLogin, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": t2("signIn.titleWithApp", { appName: ctx.app.name }), children: [
1299
+ /* @__PURE__ */ jsx(Field, { label: t2("signIn.emailLabel"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "email", autoComplete: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) }) }),
1300
+ /* @__PURE__ */ jsx(Field, { label: t2("signIn.passwordLabel"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value) }) }),
1301
+ /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? t2("signIn.submitting") : t2("signIn.submit") })
878
1302
  ] })
879
1303
  ] }),
880
- (silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */ jsxs("p", { style: { marginTop: 12, fontSize: 13, opacity: 0.75 }, children: [
881
- silent === "failed" && ctx.session ? "Couldn't resume your session. " : null,
882
- /* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", children: "Use a different account" })
883
- ] }) : null
1304
+ (silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */ jsx("p", { style: { marginTop: 12, fontSize: 13, opacity: 0.75 }, children: /* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", children: t2("signIn.useDifferentAccount") }) }) : null
884
1305
  ] });
885
1306
  }
886
1307
  function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
1308
+ const t2 = useT();
1309
+ const localeBundle = useLocale();
887
1310
  const { ctx, loading } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo || "");
888
1311
  const [name, setName] = useState("");
889
1312
  const [email, setEmail] = useState("");
@@ -905,22 +1328,19 @@ function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
905
1328
  setDone(true);
906
1329
  onSuccess?.();
907
1330
  } catch (err) {
908
- setError(err.message);
1331
+ setError(localizeError(localeBundle, err.message));
909
1332
  }
910
1333
  setSubmitting(false);
911
1334
  };
912
- if (loading) return /* @__PURE__ */ jsx(Shell, { branding: null, className, children: /* @__PURE__ */ jsx("p", { children: "Loading\u2026" }) });
913
- return /* @__PURE__ */ jsxs(Shell, { branding: ctx?.branding || null, className, children: [
914
- /* @__PURE__ */ jsx("h2", { style: { fontSize: 20, fontWeight: 600, margin: "0 0 12px" }, children: "Create your account" }),
915
- done ? /* @__PURE__ */ jsx("div", { role: "status", children: /* @__PURE__ */ jsx("p", { children: "Account created. Check your email for verification." }) }) : /* @__PURE__ */ jsxs("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
916
- error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
917
- /* @__PURE__ */ jsx(Field, { label: "Full name", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true }) }),
918
- /* @__PURE__ */ jsx(Field, { label: "Email", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "email", autoComplete: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true }) }),
919
- /* @__PURE__ */ jsx(Field, { label: "Organization (optional)", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
920
- /* @__PURE__ */ jsx(Field, { label: "Password", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: password, onChange: (e) => setPassword(e.target.value), required: true }) }),
921
- /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password || !name, children: submitting ? "Creating\u2026" : "Create account" })
922
- ] })
923
- ] });
1335
+ if (loading) return /* @__PURE__ */ jsx(Shell, { branding: null, className, children: /* @__PURE__ */ jsx("p", { children: t2("common.loading") }) });
1336
+ return /* @__PURE__ */ jsx(Shell, { branding: ctx?.branding || null, className, title: t2("signUp.title"), children: done ? /* @__PURE__ */ jsx("div", { role: "status", children: /* @__PURE__ */ jsx("p", { children: t2("magicLink.subtitle", { email }) }) }) : /* @__PURE__ */ jsxs("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1337
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
1338
+ /* @__PURE__ */ jsx(Field, { label: t2("signUp.nameLabel"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true }) }),
1339
+ /* @__PURE__ */ jsx(Field, { label: t2("signUp.emailLabel"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "email", autoComplete: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true }) }),
1340
+ /* @__PURE__ */ jsx(Field, { label: `${t2("signUp.tenantNameLabel")} (${t2("common.optional")})`, children: /* @__PURE__ */ jsx("input", { style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
1341
+ /* @__PURE__ */ jsx(Field, { label: t2("signUp.passwordLabel"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: password, onChange: (e) => setPassword(e.target.value), required: true }) }),
1342
+ /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password || !name, children: submitting ? t2("signUp.submitting") : t2("signUp.submit") })
1343
+ ] }) });
924
1344
  }
925
1345
  function initialsOf(name, email) {
926
1346
  const src = name || email || "?";
@@ -929,6 +1349,7 @@ function initialsOf(name, email) {
929
1349
  return src.substring(0, 2).toUpperCase();
930
1350
  }
931
1351
  function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1352
+ const t2 = useT();
932
1353
  const [user, setUser] = useState(null);
933
1354
  const [open, setOpen] = useState(false);
934
1355
  const branding = useResolvedSdkBranding(iqAuthBaseUrl);
@@ -999,7 +1420,7 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
999
1420
  /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
1000
1421
  /* @__PURE__ */ jsx("div", { children: user.email })
1001
1422
  ] }),
1002
- /* @__PURE__ */ jsx("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: "Account" }),
1423
+ /* @__PURE__ */ jsx("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: t2("userButton.manageAccount") }),
1003
1424
  /* @__PURE__ */ jsx(
1004
1425
  "button",
1005
1426
  {
@@ -1007,7 +1428,7 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1007
1428
  type: "button",
1008
1429
  onClick: signOut2,
1009
1430
  style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
1010
- children: "Sign out"
1431
+ children: t2("userButton.signOut")
1011
1432
  }
1012
1433
  )
1013
1434
  ] }) : null
@@ -1016,19 +1437,27 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1016
1437
  );
1017
1438
  }
1018
1439
  function UserProfile({ iqAuthBaseUrl, className }) {
1440
+ const t2 = useT();
1441
+ const localeBundle = useLocale();
1019
1442
  const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1020
1443
  const [user, setUser] = useState(null);
1021
1444
  const [oldPassword, setOldPassword] = useState("");
1022
1445
  const [newPassword, setNewPassword] = useState("");
1023
1446
  const [pwState, setPwState] = useState({ submitting: false, message: "", error: "" });
1024
1447
  const [sessions, setSessions] = useState([]);
1448
+ const [revokeAllBusy, setRevokeAllBusy] = useState(false);
1449
+ const loadSessions = () => {
1450
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions`, { credentials: "include" }).then((r) => r.ok ? r.json() : Promise.reject(r)).then((p) => setSessions(p?.data?.sessions || [])).catch(() => {
1451
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions`, { credentials: "include" }).then((r) => r.json()).then((p) => setSessions(p?.data?.sessions || p?.data || [])).catch(() => {
1452
+ });
1453
+ });
1454
+ };
1025
1455
  useEffect(() => {
1026
1456
  fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
1027
1457
  if (p?.data) setUser(p.data);
1028
1458
  }).catch(() => {
1029
1459
  });
1030
- fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions`, { credentials: "include" }).then((r) => r.json()).then((p) => setSessions(p?.data?.sessions || p?.data || [])).catch(() => {
1031
- });
1460
+ loadSessions();
1032
1461
  }, [iqAuthBaseUrl]);
1033
1462
  const changePassword = async (e) => {
1034
1463
  e.preventDefault();
@@ -1039,53 +1468,96 @@ function UserProfile({ iqAuthBaseUrl, className }) {
1039
1468
  headers: { "Content-Type": "application/json" },
1040
1469
  body: JSON.stringify({ oldPassword, newPassword })
1041
1470
  });
1042
- setPwState({ submitting: false, message: "Password updated.", error: "" });
1471
+ setPwState({ submitting: false, message: t(localeBundle, "userProfile.passwordUpdated"), error: "" });
1043
1472
  setOldPassword("");
1044
1473
  setNewPassword("");
1045
1474
  } catch (err) {
1046
- setPwState({ submitting: false, message: "", error: err.message });
1475
+ setPwState({ submitting: false, message: "", error: localizeError(localeBundle, err.message) });
1047
1476
  }
1048
1477
  };
1049
1478
  const revoke = async (sessionId) => {
1050
- await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
1479
+ const newRes = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
1480
+ if (!newRes.ok && newRes.status === 404) {
1481
+ await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
1482
+ }
1051
1483
  setSessions((prev) => prev.filter((s) => s.id !== sessionId));
1052
1484
  };
1053
- if (!user) return /* @__PURE__ */ jsx(Shell, { branding, className, children: /* @__PURE__ */ jsx("p", { children: "Loading account\u2026" }) });
1054
- return /* @__PURE__ */ jsxs(Shell, { branding, className, children: [
1055
- /* @__PURE__ */ jsx("h2", { style: { fontSize: 20, fontWeight: 600, margin: "0 0 12px" }, children: "Your account" }),
1485
+ const revokeAllOthers = async () => {
1486
+ setRevokeAllBusy(true);
1487
+ try {
1488
+ await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions/revoke-all-others`, { method: "POST", credentials: "include" });
1489
+ setSessions((prev) => prev.filter((s) => s.isCurrent));
1490
+ } finally {
1491
+ setRevokeAllBusy(false);
1492
+ }
1493
+ };
1494
+ if (!user) return /* @__PURE__ */ jsx(Shell, { branding, className, children: /* @__PURE__ */ jsx("p", { children: t2("common.loading") }) });
1495
+ return /* @__PURE__ */ jsxs(Shell, { branding, className, title: t2("userProfile.title"), children: [
1056
1496
  /* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-profile", style: { marginBottom: 20 }, children: [
1057
- /* @__PURE__ */ jsx("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: "Profile" }),
1497
+ /* @__PURE__ */ jsx("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: t2("userProfile.profileTab") }),
1058
1498
  /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
1059
- /* @__PURE__ */ jsx("strong", { children: "Name:" }),
1499
+ /* @__PURE__ */ jsxs("strong", { children: [
1500
+ t2("common.name"),
1501
+ ":"
1502
+ ] }),
1060
1503
  " ",
1061
1504
  user.name
1062
1505
  ] }),
1063
1506
  /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
1064
- /* @__PURE__ */ jsx("strong", { children: "Email:" }),
1507
+ /* @__PURE__ */ jsxs("strong", { children: [
1508
+ t2("common.email"),
1509
+ ":"
1510
+ ] }),
1065
1511
  " ",
1066
1512
  user.email
1067
1513
  ] })
1068
1514
  ] }),
1069
1515
  /* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-pw", style: { marginBottom: 20 }, children: [
1070
- /* @__PURE__ */ jsx("h3", { id: "iqauth-pw", style: { fontSize: 14, fontWeight: 600 }, children: "Change password" }),
1516
+ /* @__PURE__ */ jsx("h3", { id: "iqauth-pw", style: { fontSize: 14, fontWeight: 600 }, children: t2("userProfile.changePassword") }),
1071
1517
  pwState.error ? /* @__PURE__ */ jsx(ErrorBanner, { message: pwState.error }) : null,
1072
1518
  pwState.message ? /* @__PURE__ */ jsx("div", { role: "status", style: { fontSize: 13, color: "#047857" }, children: pwState.message }) : null,
1073
1519
  /* @__PURE__ */ jsxs("form", { onSubmit: changePassword, style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
1074
- /* @__PURE__ */ jsx(Field, { label: "Current password", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "current-password", value: oldPassword, onChange: (e) => setOldPassword(e.target.value), required: true }) }),
1075
- /* @__PURE__ */ jsx(Field, { label: "New password", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: newPassword, onChange: (e) => setNewPassword(e.target.value), required: true }) }),
1076
- /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: pwState.submitting || !oldPassword || !newPassword, children: pwState.submitting ? "Updating\u2026" : "Change password" })
1520
+ /* @__PURE__ */ jsx(Field, { label: t2("userProfile.currentPassword"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "current-password", value: oldPassword, onChange: (e) => setOldPassword(e.target.value), required: true }) }),
1521
+ /* @__PURE__ */ jsx(Field, { label: t2("userProfile.newPassword"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: newPassword, onChange: (e) => setNewPassword(e.target.value), required: true }) }),
1522
+ /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: pwState.submitting || !oldPassword || !newPassword, children: pwState.submitting ? t2("common.saving") : t2("userProfile.changePassword") })
1077
1523
  ] })
1078
1524
  ] }),
1079
1525
  /* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-sessions", children: [
1080
- /* @__PURE__ */ jsx("h3", { id: "iqauth-sessions", style: { fontSize: 14, fontWeight: 600 }, children: "Sessions" }),
1081
- sessions.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6 }, children: "No active sessions." }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: sessions.map((s) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", fontSize: 13, padding: "6px 10px", background: "#f8fafc", borderRadius: 6 }, children: [
1082
- /* @__PURE__ */ jsx("span", { children: s.userAgent || s.deviceName || "Unknown" }),
1083
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => revoke(s.id), style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "2px 8px", cursor: "pointer" }, children: "Revoke" })
1526
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1527
+ /* @__PURE__ */ jsx("h3", { id: "iqauth-sessions", style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: t2("userProfile.sessionsTab") }),
1528
+ sessions.some((s) => !s.isCurrent) && /* @__PURE__ */ jsx(
1529
+ "button",
1530
+ {
1531
+ type: "button",
1532
+ disabled: revokeAllBusy,
1533
+ onClick: revokeAllOthers,
1534
+ style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "4px 10px", cursor: "pointer" },
1535
+ children: revokeAllBusy ? t2("common.submitting") : t2("userProfile.revokeAllOthers")
1536
+ }
1537
+ )
1538
+ ] }),
1539
+ sessions.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6 }, children: t2("userProfile.sessionsEmpty") }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: "8px 0 0", display: "flex", flexDirection: "column", gap: 6 }, children: sessions.map((s) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 13, padding: "8px 10px", background: s.isCurrent ? "#ecfdf5" : "#f8fafc", borderRadius: 6, border: s.isCurrent ? "1px solid #a7f3d0" : "1px solid transparent" }, children: [
1540
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
1541
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500 }, children: [
1542
+ s.device || s.userAgent || s.deviceName || "\u2014",
1543
+ s.isCurrent && /* @__PURE__ */ jsxs("span", { style: { marginLeft: 8, fontSize: 11, color: "#047857" }, children: [
1544
+ "(",
1545
+ t2("userProfile.thisDevice"),
1546
+ ")"
1547
+ ] })
1548
+ ] }),
1549
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, opacity: 0.65 }, children: [
1550
+ s.ip || "\u2014",
1551
+ s.lastActiveAt ? ` \xB7 ${new Date(s.lastActiveAt).toLocaleString()}` : ""
1552
+ ] })
1553
+ ] }),
1554
+ !s.isCurrent && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => revoke(s.id), style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "2px 8px", cursor: "pointer" }, children: t2("userProfile.revokeSession") })
1084
1555
  ] }, s.id)) })
1085
1556
  ] })
1086
1557
  ] });
1087
1558
  }
1088
- function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1559
+ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, appearance: _appearance, className }) {
1560
+ const t2 = useT();
1089
1561
  const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1090
1562
  const accent = branding?.accentColor || "#6366f1";
1091
1563
  const [memberships, setMemberships] = useState([]);
@@ -1129,7 +1601,7 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1129
1601
  "aria-expanded": open,
1130
1602
  onClick: () => setOpen((o) => !o),
1131
1603
  style: { background: "transparent", border: `1px solid ${accent}55`, color: branding?.primaryColor || "#0f172a", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
1132
- children: active?.tenantName || active?.tenantSlug || "Select organization"
1604
+ children: active?.tenantName || active?.tenantSlug || t2("orgSwitcher.label")
1133
1605
  }
1134
1606
  ),
1135
1607
  open ? /* @__PURE__ */ jsx("div", { role: "menu", style: {
@@ -1143,7 +1615,7 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1143
1615
  boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1144
1616
  padding: 8,
1145
1617
  zIndex: 100
1146
- }, children: memberships.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "No memberships" }) : memberships.map((m) => /* @__PURE__ */ jsxs(
1618
+ }, children: memberships.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: t2("orgSwitcher.noOrgs") }) : memberships.map((m) => /* @__PURE__ */ jsxs(
1147
1619
  "button",
1148
1620
  {
1149
1621
  role: "menuitem",
@@ -1172,28 +1644,979 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1172
1644
  }
1173
1645
  );
1174
1646
  }
1647
+ function useImpersonation() {
1648
+ const { snapshot } = useCtx();
1649
+ return useMemo(() => {
1650
+ const claims = snapshot.claims;
1651
+ const isImpersonating = claims?.purpose === "impersonation" && !!claims?.act?.sub;
1652
+ return {
1653
+ isImpersonating,
1654
+ actor: isImpersonating ? claims.act : null,
1655
+ target: isImpersonating ? snapshot.user : null
1656
+ };
1657
+ }, [snapshot]);
1658
+ }
1659
+ function ImpersonationBanner({ render, onExit, className, style } = {}) {
1660
+ const t2 = useT();
1661
+ const info = useImpersonation();
1662
+ const { manager } = useCtx();
1663
+ const exit = useCallback(async () => {
1664
+ if (onExit) return void onExit();
1665
+ const { exitImpersonation } = await import("./reverify-4UEJXUS6.mjs");
1666
+ const restored = exitImpersonation(manager);
1667
+ if (restored) return;
1668
+ const { signOut: signOut2 } = await import("./signIn-CCY4JE5G.mjs");
1669
+ await signOut2(manager);
1670
+ }, [manager, onExit]);
1671
+ if (!info.isImpersonating) return null;
1672
+ if (render) return createElement(Fragment, null, render({ ...info, exit }));
1673
+ const targetLabel = info.target?.email || info.target?.name || info.target?.sub || "user";
1674
+ const _actorLabel = info.actor?.email || info.actor?.name || info.actor?.sub || "admin";
1675
+ void _actorLabel;
1676
+ return createElement(
1677
+ "div",
1678
+ {
1679
+ role: "alert",
1680
+ className,
1681
+ style: {
1682
+ position: "sticky",
1683
+ top: 0,
1684
+ left: 0,
1685
+ right: 0,
1686
+ zIndex: 9999,
1687
+ background: "#b91c1c",
1688
+ color: "#fff",
1689
+ padding: "8px 16px",
1690
+ display: "flex",
1691
+ alignItems: "center",
1692
+ justifyContent: "space-between",
1693
+ fontSize: 13,
1694
+ fontFamily: "system-ui, sans-serif",
1695
+ ...style
1696
+ }
1697
+ },
1698
+ createElement(
1699
+ "span",
1700
+ null,
1701
+ t2("impersonation.banner", { targetEmail: targetLabel })
1702
+ ),
1703
+ createElement(
1704
+ "button",
1705
+ {
1706
+ type: "button",
1707
+ onClick: exit,
1708
+ style: {
1709
+ background: "rgba(255,255,255,0.18)",
1710
+ color: "#fff",
1711
+ border: "1px solid rgba(255,255,255,0.4)",
1712
+ borderRadius: 4,
1713
+ padding: "4px 10px",
1714
+ cursor: "pointer",
1715
+ fontSize: 12
1716
+ }
1717
+ },
1718
+ t2("impersonation.exit")
1719
+ )
1720
+ );
1721
+ }
1722
+ function useReverification(fn, opts = {}) {
1723
+ const { manager } = useCtx();
1724
+ const level = opts.level ?? "password";
1725
+ return (async (...args) => {
1726
+ let token = null;
1727
+ let res = await fn(token)(...args);
1728
+ const code = await peekErrorCode(res);
1729
+ const isReverifyError = res.status === 401 && code && (code === "REVERIFICATION_REQUIRED" || code === "REVERIFICATION_EXPIRED" || code === "REVERIFICATION_INVALID" || code === "REVERIFICATION_USED" || code === "REVERIFICATION_LEVEL_INSUFFICIENT");
1730
+ if (!isReverifyError) return res;
1731
+ const prompt = opts.prompt ?? defaultReverifyPrompt;
1732
+ const credentials = await prompt(level);
1733
+ if (!credentials) {
1734
+ throw new Error("Reverification cancelled");
1735
+ }
1736
+ const { reverify } = await import("./reverify-4UEJXUS6.mjs");
1737
+ const minted = await reverify(manager, { level, ...credentials });
1738
+ token = minted.token;
1739
+ res = await fn(token)(...args);
1740
+ return res;
1741
+ });
1742
+ }
1743
+ async function peekErrorCode(res) {
1744
+ try {
1745
+ const cloned = res.clone();
1746
+ const body = await cloned.json();
1747
+ return body?.error?.code ?? null;
1748
+ } catch {
1749
+ return null;
1750
+ }
1751
+ }
1752
+ async function defaultReverifyPrompt(level) {
1753
+ if (typeof window === "undefined") return null;
1754
+ if (level === "password") {
1755
+ const password = window.prompt("Confirm your password to continue");
1756
+ if (!password) return null;
1757
+ return { password };
1758
+ }
1759
+ const totp = window.prompt("Enter your MFA code to continue");
1760
+ if (!totp) return null;
1761
+ return { totp, method: "totp" };
1762
+ }
1763
+ function slugify(input) {
1764
+ return input.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
1765
+ }
1766
+ function CreateOrganization({ iqAuthBaseUrl, onCreated, redirectUrl, unstyled, appearance, className }) {
1767
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1768
+ const [name, setName] = useState("");
1769
+ const [slug, setSlug] = useState("");
1770
+ const [slugTouched, setSlugTouched] = useState(false);
1771
+ const [submitting, setSubmitting] = useState(false);
1772
+ const [error, setError] = useState(null);
1773
+ const [created, setCreated] = useState(null);
1774
+ const [slugCheck, setSlugCheck] = useState({ status: "idle" });
1775
+ useEffect(() => {
1776
+ if (!slugTouched) setSlug(slugify(name));
1777
+ }, [name, slugTouched]);
1778
+ useEffect(() => {
1779
+ const s = slug.trim().toLowerCase();
1780
+ if (!s) {
1781
+ setSlugCheck({ status: "idle" });
1782
+ return;
1783
+ }
1784
+ if (!/^[a-z0-9-]{2,64}$/.test(s)) {
1785
+ setSlugCheck({ status: "invalid", checked: s });
1786
+ return;
1787
+ }
1788
+ setSlugCheck({ status: "checking", checked: s });
1789
+ const handle = setTimeout(async () => {
1790
+ try {
1791
+ const res = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants/check-slug?slug=${encodeURIComponent(s)}`, {
1792
+ credentials: "include",
1793
+ headers: { Accept: "application/json" }
1794
+ });
1795
+ const body = await res.json().catch(() => ({}));
1796
+ const data = body.data;
1797
+ if (!data) {
1798
+ setSlugCheck({ status: "idle", checked: s });
1799
+ return;
1800
+ }
1801
+ if (data.reason === "INVALID_FORMAT") {
1802
+ setSlugCheck({ status: "invalid", checked: s });
1803
+ return;
1804
+ }
1805
+ setSlugCheck({ status: data.available ? "available" : "taken", checked: s });
1806
+ } catch {
1807
+ setSlugCheck({ status: "idle", checked: s });
1808
+ }
1809
+ }, 350);
1810
+ return () => clearTimeout(handle);
1811
+ }, [slug, iqAuthBaseUrl]);
1812
+ const submit = async (e) => {
1813
+ e.preventDefault();
1814
+ setSubmitting(true);
1815
+ setError(null);
1816
+ try {
1817
+ const res = await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants`, {
1818
+ method: "POST",
1819
+ headers: { "Content-Type": "application/json" },
1820
+ credentials: "include",
1821
+ body: JSON.stringify({ name: name.trim(), slug: slug.trim() })
1822
+ });
1823
+ const payload = res;
1824
+ const tenant = payload.data ?? payload;
1825
+ const next = { id: tenant.id, name: tenant.name, slug: tenant.slug };
1826
+ setCreated(next);
1827
+ onCreated?.(next);
1828
+ setName("");
1829
+ setSlug("");
1830
+ setSlugTouched(false);
1831
+ if (redirectUrl && typeof window !== "undefined") {
1832
+ const url = typeof redirectUrl === "function" ? redirectUrl(next) : redirectUrl;
1833
+ if (url) window.location.assign(url);
1834
+ }
1835
+ } catch (err) {
1836
+ setError(err instanceof Error ? err.message : "Failed to create organization");
1837
+ } finally {
1838
+ setSubmitting(false);
1839
+ }
1840
+ };
1841
+ const form = /* @__PURE__ */ jsxs("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, "data-iqauth-sdk-create-org": "", "aria-labelledby": "iqauth-create-org-heading", children: [
1842
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
1843
+ created ? /* @__PURE__ */ jsxs("p", { role: "status", style: { fontSize: 13, color: "#047857" }, children: [
1844
+ "Organization \u201C",
1845
+ created.name,
1846
+ "\u201D created."
1847
+ ] }) : null,
1848
+ /* @__PURE__ */ jsx(Field, { label: "Organization name", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-create-org-name", autoFocus: true, style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true, minLength: 2, "aria-required": "true" }) }),
1849
+ /* @__PURE__ */ jsxs(Field, { label: "Organization slug", children: [
1850
+ /* @__PURE__ */ jsx(
1851
+ "input",
1852
+ {
1853
+ "data-testid": "input-create-org-slug",
1854
+ style: { ...inputStyle(), fontFamily: "monospace" },
1855
+ value: slug,
1856
+ onChange: (e) => {
1857
+ setSlugTouched(true);
1858
+ setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "-"));
1859
+ },
1860
+ required: true,
1861
+ pattern: "[a-z0-9-]+",
1862
+ minLength: 2,
1863
+ "aria-required": "true",
1864
+ "aria-describedby": "iqauth-create-org-slug-hint",
1865
+ "aria-invalid": slugCheck.status === "taken" || slugCheck.status === "invalid"
1866
+ }
1867
+ ),
1868
+ /* @__PURE__ */ jsx(
1869
+ "p",
1870
+ {
1871
+ id: "iqauth-create-org-slug-hint",
1872
+ "data-testid": "text-create-org-slug-status",
1873
+ role: "status",
1874
+ "aria-live": "polite",
1875
+ style: { fontSize: 12, marginTop: 4, color: slugCheck.status === "taken" || slugCheck.status === "invalid" ? "#b91c1c" : slugCheck.status === "available" ? "#047857" : "inherit", opacity: slugCheck.status === "checking" ? 0.6 : 1 },
1876
+ children: slugCheck.status === "checking" ? "Checking availability\u2026" : slugCheck.status === "available" ? "Slug is available." : slugCheck.status === "taken" ? "That slug is taken." : slugCheck.status === "invalid" ? "Slugs must be 2\u201364 chars, lowercase letters/numbers/hyphens." : "We'll auto-generate a slug from the name; you can override it."
1877
+ }
1878
+ )
1879
+ ] }),
1880
+ /* @__PURE__ */ jsx(
1881
+ PrimaryButton,
1882
+ {
1883
+ "data-testid": "button-create-org-submit",
1884
+ type: "submit",
1885
+ disabled: submitting || !name.trim() || !slug.trim() || slugCheck.status === "taken" || slugCheck.status === "invalid" || slugCheck.status === "checking",
1886
+ children: submitting ? "Creating\u2026" : "Create organization"
1887
+ }
1888
+ )
1889
+ ] });
1890
+ if (unstyled) {
1891
+ return /* @__PURE__ */ jsxs("div", { className, "data-iqauth-sdk-create-org-bare": "", children: [
1892
+ /* @__PURE__ */ jsx("h3", { id: "iqauth-create-org-heading", style: { fontSize: 14, fontWeight: 600, margin: "0 0 12px" }, children: "Create organization" }),
1893
+ form
1894
+ ] });
1895
+ }
1896
+ return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: "Create organization", subtitle: "Spin up a new tenant for this app.", children: form });
1897
+ }
1898
+ function OrganizationProfile({ iqAuthBaseUrl, tenantId: tenantIdProp, tabs, onDeleted, appearance, className }) {
1899
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1900
+ const baseUrl = iqAuthBaseUrl.replace(/\/$/, "");
1901
+ const visibleTabs = tabs && tabs.length > 0 ? tabs : ["general", "members", "invitations", "danger"];
1902
+ const [activeTab, setActiveTab] = useState(visibleTabs[0]);
1903
+ const [tenantId, setTenantId] = useState(tenantIdProp || null);
1904
+ const [tenant, setTenant] = useState(null);
1905
+ const [members, setMembers] = useState([]);
1906
+ const [pendingInvites, setPendingInvites] = useState([]);
1907
+ const [loading, setLoading] = useState(true);
1908
+ const [invitesLoading, setInvitesLoading] = useState(false);
1909
+ const [error, setError] = useState(null);
1910
+ const [renameValue, setRenameValue] = useState("");
1911
+ const [slugValue, setSlugValue] = useState("");
1912
+ const [renameSubmitting, setRenameSubmitting] = useState(false);
1913
+ const [inviteEmail, setInviteEmail] = useState("");
1914
+ const [inviteRole, setInviteRole] = useState("tenant_member");
1915
+ const [inviteSubmitting, setInviteSubmitting] = useState(false);
1916
+ const [actionMessage, setActionMessage] = useState(null);
1917
+ const [confirmDeleteText, setConfirmDeleteText] = useState("");
1918
+ const [confirmDeletePassword, setConfirmDeletePassword] = useState("");
1919
+ const [deleteSubmitting, setDeleteSubmitting] = useState(false);
1920
+ const { user } = useUser();
1921
+ const callerRole = user?.role || null;
1922
+ const callerIsAdmin = callerRole === "tenant_admin" || callerRole === "platform_admin";
1923
+ const visibleTabsFiltered = visibleTabs.filter((t2) => t2 !== "danger" || callerIsAdmin);
1924
+ useEffect(() => {
1925
+ let cancelled = false;
1926
+ (async () => {
1927
+ try {
1928
+ let tid = tenantIdProp || null;
1929
+ if (!tid) {
1930
+ const me = await fetch(`${baseUrl}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json());
1931
+ tid = me?.data?.tenantId || null;
1932
+ }
1933
+ if (!tid) {
1934
+ if (!cancelled) {
1935
+ setError("No active tenant");
1936
+ setLoading(false);
1937
+ }
1938
+ return;
1939
+ }
1940
+ const t2 = await fetch(`${baseUrl}/api/v1/tenants/${tid}`, { credentials: "include" }).then((r) => r.json());
1941
+ const m = await fetch(`${baseUrl}/api/v1/tenants/${tid}/users`, { credentials: "include" }).then((r) => r.json());
1942
+ if (cancelled) return;
1943
+ setTenantId(tid);
1944
+ setTenant(t2?.data ? { id: t2.data.id, name: t2.data.name, slug: t2.data.slug } : null);
1945
+ setRenameValue(t2?.data?.name || "");
1946
+ setSlugValue(t2?.data?.slug || "");
1947
+ const rows = m?.data || [];
1948
+ setMembers(rows.map((row) => ({
1949
+ userId: String(row.userId ?? row.user?.id ?? row.id ?? ""),
1950
+ email: String(row.email ?? row.user?.email ?? ""),
1951
+ name: String(row.name ?? row.user?.name ?? ""),
1952
+ role: String(row.role ?? row.roles?.[0] ?? "tenant_member"),
1953
+ joinedAt: row.joinedAt ?? row.createdAt
1954
+ })));
1955
+ } catch (err) {
1956
+ if (!cancelled) setError(err instanceof Error ? err.message : "Failed to load organization");
1957
+ } finally {
1958
+ if (!cancelled) setLoading(false);
1959
+ }
1960
+ })();
1961
+ return () => {
1962
+ cancelled = true;
1963
+ };
1964
+ }, [baseUrl, tenantIdProp]);
1965
+ const loadInvites = async (tid) => {
1966
+ setInvitesLoading(true);
1967
+ try {
1968
+ const r = await fetch(`${baseUrl}/api/v1/invites?tenantId=${encodeURIComponent(tid)}&status=pending`, { credentials: "include" }).then((res) => res.json());
1969
+ const rows = r?.data || [];
1970
+ setPendingInvites(rows.map((inv) => ({
1971
+ id: String(inv.id),
1972
+ email: String(inv.email),
1973
+ role: String(inv.role),
1974
+ status: String(inv.status),
1975
+ expiresAt: inv.expiresAt,
1976
+ createdAt: inv.createdAt
1977
+ })));
1978
+ } catch (err) {
1979
+ setError(err instanceof Error ? err.message : "Failed to load invitations");
1980
+ } finally {
1981
+ setInvitesLoading(false);
1982
+ }
1983
+ };
1984
+ useEffect(() => {
1985
+ if (activeTab === "invitations" && tenantId) loadInvites(tenantId);
1986
+ }, [activeTab, tenantId]);
1987
+ const reloadMembers = async () => {
1988
+ if (!tenantId) return;
1989
+ const m = await fetch(`${baseUrl}/api/v1/tenants/${tenantId}/users`, { credentials: "include" }).then((r) => r.json());
1990
+ const rows = m?.data || [];
1991
+ setMembers(rows.map((row) => ({
1992
+ userId: String(row.userId ?? row.user?.id ?? row.id ?? ""),
1993
+ email: String(row.email ?? row.user?.email ?? ""),
1994
+ name: String(row.name ?? row.user?.name ?? ""),
1995
+ role: String(row.role ?? row.roles?.[0] ?? "tenant_member"),
1996
+ joinedAt: row.joinedAt ?? row.createdAt
1997
+ })));
1998
+ };
1999
+ const submitRename = async (e) => {
2000
+ e.preventDefault();
2001
+ if (!tenantId) return;
2002
+ setRenameSubmitting(true);
2003
+ setError(null);
2004
+ try {
2005
+ const body = {};
2006
+ if (renameValue.trim() && renameValue.trim() !== tenant?.name) body.name = renameValue.trim();
2007
+ if (slugValue.trim() && slugValue.trim() !== tenant?.slug) body.slug = slugValue.trim();
2008
+ if (Object.keys(body).length === 0) {
2009
+ setRenameSubmitting(false);
2010
+ return;
2011
+ }
2012
+ const res = await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}`, {
2013
+ method: "PATCH",
2014
+ headers: { "Content-Type": "application/json" },
2015
+ credentials: "include",
2016
+ body: JSON.stringify(body)
2017
+ });
2018
+ const t2 = res?.data ?? res;
2019
+ setTenant({ id: t2.id, name: t2.name, slug: t2.slug });
2020
+ setActionMessage("Organization saved.");
2021
+ } catch (err) {
2022
+ setError(err instanceof Error ? err.message : "Failed to save");
2023
+ } finally {
2024
+ setRenameSubmitting(false);
2025
+ }
2026
+ };
2027
+ const submitInvite = async (e) => {
2028
+ e.preventDefault();
2029
+ if (!tenantId) return;
2030
+ setInviteSubmitting(true);
2031
+ setError(null);
2032
+ try {
2033
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/invite`, {
2034
+ method: "POST",
2035
+ headers: { "Content-Type": "application/json" },
2036
+ credentials: "include",
2037
+ body: JSON.stringify({ email: inviteEmail.trim(), role: inviteRole })
2038
+ });
2039
+ setActionMessage(`Invitation sent to ${inviteEmail.trim()}.`);
2040
+ setInviteEmail("");
2041
+ if (activeTab === "invitations") await loadInvites(tenantId);
2042
+ } catch (err) {
2043
+ setError(err instanceof Error ? err.message : "Failed to invite");
2044
+ } finally {
2045
+ setInviteSubmitting(false);
2046
+ }
2047
+ };
2048
+ const changeRole = async (userId, role) => {
2049
+ if (!tenantId) return;
2050
+ try {
2051
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/${userId}/role`, {
2052
+ method: "PATCH",
2053
+ headers: { "Content-Type": "application/json" },
2054
+ credentials: "include",
2055
+ body: JSON.stringify({ role })
2056
+ });
2057
+ await reloadMembers();
2058
+ setActionMessage("Role updated.");
2059
+ } catch (err) {
2060
+ setError(err instanceof Error ? err.message : "Failed to update role");
2061
+ }
2062
+ };
2063
+ const removeMember = async (userId) => {
2064
+ if (!tenantId) return;
2065
+ try {
2066
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/${userId}`, { method: "DELETE", credentials: "include" });
2067
+ await reloadMembers();
2068
+ setActionMessage("Member removed.");
2069
+ } catch (err) {
2070
+ setError(err instanceof Error ? err.message : "Failed to remove member");
2071
+ }
2072
+ };
2073
+ const resendInvite = async (id) => {
2074
+ if (!tenantId) return;
2075
+ try {
2076
+ await jsonFetch(`${baseUrl}/api/v1/invites/${id}/resend`, { method: "POST", credentials: "include" });
2077
+ setActionMessage("Invitation resent.");
2078
+ await loadInvites(tenantId);
2079
+ } catch (err) {
2080
+ setError(err instanceof Error ? err.message : "Failed to resend invitation");
2081
+ }
2082
+ };
2083
+ const revokeInvite = async (id) => {
2084
+ if (!tenantId) return;
2085
+ try {
2086
+ await jsonFetch(`${baseUrl}/api/v1/invites/${id}/revoke`, { method: "POST", credentials: "include" });
2087
+ setActionMessage("Invitation revoked.");
2088
+ await loadInvites(tenantId);
2089
+ } catch (err) {
2090
+ setError(err instanceof Error ? err.message : "Failed to revoke invitation");
2091
+ }
2092
+ };
2093
+ const submitDelete = async () => {
2094
+ if (!tenantId || !tenant) return;
2095
+ if (confirmDeleteText !== tenant.slug) {
2096
+ setError("Type the organization slug to confirm deletion.");
2097
+ return;
2098
+ }
2099
+ if (!confirmDeletePassword) {
2100
+ setError("Re-enter your password to confirm deletion.");
2101
+ return;
2102
+ }
2103
+ setDeleteSubmitting(true);
2104
+ setError(null);
2105
+ try {
2106
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}`, {
2107
+ method: "DELETE",
2108
+ credentials: "include",
2109
+ headers: { "Content-Type": "application/json" },
2110
+ body: JSON.stringify({ confirmPassword: confirmDeletePassword })
2111
+ });
2112
+ setActionMessage("Organization deleted.");
2113
+ setConfirmDeletePassword("");
2114
+ onDeleted?.(tenantId);
2115
+ } catch (err) {
2116
+ setError(err instanceof Error ? err.message : "Failed to delete organization");
2117
+ } finally {
2118
+ setDeleteSubmitting(false);
2119
+ }
2120
+ };
2121
+ if (loading) {
2122
+ return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: "Organization", children: /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", style: { fontSize: 13 }, children: "Loading\u2026" }) });
2123
+ }
2124
+ const tabBtnStyle = (key) => ({
2125
+ background: activeTab === key ? `${branding?.accentColor || "#6366f1"}1a` : "transparent",
2126
+ border: `1px solid ${activeTab === key ? branding?.accentColor || "#6366f1" : "rgba(15,23,42,0.12)"}`,
2127
+ color: branding?.primaryColor || "#0f172a",
2128
+ padding: "6px 12px",
2129
+ borderRadius: 6,
2130
+ cursor: "pointer",
2131
+ fontSize: 12,
2132
+ fontWeight: 500
2133
+ });
2134
+ return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: tenant?.name || "Organization", subtitle: tenant?.slug ? `slug: ${tenant.slug}` : void 0, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, "data-iqauth-sdk-org-profile": "", children: [
2135
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
2136
+ actionMessage ? /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", style: { fontSize: 13, color: "#047857", margin: 0 }, children: actionMessage }) : null,
2137
+ /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": "Organization sections", style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: visibleTabsFiltered.map((t2) => /* @__PURE__ */ jsx(
2138
+ "button",
2139
+ {
2140
+ role: "tab",
2141
+ "aria-selected": activeTab === t2,
2142
+ "aria-controls": `iqauth-org-tab-${t2}`,
2143
+ "data-testid": `tab-org-${t2}`,
2144
+ type: "button",
2145
+ onClick: () => setActiveTab(t2),
2146
+ style: tabBtnStyle(t2),
2147
+ children: t2 === "general" ? "General" : t2 === "members" ? `Members (${members.length})` : t2 === "invitations" ? "Invitations" : "Danger zone"
2148
+ },
2149
+ t2
2150
+ )) }),
2151
+ activeTab === "general" ? /* @__PURE__ */ jsxs("section", { role: "tabpanel", id: "iqauth-org-tab-general", "aria-labelledby": "tab-org-general", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2152
+ /* @__PURE__ */ jsx("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: "Settings" }),
2153
+ /* @__PURE__ */ jsxs("form", { onSubmit: submitRename, style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2154
+ /* @__PURE__ */ jsx(Field, { label: "Organization name", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-org-rename", style: inputStyle(), value: renameValue, onChange: (e) => setRenameValue(e.target.value), required: true, minLength: 2 }) }),
2155
+ /* @__PURE__ */ jsx(Field, { label: "Slug", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-org-slug", style: { ...inputStyle(), fontFamily: "monospace" }, value: slugValue, onChange: (e) => setSlugValue(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "-")), required: true, pattern: "[a-z0-9-]+", minLength: 2 }) }),
2156
+ /* @__PURE__ */ jsx(PrimaryButton, { "data-testid": "button-org-rename", type: "submit", disabled: renameSubmitting || renameValue.trim() === tenant?.name && slugValue.trim() === tenant?.slug, children: renameSubmitting ? "Saving\u2026" : "Save changes" })
2157
+ ] })
2158
+ ] }) : null,
2159
+ activeTab === "members" ? /* @__PURE__ */ jsxs("section", { role: "tabpanel", id: "iqauth-org-tab-members", "aria-labelledby": "tab-org-members", style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
2160
+ /* @__PURE__ */ jsxs("form", { onSubmit: submitInvite, style: { display: "flex", gap: 8, flexWrap: "wrap" }, "aria-label": "Invite a new member", children: [
2161
+ /* @__PURE__ */ jsx("input", { "data-testid": "input-org-invite-email", type: "email", placeholder: "email@company.com", "aria-label": "Email", style: { ...inputStyle(), flex: 1, minWidth: 180 }, value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), required: true }),
2162
+ /* @__PURE__ */ jsxs("select", { "data-testid": "select-org-invite-role", "aria-label": "Role", style: { ...inputStyle(), width: "auto" }, value: inviteRole, onChange: (e) => setInviteRole(e.target.value), children: [
2163
+ /* @__PURE__ */ jsx("option", { value: "tenant_member", children: "tenant_member" }),
2164
+ /* @__PURE__ */ jsx("option", { value: "tenant_admin", children: "tenant_admin" })
2165
+ ] }),
2166
+ /* @__PURE__ */ jsx(PrimaryButton, { "data-testid": "button-org-invite", type: "submit", disabled: inviteSubmitting || !inviteEmail.trim(), children: inviteSubmitting ? "Sending\u2026" : "Send invite" })
2167
+ ] }),
2168
+ members.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6 }, children: "No members yet." }) : /* @__PURE__ */ jsx("ul", { "aria-label": "Members", style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: members.map((m) => /* @__PURE__ */ jsxs("li", { "data-testid": `row-org-member-${m.userId}`, style: { display: "grid", gridTemplateColumns: "1fr auto auto", gap: 8, alignItems: "center", padding: "8px 10px", background: "rgba(15,23,42,0.04)", borderRadius: 6, fontSize: 13 }, children: [
2169
+ /* @__PURE__ */ jsxs("div", { children: [
2170
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: m.name || m.email }),
2171
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: m.email })
2172
+ ] }),
2173
+ /* @__PURE__ */ jsxs("select", { "data-testid": `select-org-member-role-${m.userId}`, "aria-label": `Role for ${m.email}`, value: m.role, onChange: (e) => changeRole(m.userId, e.target.value), style: { ...inputStyle(), width: "auto", padding: "4px 8px", fontSize: 12 }, children: [
2174
+ /* @__PURE__ */ jsx("option", { value: "tenant_member", children: "tenant_member" }),
2175
+ /* @__PURE__ */ jsx("option", { value: "tenant_admin", children: "tenant_admin" })
2176
+ ] }),
2177
+ /* @__PURE__ */ jsx(
2178
+ "button",
2179
+ {
2180
+ type: "button",
2181
+ "data-testid": `button-org-member-remove-${m.userId}`,
2182
+ "aria-label": `Remove ${m.email}`,
2183
+ onClick: () => removeMember(m.userId),
2184
+ style: { background: "transparent", border: "1px solid rgba(220,38,38,0.4)", color: "#b91c1c", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
2185
+ children: "Remove"
2186
+ }
2187
+ )
2188
+ ] }, m.userId)) })
2189
+ ] }) : null,
2190
+ activeTab === "invitations" ? /* @__PURE__ */ jsxs("section", { role: "tabpanel", id: "iqauth-org-tab-invitations", "aria-labelledby": "tab-org-invitations", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2191
+ /* @__PURE__ */ jsx("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: "Pending invitations" }),
2192
+ invitesLoading ? /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", style: { fontSize: 13 }, children: "Loading\u2026" }) : pendingInvites.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6 }, children: "No pending invitations." }) : /* @__PURE__ */ jsx("ul", { "aria-label": "Pending invitations", style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: pendingInvites.map((inv) => /* @__PURE__ */ jsxs("li", { "data-testid": `row-org-invite-${inv.id}`, style: { display: "grid", gridTemplateColumns: "1fr auto auto", gap: 8, alignItems: "center", padding: "8px 10px", background: "rgba(15,23,42,0.04)", borderRadius: 6, fontSize: 13 }, children: [
2193
+ /* @__PURE__ */ jsxs("div", { children: [
2194
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: inv.email }),
2195
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: [
2196
+ "role: ",
2197
+ inv.role,
2198
+ inv.expiresAt ? ` \u2022 expires ${new Date(inv.expiresAt).toLocaleDateString()}` : ""
2199
+ ] })
2200
+ ] }),
2201
+ /* @__PURE__ */ jsx(
2202
+ "button",
2203
+ {
2204
+ type: "button",
2205
+ "data-testid": `button-org-invite-resend-${inv.id}`,
2206
+ onClick: () => resendInvite(inv.id),
2207
+ style: { background: "transparent", border: "1px solid rgba(15,23,42,0.18)", color: "#0f172a", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
2208
+ children: "Resend"
2209
+ }
2210
+ ),
2211
+ /* @__PURE__ */ jsx(
2212
+ "button",
2213
+ {
2214
+ type: "button",
2215
+ "data-testid": `button-org-invite-revoke-${inv.id}`,
2216
+ onClick: () => revokeInvite(inv.id),
2217
+ style: { background: "transparent", border: "1px solid rgba(220,38,38,0.4)", color: "#b91c1c", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
2218
+ children: "Revoke"
2219
+ }
2220
+ )
2221
+ ] }, inv.id)) })
2222
+ ] }) : null,
2223
+ activeTab === "danger" ? /* @__PURE__ */ jsxs("section", { role: "tabpanel", id: "iqauth-org-tab-danger", "aria-labelledby": "tab-org-danger", style: { display: "flex", flexDirection: "column", gap: 10, border: "1px solid rgba(220,38,38,0.3)", padding: 12, borderRadius: 8, background: "rgba(220,38,38,0.04)" }, children: [
2224
+ /* @__PURE__ */ jsx("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0, color: "#b91c1c" }, children: "Delete organization" }),
2225
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 12, opacity: 0.85, margin: 0 }, children: [
2226
+ "This permanently deletes ",
2227
+ /* @__PURE__ */ jsx("strong", { children: tenant?.name }),
2228
+ " and all of its members, roles, and audit history. To confirm, type the slug ",
2229
+ /* @__PURE__ */ jsx("code", { children: tenant?.slug }),
2230
+ " below."
2231
+ ] }),
2232
+ /* @__PURE__ */ jsx(Field, { label: "Type the organization slug to confirm", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-org-delete-confirm", autoComplete: "off", placeholder: tenant?.slug || "", "aria-label": "Type slug to confirm", style: { ...inputStyle(), fontFamily: "monospace" }, value: confirmDeleteText, onChange: (e) => setConfirmDeleteText(e.target.value) }) }),
2233
+ /* @__PURE__ */ jsx(Field, { label: "Re-enter your password", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-org-delete-password", type: "password", autoComplete: "current-password", "aria-label": "Password to confirm deletion", style: inputStyle(), value: confirmDeletePassword, onChange: (e) => setConfirmDeletePassword(e.target.value) }) }),
2234
+ /* @__PURE__ */ jsx(
2235
+ "button",
2236
+ {
2237
+ type: "button",
2238
+ "data-testid": "button-org-delete",
2239
+ onClick: submitDelete,
2240
+ disabled: deleteSubmitting || confirmDeleteText !== tenant?.slug || !confirmDeletePassword,
2241
+ style: { background: "#b91c1c", color: "#fff", border: "none", padding: "8px 14px", borderRadius: 6, cursor: confirmDeleteText === tenant?.slug && confirmDeletePassword ? "pointer" : "not-allowed", fontSize: 13, opacity: confirmDeleteText === tenant?.slug && confirmDeletePassword ? 1 : 0.5 },
2242
+ children: deleteSubmitting ? "Deleting\u2026" : "Permanently delete organization"
2243
+ }
2244
+ )
2245
+ ] }) : null
2246
+ ] }) });
2247
+ }
2248
+ function OrganizationList({ iqAuthBaseUrl, onSelect, showCreate = true, createRedirectUrl, appearance, className }) {
2249
+ const [showCreateForm, setShowCreateForm] = useState(false);
2250
+ const reloadList = () => {
2251
+ setShowCreateForm(false);
2252
+ if (typeof window !== "undefined") setTimeout(() => window.location.reload(), 50);
2253
+ };
2254
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
2255
+ const baseUrl = iqAuthBaseUrl.replace(/\/$/, "");
2256
+ const accent = branding?.accentColor || "#6366f1";
2257
+ const [memberships, setMemberships] = useState([]);
2258
+ const [activeTenantId, setActiveTenantId] = useState(null);
2259
+ const [loading, setLoading] = useState(true);
2260
+ const [error, setError] = useState(null);
2261
+ useEffect(() => {
2262
+ let cancelled = false;
2263
+ Promise.all([
2264
+ fetch(`${baseUrl}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()),
2265
+ fetch(`${baseUrl}/api/v1/tenants/memberships`, { credentials: "include" }).then((r) => r.json())
2266
+ ]).then(([me, mems]) => {
2267
+ if (cancelled) return;
2268
+ setActiveTenantId(me?.data?.tenantId || null);
2269
+ setMemberships(mems?.data?.memberships || mems?.data || []);
2270
+ }).catch((err) => {
2271
+ if (!cancelled) setError(err instanceof Error ? err.message : "Failed to load organizations");
2272
+ }).finally(() => {
2273
+ if (!cancelled) setLoading(false);
2274
+ });
2275
+ return () => {
2276
+ cancelled = true;
2277
+ };
2278
+ }, [baseUrl]);
2279
+ const select = async (tenantId) => {
2280
+ if (tenantId === activeTenantId) {
2281
+ onSelect?.(tenantId);
2282
+ return;
2283
+ }
2284
+ try {
2285
+ await jsonFetch(`${baseUrl}/api/v1/auth/select-tenant`, {
2286
+ method: "POST",
2287
+ headers: { "Content-Type": "application/json" },
2288
+ credentials: "include",
2289
+ body: JSON.stringify({ tenantId })
2290
+ });
2291
+ setActiveTenantId(tenantId);
2292
+ onSelect?.(tenantId);
2293
+ } catch (err) {
2294
+ setError(err instanceof Error ? err.message : "Failed to switch organization");
2295
+ }
2296
+ };
2297
+ return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: "Your organizations", subtitle: "Select an organization to make it active.", children: /* @__PURE__ */ jsxs("div", { "data-iqauth-sdk-org-list": "", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2298
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
2299
+ loading ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13 }, children: "Loading\u2026" }) : memberships.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6 }, children: "You don\u2019t belong to any organizations yet." }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: memberships.map((m) => {
2300
+ const active = m.tenantId === activeTenantId;
2301
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
2302
+ "button",
2303
+ {
2304
+ type: "button",
2305
+ "data-testid": `button-org-list-${m.tenantId}`,
2306
+ onClick: () => select(m.tenantId),
2307
+ style: {
2308
+ display: "block",
2309
+ width: "100%",
2310
+ textAlign: "left",
2311
+ padding: "10px 12px",
2312
+ borderRadius: 6,
2313
+ cursor: "pointer",
2314
+ fontSize: 13,
2315
+ background: active ? `${accent}1a` : "transparent",
2316
+ border: `1px solid ${active ? accent : "rgba(15,23,42,0.12)"}`,
2317
+ color: branding?.primaryColor || "#0f172a"
2318
+ },
2319
+ children: [
2320
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
2321
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: [
2322
+ (m.roles || []).join(", ") || "\u2014",
2323
+ active ? /* @__PURE__ */ jsx("span", { style: { marginLeft: 8, color: accent, fontWeight: 600 }, children: "active" }) : null
2324
+ ] })
2325
+ ]
2326
+ }
2327
+ ) }, m.tenantId);
2328
+ }) }),
2329
+ showCreate ? /* @__PURE__ */ jsx("div", { style: { marginTop: 12, paddingTop: 12, borderTop: "1px solid rgba(15,23,42,0.08)" }, children: showCreateForm ? /* @__PURE__ */ jsxs(Fragment2, { children: [
2330
+ /* @__PURE__ */ jsx(
2331
+ CreateOrganization,
2332
+ {
2333
+ iqAuthBaseUrl,
2334
+ unstyled: true,
2335
+ appearance,
2336
+ onCreated: (t2) => {
2337
+ onSelect?.(t2.id);
2338
+ reloadList();
2339
+ },
2340
+ redirectUrl: createRedirectUrl
2341
+ }
2342
+ ),
2343
+ /* @__PURE__ */ jsx(
2344
+ "button",
2345
+ {
2346
+ type: "button",
2347
+ "data-testid": "button-org-list-create-cancel",
2348
+ onClick: () => setShowCreateForm(false),
2349
+ style: { marginTop: 8, background: "transparent", border: "none", color: branding?.accentColor || "#6366f1", cursor: "pointer", fontSize: 12, padding: 0 },
2350
+ children: "Cancel"
2351
+ }
2352
+ )
2353
+ ] }) : /* @__PURE__ */ jsx(
2354
+ "button",
2355
+ {
2356
+ type: "button",
2357
+ "data-testid": "button-org-list-create",
2358
+ onClick: () => setShowCreateForm(true),
2359
+ style: { background: "transparent", border: `1px dashed ${accent}`, color: branding?.primaryColor || "#0f172a", padding: "10px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13, width: "100%" },
2360
+ children: "+ Create new organization"
2361
+ }
2362
+ ) }) : null
2363
+ ] }) });
2364
+ }
2365
+ function Waitlist({ iqAuthBaseUrl, appKey, appId, title, subtitle, successMessage, appearance, className }) {
2366
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl, appId);
2367
+ const [email, setEmail] = useState("");
2368
+ const [name, setName] = useState("");
2369
+ const [organizationName, setOrganizationName] = useState("");
2370
+ const [submitting, setSubmitting] = useState(false);
2371
+ const [error, setError] = useState(null);
2372
+ const [submitted, setSubmitted] = useState(null);
2373
+ const submit = async (e) => {
2374
+ e.preventDefault();
2375
+ setSubmitting(true);
2376
+ setError(null);
2377
+ try {
2378
+ const res = await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/waitlist`, {
2379
+ method: "POST",
2380
+ headers: { "Content-Type": "application/json" },
2381
+ body: JSON.stringify({
2382
+ email: email.trim().toLowerCase(),
2383
+ name: name.trim() || void 0,
2384
+ organizationName: organizationName.trim() || void 0,
2385
+ appKey: appKey || void 0,
2386
+ appId: appId || void 0
2387
+ })
2388
+ });
2389
+ const data = res?.data ?? res;
2390
+ setSubmitted({ duplicate: !!data?.duplicate });
2391
+ } catch (err) {
2392
+ setError(err instanceof Error ? err.message : "Failed to join the waitlist");
2393
+ } finally {
2394
+ setSubmitting(false);
2395
+ }
2396
+ };
2397
+ if (submitted) {
2398
+ return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: title || "You\u2019re on the list", children: /* @__PURE__ */ jsx("p", { "data-testid": "text-waitlist-success", style: { fontSize: 14 }, children: successMessage || (submitted.duplicate ? "You were already on the waitlist \u2014 we\u2019ll be in touch." : "Thanks! We\u2019ll email you when access opens up.") }) });
2399
+ }
2400
+ return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: title || "Join the waitlist", subtitle: subtitle || "Enter your email and we\u2019ll be in touch when access opens up.", children: /* @__PURE__ */ jsxs("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, "data-iqauth-sdk-waitlist": "", children: [
2401
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
2402
+ /* @__PURE__ */ jsx(Field, { label: "Work email", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-waitlist-email", type: "email", autoComplete: "email", required: true, style: inputStyle(), value: email, onChange: (e) => setEmail(e.target.value) }) }),
2403
+ /* @__PURE__ */ jsx(Field, { label: "Name (optional)", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-waitlist-name", autoComplete: "name", style: inputStyle(), value: name, onChange: (e) => setName(e.target.value) }) }),
2404
+ /* @__PURE__ */ jsx(Field, { label: "Organization (optional)", children: /* @__PURE__ */ jsx("input", { "data-testid": "input-waitlist-org", autoComplete: "organization", style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
2405
+ /* @__PURE__ */ jsx(PrimaryButton, { "data-testid": "button-waitlist-submit", type: "submit", disabled: submitting || !email, children: submitting ? "Joining\u2026" : "Join the waitlist" })
2406
+ ] }) });
2407
+ }
2408
+ function usePasswordlessOptions(override) {
2409
+ const ctx = useContext(IQAuthContext);
2410
+ const baseFromCtx = ctx?.manager?.issuerUrl;
2411
+ const iqAuthBaseUrl = override?.iqAuthBaseUrl || baseFromCtx || (typeof window !== "undefined" ? window.location.origin : "");
2412
+ return { iqAuthBaseUrl, cookieSession: override?.cookieSession ?? true };
2413
+ }
2414
+ function useMagicLink(override) {
2415
+ const opts = usePasswordlessOptions(override);
2416
+ const [sent, setSent] = useState(false);
2417
+ const [busy, setBusy] = useState(false);
2418
+ const [error, setError] = useState(null);
2419
+ const request = useCallback(async (input) => {
2420
+ setBusy(true);
2421
+ setError(null);
2422
+ setSent(false);
2423
+ try {
2424
+ await requestMagicLink(opts, input);
2425
+ setSent(true);
2426
+ } catch (e) {
2427
+ setError(e instanceof Error ? e.message : "Magic link request failed");
2428
+ } finally {
2429
+ setBusy(false);
2430
+ }
2431
+ }, [opts.iqAuthBaseUrl, opts.cookieSession]);
2432
+ return { request, sent, busy, error };
2433
+ }
2434
+ function usePasskey(override) {
2435
+ const opts = usePasswordlessOptions(override);
2436
+ const [busy, setBusy] = useState(false);
2437
+ const [error, setError] = useState(null);
2438
+ const signIn2 = useCallback(async (input = {}) => {
2439
+ setBusy(true);
2440
+ setError(null);
2441
+ try {
2442
+ return await signInWithPasskey(opts, input);
2443
+ } catch (e) {
2444
+ const msg = e instanceof Error ? e.message : "Passkey sign-in failed";
2445
+ setError(msg);
2446
+ throw e;
2447
+ } finally {
2448
+ setBusy(false);
2449
+ }
2450
+ }, [opts.iqAuthBaseUrl]);
2451
+ const enroll = useCallback(async (name) => {
2452
+ setBusy(true);
2453
+ setError(null);
2454
+ try {
2455
+ return await enrollPasskey(opts, name);
2456
+ } catch (e) {
2457
+ const msg = e instanceof Error ? e.message : "Passkey enrollment failed";
2458
+ setError(msg);
2459
+ throw e;
2460
+ } finally {
2461
+ setBusy(false);
2462
+ }
2463
+ }, [opts.iqAuthBaseUrl]);
2464
+ return { signIn: signIn2, enroll, busy, error };
2465
+ }
2466
+ function useLinkedIdentities(override) {
2467
+ const opts = usePasswordlessOptions(override);
2468
+ const [identities, setIdentities] = useState([]);
2469
+ const [loading, setLoading] = useState(true);
2470
+ const [error, setError] = useState(null);
2471
+ const refresh = useCallback(async () => {
2472
+ setLoading(true);
2473
+ setError(null);
2474
+ try {
2475
+ setIdentities(await listLinkedIdentities(opts));
2476
+ } catch (e) {
2477
+ setError(e instanceof Error ? e.message : "Failed to load identities");
2478
+ } finally {
2479
+ setLoading(false);
2480
+ }
2481
+ }, [opts.iqAuthBaseUrl]);
2482
+ const link = useCallback(async (input) => {
2483
+ await linkProvider(opts, input);
2484
+ await refresh();
2485
+ }, [opts.iqAuthBaseUrl, refresh]);
2486
+ const unlink = useCallback(async (provider, password) => {
2487
+ await unlinkProvider(opts, { provider, reauth: { password } });
2488
+ await refresh();
2489
+ }, [opts.iqAuthBaseUrl, refresh]);
2490
+ useEffect(() => {
2491
+ refresh();
2492
+ }, [refresh]);
2493
+ return { identities, loading, error, refresh, link, unlink };
2494
+ }
2495
+ function MagicLinkSignInForm(props) {
2496
+ const { request, sent, busy, error } = useMagicLink(props);
2497
+ const [email, setEmail] = useState("");
2498
+ return /* @__PURE__ */ jsxs(
2499
+ "form",
2500
+ {
2501
+ "data-testid": "form-magic-link",
2502
+ className: props.className,
2503
+ onSubmit: (e) => {
2504
+ e.preventDefault();
2505
+ if (email) void request({ email, appId: props.appId, redirectUri: props.redirectUri });
2506
+ },
2507
+ style: { display: "flex", flexDirection: "column", gap: 8 },
2508
+ children: [
2509
+ /* @__PURE__ */ jsx(
2510
+ "input",
2511
+ {
2512
+ "data-testid": "input-magic-link-email",
2513
+ type: "email",
2514
+ required: true,
2515
+ value: email,
2516
+ placeholder: props.placeholder ?? "you@example.com",
2517
+ onChange: (e) => setEmail(e.target.value),
2518
+ style: { padding: 8, border: "1px solid rgba(15,23,42,0.15)", borderRadius: 6 }
2519
+ }
2520
+ ),
2521
+ /* @__PURE__ */ jsx("button", { "data-testid": "button-magic-link-submit", type: "submit", disabled: busy || !email, children: busy ? "Sending\u2026" : props.buttonLabel ?? "Email me a sign-in link" }),
2522
+ sent ? /* @__PURE__ */ jsx("p", { "data-testid": "text-magic-link-sent", style: { fontSize: 12 }, children: "If the email is on file, a link is on the way." }) : null,
2523
+ error ? /* @__PURE__ */ jsx("p", { "data-testid": "text-magic-link-error", style: { fontSize: 12, color: "#b91c1c" }, children: error }) : null
2524
+ ]
2525
+ }
2526
+ );
2527
+ }
2528
+ function PasskeySignInButton({ email, className, children, ...rest }) {
2529
+ const { signIn: signIn2, busy, error } = usePasskey(rest);
2530
+ return /* @__PURE__ */ jsxs("div", { className, children: [
2531
+ /* @__PURE__ */ jsx(
2532
+ "button",
2533
+ {
2534
+ "data-testid": "button-passkey-signin",
2535
+ disabled: busy,
2536
+ onClick: () => void signIn2({ email }).catch(() => {
2537
+ }),
2538
+ style: { padding: "8px 12px", borderRadius: 6, border: "1px solid rgba(15,23,42,0.15)", background: "transparent", cursor: "pointer" },
2539
+ children: busy ? "Verifying\u2026" : children ?? "Sign in with a passkey"
2540
+ }
2541
+ ),
2542
+ error ? /* @__PURE__ */ jsx("p", { "data-testid": "text-passkey-error", style: { fontSize: 12, color: "#b91c1c", marginTop: 4 }, children: error }) : null
2543
+ ] });
2544
+ }
2545
+ function LinkedAccounts({ className, onChange, ...rest }) {
2546
+ const { identities, loading, error, unlink } = useLinkedIdentities(rest);
2547
+ return /* @__PURE__ */ jsx("div", { "data-testid": "section-linked-accounts", className, children: loading ? /* @__PURE__ */ jsx("p", { children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx("p", { style: { color: "#b91c1c" }, children: error }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: identities.map((i) => /* @__PURE__ */ jsxs("li", { "data-testid": `row-identity-${i.provider}`, style: { display: "flex", justifyContent: "space-between", padding: "6px 0" }, children: [
2548
+ /* @__PURE__ */ jsxs("span", { children: [
2549
+ /* @__PURE__ */ jsx("strong", { style: { textTransform: "capitalize" }, children: i.provider }),
2550
+ " ",
2551
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.7 }, children: i.label || i.providerUserId || "" })
2552
+ ] }),
2553
+ i.canUnlink ? /* @__PURE__ */ jsx(
2554
+ "button",
2555
+ {
2556
+ "data-testid": `button-unlink-${i.provider}`,
2557
+ onClick: async () => {
2558
+ const pw = window.prompt("Confirm your password to unlink this identity") || void 0;
2559
+ try {
2560
+ await unlink(i.provider, pw);
2561
+ onChange?.();
2562
+ } catch {
2563
+ }
2564
+ },
2565
+ children: "Unlink"
2566
+ }
2567
+ ) : null
2568
+ ] }, i.id)) }) });
2569
+ }
1175
2570
  var __version__ = "phase-bc-1.0.0";
1176
2571
  export {
1177
2572
  AuthCallback,
2573
+ CreateOrganization,
1178
2574
  IQAuthLoaded,
1179
2575
  IQAuthLoading,
1180
2576
  IQAuthProvider,
2577
+ IQAuthReturnToBouncer,
2578
+ ImpersonationBanner,
2579
+ LinkedAccounts,
2580
+ MagicLinkSignInForm,
2581
+ MultisessionAppSupport,
2582
+ OrganizationList,
2583
+ OrganizationProfile,
1181
2584
  OrganizationSwitcher,
2585
+ PasskeySignInButton,
2586
+ Protect,
1182
2587
  RedirectToSignIn,
2588
+ RedirectToSignedIn,
1183
2589
  SignIn,
1184
2590
  SignUp,
1185
2591
  SignedIn,
1186
2592
  SignedOut,
1187
2593
  UserButton,
1188
2594
  UserProfile,
2595
+ Waitlist,
1189
2596
  __version__,
2597
+ isReturnToAllowed,
1190
2598
  isSilentSsoEligible,
2599
+ preflightReturnTo,
2600
+ revokeSession,
1191
2601
  sanitizeBrandCss,
2602
+ sanitizeReturnTo,
2603
+ slugify,
2604
+ useAccountList,
2605
+ useAccountSwitcher,
1192
2606
  useAuth,
1193
2607
  useAuthFetch,
1194
2608
  useIQAuthSignInContext,
2609
+ useImpersonation,
2610
+ useLinkedIdentities,
2611
+ useLocale,
2612
+ useMagicLink,
1195
2613
  useOrganization,
2614
+ usePasskey,
1196
2615
  useResolvedSdkBranding,
2616
+ useReturnTo,
2617
+ useReverification,
1197
2618
  useSession,
2619
+ useSessionList,
2620
+ useT,
1198
2621
  useUser
1199
2622
  };