@iqauth/sdk 2.3.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/browser-session.d.mts +3 -2
- package/dist/browser-session.d.ts +3 -2
- package/dist/browser.d.mts +64 -29
- package/dist/browser.d.ts +64 -29
- package/dist/browser.js +782 -38
- package/dist/browser.mjs +43 -3
- package/dist/bundle-LUKDQYVQ.mjs +374 -0
- package/dist/chunk-3JULWS6F.mjs +106 -0
- package/dist/chunk-5T7GHBX6.mjs +1165 -0
- package/dist/{chunk-KGEPDXHU.mjs → chunk-6TDJJER7.mjs} +2 -2
- package/dist/{chunk-RACIPVLD.mjs → chunk-76W5TLQQ.mjs} +262 -220
- package/dist/{chunk-EKTNEZIH.mjs → chunk-BVV54LPI.mjs} +37 -5
- package/dist/chunk-LIZYFXH7.mjs +90 -0
- package/dist/chunk-MKKZULZR.mjs +241 -0
- package/dist/chunk-SL3KRS4W.mjs +54 -0
- package/dist/chunk-TKZTCPEK.mjs +232 -0
- package/dist/chunk-UKZLOHZG.mjs +83 -0
- package/dist/cli/index.js +144 -36
- package/dist/cli/index.mjs +1 -1
- package/dist/{client-DTX4hNdS.d.ts → client-BNQe3AgF.d.ts} +3 -62
- package/dist/{client-vdh2a9fJ.d.mts → client-kYlJFgPv.d.mts} +3 -62
- package/dist/doctor-YYNHNMLD.mjs +198 -0
- package/dist/{express-A0-dWEMy.d.mts → express-B6_1vBYZ.d.mts} +23 -2
- package/dist/{express-Bo_pJKHN.d.ts → express-CHpfa7D_.d.ts} +23 -2
- package/dist/express.d.mts +5 -4
- package/dist/express.d.ts +5 -4
- package/dist/express.js +36 -4
- package/dist/express.mjs +8 -8
- package/dist/fastify.js +2 -2
- package/dist/fastify.mjs +4 -4
- package/dist/hono.js +2 -2
- package/dist/hono.mjs +4 -4
- package/dist/index.d.mts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +500 -4
- package/dist/index.mjs +29 -9
- package/dist/locales.d.mts +53 -0
- package/dist/locales.d.ts +53 -0
- package/dist/locales.js +1202 -0
- package/dist/locales.mjs +29 -0
- package/dist/mobile.d.mts +3 -2
- package/dist/mobile.d.ts +3 -2
- package/dist/next.d.mts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +2 -2
- package/dist/next.mjs +1 -1
- package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
- package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
- package/dist/react.d.mts +1349 -10
- package/dist/react.d.ts +1349 -10
- package/dist/react.js +3006 -568
- package/dist/react.mjs +1540 -97
- package/dist/reverify-4UEJXUS6.mjs +16 -0
- package/dist/server/handlers.d.mts +10 -1
- package/dist/server/handlers.d.ts +10 -1
- package/dist/server/handlers.js +2 -2
- package/dist/server/handlers.mjs +1 -1
- package/dist/server.d.mts +5 -3
- package/dist/server.d.ts +5 -3
- package/dist/server.js +89 -4
- package/dist/server.mjs +12 -8
- package/dist/service.d.mts +3 -2
- package/dist/service.d.ts +3 -2
- package/dist/signIn-CCY4JE5G.mjs +15 -0
- package/dist/{signIn-Cd0P4y9d.d.mts → signIn-CiIBTJIh.d.mts} +224 -4
- package/dist/{signIn-DKakyzeu.d.ts → signIn-OCr88Zf8.d.ts} +224 -4
- package/dist/test.d.mts +86 -0
- package/dist/test.d.ts +86 -0
- package/dist/test.js +289 -0
- package/dist/test.mjs +9 -0
- package/dist/tokens-DCyzzn8L.d.mts +63 -0
- package/dist/tokens-aHiGFr_E.d.ts +63 -0
- package/dist/types-6bNdxesb.d.mts +196 -0
- package/dist/types-6bNdxesb.d.ts +196 -0
- package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.mts} +6 -0
- package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.ts} +6 -0
- package/dist/webhooks.d.mts +61 -0
- package/dist/webhooks.d.ts +61 -0
- package/dist/webhooks.js +119 -0
- package/dist/webhooks.mjs +11 -0
- package/dist/ws.d.mts +73 -0
- package/dist/ws.d.ts +73 -0
- package/dist/ws.js +397 -0
- package/dist/ws.mjs +12 -0
- package/package.json +22 -2
- 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-
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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(() => {
|
|
@@ -252,7 +629,19 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
|
|
|
252
629
|
let cancelled = false;
|
|
253
630
|
setLoading(true);
|
|
254
631
|
const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
|
|
255
|
-
fetch(url, { credentials: "include" }).then((r) =>
|
|
632
|
+
fetch(url, { credentials: "include" }).then(async (r) => {
|
|
633
|
+
const contentType = r.headers.get("content-type") || "";
|
|
634
|
+
if (!r.ok || !contentType.includes("json")) {
|
|
635
|
+
const bodyPreview = await r.text().then((t2) => t2.slice(0, 160)).catch(() => "");
|
|
636
|
+
console.error(
|
|
637
|
+
`[IQAuth] sign-in-context request failed: ${r.status} ${r.statusText} (content-type: ${contentType || "\u2014"}). URL: ${url}. Common causes: (1) iqAuthBaseUrl points at the wrong host (it should be your IQAuth issuer, e.g. https://auth.dispositioniq.com \u2014 NOT your own app's domain); (2) the app key "${appKey}" is wrong or revoked; (3) CORS preflight is blocked because this origin isn't in the app's allowed-origins list. Response body preview: ${bodyPreview}`
|
|
638
|
+
);
|
|
639
|
+
throw new Error(
|
|
640
|
+
r.status >= 500 ? "Failed to load sign-in context (server error)" : `Failed to load sign-in context (HTTP ${r.status})`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return r.json();
|
|
644
|
+
}).then((payload) => {
|
|
256
645
|
if (cancelled) return;
|
|
257
646
|
if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
|
|
258
647
|
setCtx(payload.data);
|
|
@@ -275,6 +664,14 @@ var SHELL_CSS = `
|
|
|
275
664
|
grid-template-columns: 1fr;
|
|
276
665
|
background: var(--brand-bg, #f7f7f6);
|
|
277
666
|
color: var(--brand-text, #0f172a);
|
|
667
|
+
/* Container queries so the two-pane layout responds to the SDK's
|
|
668
|
+
RENDERED width, not the viewport. This keeps the form usable when
|
|
669
|
+
a host app embeds <SignIn/> inside a narrower card or sidebar
|
|
670
|
+
instead of full-screen \u2014 the previous viewport @media query would
|
|
671
|
+
happily render the side-by-side hero+form into a 200px container
|
|
672
|
+
and wrap text one character per line. */
|
|
673
|
+
container-type: inline-size;
|
|
674
|
+
container-name: iqauth-sdk;
|
|
278
675
|
}
|
|
279
676
|
.iqauth-sdk-hero { display: none; }
|
|
280
677
|
.iqauth-sdk-pane {
|
|
@@ -324,7 +721,7 @@ var SHELL_CSS = `
|
|
|
324
721
|
.iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
|
|
325
722
|
.iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
|
|
326
723
|
|
|
327
|
-
@
|
|
724
|
+
@container iqauth-sdk (min-width: 768px) {
|
|
328
725
|
.iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
|
329
726
|
.iqauth-sdk-hero {
|
|
330
727
|
display: flex; flex-direction: column; justify-content: space-between;
|
|
@@ -345,7 +742,7 @@ var SHELL_CSS = `
|
|
|
345
742
|
.iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
|
|
346
743
|
.iqauth-sdk-mobile-brand { display: none; }
|
|
347
744
|
}
|
|
348
|
-
@
|
|
745
|
+
@container iqauth-sdk (min-width: 1280px) {
|
|
349
746
|
.iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
|
|
350
747
|
}
|
|
351
748
|
`;
|
|
@@ -482,10 +879,12 @@ function Shell({
|
|
|
482
879
|
className,
|
|
483
880
|
children,
|
|
484
881
|
title,
|
|
485
|
-
subtitle
|
|
882
|
+
subtitle,
|
|
883
|
+
appearance
|
|
486
884
|
}) {
|
|
487
885
|
ensureSdkShellStyles();
|
|
488
|
-
|
|
886
|
+
const t2 = useT();
|
|
887
|
+
useDocumentBranding(branding, title || t2("signIn.title"));
|
|
489
888
|
const brandVars = brandStyle(branding);
|
|
490
889
|
const brandName = branding?.brandName || "IQAuth";
|
|
491
890
|
const heroImage = branding?.heroImageUrl || null;
|
|
@@ -496,14 +895,16 @@ function Shell({
|
|
|
496
895
|
const heroStyle = heroImage ? { ["--iqauth-sdk-hero-image"]: `url("${heroImage.replace(/"/g, '\\"')}")` } : {};
|
|
497
896
|
const shellStyle = {
|
|
498
897
|
...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, '\\"')}")` } : {}
|
|
898
|
+
...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {},
|
|
899
|
+
...appearance?.elements?.rootBox?.style || {}
|
|
500
900
|
};
|
|
901
|
+
const ap = appearance?.elements;
|
|
501
902
|
const supportLink = branding?.supportUrl ? branding.supportUrl : branding?.supportEmail ? `mailto:${branding.supportEmail}` : null;
|
|
502
903
|
const hasFooterLinks = !!(branding?.termsUrl || branding?.privacyUrl || supportLink);
|
|
503
904
|
return /* @__PURE__ */ jsxs(
|
|
504
905
|
"div",
|
|
505
906
|
{
|
|
506
|
-
className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`,
|
|
907
|
+
className: `iqauth-sdk-shell${className ? ` ${className}` : ""}${ap?.rootBox?.className ? ` ${ap.rootBox.className}` : ""}`,
|
|
507
908
|
"data-layout": layout,
|
|
508
909
|
"data-social-style": socialStyle || void 0,
|
|
509
910
|
style: shellStyle,
|
|
@@ -524,13 +925,34 @@ function Shell({
|
|
|
524
925
|
] }),
|
|
525
926
|
/* @__PURE__ */ jsx("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ jsxs("main", { style: { width: "100%", maxWidth: 420 }, children: [
|
|
526
927
|
/* @__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(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
928
|
+
/* @__PURE__ */ jsxs(
|
|
929
|
+
"section",
|
|
930
|
+
{
|
|
931
|
+
className: `iqauth-sdk-card${ap?.card?.className ? ` ${ap.card.className}` : ""}`,
|
|
932
|
+
style: ap?.card?.style,
|
|
933
|
+
children: [
|
|
934
|
+
title || subtitle ? /* @__PURE__ */ jsxs(
|
|
935
|
+
"div",
|
|
936
|
+
{
|
|
937
|
+
className: `iqauth-sdk-card-header${ap?.cardHeader?.className ? ` ${ap.cardHeader.className}` : ""}`,
|
|
938
|
+
style: ap?.cardHeader?.style,
|
|
939
|
+
children: [
|
|
940
|
+
title ? /* @__PURE__ */ jsx("h1", { className: ap?.headerTitle?.className, style: ap?.headerTitle?.style, children: title }) : null,
|
|
941
|
+
subtitle ? /* @__PURE__ */ jsx("p", { className: ap?.headerSubtitle?.className, style: ap?.headerSubtitle?.style, children: subtitle }) : null
|
|
942
|
+
]
|
|
943
|
+
}
|
|
944
|
+
) : null,
|
|
945
|
+
/* @__PURE__ */ jsx(
|
|
946
|
+
"div",
|
|
947
|
+
{
|
|
948
|
+
className: `iqauth-sdk-card-body${ap?.cardBody?.className ? ` ${ap.cardBody.className}` : ""}`,
|
|
949
|
+
style: ap?.cardBody?.style,
|
|
950
|
+
children
|
|
951
|
+
}
|
|
952
|
+
)
|
|
953
|
+
]
|
|
954
|
+
}
|
|
955
|
+
),
|
|
534
956
|
hasFooterLinks || branding?.footerText ? /* @__PURE__ */ jsxs("footer", { className: "iqauth-sdk-footer", children: [
|
|
535
957
|
branding?.footerText ? /* @__PURE__ */ jsx("div", { "data-testid": "text-brand-footer-sdk", style: { fontSize: 12, opacity: 0.75 }, children: branding.footerText }) : null,
|
|
536
958
|
hasFooterLinks ? /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-footer-links", children: [
|
|
@@ -621,8 +1043,30 @@ function isSilentSsoEligible(ctx, effectivePrompt) {
|
|
|
621
1043
|
if (!ctx.returnAllowed) return false;
|
|
622
1044
|
return true;
|
|
623
1045
|
}
|
|
624
|
-
function SignIn(
|
|
1046
|
+
function SignIn(props) {
|
|
1047
|
+
const providerCtx = useContext(IQAuthContext);
|
|
1048
|
+
const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
|
|
1049
|
+
const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
|
|
1050
|
+
const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
|
|
1051
|
+
const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
|
|
1052
|
+
const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
|
|
1053
|
+
if (!iqAuthBaseUrl || !appKey) {
|
|
1054
|
+
console.error(
|
|
1055
|
+
"[IQAuth] <SignIn /> could not determine iqAuthBaseUrl/appKey. Either pass them explicitly OR wrap the component in <IQAuthProvider publishableKey=\u2026/>."
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
const t2 = useT();
|
|
1059
|
+
const localeBundle = useLocale();
|
|
625
1060
|
const { ctx, loading, error } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo);
|
|
1061
|
+
const preflightLoggedRef = useRef(false);
|
|
1062
|
+
useEffect(() => {
|
|
1063
|
+
if (!ctx || preflightLoggedRef.current) return;
|
|
1064
|
+
if (ctx.returnAllowed) return;
|
|
1065
|
+
preflightLoggedRef.current = true;
|
|
1066
|
+
console.error(
|
|
1067
|
+
`[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"}].`
|
|
1068
|
+
);
|
|
1069
|
+
}, [ctx, returnTo]);
|
|
626
1070
|
const [email, setEmail] = useState("");
|
|
627
1071
|
const [password, setPassword] = useState("");
|
|
628
1072
|
const [submitting, setSubmitting] = useState(false);
|
|
@@ -681,9 +1125,9 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
|
|
|
681
1125
|
body: JSON.stringify({ email, password, ...oidcPayload() })
|
|
682
1126
|
});
|
|
683
1127
|
const payload = await r.json().catch(() => ({}));
|
|
684
|
-
if (!handlePayload(payload)) setFormError(
|
|
1128
|
+
if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
|
|
685
1129
|
} catch (err) {
|
|
686
|
-
setFormError(err.message ||
|
|
1130
|
+
setFormError(err.message || t(localeBundle, "errors.network"));
|
|
687
1131
|
}
|
|
688
1132
|
setSubmitting(false);
|
|
689
1133
|
};
|
|
@@ -705,7 +1149,7 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
|
|
|
705
1149
|
})
|
|
706
1150
|
});
|
|
707
1151
|
const payload = await r.json().catch(() => ({}));
|
|
708
|
-
if (!handlePayload(payload)) setFormError(
|
|
1152
|
+
if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
|
|
709
1153
|
setSubmitting(false);
|
|
710
1154
|
};
|
|
711
1155
|
const submitTenant = async (tenantId) => {
|
|
@@ -719,7 +1163,7 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
|
|
|
719
1163
|
body: JSON.stringify({ tenantSelectionToken: tenantSel.token, tenantId, ...oidcPayload() })
|
|
720
1164
|
});
|
|
721
1165
|
const payload = await r.json().catch(() => ({}));
|
|
722
|
-
if (!handlePayload(payload)) setFormError(
|
|
1166
|
+
if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
|
|
723
1167
|
setSubmitting(false);
|
|
724
1168
|
};
|
|
725
1169
|
const startGoogleLogin = () => {
|
|
@@ -817,36 +1261,36 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
|
|
|
817
1261
|
setOauthExchanging(false);
|
|
818
1262
|
})();
|
|
819
1263
|
}, [ctx?.app.defaultClientId]);
|
|
820
|
-
if (loading || oauthExchanging) return /* @__PURE__ */ jsx(Shell, { branding: ctx?.branding || null, className, title: oauthExchanging ? "
|
|
821
|
-
if (error || !ctx) return /* @__PURE__ */ jsx(Shell, { branding: null, className, title: "
|
|
822
|
-
if (!ctx.returnAllowed) return /* @__PURE__ */ jsx(Shell, { branding: ctx.branding, className, title: "
|
|
1264
|
+
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") }) });
|
|
1265
|
+
if (error || !ctx) return /* @__PURE__ */ jsx(Shell, { appearance, branding: null, className, title: t2("errors.serverError"), children: /* @__PURE__ */ jsx(ErrorBanner, { message: error || t2("errors.serverError") }) });
|
|
1266
|
+
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
1267
|
const silentEligible = isSilentSsoEligible(ctx, effectivePrompt);
|
|
824
1268
|
if (silentEligible && silent !== "failed") {
|
|
825
|
-
return /* @__PURE__ */ jsxs(Shell, { branding: ctx.branding, className, title: "
|
|
826
|
-
/* @__PURE__ */ jsx("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: "
|
|
827
|
-
/* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: "
|
|
1269
|
+
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: [
|
|
1270
|
+
/* @__PURE__ */ jsx("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: t2("signIn.resumingSession") }),
|
|
1271
|
+
/* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: t2("signIn.useDifferentAccount") })
|
|
828
1272
|
] });
|
|
829
1273
|
}
|
|
830
|
-
const cardTitle = ctx.branding?.loginHeadline ||
|
|
1274
|
+
const cardTitle = ctx.branding?.loginHeadline || t2("signIn.titleWithApp", { appName: ctx.app.name });
|
|
831
1275
|
const cardSubtitle = ctx.branding?.loginSubheadline || void 0;
|
|
832
|
-
return /* @__PURE__ */ jsxs(Shell, { branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
|
|
1276
|
+
return /* @__PURE__ */ jsxs(Shell, { appearance, branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
|
|
833
1277
|
formError ? /* @__PURE__ */ jsx(ErrorBanner, { message: formError }) : null,
|
|
834
|
-
tenantSel ? /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": "
|
|
1278
|
+
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
1279
|
"button",
|
|
836
1280
|
{
|
|
837
1281
|
type: "button",
|
|
838
|
-
"data-iqauth-tenant":
|
|
839
|
-
onClick: () => submitTenant(
|
|
1282
|
+
"data-iqauth-tenant": tn.tenantId,
|
|
1283
|
+
onClick: () => submitTenant(tn.tenantId),
|
|
840
1284
|
style: { textAlign: "left", padding: "10px 14px", border: "1px solid rgba(15,23,42,0.15)", borderRadius: 8, background: "transparent", color: "inherit", cursor: "pointer" },
|
|
841
1285
|
children: [
|
|
842
|
-
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 500 }, children:
|
|
843
|
-
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 12, opacity: 0.6 }, children:
|
|
1286
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 500 }, children: tn.tenantName || tn.tenantSlug || tn.tenantId }),
|
|
1287
|
+
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 12, opacity: 0.6 }, children: tn.roles.join(", ") })
|
|
844
1288
|
]
|
|
845
1289
|
},
|
|
846
|
-
|
|
847
|
-
)) }) : mfa ? /* @__PURE__ */ jsxs("form", { onSubmit: submitMfa, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": "
|
|
848
|
-
!mfa.backup && mfa.methods.length > 1 ? /* @__PURE__ */ jsx(Field, { label: "
|
|
849
|
-
/* @__PURE__ */ jsx(Field, { label: mfa.backup ? "
|
|
1290
|
+
tn.tenantId
|
|
1291
|
+
)) }) : mfa ? /* @__PURE__ */ jsxs("form", { onSubmit: submitMfa, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": t2("mfa.title"), children: [
|
|
1292
|
+
!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,
|
|
1293
|
+
/* @__PURE__ */ jsx(Field, { label: mfa.backup ? t2("mfa.backupCodeLabel") : t2("mfa.totpLabel"), children: /* @__PURE__ */ jsx(
|
|
850
1294
|
"input",
|
|
851
1295
|
{
|
|
852
1296
|
style: { ...inputStyle(), fontFamily: "monospace", textAlign: mfa.backup ? "left" : "center", letterSpacing: mfa.backup ? "0.04em" : "0.3em" },
|
|
@@ -856,34 +1300,33 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
|
|
|
856
1300
|
inputMode: mfa.backup ? "text" : "numeric"
|
|
857
1301
|
}
|
|
858
1302
|
) }),
|
|
859
|
-
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !mfa.code, children: submitting ? "
|
|
860
|
-
/* @__PURE__ */ jsx(GhostButton, { type: "button", onClick: () => setMfa({ ...mfa, backup: !mfa.backup, code: "" }), children: mfa.backup ? "
|
|
1303
|
+
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !mfa.code, children: submitting ? t2("mfa.submitting") : t2("mfa.submit") }),
|
|
1304
|
+
/* @__PURE__ */ jsx(GhostButton, { type: "button", onClick: () => setMfa({ ...mfa, backup: !mfa.backup, code: "" }), children: mfa.backup ? t2("mfa.useAuthenticator") : t2("mfa.useBackupCode") })
|
|
861
1305
|
] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
862
1306
|
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 || "
|
|
1307
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: "iqauth-sdk-google-btn", onClick: startGoogleLogin, disabled: submitting, "aria-label": ctx.branding?.googleButtonLabel || t2("signIn.continueWithGoogle"), children: [
|
|
864
1308
|
/* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
|
|
865
1309
|
/* @__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
1310
|
/* @__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
1311
|
/* @__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
1312
|
/* @__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
1313
|
] }),
|
|
870
|
-
ctx.branding?.googleButtonLabel || "
|
|
1314
|
+
ctx.branding?.googleButtonLabel || t2("signIn.continueWithGoogle")
|
|
871
1315
|
] }),
|
|
872
|
-
/* @__PURE__ */ jsx("div", { role: "separator", "aria-label": "or", className: "iqauth-sdk-divider", children: "
|
|
1316
|
+
/* @__PURE__ */ jsx("div", { role: "separator", "aria-label": t2("common.or"), className: "iqauth-sdk-divider", children: t2("signIn.dividerOr").toUpperCase() })
|
|
873
1317
|
] }) : null,
|
|
874
|
-
/* @__PURE__ */ jsxs("form", { onSubmit: submitLogin, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label":
|
|
875
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
876
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
877
|
-
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? "
|
|
1318
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: submitLogin, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": t2("signIn.titleWithApp", { appName: ctx.app.name }), children: [
|
|
1319
|
+
/* @__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) }) }),
|
|
1320
|
+
/* @__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) }) }),
|
|
1321
|
+
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? t2("signIn.submitting") : t2("signIn.submit") })
|
|
878
1322
|
] })
|
|
879
1323
|
] }),
|
|
880
|
-
(silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */
|
|
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
|
|
1324
|
+
(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
1325
|
] });
|
|
885
1326
|
}
|
|
886
1327
|
function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
|
|
1328
|
+
const t2 = useT();
|
|
1329
|
+
const localeBundle = useLocale();
|
|
887
1330
|
const { ctx, loading } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo || "");
|
|
888
1331
|
const [name, setName] = useState("");
|
|
889
1332
|
const [email, setEmail] = useState("");
|
|
@@ -905,22 +1348,19 @@ function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
|
|
|
905
1348
|
setDone(true);
|
|
906
1349
|
onSuccess?.();
|
|
907
1350
|
} catch (err) {
|
|
908
|
-
setError(err.message);
|
|
1351
|
+
setError(localizeError(localeBundle, err.message));
|
|
909
1352
|
}
|
|
910
1353
|
setSubmitting(false);
|
|
911
1354
|
};
|
|
912
|
-
if (loading) return /* @__PURE__ */ jsx(Shell, { branding: null, className, children: /* @__PURE__ */ jsx("p", { children: "
|
|
913
|
-
return /* @__PURE__ */
|
|
914
|
-
/* @__PURE__ */ jsx(
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password || !name, children: submitting ? "Creating\u2026" : "Create account" })
|
|
922
|
-
] })
|
|
923
|
-
] });
|
|
1355
|
+
if (loading) return /* @__PURE__ */ jsx(Shell, { branding: null, className, children: /* @__PURE__ */ jsx("p", { children: t2("common.loading") }) });
|
|
1356
|
+
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: [
|
|
1357
|
+
error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
|
|
1358
|
+
/* @__PURE__ */ jsx(Field, { label: t2("signUp.nameLabel"), children: /* @__PURE__ */ jsx("input", { style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true }) }),
|
|
1359
|
+
/* @__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 }) }),
|
|
1360
|
+
/* @__PURE__ */ jsx(Field, { label: `${t2("signUp.tenantNameLabel")} (${t2("common.optional")})`, children: /* @__PURE__ */ jsx("input", { style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
|
|
1361
|
+
/* @__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 }) }),
|
|
1362
|
+
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password || !name, children: submitting ? t2("signUp.submitting") : t2("signUp.submit") })
|
|
1363
|
+
] }) });
|
|
924
1364
|
}
|
|
925
1365
|
function initialsOf(name, email) {
|
|
926
1366
|
const src = name || email || "?";
|
|
@@ -929,6 +1369,7 @@ function initialsOf(name, email) {
|
|
|
929
1369
|
return src.substring(0, 2).toUpperCase();
|
|
930
1370
|
}
|
|
931
1371
|
function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
|
|
1372
|
+
const t2 = useT();
|
|
932
1373
|
const [user, setUser] = useState(null);
|
|
933
1374
|
const [open, setOpen] = useState(false);
|
|
934
1375
|
const branding = useResolvedSdkBranding(iqAuthBaseUrl);
|
|
@@ -999,7 +1440,7 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
|
|
|
999
1440
|
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
|
|
1000
1441
|
/* @__PURE__ */ jsx("div", { children: user.email })
|
|
1001
1442
|
] }),
|
|
1002
|
-
/* @__PURE__ */ jsx("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: "
|
|
1443
|
+
/* @__PURE__ */ jsx("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: t2("userButton.manageAccount") }),
|
|
1003
1444
|
/* @__PURE__ */ jsx(
|
|
1004
1445
|
"button",
|
|
1005
1446
|
{
|
|
@@ -1007,7 +1448,7 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
|
|
|
1007
1448
|
type: "button",
|
|
1008
1449
|
onClick: signOut2,
|
|
1009
1450
|
style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
|
|
1010
|
-
children: "
|
|
1451
|
+
children: t2("userButton.signOut")
|
|
1011
1452
|
}
|
|
1012
1453
|
)
|
|
1013
1454
|
] }) : null
|
|
@@ -1016,19 +1457,27 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
|
|
|
1016
1457
|
);
|
|
1017
1458
|
}
|
|
1018
1459
|
function UserProfile({ iqAuthBaseUrl, className }) {
|
|
1460
|
+
const t2 = useT();
|
|
1461
|
+
const localeBundle = useLocale();
|
|
1019
1462
|
const branding = useResolvedSdkBranding(iqAuthBaseUrl);
|
|
1020
1463
|
const [user, setUser] = useState(null);
|
|
1021
1464
|
const [oldPassword, setOldPassword] = useState("");
|
|
1022
1465
|
const [newPassword, setNewPassword] = useState("");
|
|
1023
1466
|
const [pwState, setPwState] = useState({ submitting: false, message: "", error: "" });
|
|
1024
1467
|
const [sessions, setSessions] = useState([]);
|
|
1468
|
+
const [revokeAllBusy, setRevokeAllBusy] = useState(false);
|
|
1469
|
+
const loadSessions = () => {
|
|
1470
|
+
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(() => {
|
|
1471
|
+
fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions`, { credentials: "include" }).then((r) => r.json()).then((p) => setSessions(p?.data?.sessions || p?.data || [])).catch(() => {
|
|
1472
|
+
});
|
|
1473
|
+
});
|
|
1474
|
+
};
|
|
1025
1475
|
useEffect(() => {
|
|
1026
1476
|
fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
|
|
1027
1477
|
if (p?.data) setUser(p.data);
|
|
1028
1478
|
}).catch(() => {
|
|
1029
1479
|
});
|
|
1030
|
-
|
|
1031
|
-
});
|
|
1480
|
+
loadSessions();
|
|
1032
1481
|
}, [iqAuthBaseUrl]);
|
|
1033
1482
|
const changePassword = async (e) => {
|
|
1034
1483
|
e.preventDefault();
|
|
@@ -1039,53 +1488,96 @@ function UserProfile({ iqAuthBaseUrl, className }) {
|
|
|
1039
1488
|
headers: { "Content-Type": "application/json" },
|
|
1040
1489
|
body: JSON.stringify({ oldPassword, newPassword })
|
|
1041
1490
|
});
|
|
1042
|
-
setPwState({ submitting: false, message: "
|
|
1491
|
+
setPwState({ submitting: false, message: t(localeBundle, "userProfile.passwordUpdated"), error: "" });
|
|
1043
1492
|
setOldPassword("");
|
|
1044
1493
|
setNewPassword("");
|
|
1045
1494
|
} catch (err) {
|
|
1046
|
-
setPwState({ submitting: false, message: "", error: err.message });
|
|
1495
|
+
setPwState({ submitting: false, message: "", error: localizeError(localeBundle, err.message) });
|
|
1047
1496
|
}
|
|
1048
1497
|
};
|
|
1049
1498
|
const revoke = async (sessionId) => {
|
|
1050
|
-
await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/
|
|
1499
|
+
const newRes = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
|
|
1500
|
+
if (!newRes.ok && newRes.status === 404) {
|
|
1501
|
+
await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
|
|
1502
|
+
}
|
|
1051
1503
|
setSessions((prev) => prev.filter((s) => s.id !== sessionId));
|
|
1052
1504
|
};
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1505
|
+
const revokeAllOthers = async () => {
|
|
1506
|
+
setRevokeAllBusy(true);
|
|
1507
|
+
try {
|
|
1508
|
+
await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions/revoke-all-others`, { method: "POST", credentials: "include" });
|
|
1509
|
+
setSessions((prev) => prev.filter((s) => s.isCurrent));
|
|
1510
|
+
} finally {
|
|
1511
|
+
setRevokeAllBusy(false);
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
if (!user) return /* @__PURE__ */ jsx(Shell, { branding, className, children: /* @__PURE__ */ jsx("p", { children: t2("common.loading") }) });
|
|
1515
|
+
return /* @__PURE__ */ jsxs(Shell, { branding, className, title: t2("userProfile.title"), children: [
|
|
1056
1516
|
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-profile", style: { marginBottom: 20 }, children: [
|
|
1057
|
-
/* @__PURE__ */ jsx("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: "
|
|
1517
|
+
/* @__PURE__ */ jsx("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: t2("userProfile.profileTab") }),
|
|
1058
1518
|
/* @__PURE__ */ jsxs("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
|
|
1059
|
-
/* @__PURE__ */
|
|
1519
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
1520
|
+
t2("common.name"),
|
|
1521
|
+
":"
|
|
1522
|
+
] }),
|
|
1060
1523
|
" ",
|
|
1061
1524
|
user.name
|
|
1062
1525
|
] }),
|
|
1063
1526
|
/* @__PURE__ */ jsxs("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
|
|
1064
|
-
/* @__PURE__ */
|
|
1527
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
1528
|
+
t2("common.email"),
|
|
1529
|
+
":"
|
|
1530
|
+
] }),
|
|
1065
1531
|
" ",
|
|
1066
1532
|
user.email
|
|
1067
1533
|
] })
|
|
1068
1534
|
] }),
|
|
1069
1535
|
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-pw", style: { marginBottom: 20 }, children: [
|
|
1070
|
-
/* @__PURE__ */ jsx("h3", { id: "iqauth-pw", style: { fontSize: 14, fontWeight: 600 }, children: "
|
|
1536
|
+
/* @__PURE__ */ jsx("h3", { id: "iqauth-pw", style: { fontSize: 14, fontWeight: 600 }, children: t2("userProfile.changePassword") }),
|
|
1071
1537
|
pwState.error ? /* @__PURE__ */ jsx(ErrorBanner, { message: pwState.error }) : null,
|
|
1072
1538
|
pwState.message ? /* @__PURE__ */ jsx("div", { role: "status", style: { fontSize: 13, color: "#047857" }, children: pwState.message }) : null,
|
|
1073
1539
|
/* @__PURE__ */ jsxs("form", { onSubmit: changePassword, style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
1074
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
1075
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
1076
|
-
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: pwState.submitting || !oldPassword || !newPassword, children: pwState.submitting ? "
|
|
1540
|
+
/* @__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 }) }),
|
|
1541
|
+
/* @__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 }) }),
|
|
1542
|
+
/* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: pwState.submitting || !oldPassword || !newPassword, children: pwState.submitting ? t2("common.saving") : t2("userProfile.changePassword") })
|
|
1077
1543
|
] })
|
|
1078
1544
|
] }),
|
|
1079
1545
|
/* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-sessions", children: [
|
|
1080
|
-
/* @__PURE__ */
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1546
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
|
|
1547
|
+
/* @__PURE__ */ jsx("h3", { id: "iqauth-sessions", style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: t2("userProfile.sessionsTab") }),
|
|
1548
|
+
sessions.some((s) => !s.isCurrent) && /* @__PURE__ */ jsx(
|
|
1549
|
+
"button",
|
|
1550
|
+
{
|
|
1551
|
+
type: "button",
|
|
1552
|
+
disabled: revokeAllBusy,
|
|
1553
|
+
onClick: revokeAllOthers,
|
|
1554
|
+
style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "4px 10px", cursor: "pointer" },
|
|
1555
|
+
children: revokeAllBusy ? t2("common.submitting") : t2("userProfile.revokeAllOthers")
|
|
1556
|
+
}
|
|
1557
|
+
)
|
|
1558
|
+
] }),
|
|
1559
|
+
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: [
|
|
1560
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
1561
|
+
/* @__PURE__ */ jsxs("span", { style: { fontWeight: 500 }, children: [
|
|
1562
|
+
s.device || s.userAgent || s.deviceName || "\u2014",
|
|
1563
|
+
s.isCurrent && /* @__PURE__ */ jsxs("span", { style: { marginLeft: 8, fontSize: 11, color: "#047857" }, children: [
|
|
1564
|
+
"(",
|
|
1565
|
+
t2("userProfile.thisDevice"),
|
|
1566
|
+
")"
|
|
1567
|
+
] })
|
|
1568
|
+
] }),
|
|
1569
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 11, opacity: 0.65 }, children: [
|
|
1570
|
+
s.ip || "\u2014",
|
|
1571
|
+
s.lastActiveAt ? ` \xB7 ${new Date(s.lastActiveAt).toLocaleString()}` : ""
|
|
1572
|
+
] })
|
|
1573
|
+
] }),
|
|
1574
|
+
!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
1575
|
] }, s.id)) })
|
|
1085
1576
|
] })
|
|
1086
1577
|
] });
|
|
1087
1578
|
}
|
|
1088
|
-
function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
|
|
1579
|
+
function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, appearance: _appearance, className }) {
|
|
1580
|
+
const t2 = useT();
|
|
1089
1581
|
const branding = useResolvedSdkBranding(iqAuthBaseUrl);
|
|
1090
1582
|
const accent = branding?.accentColor || "#6366f1";
|
|
1091
1583
|
const [memberships, setMemberships] = useState([]);
|
|
@@ -1129,7 +1621,7 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
|
|
|
1129
1621
|
"aria-expanded": open,
|
|
1130
1622
|
onClick: () => setOpen((o) => !o),
|
|
1131
1623
|
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 || "
|
|
1624
|
+
children: active?.tenantName || active?.tenantSlug || t2("orgSwitcher.label")
|
|
1133
1625
|
}
|
|
1134
1626
|
),
|
|
1135
1627
|
open ? /* @__PURE__ */ jsx("div", { role: "menu", style: {
|
|
@@ -1143,7 +1635,7 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
|
|
|
1143
1635
|
boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
|
|
1144
1636
|
padding: 8,
|
|
1145
1637
|
zIndex: 100
|
|
1146
|
-
}, children: memberships.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "
|
|
1638
|
+
}, 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
1639
|
"button",
|
|
1148
1640
|
{
|
|
1149
1641
|
role: "menuitem",
|
|
@@ -1172,28 +1664,979 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
|
|
|
1172
1664
|
}
|
|
1173
1665
|
);
|
|
1174
1666
|
}
|
|
1667
|
+
function useImpersonation() {
|
|
1668
|
+
const { snapshot } = useCtx();
|
|
1669
|
+
return useMemo(() => {
|
|
1670
|
+
const claims = snapshot.claims;
|
|
1671
|
+
const isImpersonating = claims?.purpose === "impersonation" && !!claims?.act?.sub;
|
|
1672
|
+
return {
|
|
1673
|
+
isImpersonating,
|
|
1674
|
+
actor: isImpersonating ? claims.act : null,
|
|
1675
|
+
target: isImpersonating ? snapshot.user : null
|
|
1676
|
+
};
|
|
1677
|
+
}, [snapshot]);
|
|
1678
|
+
}
|
|
1679
|
+
function ImpersonationBanner({ render, onExit, className, style } = {}) {
|
|
1680
|
+
const t2 = useT();
|
|
1681
|
+
const info = useImpersonation();
|
|
1682
|
+
const { manager } = useCtx();
|
|
1683
|
+
const exit = useCallback(async () => {
|
|
1684
|
+
if (onExit) return void onExit();
|
|
1685
|
+
const { exitImpersonation } = await import("./reverify-4UEJXUS6.mjs");
|
|
1686
|
+
const restored = exitImpersonation(manager);
|
|
1687
|
+
if (restored) return;
|
|
1688
|
+
const { signOut: signOut2 } = await import("./signIn-CCY4JE5G.mjs");
|
|
1689
|
+
await signOut2(manager);
|
|
1690
|
+
}, [manager, onExit]);
|
|
1691
|
+
if (!info.isImpersonating) return null;
|
|
1692
|
+
if (render) return createElement(Fragment, null, render({ ...info, exit }));
|
|
1693
|
+
const targetLabel = info.target?.email || info.target?.name || info.target?.sub || "user";
|
|
1694
|
+
const _actorLabel = info.actor?.email || info.actor?.name || info.actor?.sub || "admin";
|
|
1695
|
+
void _actorLabel;
|
|
1696
|
+
return createElement(
|
|
1697
|
+
"div",
|
|
1698
|
+
{
|
|
1699
|
+
role: "alert",
|
|
1700
|
+
className,
|
|
1701
|
+
style: {
|
|
1702
|
+
position: "sticky",
|
|
1703
|
+
top: 0,
|
|
1704
|
+
left: 0,
|
|
1705
|
+
right: 0,
|
|
1706
|
+
zIndex: 9999,
|
|
1707
|
+
background: "#b91c1c",
|
|
1708
|
+
color: "#fff",
|
|
1709
|
+
padding: "8px 16px",
|
|
1710
|
+
display: "flex",
|
|
1711
|
+
alignItems: "center",
|
|
1712
|
+
justifyContent: "space-between",
|
|
1713
|
+
fontSize: 13,
|
|
1714
|
+
fontFamily: "system-ui, sans-serif",
|
|
1715
|
+
...style
|
|
1716
|
+
}
|
|
1717
|
+
},
|
|
1718
|
+
createElement(
|
|
1719
|
+
"span",
|
|
1720
|
+
null,
|
|
1721
|
+
t2("impersonation.banner", { targetEmail: targetLabel })
|
|
1722
|
+
),
|
|
1723
|
+
createElement(
|
|
1724
|
+
"button",
|
|
1725
|
+
{
|
|
1726
|
+
type: "button",
|
|
1727
|
+
onClick: exit,
|
|
1728
|
+
style: {
|
|
1729
|
+
background: "rgba(255,255,255,0.18)",
|
|
1730
|
+
color: "#fff",
|
|
1731
|
+
border: "1px solid rgba(255,255,255,0.4)",
|
|
1732
|
+
borderRadius: 4,
|
|
1733
|
+
padding: "4px 10px",
|
|
1734
|
+
cursor: "pointer",
|
|
1735
|
+
fontSize: 12
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
t2("impersonation.exit")
|
|
1739
|
+
)
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
function useReverification(fn, opts = {}) {
|
|
1743
|
+
const { manager } = useCtx();
|
|
1744
|
+
const level = opts.level ?? "password";
|
|
1745
|
+
return (async (...args) => {
|
|
1746
|
+
let token = null;
|
|
1747
|
+
let res = await fn(token)(...args);
|
|
1748
|
+
const code = await peekErrorCode(res);
|
|
1749
|
+
const isReverifyError = res.status === 401 && code && (code === "REVERIFICATION_REQUIRED" || code === "REVERIFICATION_EXPIRED" || code === "REVERIFICATION_INVALID" || code === "REVERIFICATION_USED" || code === "REVERIFICATION_LEVEL_INSUFFICIENT");
|
|
1750
|
+
if (!isReverifyError) return res;
|
|
1751
|
+
const prompt = opts.prompt ?? defaultReverifyPrompt;
|
|
1752
|
+
const credentials = await prompt(level);
|
|
1753
|
+
if (!credentials) {
|
|
1754
|
+
throw new Error("Reverification cancelled");
|
|
1755
|
+
}
|
|
1756
|
+
const { reverify } = await import("./reverify-4UEJXUS6.mjs");
|
|
1757
|
+
const minted = await reverify(manager, { level, ...credentials });
|
|
1758
|
+
token = minted.token;
|
|
1759
|
+
res = await fn(token)(...args);
|
|
1760
|
+
return res;
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
async function peekErrorCode(res) {
|
|
1764
|
+
try {
|
|
1765
|
+
const cloned = res.clone();
|
|
1766
|
+
const body = await cloned.json();
|
|
1767
|
+
return body?.error?.code ?? null;
|
|
1768
|
+
} catch {
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
async function defaultReverifyPrompt(level) {
|
|
1773
|
+
if (typeof window === "undefined") return null;
|
|
1774
|
+
if (level === "password") {
|
|
1775
|
+
const password = window.prompt("Confirm your password to continue");
|
|
1776
|
+
if (!password) return null;
|
|
1777
|
+
return { password };
|
|
1778
|
+
}
|
|
1779
|
+
const totp = window.prompt("Enter your MFA code to continue");
|
|
1780
|
+
if (!totp) return null;
|
|
1781
|
+
return { totp, method: "totp" };
|
|
1782
|
+
}
|
|
1783
|
+
function slugify(input) {
|
|
1784
|
+
return input.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
1785
|
+
}
|
|
1786
|
+
function CreateOrganization({ iqAuthBaseUrl, onCreated, redirectUrl, unstyled, appearance, className }) {
|
|
1787
|
+
const branding = useResolvedSdkBranding(iqAuthBaseUrl);
|
|
1788
|
+
const [name, setName] = useState("");
|
|
1789
|
+
const [slug, setSlug] = useState("");
|
|
1790
|
+
const [slugTouched, setSlugTouched] = useState(false);
|
|
1791
|
+
const [submitting, setSubmitting] = useState(false);
|
|
1792
|
+
const [error, setError] = useState(null);
|
|
1793
|
+
const [created, setCreated] = useState(null);
|
|
1794
|
+
const [slugCheck, setSlugCheck] = useState({ status: "idle" });
|
|
1795
|
+
useEffect(() => {
|
|
1796
|
+
if (!slugTouched) setSlug(slugify(name));
|
|
1797
|
+
}, [name, slugTouched]);
|
|
1798
|
+
useEffect(() => {
|
|
1799
|
+
const s = slug.trim().toLowerCase();
|
|
1800
|
+
if (!s) {
|
|
1801
|
+
setSlugCheck({ status: "idle" });
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
if (!/^[a-z0-9-]{2,64}$/.test(s)) {
|
|
1805
|
+
setSlugCheck({ status: "invalid", checked: s });
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
setSlugCheck({ status: "checking", checked: s });
|
|
1809
|
+
const handle = setTimeout(async () => {
|
|
1810
|
+
try {
|
|
1811
|
+
const res = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants/check-slug?slug=${encodeURIComponent(s)}`, {
|
|
1812
|
+
credentials: "include",
|
|
1813
|
+
headers: { Accept: "application/json" }
|
|
1814
|
+
});
|
|
1815
|
+
const body = await res.json().catch(() => ({}));
|
|
1816
|
+
const data = body.data;
|
|
1817
|
+
if (!data) {
|
|
1818
|
+
setSlugCheck({ status: "idle", checked: s });
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (data.reason === "INVALID_FORMAT") {
|
|
1822
|
+
setSlugCheck({ status: "invalid", checked: s });
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
setSlugCheck({ status: data.available ? "available" : "taken", checked: s });
|
|
1826
|
+
} catch {
|
|
1827
|
+
setSlugCheck({ status: "idle", checked: s });
|
|
1828
|
+
}
|
|
1829
|
+
}, 350);
|
|
1830
|
+
return () => clearTimeout(handle);
|
|
1831
|
+
}, [slug, iqAuthBaseUrl]);
|
|
1832
|
+
const submit = async (e) => {
|
|
1833
|
+
e.preventDefault();
|
|
1834
|
+
setSubmitting(true);
|
|
1835
|
+
setError(null);
|
|
1836
|
+
try {
|
|
1837
|
+
const res = await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants`, {
|
|
1838
|
+
method: "POST",
|
|
1839
|
+
headers: { "Content-Type": "application/json" },
|
|
1840
|
+
credentials: "include",
|
|
1841
|
+
body: JSON.stringify({ name: name.trim(), slug: slug.trim() })
|
|
1842
|
+
});
|
|
1843
|
+
const payload = res;
|
|
1844
|
+
const tenant = payload.data ?? payload;
|
|
1845
|
+
const next = { id: tenant.id, name: tenant.name, slug: tenant.slug };
|
|
1846
|
+
setCreated(next);
|
|
1847
|
+
onCreated?.(next);
|
|
1848
|
+
setName("");
|
|
1849
|
+
setSlug("");
|
|
1850
|
+
setSlugTouched(false);
|
|
1851
|
+
if (redirectUrl && typeof window !== "undefined") {
|
|
1852
|
+
const url = typeof redirectUrl === "function" ? redirectUrl(next) : redirectUrl;
|
|
1853
|
+
if (url) window.location.assign(url);
|
|
1854
|
+
}
|
|
1855
|
+
} catch (err) {
|
|
1856
|
+
setError(err instanceof Error ? err.message : "Failed to create organization");
|
|
1857
|
+
} finally {
|
|
1858
|
+
setSubmitting(false);
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
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: [
|
|
1862
|
+
error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
|
|
1863
|
+
created ? /* @__PURE__ */ jsxs("p", { role: "status", style: { fontSize: 13, color: "#047857" }, children: [
|
|
1864
|
+
"Organization \u201C",
|
|
1865
|
+
created.name,
|
|
1866
|
+
"\u201D created."
|
|
1867
|
+
] }) : null,
|
|
1868
|
+
/* @__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" }) }),
|
|
1869
|
+
/* @__PURE__ */ jsxs(Field, { label: "Organization slug", children: [
|
|
1870
|
+
/* @__PURE__ */ jsx(
|
|
1871
|
+
"input",
|
|
1872
|
+
{
|
|
1873
|
+
"data-testid": "input-create-org-slug",
|
|
1874
|
+
style: { ...inputStyle(), fontFamily: "monospace" },
|
|
1875
|
+
value: slug,
|
|
1876
|
+
onChange: (e) => {
|
|
1877
|
+
setSlugTouched(true);
|
|
1878
|
+
setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "-"));
|
|
1879
|
+
},
|
|
1880
|
+
required: true,
|
|
1881
|
+
pattern: "[a-z0-9-]+",
|
|
1882
|
+
minLength: 2,
|
|
1883
|
+
"aria-required": "true",
|
|
1884
|
+
"aria-describedby": "iqauth-create-org-slug-hint",
|
|
1885
|
+
"aria-invalid": slugCheck.status === "taken" || slugCheck.status === "invalid"
|
|
1886
|
+
}
|
|
1887
|
+
),
|
|
1888
|
+
/* @__PURE__ */ jsx(
|
|
1889
|
+
"p",
|
|
1890
|
+
{
|
|
1891
|
+
id: "iqauth-create-org-slug-hint",
|
|
1892
|
+
"data-testid": "text-create-org-slug-status",
|
|
1893
|
+
role: "status",
|
|
1894
|
+
"aria-live": "polite",
|
|
1895
|
+
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 },
|
|
1896
|
+
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."
|
|
1897
|
+
}
|
|
1898
|
+
)
|
|
1899
|
+
] }),
|
|
1900
|
+
/* @__PURE__ */ jsx(
|
|
1901
|
+
PrimaryButton,
|
|
1902
|
+
{
|
|
1903
|
+
"data-testid": "button-create-org-submit",
|
|
1904
|
+
type: "submit",
|
|
1905
|
+
disabled: submitting || !name.trim() || !slug.trim() || slugCheck.status === "taken" || slugCheck.status === "invalid" || slugCheck.status === "checking",
|
|
1906
|
+
children: submitting ? "Creating\u2026" : "Create organization"
|
|
1907
|
+
}
|
|
1908
|
+
)
|
|
1909
|
+
] });
|
|
1910
|
+
if (unstyled) {
|
|
1911
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-iqauth-sdk-create-org-bare": "", children: [
|
|
1912
|
+
/* @__PURE__ */ jsx("h3", { id: "iqauth-create-org-heading", style: { fontSize: 14, fontWeight: 600, margin: "0 0 12px" }, children: "Create organization" }),
|
|
1913
|
+
form
|
|
1914
|
+
] });
|
|
1915
|
+
}
|
|
1916
|
+
return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: "Create organization", subtitle: "Spin up a new tenant for this app.", children: form });
|
|
1917
|
+
}
|
|
1918
|
+
function OrganizationProfile({ iqAuthBaseUrl, tenantId: tenantIdProp, tabs, onDeleted, appearance, className }) {
|
|
1919
|
+
const branding = useResolvedSdkBranding(iqAuthBaseUrl);
|
|
1920
|
+
const baseUrl = iqAuthBaseUrl.replace(/\/$/, "");
|
|
1921
|
+
const visibleTabs = tabs && tabs.length > 0 ? tabs : ["general", "members", "invitations", "danger"];
|
|
1922
|
+
const [activeTab, setActiveTab] = useState(visibleTabs[0]);
|
|
1923
|
+
const [tenantId, setTenantId] = useState(tenantIdProp || null);
|
|
1924
|
+
const [tenant, setTenant] = useState(null);
|
|
1925
|
+
const [members, setMembers] = useState([]);
|
|
1926
|
+
const [pendingInvites, setPendingInvites] = useState([]);
|
|
1927
|
+
const [loading, setLoading] = useState(true);
|
|
1928
|
+
const [invitesLoading, setInvitesLoading] = useState(false);
|
|
1929
|
+
const [error, setError] = useState(null);
|
|
1930
|
+
const [renameValue, setRenameValue] = useState("");
|
|
1931
|
+
const [slugValue, setSlugValue] = useState("");
|
|
1932
|
+
const [renameSubmitting, setRenameSubmitting] = useState(false);
|
|
1933
|
+
const [inviteEmail, setInviteEmail] = useState("");
|
|
1934
|
+
const [inviteRole, setInviteRole] = useState("tenant_member");
|
|
1935
|
+
const [inviteSubmitting, setInviteSubmitting] = useState(false);
|
|
1936
|
+
const [actionMessage, setActionMessage] = useState(null);
|
|
1937
|
+
const [confirmDeleteText, setConfirmDeleteText] = useState("");
|
|
1938
|
+
const [confirmDeletePassword, setConfirmDeletePassword] = useState("");
|
|
1939
|
+
const [deleteSubmitting, setDeleteSubmitting] = useState(false);
|
|
1940
|
+
const { user } = useUser();
|
|
1941
|
+
const callerRole = user?.role || null;
|
|
1942
|
+
const callerIsAdmin = callerRole === "tenant_admin" || callerRole === "platform_admin";
|
|
1943
|
+
const visibleTabsFiltered = visibleTabs.filter((t2) => t2 !== "danger" || callerIsAdmin);
|
|
1944
|
+
useEffect(() => {
|
|
1945
|
+
let cancelled = false;
|
|
1946
|
+
(async () => {
|
|
1947
|
+
try {
|
|
1948
|
+
let tid = tenantIdProp || null;
|
|
1949
|
+
if (!tid) {
|
|
1950
|
+
const me = await fetch(`${baseUrl}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json());
|
|
1951
|
+
tid = me?.data?.tenantId || null;
|
|
1952
|
+
}
|
|
1953
|
+
if (!tid) {
|
|
1954
|
+
if (!cancelled) {
|
|
1955
|
+
setError("No active tenant");
|
|
1956
|
+
setLoading(false);
|
|
1957
|
+
}
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
const t2 = await fetch(`${baseUrl}/api/v1/tenants/${tid}`, { credentials: "include" }).then((r) => r.json());
|
|
1961
|
+
const m = await fetch(`${baseUrl}/api/v1/tenants/${tid}/users`, { credentials: "include" }).then((r) => r.json());
|
|
1962
|
+
if (cancelled) return;
|
|
1963
|
+
setTenantId(tid);
|
|
1964
|
+
setTenant(t2?.data ? { id: t2.data.id, name: t2.data.name, slug: t2.data.slug } : null);
|
|
1965
|
+
setRenameValue(t2?.data?.name || "");
|
|
1966
|
+
setSlugValue(t2?.data?.slug || "");
|
|
1967
|
+
const rows = m?.data || [];
|
|
1968
|
+
setMembers(rows.map((row) => ({
|
|
1969
|
+
userId: String(row.userId ?? row.user?.id ?? row.id ?? ""),
|
|
1970
|
+
email: String(row.email ?? row.user?.email ?? ""),
|
|
1971
|
+
name: String(row.name ?? row.user?.name ?? ""),
|
|
1972
|
+
role: String(row.role ?? row.roles?.[0] ?? "tenant_member"),
|
|
1973
|
+
joinedAt: row.joinedAt ?? row.createdAt
|
|
1974
|
+
})));
|
|
1975
|
+
} catch (err) {
|
|
1976
|
+
if (!cancelled) setError(err instanceof Error ? err.message : "Failed to load organization");
|
|
1977
|
+
} finally {
|
|
1978
|
+
if (!cancelled) setLoading(false);
|
|
1979
|
+
}
|
|
1980
|
+
})();
|
|
1981
|
+
return () => {
|
|
1982
|
+
cancelled = true;
|
|
1983
|
+
};
|
|
1984
|
+
}, [baseUrl, tenantIdProp]);
|
|
1985
|
+
const loadInvites = async (tid) => {
|
|
1986
|
+
setInvitesLoading(true);
|
|
1987
|
+
try {
|
|
1988
|
+
const r = await fetch(`${baseUrl}/api/v1/invites?tenantId=${encodeURIComponent(tid)}&status=pending`, { credentials: "include" }).then((res) => res.json());
|
|
1989
|
+
const rows = r?.data || [];
|
|
1990
|
+
setPendingInvites(rows.map((inv) => ({
|
|
1991
|
+
id: String(inv.id),
|
|
1992
|
+
email: String(inv.email),
|
|
1993
|
+
role: String(inv.role),
|
|
1994
|
+
status: String(inv.status),
|
|
1995
|
+
expiresAt: inv.expiresAt,
|
|
1996
|
+
createdAt: inv.createdAt
|
|
1997
|
+
})));
|
|
1998
|
+
} catch (err) {
|
|
1999
|
+
setError(err instanceof Error ? err.message : "Failed to load invitations");
|
|
2000
|
+
} finally {
|
|
2001
|
+
setInvitesLoading(false);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
useEffect(() => {
|
|
2005
|
+
if (activeTab === "invitations" && tenantId) loadInvites(tenantId);
|
|
2006
|
+
}, [activeTab, tenantId]);
|
|
2007
|
+
const reloadMembers = async () => {
|
|
2008
|
+
if (!tenantId) return;
|
|
2009
|
+
const m = await fetch(`${baseUrl}/api/v1/tenants/${tenantId}/users`, { credentials: "include" }).then((r) => r.json());
|
|
2010
|
+
const rows = m?.data || [];
|
|
2011
|
+
setMembers(rows.map((row) => ({
|
|
2012
|
+
userId: String(row.userId ?? row.user?.id ?? row.id ?? ""),
|
|
2013
|
+
email: String(row.email ?? row.user?.email ?? ""),
|
|
2014
|
+
name: String(row.name ?? row.user?.name ?? ""),
|
|
2015
|
+
role: String(row.role ?? row.roles?.[0] ?? "tenant_member"),
|
|
2016
|
+
joinedAt: row.joinedAt ?? row.createdAt
|
|
2017
|
+
})));
|
|
2018
|
+
};
|
|
2019
|
+
const submitRename = async (e) => {
|
|
2020
|
+
e.preventDefault();
|
|
2021
|
+
if (!tenantId) return;
|
|
2022
|
+
setRenameSubmitting(true);
|
|
2023
|
+
setError(null);
|
|
2024
|
+
try {
|
|
2025
|
+
const body = {};
|
|
2026
|
+
if (renameValue.trim() && renameValue.trim() !== tenant?.name) body.name = renameValue.trim();
|
|
2027
|
+
if (slugValue.trim() && slugValue.trim() !== tenant?.slug) body.slug = slugValue.trim();
|
|
2028
|
+
if (Object.keys(body).length === 0) {
|
|
2029
|
+
setRenameSubmitting(false);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const res = await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}`, {
|
|
2033
|
+
method: "PATCH",
|
|
2034
|
+
headers: { "Content-Type": "application/json" },
|
|
2035
|
+
credentials: "include",
|
|
2036
|
+
body: JSON.stringify(body)
|
|
2037
|
+
});
|
|
2038
|
+
const t2 = res?.data ?? res;
|
|
2039
|
+
setTenant({ id: t2.id, name: t2.name, slug: t2.slug });
|
|
2040
|
+
setActionMessage("Organization saved.");
|
|
2041
|
+
} catch (err) {
|
|
2042
|
+
setError(err instanceof Error ? err.message : "Failed to save");
|
|
2043
|
+
} finally {
|
|
2044
|
+
setRenameSubmitting(false);
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
const submitInvite = async (e) => {
|
|
2048
|
+
e.preventDefault();
|
|
2049
|
+
if (!tenantId) return;
|
|
2050
|
+
setInviteSubmitting(true);
|
|
2051
|
+
setError(null);
|
|
2052
|
+
try {
|
|
2053
|
+
await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/invite`, {
|
|
2054
|
+
method: "POST",
|
|
2055
|
+
headers: { "Content-Type": "application/json" },
|
|
2056
|
+
credentials: "include",
|
|
2057
|
+
body: JSON.stringify({ email: inviteEmail.trim(), role: inviteRole })
|
|
2058
|
+
});
|
|
2059
|
+
setActionMessage(`Invitation sent to ${inviteEmail.trim()}.`);
|
|
2060
|
+
setInviteEmail("");
|
|
2061
|
+
if (activeTab === "invitations") await loadInvites(tenantId);
|
|
2062
|
+
} catch (err) {
|
|
2063
|
+
setError(err instanceof Error ? err.message : "Failed to invite");
|
|
2064
|
+
} finally {
|
|
2065
|
+
setInviteSubmitting(false);
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
const changeRole = async (userId, role) => {
|
|
2069
|
+
if (!tenantId) return;
|
|
2070
|
+
try {
|
|
2071
|
+
await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/${userId}/role`, {
|
|
2072
|
+
method: "PATCH",
|
|
2073
|
+
headers: { "Content-Type": "application/json" },
|
|
2074
|
+
credentials: "include",
|
|
2075
|
+
body: JSON.stringify({ role })
|
|
2076
|
+
});
|
|
2077
|
+
await reloadMembers();
|
|
2078
|
+
setActionMessage("Role updated.");
|
|
2079
|
+
} catch (err) {
|
|
2080
|
+
setError(err instanceof Error ? err.message : "Failed to update role");
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
const removeMember = async (userId) => {
|
|
2084
|
+
if (!tenantId) return;
|
|
2085
|
+
try {
|
|
2086
|
+
await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/${userId}`, { method: "DELETE", credentials: "include" });
|
|
2087
|
+
await reloadMembers();
|
|
2088
|
+
setActionMessage("Member removed.");
|
|
2089
|
+
} catch (err) {
|
|
2090
|
+
setError(err instanceof Error ? err.message : "Failed to remove member");
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
const resendInvite = async (id) => {
|
|
2094
|
+
if (!tenantId) return;
|
|
2095
|
+
try {
|
|
2096
|
+
await jsonFetch(`${baseUrl}/api/v1/invites/${id}/resend`, { method: "POST", credentials: "include" });
|
|
2097
|
+
setActionMessage("Invitation resent.");
|
|
2098
|
+
await loadInvites(tenantId);
|
|
2099
|
+
} catch (err) {
|
|
2100
|
+
setError(err instanceof Error ? err.message : "Failed to resend invitation");
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
const revokeInvite = async (id) => {
|
|
2104
|
+
if (!tenantId) return;
|
|
2105
|
+
try {
|
|
2106
|
+
await jsonFetch(`${baseUrl}/api/v1/invites/${id}/revoke`, { method: "POST", credentials: "include" });
|
|
2107
|
+
setActionMessage("Invitation revoked.");
|
|
2108
|
+
await loadInvites(tenantId);
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
setError(err instanceof Error ? err.message : "Failed to revoke invitation");
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
const submitDelete = async () => {
|
|
2114
|
+
if (!tenantId || !tenant) return;
|
|
2115
|
+
if (confirmDeleteText !== tenant.slug) {
|
|
2116
|
+
setError("Type the organization slug to confirm deletion.");
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
if (!confirmDeletePassword) {
|
|
2120
|
+
setError("Re-enter your password to confirm deletion.");
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
setDeleteSubmitting(true);
|
|
2124
|
+
setError(null);
|
|
2125
|
+
try {
|
|
2126
|
+
await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}`, {
|
|
2127
|
+
method: "DELETE",
|
|
2128
|
+
credentials: "include",
|
|
2129
|
+
headers: { "Content-Type": "application/json" },
|
|
2130
|
+
body: JSON.stringify({ confirmPassword: confirmDeletePassword })
|
|
2131
|
+
});
|
|
2132
|
+
setActionMessage("Organization deleted.");
|
|
2133
|
+
setConfirmDeletePassword("");
|
|
2134
|
+
onDeleted?.(tenantId);
|
|
2135
|
+
} catch (err) {
|
|
2136
|
+
setError(err instanceof Error ? err.message : "Failed to delete organization");
|
|
2137
|
+
} finally {
|
|
2138
|
+
setDeleteSubmitting(false);
|
|
2139
|
+
}
|
|
2140
|
+
};
|
|
2141
|
+
if (loading) {
|
|
2142
|
+
return /* @__PURE__ */ jsx(Shell, { appearance, branding, className, title: "Organization", children: /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", style: { fontSize: 13 }, children: "Loading\u2026" }) });
|
|
2143
|
+
}
|
|
2144
|
+
const tabBtnStyle = (key) => ({
|
|
2145
|
+
background: activeTab === key ? `${branding?.accentColor || "#6366f1"}1a` : "transparent",
|
|
2146
|
+
border: `1px solid ${activeTab === key ? branding?.accentColor || "#6366f1" : "rgba(15,23,42,0.12)"}`,
|
|
2147
|
+
color: branding?.primaryColor || "#0f172a",
|
|
2148
|
+
padding: "6px 12px",
|
|
2149
|
+
borderRadius: 6,
|
|
2150
|
+
cursor: "pointer",
|
|
2151
|
+
fontSize: 12,
|
|
2152
|
+
fontWeight: 500
|
|
2153
|
+
});
|
|
2154
|
+
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: [
|
|
2155
|
+
error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
|
|
2156
|
+
actionMessage ? /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", style: { fontSize: 13, color: "#047857", margin: 0 }, children: actionMessage }) : null,
|
|
2157
|
+
/* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": "Organization sections", style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: visibleTabsFiltered.map((t2) => /* @__PURE__ */ jsx(
|
|
2158
|
+
"button",
|
|
2159
|
+
{
|
|
2160
|
+
role: "tab",
|
|
2161
|
+
"aria-selected": activeTab === t2,
|
|
2162
|
+
"aria-controls": `iqauth-org-tab-${t2}`,
|
|
2163
|
+
"data-testid": `tab-org-${t2}`,
|
|
2164
|
+
type: "button",
|
|
2165
|
+
onClick: () => setActiveTab(t2),
|
|
2166
|
+
style: tabBtnStyle(t2),
|
|
2167
|
+
children: t2 === "general" ? "General" : t2 === "members" ? `Members (${members.length})` : t2 === "invitations" ? "Invitations" : "Danger zone"
|
|
2168
|
+
},
|
|
2169
|
+
t2
|
|
2170
|
+
)) }),
|
|
2171
|
+
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: [
|
|
2172
|
+
/* @__PURE__ */ jsx("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: "Settings" }),
|
|
2173
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: submitRename, style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
2174
|
+
/* @__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 }) }),
|
|
2175
|
+
/* @__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 }) }),
|
|
2176
|
+
/* @__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" })
|
|
2177
|
+
] })
|
|
2178
|
+
] }) : null,
|
|
2179
|
+
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: [
|
|
2180
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: submitInvite, style: { display: "flex", gap: 8, flexWrap: "wrap" }, "aria-label": "Invite a new member", children: [
|
|
2181
|
+
/* @__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 }),
|
|
2182
|
+
/* @__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: [
|
|
2183
|
+
/* @__PURE__ */ jsx("option", { value: "tenant_member", children: "tenant_member" }),
|
|
2184
|
+
/* @__PURE__ */ jsx("option", { value: "tenant_admin", children: "tenant_admin" })
|
|
2185
|
+
] }),
|
|
2186
|
+
/* @__PURE__ */ jsx(PrimaryButton, { "data-testid": "button-org-invite", type: "submit", disabled: inviteSubmitting || !inviteEmail.trim(), children: inviteSubmitting ? "Sending\u2026" : "Send invite" })
|
|
2187
|
+
] }),
|
|
2188
|
+
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: [
|
|
2189
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2190
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: m.name || m.email }),
|
|
2191
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: m.email })
|
|
2192
|
+
] }),
|
|
2193
|
+
/* @__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: [
|
|
2194
|
+
/* @__PURE__ */ jsx("option", { value: "tenant_member", children: "tenant_member" }),
|
|
2195
|
+
/* @__PURE__ */ jsx("option", { value: "tenant_admin", children: "tenant_admin" })
|
|
2196
|
+
] }),
|
|
2197
|
+
/* @__PURE__ */ jsx(
|
|
2198
|
+
"button",
|
|
2199
|
+
{
|
|
2200
|
+
type: "button",
|
|
2201
|
+
"data-testid": `button-org-member-remove-${m.userId}`,
|
|
2202
|
+
"aria-label": `Remove ${m.email}`,
|
|
2203
|
+
onClick: () => removeMember(m.userId),
|
|
2204
|
+
style: { background: "transparent", border: "1px solid rgba(220,38,38,0.4)", color: "#b91c1c", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
|
|
2205
|
+
children: "Remove"
|
|
2206
|
+
}
|
|
2207
|
+
)
|
|
2208
|
+
] }, m.userId)) })
|
|
2209
|
+
] }) : null,
|
|
2210
|
+
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: [
|
|
2211
|
+
/* @__PURE__ */ jsx("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: "Pending invitations" }),
|
|
2212
|
+
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: [
|
|
2213
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2214
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: inv.email }),
|
|
2215
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: [
|
|
2216
|
+
"role: ",
|
|
2217
|
+
inv.role,
|
|
2218
|
+
inv.expiresAt ? ` \u2022 expires ${new Date(inv.expiresAt).toLocaleDateString()}` : ""
|
|
2219
|
+
] })
|
|
2220
|
+
] }),
|
|
2221
|
+
/* @__PURE__ */ jsx(
|
|
2222
|
+
"button",
|
|
2223
|
+
{
|
|
2224
|
+
type: "button",
|
|
2225
|
+
"data-testid": `button-org-invite-resend-${inv.id}`,
|
|
2226
|
+
onClick: () => resendInvite(inv.id),
|
|
2227
|
+
style: { background: "transparent", border: "1px solid rgba(15,23,42,0.18)", color: "#0f172a", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
|
|
2228
|
+
children: "Resend"
|
|
2229
|
+
}
|
|
2230
|
+
),
|
|
2231
|
+
/* @__PURE__ */ jsx(
|
|
2232
|
+
"button",
|
|
2233
|
+
{
|
|
2234
|
+
type: "button",
|
|
2235
|
+
"data-testid": `button-org-invite-revoke-${inv.id}`,
|
|
2236
|
+
onClick: () => revokeInvite(inv.id),
|
|
2237
|
+
style: { background: "transparent", border: "1px solid rgba(220,38,38,0.4)", color: "#b91c1c", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
|
|
2238
|
+
children: "Revoke"
|
|
2239
|
+
}
|
|
2240
|
+
)
|
|
2241
|
+
] }, inv.id)) })
|
|
2242
|
+
] }) : null,
|
|
2243
|
+
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: [
|
|
2244
|
+
/* @__PURE__ */ jsx("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0, color: "#b91c1c" }, children: "Delete organization" }),
|
|
2245
|
+
/* @__PURE__ */ jsxs("p", { style: { fontSize: 12, opacity: 0.85, margin: 0 }, children: [
|
|
2246
|
+
"This permanently deletes ",
|
|
2247
|
+
/* @__PURE__ */ jsx("strong", { children: tenant?.name }),
|
|
2248
|
+
" and all of its members, roles, and audit history. To confirm, type the slug ",
|
|
2249
|
+
/* @__PURE__ */ jsx("code", { children: tenant?.slug }),
|
|
2250
|
+
" below."
|
|
2251
|
+
] }),
|
|
2252
|
+
/* @__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) }) }),
|
|
2253
|
+
/* @__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) }) }),
|
|
2254
|
+
/* @__PURE__ */ jsx(
|
|
2255
|
+
"button",
|
|
2256
|
+
{
|
|
2257
|
+
type: "button",
|
|
2258
|
+
"data-testid": "button-org-delete",
|
|
2259
|
+
onClick: submitDelete,
|
|
2260
|
+
disabled: deleteSubmitting || confirmDeleteText !== tenant?.slug || !confirmDeletePassword,
|
|
2261
|
+
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 },
|
|
2262
|
+
children: deleteSubmitting ? "Deleting\u2026" : "Permanently delete organization"
|
|
2263
|
+
}
|
|
2264
|
+
)
|
|
2265
|
+
] }) : null
|
|
2266
|
+
] }) });
|
|
2267
|
+
}
|
|
2268
|
+
function OrganizationList({ iqAuthBaseUrl, onSelect, showCreate = true, createRedirectUrl, appearance, className }) {
|
|
2269
|
+
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
2270
|
+
const reloadList = () => {
|
|
2271
|
+
setShowCreateForm(false);
|
|
2272
|
+
if (typeof window !== "undefined") setTimeout(() => window.location.reload(), 50);
|
|
2273
|
+
};
|
|
2274
|
+
const branding = useResolvedSdkBranding(iqAuthBaseUrl);
|
|
2275
|
+
const baseUrl = iqAuthBaseUrl.replace(/\/$/, "");
|
|
2276
|
+
const accent = branding?.accentColor || "#6366f1";
|
|
2277
|
+
const [memberships, setMemberships] = useState([]);
|
|
2278
|
+
const [activeTenantId, setActiveTenantId] = useState(null);
|
|
2279
|
+
const [loading, setLoading] = useState(true);
|
|
2280
|
+
const [error, setError] = useState(null);
|
|
2281
|
+
useEffect(() => {
|
|
2282
|
+
let cancelled = false;
|
|
2283
|
+
Promise.all([
|
|
2284
|
+
fetch(`${baseUrl}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()),
|
|
2285
|
+
fetch(`${baseUrl}/api/v1/tenants/memberships`, { credentials: "include" }).then((r) => r.json())
|
|
2286
|
+
]).then(([me, mems]) => {
|
|
2287
|
+
if (cancelled) return;
|
|
2288
|
+
setActiveTenantId(me?.data?.tenantId || null);
|
|
2289
|
+
setMemberships(mems?.data?.memberships || mems?.data || []);
|
|
2290
|
+
}).catch((err) => {
|
|
2291
|
+
if (!cancelled) setError(err instanceof Error ? err.message : "Failed to load organizations");
|
|
2292
|
+
}).finally(() => {
|
|
2293
|
+
if (!cancelled) setLoading(false);
|
|
2294
|
+
});
|
|
2295
|
+
return () => {
|
|
2296
|
+
cancelled = true;
|
|
2297
|
+
};
|
|
2298
|
+
}, [baseUrl]);
|
|
2299
|
+
const select = async (tenantId) => {
|
|
2300
|
+
if (tenantId === activeTenantId) {
|
|
2301
|
+
onSelect?.(tenantId);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
try {
|
|
2305
|
+
await jsonFetch(`${baseUrl}/api/v1/auth/select-tenant`, {
|
|
2306
|
+
method: "POST",
|
|
2307
|
+
headers: { "Content-Type": "application/json" },
|
|
2308
|
+
credentials: "include",
|
|
2309
|
+
body: JSON.stringify({ tenantId })
|
|
2310
|
+
});
|
|
2311
|
+
setActiveTenantId(tenantId);
|
|
2312
|
+
onSelect?.(tenantId);
|
|
2313
|
+
} catch (err) {
|
|
2314
|
+
setError(err instanceof Error ? err.message : "Failed to switch organization");
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
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: [
|
|
2318
|
+
error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
|
|
2319
|
+
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) => {
|
|
2320
|
+
const active = m.tenantId === activeTenantId;
|
|
2321
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2322
|
+
"button",
|
|
2323
|
+
{
|
|
2324
|
+
type: "button",
|
|
2325
|
+
"data-testid": `button-org-list-${m.tenantId}`,
|
|
2326
|
+
onClick: () => select(m.tenantId),
|
|
2327
|
+
style: {
|
|
2328
|
+
display: "block",
|
|
2329
|
+
width: "100%",
|
|
2330
|
+
textAlign: "left",
|
|
2331
|
+
padding: "10px 12px",
|
|
2332
|
+
borderRadius: 6,
|
|
2333
|
+
cursor: "pointer",
|
|
2334
|
+
fontSize: 13,
|
|
2335
|
+
background: active ? `${accent}1a` : "transparent",
|
|
2336
|
+
border: `1px solid ${active ? accent : "rgba(15,23,42,0.12)"}`,
|
|
2337
|
+
color: branding?.primaryColor || "#0f172a"
|
|
2338
|
+
},
|
|
2339
|
+
children: [
|
|
2340
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
|
|
2341
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: [
|
|
2342
|
+
(m.roles || []).join(", ") || "\u2014",
|
|
2343
|
+
active ? /* @__PURE__ */ jsx("span", { style: { marginLeft: 8, color: accent, fontWeight: 600 }, children: "active" }) : null
|
|
2344
|
+
] })
|
|
2345
|
+
]
|
|
2346
|
+
}
|
|
2347
|
+
) }, m.tenantId);
|
|
2348
|
+
}) }),
|
|
2349
|
+
showCreate ? /* @__PURE__ */ jsx("div", { style: { marginTop: 12, paddingTop: 12, borderTop: "1px solid rgba(15,23,42,0.08)" }, children: showCreateForm ? /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
2350
|
+
/* @__PURE__ */ jsx(
|
|
2351
|
+
CreateOrganization,
|
|
2352
|
+
{
|
|
2353
|
+
iqAuthBaseUrl,
|
|
2354
|
+
unstyled: true,
|
|
2355
|
+
appearance,
|
|
2356
|
+
onCreated: (t2) => {
|
|
2357
|
+
onSelect?.(t2.id);
|
|
2358
|
+
reloadList();
|
|
2359
|
+
},
|
|
2360
|
+
redirectUrl: createRedirectUrl
|
|
2361
|
+
}
|
|
2362
|
+
),
|
|
2363
|
+
/* @__PURE__ */ jsx(
|
|
2364
|
+
"button",
|
|
2365
|
+
{
|
|
2366
|
+
type: "button",
|
|
2367
|
+
"data-testid": "button-org-list-create-cancel",
|
|
2368
|
+
onClick: () => setShowCreateForm(false),
|
|
2369
|
+
style: { marginTop: 8, background: "transparent", border: "none", color: branding?.accentColor || "#6366f1", cursor: "pointer", fontSize: 12, padding: 0 },
|
|
2370
|
+
children: "Cancel"
|
|
2371
|
+
}
|
|
2372
|
+
)
|
|
2373
|
+
] }) : /* @__PURE__ */ jsx(
|
|
2374
|
+
"button",
|
|
2375
|
+
{
|
|
2376
|
+
type: "button",
|
|
2377
|
+
"data-testid": "button-org-list-create",
|
|
2378
|
+
onClick: () => setShowCreateForm(true),
|
|
2379
|
+
style: { background: "transparent", border: `1px dashed ${accent}`, color: branding?.primaryColor || "#0f172a", padding: "10px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13, width: "100%" },
|
|
2380
|
+
children: "+ Create new organization"
|
|
2381
|
+
}
|
|
2382
|
+
) }) : null
|
|
2383
|
+
] }) });
|
|
2384
|
+
}
|
|
2385
|
+
function Waitlist({ iqAuthBaseUrl, appKey, appId, title, subtitle, successMessage, appearance, className }) {
|
|
2386
|
+
const branding = useResolvedSdkBranding(iqAuthBaseUrl, appId);
|
|
2387
|
+
const [email, setEmail] = useState("");
|
|
2388
|
+
const [name, setName] = useState("");
|
|
2389
|
+
const [organizationName, setOrganizationName] = useState("");
|
|
2390
|
+
const [submitting, setSubmitting] = useState(false);
|
|
2391
|
+
const [error, setError] = useState(null);
|
|
2392
|
+
const [submitted, setSubmitted] = useState(null);
|
|
2393
|
+
const submit = async (e) => {
|
|
2394
|
+
e.preventDefault();
|
|
2395
|
+
setSubmitting(true);
|
|
2396
|
+
setError(null);
|
|
2397
|
+
try {
|
|
2398
|
+
const res = await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/waitlist`, {
|
|
2399
|
+
method: "POST",
|
|
2400
|
+
headers: { "Content-Type": "application/json" },
|
|
2401
|
+
body: JSON.stringify({
|
|
2402
|
+
email: email.trim().toLowerCase(),
|
|
2403
|
+
name: name.trim() || void 0,
|
|
2404
|
+
organizationName: organizationName.trim() || void 0,
|
|
2405
|
+
appKey: appKey || void 0,
|
|
2406
|
+
appId: appId || void 0
|
|
2407
|
+
})
|
|
2408
|
+
});
|
|
2409
|
+
const data = res?.data ?? res;
|
|
2410
|
+
setSubmitted({ duplicate: !!data?.duplicate });
|
|
2411
|
+
} catch (err) {
|
|
2412
|
+
setError(err instanceof Error ? err.message : "Failed to join the waitlist");
|
|
2413
|
+
} finally {
|
|
2414
|
+
setSubmitting(false);
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
if (submitted) {
|
|
2418
|
+
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.") }) });
|
|
2419
|
+
}
|
|
2420
|
+
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: [
|
|
2421
|
+
error ? /* @__PURE__ */ jsx(ErrorBanner, { message: error }) : null,
|
|
2422
|
+
/* @__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) }) }),
|
|
2423
|
+
/* @__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) }) }),
|
|
2424
|
+
/* @__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) }) }),
|
|
2425
|
+
/* @__PURE__ */ jsx(PrimaryButton, { "data-testid": "button-waitlist-submit", type: "submit", disabled: submitting || !email, children: submitting ? "Joining\u2026" : "Join the waitlist" })
|
|
2426
|
+
] }) });
|
|
2427
|
+
}
|
|
2428
|
+
function usePasswordlessOptions(override) {
|
|
2429
|
+
const ctx = useContext(IQAuthContext);
|
|
2430
|
+
const baseFromCtx = ctx?.manager?.issuerUrl;
|
|
2431
|
+
const iqAuthBaseUrl = override?.iqAuthBaseUrl || baseFromCtx || (typeof window !== "undefined" ? window.location.origin : "");
|
|
2432
|
+
return { iqAuthBaseUrl, cookieSession: override?.cookieSession ?? true };
|
|
2433
|
+
}
|
|
2434
|
+
function useMagicLink(override) {
|
|
2435
|
+
const opts = usePasswordlessOptions(override);
|
|
2436
|
+
const [sent, setSent] = useState(false);
|
|
2437
|
+
const [busy, setBusy] = useState(false);
|
|
2438
|
+
const [error, setError] = useState(null);
|
|
2439
|
+
const request = useCallback(async (input) => {
|
|
2440
|
+
setBusy(true);
|
|
2441
|
+
setError(null);
|
|
2442
|
+
setSent(false);
|
|
2443
|
+
try {
|
|
2444
|
+
await requestMagicLink(opts, input);
|
|
2445
|
+
setSent(true);
|
|
2446
|
+
} catch (e) {
|
|
2447
|
+
setError(e instanceof Error ? e.message : "Magic link request failed");
|
|
2448
|
+
} finally {
|
|
2449
|
+
setBusy(false);
|
|
2450
|
+
}
|
|
2451
|
+
}, [opts.iqAuthBaseUrl, opts.cookieSession]);
|
|
2452
|
+
return { request, sent, busy, error };
|
|
2453
|
+
}
|
|
2454
|
+
function usePasskey(override) {
|
|
2455
|
+
const opts = usePasswordlessOptions(override);
|
|
2456
|
+
const [busy, setBusy] = useState(false);
|
|
2457
|
+
const [error, setError] = useState(null);
|
|
2458
|
+
const signIn2 = useCallback(async (input = {}) => {
|
|
2459
|
+
setBusy(true);
|
|
2460
|
+
setError(null);
|
|
2461
|
+
try {
|
|
2462
|
+
return await signInWithPasskey(opts, input);
|
|
2463
|
+
} catch (e) {
|
|
2464
|
+
const msg = e instanceof Error ? e.message : "Passkey sign-in failed";
|
|
2465
|
+
setError(msg);
|
|
2466
|
+
throw e;
|
|
2467
|
+
} finally {
|
|
2468
|
+
setBusy(false);
|
|
2469
|
+
}
|
|
2470
|
+
}, [opts.iqAuthBaseUrl]);
|
|
2471
|
+
const enroll = useCallback(async (name) => {
|
|
2472
|
+
setBusy(true);
|
|
2473
|
+
setError(null);
|
|
2474
|
+
try {
|
|
2475
|
+
return await enrollPasskey(opts, name);
|
|
2476
|
+
} catch (e) {
|
|
2477
|
+
const msg = e instanceof Error ? e.message : "Passkey enrollment failed";
|
|
2478
|
+
setError(msg);
|
|
2479
|
+
throw e;
|
|
2480
|
+
} finally {
|
|
2481
|
+
setBusy(false);
|
|
2482
|
+
}
|
|
2483
|
+
}, [opts.iqAuthBaseUrl]);
|
|
2484
|
+
return { signIn: signIn2, enroll, busy, error };
|
|
2485
|
+
}
|
|
2486
|
+
function useLinkedIdentities(override) {
|
|
2487
|
+
const opts = usePasswordlessOptions(override);
|
|
2488
|
+
const [identities, setIdentities] = useState([]);
|
|
2489
|
+
const [loading, setLoading] = useState(true);
|
|
2490
|
+
const [error, setError] = useState(null);
|
|
2491
|
+
const refresh = useCallback(async () => {
|
|
2492
|
+
setLoading(true);
|
|
2493
|
+
setError(null);
|
|
2494
|
+
try {
|
|
2495
|
+
setIdentities(await listLinkedIdentities(opts));
|
|
2496
|
+
} catch (e) {
|
|
2497
|
+
setError(e instanceof Error ? e.message : "Failed to load identities");
|
|
2498
|
+
} finally {
|
|
2499
|
+
setLoading(false);
|
|
2500
|
+
}
|
|
2501
|
+
}, [opts.iqAuthBaseUrl]);
|
|
2502
|
+
const link = useCallback(async (input) => {
|
|
2503
|
+
await linkProvider(opts, input);
|
|
2504
|
+
await refresh();
|
|
2505
|
+
}, [opts.iqAuthBaseUrl, refresh]);
|
|
2506
|
+
const unlink = useCallback(async (provider, password) => {
|
|
2507
|
+
await unlinkProvider(opts, { provider, reauth: { password } });
|
|
2508
|
+
await refresh();
|
|
2509
|
+
}, [opts.iqAuthBaseUrl, refresh]);
|
|
2510
|
+
useEffect(() => {
|
|
2511
|
+
refresh();
|
|
2512
|
+
}, [refresh]);
|
|
2513
|
+
return { identities, loading, error, refresh, link, unlink };
|
|
2514
|
+
}
|
|
2515
|
+
function MagicLinkSignInForm(props) {
|
|
2516
|
+
const { request, sent, busy, error } = useMagicLink(props);
|
|
2517
|
+
const [email, setEmail] = useState("");
|
|
2518
|
+
return /* @__PURE__ */ jsxs(
|
|
2519
|
+
"form",
|
|
2520
|
+
{
|
|
2521
|
+
"data-testid": "form-magic-link",
|
|
2522
|
+
className: props.className,
|
|
2523
|
+
onSubmit: (e) => {
|
|
2524
|
+
e.preventDefault();
|
|
2525
|
+
if (email) void request({ email, appId: props.appId, redirectUri: props.redirectUri });
|
|
2526
|
+
},
|
|
2527
|
+
style: { display: "flex", flexDirection: "column", gap: 8 },
|
|
2528
|
+
children: [
|
|
2529
|
+
/* @__PURE__ */ jsx(
|
|
2530
|
+
"input",
|
|
2531
|
+
{
|
|
2532
|
+
"data-testid": "input-magic-link-email",
|
|
2533
|
+
type: "email",
|
|
2534
|
+
required: true,
|
|
2535
|
+
value: email,
|
|
2536
|
+
placeholder: props.placeholder ?? "you@example.com",
|
|
2537
|
+
onChange: (e) => setEmail(e.target.value),
|
|
2538
|
+
style: { padding: 8, border: "1px solid rgba(15,23,42,0.15)", borderRadius: 6 }
|
|
2539
|
+
}
|
|
2540
|
+
),
|
|
2541
|
+
/* @__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" }),
|
|
2542
|
+
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,
|
|
2543
|
+
error ? /* @__PURE__ */ jsx("p", { "data-testid": "text-magic-link-error", style: { fontSize: 12, color: "#b91c1c" }, children: error }) : null
|
|
2544
|
+
]
|
|
2545
|
+
}
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
function PasskeySignInButton({ email, className, children, ...rest }) {
|
|
2549
|
+
const { signIn: signIn2, busy, error } = usePasskey(rest);
|
|
2550
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
2551
|
+
/* @__PURE__ */ jsx(
|
|
2552
|
+
"button",
|
|
2553
|
+
{
|
|
2554
|
+
"data-testid": "button-passkey-signin",
|
|
2555
|
+
disabled: busy,
|
|
2556
|
+
onClick: () => void signIn2({ email }).catch(() => {
|
|
2557
|
+
}),
|
|
2558
|
+
style: { padding: "8px 12px", borderRadius: 6, border: "1px solid rgba(15,23,42,0.15)", background: "transparent", cursor: "pointer" },
|
|
2559
|
+
children: busy ? "Verifying\u2026" : children ?? "Sign in with a passkey"
|
|
2560
|
+
}
|
|
2561
|
+
),
|
|
2562
|
+
error ? /* @__PURE__ */ jsx("p", { "data-testid": "text-passkey-error", style: { fontSize: 12, color: "#b91c1c", marginTop: 4 }, children: error }) : null
|
|
2563
|
+
] });
|
|
2564
|
+
}
|
|
2565
|
+
function LinkedAccounts({ className, onChange, ...rest }) {
|
|
2566
|
+
const { identities, loading, error, unlink } = useLinkedIdentities(rest);
|
|
2567
|
+
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: [
|
|
2568
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2569
|
+
/* @__PURE__ */ jsx("strong", { style: { textTransform: "capitalize" }, children: i.provider }),
|
|
2570
|
+
" ",
|
|
2571
|
+
/* @__PURE__ */ jsx("span", { style: { opacity: 0.7 }, children: i.label || i.providerUserId || "" })
|
|
2572
|
+
] }),
|
|
2573
|
+
i.canUnlink ? /* @__PURE__ */ jsx(
|
|
2574
|
+
"button",
|
|
2575
|
+
{
|
|
2576
|
+
"data-testid": `button-unlink-${i.provider}`,
|
|
2577
|
+
onClick: async () => {
|
|
2578
|
+
const pw = window.prompt("Confirm your password to unlink this identity") || void 0;
|
|
2579
|
+
try {
|
|
2580
|
+
await unlink(i.provider, pw);
|
|
2581
|
+
onChange?.();
|
|
2582
|
+
} catch {
|
|
2583
|
+
}
|
|
2584
|
+
},
|
|
2585
|
+
children: "Unlink"
|
|
2586
|
+
}
|
|
2587
|
+
) : null
|
|
2588
|
+
] }, i.id)) }) });
|
|
2589
|
+
}
|
|
1175
2590
|
var __version__ = "phase-bc-1.0.0";
|
|
1176
2591
|
export {
|
|
1177
2592
|
AuthCallback,
|
|
2593
|
+
CreateOrganization,
|
|
1178
2594
|
IQAuthLoaded,
|
|
1179
2595
|
IQAuthLoading,
|
|
1180
2596
|
IQAuthProvider,
|
|
2597
|
+
IQAuthReturnToBouncer,
|
|
2598
|
+
ImpersonationBanner,
|
|
2599
|
+
LinkedAccounts,
|
|
2600
|
+
MagicLinkSignInForm,
|
|
2601
|
+
MultisessionAppSupport,
|
|
2602
|
+
OrganizationList,
|
|
2603
|
+
OrganizationProfile,
|
|
1181
2604
|
OrganizationSwitcher,
|
|
2605
|
+
PasskeySignInButton,
|
|
2606
|
+
Protect,
|
|
1182
2607
|
RedirectToSignIn,
|
|
2608
|
+
RedirectToSignedIn,
|
|
1183
2609
|
SignIn,
|
|
1184
2610
|
SignUp,
|
|
1185
2611
|
SignedIn,
|
|
1186
2612
|
SignedOut,
|
|
1187
2613
|
UserButton,
|
|
1188
2614
|
UserProfile,
|
|
2615
|
+
Waitlist,
|
|
1189
2616
|
__version__,
|
|
2617
|
+
isReturnToAllowed,
|
|
1190
2618
|
isSilentSsoEligible,
|
|
2619
|
+
preflightReturnTo,
|
|
2620
|
+
revokeSession,
|
|
1191
2621
|
sanitizeBrandCss,
|
|
2622
|
+
sanitizeReturnTo,
|
|
2623
|
+
slugify,
|
|
2624
|
+
useAccountList,
|
|
2625
|
+
useAccountSwitcher,
|
|
1192
2626
|
useAuth,
|
|
1193
2627
|
useAuthFetch,
|
|
1194
2628
|
useIQAuthSignInContext,
|
|
2629
|
+
useImpersonation,
|
|
2630
|
+
useLinkedIdentities,
|
|
2631
|
+
useLocale,
|
|
2632
|
+
useMagicLink,
|
|
1195
2633
|
useOrganization,
|
|
2634
|
+
usePasskey,
|
|
1196
2635
|
useResolvedSdkBranding,
|
|
2636
|
+
useReturnTo,
|
|
2637
|
+
useReverification,
|
|
1197
2638
|
useSession,
|
|
2639
|
+
useSessionList,
|
|
2640
|
+
useT,
|
|
1198
2641
|
useUser
|
|
1199
2642
|
};
|