@authowl/react 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/TeamsSection-OFWSOAZ6.js +295 -0
- package/dist/chunk-DPWUE5HS.js +921 -0
- package/dist/index.cjs +1029 -351
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1076 -1884
- package/dist/styles.css +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
// src/brand.ts
|
|
2
|
+
var DEFAULT_BRAND_COLOR = "#F5B84C";
|
|
3
|
+
var HEX6 = /^#[0-9a-fA-F]{6}$/;
|
|
4
|
+
function isHex6(value) {
|
|
5
|
+
return typeof value === "string" && HEX6.test(value);
|
|
6
|
+
}
|
|
7
|
+
function normalizeHex(value) {
|
|
8
|
+
return isHex6(value) ? value.toLowerCase() : null;
|
|
9
|
+
}
|
|
10
|
+
function srgbToLinear(c) {
|
|
11
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
12
|
+
}
|
|
13
|
+
function linearToSrgb(l) {
|
|
14
|
+
return l <= 31308e-7 ? 12.92 * l : 1.055 * Math.pow(l, 1 / 2.4) - 0.055;
|
|
15
|
+
}
|
|
16
|
+
function hexToRgb(hex) {
|
|
17
|
+
const n = parseInt(hex.slice(1), 16);
|
|
18
|
+
return {
|
|
19
|
+
r: (n >> 16 & 255) / 255,
|
|
20
|
+
g: (n >> 8 & 255) / 255,
|
|
21
|
+
b: (n & 255) / 255
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function clamp01(x) {
|
|
25
|
+
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
26
|
+
}
|
|
27
|
+
function rgbToHex({ r, g, b }) {
|
|
28
|
+
const to = (c) => Math.round(clamp01(c) * 255).toString(16).padStart(2, "0");
|
|
29
|
+
return `#${to(r)}${to(g)}${to(b)}`;
|
|
30
|
+
}
|
|
31
|
+
function rgbToOklch({ r, g, b }) {
|
|
32
|
+
const lr = srgbToLinear(r);
|
|
33
|
+
const lg = srgbToLinear(g);
|
|
34
|
+
const lb = srgbToLinear(b);
|
|
35
|
+
const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
|
|
36
|
+
const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
|
|
37
|
+
const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
|
|
38
|
+
const l_ = Math.cbrt(l);
|
|
39
|
+
const m_ = Math.cbrt(m);
|
|
40
|
+
const s_ = Math.cbrt(s);
|
|
41
|
+
const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
|
|
42
|
+
const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;
|
|
43
|
+
const bb = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;
|
|
44
|
+
return { L, C: Math.hypot(a, bb), h: Math.atan2(bb, a) };
|
|
45
|
+
}
|
|
46
|
+
function oklchToLinearRgb({ L, C, h }) {
|
|
47
|
+
const a = C * Math.cos(h);
|
|
48
|
+
const b = C * Math.sin(h);
|
|
49
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
50
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
51
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
52
|
+
const l = l_ * l_ * l_;
|
|
53
|
+
const m = m_ * m_ * m_;
|
|
54
|
+
const s = s_ * s_ * s_;
|
|
55
|
+
return {
|
|
56
|
+
r: 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
57
|
+
g: -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
58
|
+
b: -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var GAMUT_EPS = 1e-4;
|
|
62
|
+
function inGamut({ r, g, b }) {
|
|
63
|
+
return r >= -GAMUT_EPS && r <= 1 + GAMUT_EPS && g >= -GAMUT_EPS && g <= 1 + GAMUT_EPS && b >= -GAMUT_EPS && b <= 1 + GAMUT_EPS;
|
|
64
|
+
}
|
|
65
|
+
function oklchToHex(color) {
|
|
66
|
+
const linear = oklchToLinearRgb(color);
|
|
67
|
+
if (inGamut(linear)) return rgbToHex(linearToGamma(linear));
|
|
68
|
+
let lo = 0;
|
|
69
|
+
let hi = color.C;
|
|
70
|
+
for (let i = 0; i < 24; i++) {
|
|
71
|
+
const mid = (lo + hi) / 2;
|
|
72
|
+
if (inGamut(oklchToLinearRgb({ ...color, C: mid }))) lo = mid;
|
|
73
|
+
else hi = mid;
|
|
74
|
+
}
|
|
75
|
+
return rgbToHex(linearToGamma(oklchToLinearRgb({ ...color, C: lo })));
|
|
76
|
+
}
|
|
77
|
+
function linearToGamma({ r, g, b }) {
|
|
78
|
+
return {
|
|
79
|
+
r: linearToSrgb(clamp01(r)),
|
|
80
|
+
g: linearToSrgb(clamp01(g)),
|
|
81
|
+
b: linearToSrgb(clamp01(b))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function relativeLuminance({ r, g, b }) {
|
|
85
|
+
return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
|
|
86
|
+
}
|
|
87
|
+
function contrastRatio(hexA, hexB) {
|
|
88
|
+
const la = relativeLuminance(hexToRgb(hexA));
|
|
89
|
+
const lb = relativeLuminance(hexToRgb(hexB));
|
|
90
|
+
const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
|
|
91
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
92
|
+
}
|
|
93
|
+
var NEAR_BLACK = "#18181b";
|
|
94
|
+
var WHITE = "#ffffff";
|
|
95
|
+
var AA_TEXT = 4.5;
|
|
96
|
+
var PURE_BLACK = "#000000";
|
|
97
|
+
function readableForeground(solidHex) {
|
|
98
|
+
const white = contrastRatio(WHITE, solidHex);
|
|
99
|
+
const nearBlack = contrastRatio(NEAR_BLACK, solidHex);
|
|
100
|
+
const best = Math.max(white, nearBlack);
|
|
101
|
+
if (best >= AA_TEXT) return white >= nearBlack ? WHITE : NEAR_BLACK;
|
|
102
|
+
return contrastRatio(WHITE, solidHex) >= contrastRatio(PURE_BLACK, solidHex) ? WHITE : PURE_BLACK;
|
|
103
|
+
}
|
|
104
|
+
function shiftLightness(baseHex, dl) {
|
|
105
|
+
const { L, C, h } = rgbToOklch(hexToRgb(baseHex));
|
|
106
|
+
return oklchToHex({ L: clamp01(L + dl), C, h });
|
|
107
|
+
}
|
|
108
|
+
function readableAccent(brandHex, surfaceHex, dir) {
|
|
109
|
+
const { L, C, h } = rgbToOklch(hexToRgb(brandHex));
|
|
110
|
+
for (let i = 0; i <= 50; i++) {
|
|
111
|
+
const stepL = L + dir * 0.02 * i;
|
|
112
|
+
if (stepL < 0 || stepL > 1) break;
|
|
113
|
+
const candidate = oklchToHex({ L: stepL, C, h });
|
|
114
|
+
if (contrastRatio(candidate, surfaceHex) >= AA_TEXT) return candidate;
|
|
115
|
+
}
|
|
116
|
+
return dir === 1 ? WHITE : NEAR_BLACK;
|
|
117
|
+
}
|
|
118
|
+
var LIGHT_SURFACE = "#ffffff";
|
|
119
|
+
var DARK_SURFACE = "#18181b";
|
|
120
|
+
function deriveBrandRamp(primaryHex) {
|
|
121
|
+
const solid = normalizeHex(primaryHex) ?? DEFAULT_BRAND_COLOR.toLowerCase();
|
|
122
|
+
const fg = readableForeground(solid);
|
|
123
|
+
return {
|
|
124
|
+
light: {
|
|
125
|
+
accentSolid: solid,
|
|
126
|
+
accentSolidHover: shiftLightness(solid, -0.06),
|
|
127
|
+
accentSolidActive: shiftLightness(solid, -0.1),
|
|
128
|
+
accentFg: fg,
|
|
129
|
+
accent: readableAccent(solid, LIGHT_SURFACE, -1)
|
|
130
|
+
},
|
|
131
|
+
dark: {
|
|
132
|
+
accentSolid: solid,
|
|
133
|
+
accentSolidHover: shiftLightness(solid, 0.06),
|
|
134
|
+
accentSolidActive: shiftLightness(solid, 0.1),
|
|
135
|
+
accentFg: fg,
|
|
136
|
+
accent: readableAccent(solid, DARK_SURFACE, 1)
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function brandRampVars(set) {
|
|
141
|
+
return {
|
|
142
|
+
"--ba-rt-solid-hover-l": set.light.accentSolidHover,
|
|
143
|
+
"--ba-rt-solid-hover-d": set.dark.accentSolidHover,
|
|
144
|
+
"--ba-rt-solid-active-l": set.light.accentSolidActive,
|
|
145
|
+
"--ba-rt-solid-active-d": set.dark.accentSolidActive,
|
|
146
|
+
"--ba-rt-accent-l": set.light.accent,
|
|
147
|
+
"--ba-rt-accent-d": set.dark.accent,
|
|
148
|
+
// Foreground is chosen against the solid fill, which is theme-independent.
|
|
149
|
+
"--ba-rt-accent-fg": set.light.accentFg
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/hooks.ts
|
|
154
|
+
import * as React5 from "react";
|
|
155
|
+
import {
|
|
156
|
+
clearInvitationClaim,
|
|
157
|
+
createMembershipHas,
|
|
158
|
+
readInvitationClaim
|
|
159
|
+
} from "@authowl/core";
|
|
160
|
+
|
|
161
|
+
// src/provider.tsx
|
|
162
|
+
import * as React4 from "react";
|
|
163
|
+
import {
|
|
164
|
+
captureInvitationClaim,
|
|
165
|
+
createAuthOwlClient,
|
|
166
|
+
directionFor,
|
|
167
|
+
getPublicConfig,
|
|
168
|
+
isLocale,
|
|
169
|
+
resolveConfig
|
|
170
|
+
} from "@authowl/core";
|
|
171
|
+
|
|
172
|
+
// src/appearance.ts
|
|
173
|
+
function resolveAppearance(appearance, config) {
|
|
174
|
+
const theme = appearance?.theme ?? config?.branding?.theme ?? "light";
|
|
175
|
+
const primaryColor = appearance?.primaryColor ?? config?.branding?.primaryColor;
|
|
176
|
+
const style = {};
|
|
177
|
+
if (primaryColor) {
|
|
178
|
+
style["--ba-primary"] = primaryColor;
|
|
179
|
+
const hex = normalizeHex(primaryColor);
|
|
180
|
+
if (hex) Object.assign(style, brandRampVars(deriveBrandRamp(hex)));
|
|
181
|
+
}
|
|
182
|
+
return { theme, primaryColor, style };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/components/InvitationPrompt.tsx
|
|
186
|
+
import * as React3 from "react";
|
|
187
|
+
|
|
188
|
+
// src/i18n/index.tsx
|
|
189
|
+
import * as React from "react";
|
|
190
|
+
import {
|
|
191
|
+
formatMessage,
|
|
192
|
+
resolveServerError
|
|
193
|
+
} from "@authowl/core/i18n";
|
|
194
|
+
import {
|
|
195
|
+
formatMessage as formatMessage2,
|
|
196
|
+
serverErrorMessage,
|
|
197
|
+
resolveServerError as resolveServerError2,
|
|
198
|
+
formatRetryDuration,
|
|
199
|
+
catalogs
|
|
200
|
+
} from "@authowl/core/i18n";
|
|
201
|
+
import { jsx } from "react/jsx-runtime";
|
|
202
|
+
function useLocale() {
|
|
203
|
+
return useAuthOwlContext().locale;
|
|
204
|
+
}
|
|
205
|
+
function useT() {
|
|
206
|
+
const locale = useLocale();
|
|
207
|
+
return React.useCallback(
|
|
208
|
+
(key, params) => formatMessage(locale, key, params),
|
|
209
|
+
[locale]
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
function richMessage(template, slots) {
|
|
213
|
+
return template.split(/(\{\w+\})/g).filter(Boolean).map((part, i) => {
|
|
214
|
+
const token = /^\{(\w+)\}$/.exec(part);
|
|
215
|
+
if (token && token[1] in slots) {
|
|
216
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: slots[token[1]] }, i);
|
|
217
|
+
}
|
|
218
|
+
return part;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function Bidi({ children }) {
|
|
222
|
+
return /* @__PURE__ */ jsx("bdi", { children });
|
|
223
|
+
}
|
|
224
|
+
function useServerError() {
|
|
225
|
+
const locale = useLocale();
|
|
226
|
+
return React.useCallback(
|
|
227
|
+
(error, fallback) => resolveServerError(locale, error, fallback),
|
|
228
|
+
[locale]
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/components/ModalSurface.tsx
|
|
233
|
+
import * as React2 from "react";
|
|
234
|
+
import { createPortal } from "react-dom";
|
|
235
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
236
|
+
var useClientLayoutEffect = typeof document === "undefined" ? React2.useEffect : React2.useLayoutEffect;
|
|
237
|
+
function ModalSurface({
|
|
238
|
+
overlayClassName,
|
|
239
|
+
panelClassName,
|
|
240
|
+
labelledBy,
|
|
241
|
+
testId,
|
|
242
|
+
returnFocusRef,
|
|
243
|
+
onClose,
|
|
244
|
+
children
|
|
245
|
+
}) {
|
|
246
|
+
const ownerRef = React2.useRef(null);
|
|
247
|
+
const panelRef = React2.useRef(null);
|
|
248
|
+
const [portalTarget, setPortalTarget] = React2.useState(null);
|
|
249
|
+
const onCloseRef = React2.useRef(onClose);
|
|
250
|
+
const returnFocusRefRef = React2.useRef(returnFocusRef);
|
|
251
|
+
onCloseRef.current = onClose;
|
|
252
|
+
returnFocusRefRef.current = returnFocusRef;
|
|
253
|
+
useClientLayoutEffect(() => {
|
|
254
|
+
setPortalTarget(ownerRef.current?.closest(".authowl-root") ?? document.body);
|
|
255
|
+
}, []);
|
|
256
|
+
React2.useEffect(() => {
|
|
257
|
+
if (!portalTarget) return;
|
|
258
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
259
|
+
const previousOverflow = document.body.style.overflow;
|
|
260
|
+
document.body.style.overflow = "hidden";
|
|
261
|
+
const onKeyDown = (event) => {
|
|
262
|
+
if (event.key === "Escape") {
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
onCloseRef.current();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (event.key !== "Tab" || !panelRef.current) return;
|
|
268
|
+
const focusable = [...panelRef.current.querySelectorAll(
|
|
269
|
+
'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'
|
|
270
|
+
)];
|
|
271
|
+
if (focusable.length === 0) return;
|
|
272
|
+
const first = focusable[0];
|
|
273
|
+
const last = focusable[focusable.length - 1];
|
|
274
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
275
|
+
event.preventDefault();
|
|
276
|
+
last.focus();
|
|
277
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
first.focus();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
document.addEventListener("keydown", onKeyDown);
|
|
283
|
+
return () => {
|
|
284
|
+
document.body.style.overflow = previousOverflow;
|
|
285
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
286
|
+
const returnTarget = returnFocusRefRef.current?.current ?? previousFocus;
|
|
287
|
+
window.setTimeout(() => returnTarget?.focus(), 0);
|
|
288
|
+
};
|
|
289
|
+
}, [portalTarget]);
|
|
290
|
+
const surface = /* @__PURE__ */ jsx2("div", { className: overlayClassName, role: "presentation", onMouseDown: (event) => {
|
|
291
|
+
if (event.target === event.currentTarget) onClose();
|
|
292
|
+
}, children: /* @__PURE__ */ jsx2(
|
|
293
|
+
"div",
|
|
294
|
+
{
|
|
295
|
+
ref: panelRef,
|
|
296
|
+
className: panelClassName,
|
|
297
|
+
role: "dialog",
|
|
298
|
+
"aria-modal": "true",
|
|
299
|
+
"aria-labelledby": labelledBy,
|
|
300
|
+
"data-testid": testId,
|
|
301
|
+
children
|
|
302
|
+
}
|
|
303
|
+
) });
|
|
304
|
+
return /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
305
|
+
/* @__PURE__ */ jsx2("span", { ref: ownerRef, hidden: true, "aria-hidden": "true" }),
|
|
306
|
+
portalTarget ? createPortal(surface, portalTarget) : null
|
|
307
|
+
] });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/components/InvitationPrompt.tsx
|
|
311
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
312
|
+
function InvitationPrompt() {
|
|
313
|
+
const t = useT();
|
|
314
|
+
const { invitation, status, accept, dismiss } = useOrganizationInvitation();
|
|
315
|
+
const { signOut } = useSignOut();
|
|
316
|
+
const headingId = React3.useId();
|
|
317
|
+
if (status === "idle" || status === "loading") return null;
|
|
318
|
+
const message = (() => {
|
|
319
|
+
switch (status) {
|
|
320
|
+
case "wrong_account":
|
|
321
|
+
return t("organization.invitationPrompt.error.wrongAccount");
|
|
322
|
+
case "verify_email":
|
|
323
|
+
return t("organization.invitationPrompt.error.verifyEmail");
|
|
324
|
+
case "gone":
|
|
325
|
+
return t("organization.invitationPrompt.error.gone");
|
|
326
|
+
case "error":
|
|
327
|
+
return t("organization.invitationPrompt.error.generic");
|
|
328
|
+
default:
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
})();
|
|
332
|
+
const joining = status === "joining";
|
|
333
|
+
const canAccept = status === "ready" || status === "error" || joining;
|
|
334
|
+
return /* @__PURE__ */ jsxs2(
|
|
335
|
+
ModalSurface,
|
|
336
|
+
{
|
|
337
|
+
overlayClassName: "ba-invitation-overlay",
|
|
338
|
+
panelClassName: "ba-invitation-panel",
|
|
339
|
+
labelledBy: headingId,
|
|
340
|
+
testId: "authowl-invitation-prompt",
|
|
341
|
+
onClose: dismiss,
|
|
342
|
+
children: [
|
|
343
|
+
/* @__PURE__ */ jsx3("h2", { id: headingId, className: "ba-title", children: t("organization.invitationPrompt.title") }),
|
|
344
|
+
invitation ? /* @__PURE__ */ jsx3("p", { className: "ba-subtitle", children: t("organization.invitationPrompt.body", { organization: invitation.organizationName }) }) : null,
|
|
345
|
+
message ? /* @__PURE__ */ jsx3("p", { className: "ba-error", role: "alert", children: message }) : null,
|
|
346
|
+
/* @__PURE__ */ jsxs2("div", { className: "ba-invitation-actions", children: [
|
|
347
|
+
canAccept ? /* @__PURE__ */ jsx3(
|
|
348
|
+
"button",
|
|
349
|
+
{
|
|
350
|
+
type: "button",
|
|
351
|
+
className: "ba-button",
|
|
352
|
+
onClick: () => void accept(),
|
|
353
|
+
disabled: joining,
|
|
354
|
+
children: joining ? t("organization.invitationPrompt.joining") : t("organization.invitationPrompt.accept")
|
|
355
|
+
}
|
|
356
|
+
) : null,
|
|
357
|
+
status === "wrong_account" ? (
|
|
358
|
+
// Deliberately keeps the claim: the invitation is still live and still
|
|
359
|
+
// theirs to accept from the right account, so it must survive the
|
|
360
|
+
// sign-out and reappear after they sign back in.
|
|
361
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "ba-button ba-button-secondary", onClick: () => void signOut(), children: t("organization.invitationPrompt.signOut") })
|
|
362
|
+
) : null,
|
|
363
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "ba-button ba-button-secondary", onClick: dismiss, children: t("organization.invitationPrompt.dismiss") })
|
|
364
|
+
] })
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/provider.tsx
|
|
371
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
372
|
+
var warnedConfigProjects = /* @__PURE__ */ new Set();
|
|
373
|
+
function warnPublicConfigFailed(resolved, error) {
|
|
374
|
+
let origin = resolved.apiUrl;
|
|
375
|
+
try {
|
|
376
|
+
origin = new URL(resolved.apiUrl).origin;
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
const key = `${origin}:${resolved.decoded.projectId}`;
|
|
380
|
+
if (warnedConfigProjects.has(key)) return;
|
|
381
|
+
warnedConfigProjects.add(key);
|
|
382
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
383
|
+
console.warn(
|
|
384
|
+
`[AuthOwl] Could not load this project's public config from ${origin} (${reason}). Falling back to a password-only sign-in form, regardless of the methods this project actually enabled. Likely causes: the API is unreachable, \`apiUrl\` is wrong, or the publishable key points at a missing/mismatched project. If password sign-in is not enabled for this project, the form will dead-end at submit - check \`apiUrl\` and \`publishableKey\` on <AuthOwlProvider>. (This warning is dev-only.)`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
var Context = React4.createContext(null);
|
|
388
|
+
function detectLocale() {
|
|
389
|
+
if (typeof document !== "undefined") {
|
|
390
|
+
const root = document.documentElement;
|
|
391
|
+
if (root.lang?.toLowerCase().startsWith("ar") || root.dir === "rtl") return "ar";
|
|
392
|
+
}
|
|
393
|
+
if (typeof navigator !== "undefined" && navigator.language?.toLowerCase().startsWith("ar")) {
|
|
394
|
+
return "ar";
|
|
395
|
+
}
|
|
396
|
+
return "en";
|
|
397
|
+
}
|
|
398
|
+
function resolveLocale(prop, autoDetected, configLocale) {
|
|
399
|
+
if (prop !== "auto" && isLocale(prop)) return prop;
|
|
400
|
+
if (prop === "auto") return autoDetected ?? "en";
|
|
401
|
+
return isLocale(configLocale) ? configLocale : "en";
|
|
402
|
+
}
|
|
403
|
+
function AuthOwlProvider({
|
|
404
|
+
publishableKey,
|
|
405
|
+
apiUrl,
|
|
406
|
+
fetch,
|
|
407
|
+
appearance,
|
|
408
|
+
locale: localeProp,
|
|
409
|
+
invitationPrompt = true,
|
|
410
|
+
children
|
|
411
|
+
}) {
|
|
412
|
+
const fetchRef = React4.useRef(fetch);
|
|
413
|
+
fetchRef.current = fetch;
|
|
414
|
+
const resolved = React4.useMemo(
|
|
415
|
+
() => resolveConfig({ publishableKey, apiUrl, fetch: fetchRef.current }),
|
|
416
|
+
[publishableKey, apiUrl]
|
|
417
|
+
);
|
|
418
|
+
const client = React4.useMemo(() => createAuthOwlClient(resolved), [resolved]);
|
|
419
|
+
const [config, setConfig] = React4.useState(null);
|
|
420
|
+
const [configState, setConfigState] = React4.useState("loading");
|
|
421
|
+
React4.useEffect(() => {
|
|
422
|
+
let active = true;
|
|
423
|
+
setConfigState("loading");
|
|
424
|
+
getPublicConfig(resolved).then((c) => {
|
|
425
|
+
if (!active) return;
|
|
426
|
+
setConfig(c);
|
|
427
|
+
setConfigState("ready");
|
|
428
|
+
}).catch((err) => {
|
|
429
|
+
if (!active) return;
|
|
430
|
+
setConfig(null);
|
|
431
|
+
setConfigState("error");
|
|
432
|
+
if (process.env.NODE_ENV !== "production") warnPublicConfigFailed(resolved, err);
|
|
433
|
+
});
|
|
434
|
+
return () => {
|
|
435
|
+
active = false;
|
|
436
|
+
};
|
|
437
|
+
}, [resolved]);
|
|
438
|
+
const merged = resolveAppearance(appearance, config);
|
|
439
|
+
const [autoLocale, setAutoLocale] = React4.useState(null);
|
|
440
|
+
React4.useEffect(() => {
|
|
441
|
+
if (localeProp === "auto") setAutoLocale(detectLocale());
|
|
442
|
+
}, [localeProp]);
|
|
443
|
+
const locale = resolveLocale(localeProp, autoLocale, config?.locale);
|
|
444
|
+
React4.useEffect(() => {
|
|
445
|
+
captureInvitationClaim();
|
|
446
|
+
}, []);
|
|
447
|
+
const ctxValue = React4.useMemo(
|
|
448
|
+
() => ({ client, appearance, config, configState, locale }),
|
|
449
|
+
[client, appearance, config, configState, locale]
|
|
450
|
+
);
|
|
451
|
+
return /* @__PURE__ */ jsx4(Context.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxs3(
|
|
452
|
+
"div",
|
|
453
|
+
{
|
|
454
|
+
className: "authowl-root",
|
|
455
|
+
"data-authowl-theme": merged.theme,
|
|
456
|
+
"data-authowl-locale": locale,
|
|
457
|
+
dir: directionFor(locale),
|
|
458
|
+
style: { display: "contents", ...merged.style },
|
|
459
|
+
children: [
|
|
460
|
+
children,
|
|
461
|
+
invitationPrompt ? /* @__PURE__ */ jsx4(InvitationPrompt, {}) : null
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
) });
|
|
465
|
+
}
|
|
466
|
+
function useAuthOwlContext() {
|
|
467
|
+
const v = React4.useContext(Context);
|
|
468
|
+
if (!v) {
|
|
469
|
+
throw new Error("AuthOwl hooks must be used inside <AuthOwlProvider>");
|
|
470
|
+
}
|
|
471
|
+
return v;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/components/auth-state.ts
|
|
475
|
+
function sessionAuthState(data) {
|
|
476
|
+
const hasUser = !!data?.user;
|
|
477
|
+
const pending = data?.session?.pendingMfaEnrollment === true;
|
|
478
|
+
return {
|
|
479
|
+
isSignedIn: hasUser && !pending,
|
|
480
|
+
needsMfaEnrollment: hasUser && pending
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/hooks.ts
|
|
485
|
+
function useAuthClient() {
|
|
486
|
+
return useAuthOwlContext().client;
|
|
487
|
+
}
|
|
488
|
+
function useAccount() {
|
|
489
|
+
return useAuthClient().account;
|
|
490
|
+
}
|
|
491
|
+
function usePublicConfig() {
|
|
492
|
+
const { config, configState } = useAuthOwlContext();
|
|
493
|
+
return {
|
|
494
|
+
config,
|
|
495
|
+
isLoading: configState === "loading",
|
|
496
|
+
isError: configState === "error"
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function useSession() {
|
|
500
|
+
const client = useAuthClient();
|
|
501
|
+
return React5.useSyncExternalStore(
|
|
502
|
+
client.sessionStore.subscribe,
|
|
503
|
+
client.sessionStore.getSnapshot,
|
|
504
|
+
client.sessionStore.getSnapshot
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
function useUser() {
|
|
508
|
+
const { data, isPending, error } = useSession();
|
|
509
|
+
const state = sessionAuthState(data);
|
|
510
|
+
return {
|
|
511
|
+
user: data?.user ?? null,
|
|
512
|
+
isLoaded: !isPending,
|
|
513
|
+
isSignedIn: state.isSignedIn,
|
|
514
|
+
needsMfaEnrollment: state.needsMfaEnrollment,
|
|
515
|
+
error: error ?? null
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function useAuth() {
|
|
519
|
+
const client = useAuthClient();
|
|
520
|
+
const { data, isPending } = useSession();
|
|
521
|
+
return {
|
|
522
|
+
isLoaded: !isPending,
|
|
523
|
+
isSignedIn: sessionAuthState(data).isSignedIn,
|
|
524
|
+
userId: data?.user?.id ?? null,
|
|
525
|
+
orgId: data?.session?.activeOrganizationId ?? null,
|
|
526
|
+
getToken: client.getToken
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function useOrganization() {
|
|
530
|
+
const client = useAuthClient();
|
|
531
|
+
const apiRef = React5.useRef(client.organization);
|
|
532
|
+
apiRef.current = client.organization;
|
|
533
|
+
const { data, isPending } = useSession();
|
|
534
|
+
const membership = data?.session?.membership ?? null;
|
|
535
|
+
const activeOrganizationId = data?.session?.activeOrganizationId ?? null;
|
|
536
|
+
const identity = data?.user?.id ?? null;
|
|
537
|
+
const [organization, setOrganization] = React5.useState(null);
|
|
538
|
+
const [orgLoaded, setOrgLoaded] = React5.useState(false);
|
|
539
|
+
const requestRef = React5.useRef(0);
|
|
540
|
+
React5.useEffect(() => {
|
|
541
|
+
const token = ++requestRef.current;
|
|
542
|
+
setOrganization(null);
|
|
543
|
+
setOrgLoaded(false);
|
|
544
|
+
if (isPending) return;
|
|
545
|
+
if (!identity || !activeOrganizationId) {
|
|
546
|
+
setOrgLoaded(true);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
void (async () => {
|
|
550
|
+
try {
|
|
551
|
+
const result = await apiRef.current.get({ organizationId: activeOrganizationId });
|
|
552
|
+
if (token !== requestRef.current) return;
|
|
553
|
+
setOrganization(result.data ?? null);
|
|
554
|
+
} catch {
|
|
555
|
+
if (token === requestRef.current) setOrganization(null);
|
|
556
|
+
} finally {
|
|
557
|
+
if (token === requestRef.current) setOrgLoaded(true);
|
|
558
|
+
}
|
|
559
|
+
})();
|
|
560
|
+
return () => {
|
|
561
|
+
requestRef.current += 1;
|
|
562
|
+
};
|
|
563
|
+
}, [identity, activeOrganizationId, isPending]);
|
|
564
|
+
const bound = React5.useMemo(() => createMembershipHas(membership), [membership]);
|
|
565
|
+
return {
|
|
566
|
+
organization,
|
|
567
|
+
membership,
|
|
568
|
+
teams: membership?.teams ?? [],
|
|
569
|
+
// AuthOwl nulls this server-side when the stored pointer names a team the
|
|
570
|
+
// member no longer holds, so it never needs re-validating here.
|
|
571
|
+
activeTeamId: data?.session?.activeTeamId ?? null,
|
|
572
|
+
has: bound.has,
|
|
573
|
+
hasPermission: bound.hasPermission,
|
|
574
|
+
isLoaded: !isPending && orgLoaded
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function useOrganizationInvitation() {
|
|
578
|
+
const client = useAuthClient();
|
|
579
|
+
const apiRef = React5.useRef(client.organization);
|
|
580
|
+
apiRef.current = client.organization;
|
|
581
|
+
const { data, isPending } = useSession();
|
|
582
|
+
const identity = data?.user?.id ?? null;
|
|
583
|
+
const [claim, setClaim] = React5.useState(null);
|
|
584
|
+
const [invitation, setInvitation] = React5.useState(null);
|
|
585
|
+
const [status, setStatus] = React5.useState("idle");
|
|
586
|
+
const requestRef = React5.useRef(0);
|
|
587
|
+
React5.useEffect(() => {
|
|
588
|
+
setClaim(readInvitationClaim());
|
|
589
|
+
}, [identity]);
|
|
590
|
+
React5.useEffect(() => {
|
|
591
|
+
const token = ++requestRef.current;
|
|
592
|
+
if (isPending || !claim) return;
|
|
593
|
+
if (!identity) {
|
|
594
|
+
setStatus("idle");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
setStatus("loading");
|
|
598
|
+
void (async () => {
|
|
599
|
+
const result = await apiRef.current.getInvitation({ id: claim.id });
|
|
600
|
+
if (token !== requestRef.current) return;
|
|
601
|
+
if (result.data) {
|
|
602
|
+
setInvitation(result.data);
|
|
603
|
+
setStatus("ready");
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
setInvitation(null);
|
|
607
|
+
setStatus(result.error?.code === "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION" ? "wrong_account" : "gone");
|
|
608
|
+
})();
|
|
609
|
+
return () => {
|
|
610
|
+
requestRef.current += 1;
|
|
611
|
+
};
|
|
612
|
+
}, [claim, identity, isPending]);
|
|
613
|
+
const accept = React5.useCallback(async () => {
|
|
614
|
+
const current = readInvitationClaim();
|
|
615
|
+
if (!current) return false;
|
|
616
|
+
setStatus("joining");
|
|
617
|
+
const result = await apiRef.current.acceptInvitation({ invitationId: current.id });
|
|
618
|
+
if (result.data) {
|
|
619
|
+
clearInvitationClaim();
|
|
620
|
+
setClaim(null);
|
|
621
|
+
setStatus("idle");
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
const code = result.error?.code;
|
|
625
|
+
if (code === "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION") setStatus("wrong_account");
|
|
626
|
+
else if (code !== void 0 && code.startsWith("EMAIL_VERIFICATION_REQUIRED")) {
|
|
627
|
+
setStatus("verify_email");
|
|
628
|
+
} else if (code === "INVITATION_NOT_FOUND") {
|
|
629
|
+
clearInvitationClaim();
|
|
630
|
+
setClaim(null);
|
|
631
|
+
setStatus("gone");
|
|
632
|
+
} else setStatus("error");
|
|
633
|
+
return false;
|
|
634
|
+
}, []);
|
|
635
|
+
const dismiss = React5.useCallback(() => {
|
|
636
|
+
clearInvitationClaim();
|
|
637
|
+
setClaim(null);
|
|
638
|
+
setInvitation(null);
|
|
639
|
+
setStatus("idle");
|
|
640
|
+
}, []);
|
|
641
|
+
return { invitation, status, accept, dismiss };
|
|
642
|
+
}
|
|
643
|
+
function useSignIn() {
|
|
644
|
+
const client = useAuthClient();
|
|
645
|
+
return {
|
|
646
|
+
signIn: client.signIn.email,
|
|
647
|
+
signInUsername: client.signIn.username,
|
|
648
|
+
signInSocial: client.signIn.social,
|
|
649
|
+
signInSso: client.signIn.sso,
|
|
650
|
+
signInMagicLink: client.signIn.magicLink,
|
|
651
|
+
signInPasskey: client.signIn.passkey,
|
|
652
|
+
sendEmailOtp: client.emailOtp.sendVerificationOtp,
|
|
653
|
+
signInEmailOtp: client.signIn.emailOtp,
|
|
654
|
+
preparePhoneOtp: client.phoneOtp.prepare,
|
|
655
|
+
startPhoneOtp: client.phoneOtp.start,
|
|
656
|
+
verifyPhoneOtp: client.phoneOtp.verify
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function usePasskeys() {
|
|
660
|
+
const client = useAuthClient();
|
|
661
|
+
return {
|
|
662
|
+
listPasskeys: client.passkey.listUserPasskeys,
|
|
663
|
+
addPasskey: client.passkey.addPasskey,
|
|
664
|
+
updatePasskey: client.passkey.updatePasskey,
|
|
665
|
+
deletePasskey: client.passkey.deletePasskey
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function useMFA() {
|
|
669
|
+
const client = useAuthClient();
|
|
670
|
+
return {
|
|
671
|
+
enable: client.twoFactor.enable,
|
|
672
|
+
disable: client.twoFactor.disable,
|
|
673
|
+
verifyTotp: client.twoFactor.verifyTotp,
|
|
674
|
+
verifyBackupCode: client.twoFactor.verifyBackupCode,
|
|
675
|
+
sendOtp: client.twoFactor.sendOtp,
|
|
676
|
+
verifyOtp: client.twoFactor.verifyOtp,
|
|
677
|
+
regenerateBackupCodes: client.twoFactor.generateBackupCodes
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function usePasswordReset() {
|
|
681
|
+
const client = useAuthClient();
|
|
682
|
+
return { requestPasswordReset: client.requestPasswordReset, resetPassword: client.resetPassword };
|
|
683
|
+
}
|
|
684
|
+
function useEmailVerification() {
|
|
685
|
+
const client = useAuthClient();
|
|
686
|
+
return {
|
|
687
|
+
sendVerificationEmail: client.sendVerificationEmail,
|
|
688
|
+
sendVerificationCode: client.emailOtp.sendVerificationOtp,
|
|
689
|
+
verifyEmailCode: client.emailOtp.verifyEmail
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function useConsent() {
|
|
693
|
+
const client = useAuthClient();
|
|
694
|
+
const { user, isLoaded } = useUser();
|
|
695
|
+
const userId = user?.id ?? null;
|
|
696
|
+
const [status, setStatus] = React5.useState(null);
|
|
697
|
+
const [isLoading, setIsLoading] = React5.useState(true);
|
|
698
|
+
const reqRef = React5.useRef(0);
|
|
699
|
+
const load = React5.useCallback(async () => {
|
|
700
|
+
const reqId = ++reqRef.current;
|
|
701
|
+
setIsLoading(true);
|
|
702
|
+
let next;
|
|
703
|
+
try {
|
|
704
|
+
next = await client.getConsentStatus();
|
|
705
|
+
} catch {
|
|
706
|
+
next = { required: false };
|
|
707
|
+
}
|
|
708
|
+
if (reqId !== reqRef.current) return;
|
|
709
|
+
setStatus(next);
|
|
710
|
+
setIsLoading(false);
|
|
711
|
+
}, [client]);
|
|
712
|
+
React5.useEffect(() => {
|
|
713
|
+
reqRef.current += 1;
|
|
714
|
+
setStatus(null);
|
|
715
|
+
if (!isLoaded) {
|
|
716
|
+
setIsLoading(true);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
void load();
|
|
720
|
+
}, [load, isLoaded, userId]);
|
|
721
|
+
const version = typeof status?.version === "number" && Number.isFinite(status.version) ? status.version : null;
|
|
722
|
+
const accept = React5.useCallback(async () => {
|
|
723
|
+
if (version == null) return;
|
|
724
|
+
try {
|
|
725
|
+
await client.acceptConsent(version);
|
|
726
|
+
} finally {
|
|
727
|
+
await load();
|
|
728
|
+
}
|
|
729
|
+
}, [client, load, version]);
|
|
730
|
+
const needsConsent = Boolean(status?.needsConsent) && version != null;
|
|
731
|
+
return { isLoading, needsConsent, status, accept, refresh: load };
|
|
732
|
+
}
|
|
733
|
+
function useSignUp() {
|
|
734
|
+
const client = useAuthClient();
|
|
735
|
+
return { signUp: client.signUp.email };
|
|
736
|
+
}
|
|
737
|
+
function useWaitlist() {
|
|
738
|
+
return useAuthClient().waitlist;
|
|
739
|
+
}
|
|
740
|
+
function useSignOut() {
|
|
741
|
+
const client = useAuthClient();
|
|
742
|
+
return { signOut: client.signOut };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/components/use-submit-action.ts
|
|
746
|
+
import * as React6 from "react";
|
|
747
|
+
function useSubmitAction() {
|
|
748
|
+
const [pending, setPending] = React6.useState(false);
|
|
749
|
+
const [error, setError] = React6.useState(null);
|
|
750
|
+
const toMessage = useServerError();
|
|
751
|
+
const run = React6.useCallback(
|
|
752
|
+
async (action, { failure, onSuccess, mapError, keepPendingOnSuccess }) => {
|
|
753
|
+
setError(null);
|
|
754
|
+
setPending(true);
|
|
755
|
+
try {
|
|
756
|
+
const res = await action();
|
|
757
|
+
if (res?.error) {
|
|
758
|
+
setError(mapError?.(res.error) ?? toMessage(res.error, failure));
|
|
759
|
+
setPending(false);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
await onSuccess?.(res ?? { data: null, error: null });
|
|
763
|
+
if (!keepPendingOnSuccess) setPending(false);
|
|
764
|
+
} catch {
|
|
765
|
+
setError(failure);
|
|
766
|
+
setPending(false);
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
[toMessage]
|
|
770
|
+
);
|
|
771
|
+
return { pending, error, setError, run };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/components/Spinner.tsx
|
|
775
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
776
|
+
function Spinner() {
|
|
777
|
+
return /* @__PURE__ */ jsx5("span", { className: "ba-spinner", "aria-hidden": "true" });
|
|
778
|
+
}
|
|
779
|
+
function Busy({ busy, label, children }) {
|
|
780
|
+
return busy ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
781
|
+
/* @__PURE__ */ jsx5(Spinner, {}),
|
|
782
|
+
label
|
|
783
|
+
] }) : /* @__PURE__ */ jsx5(Fragment3, { children });
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/components/FormError.tsx
|
|
787
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
788
|
+
function FormError({ children, className, "data-testid": testId }) {
|
|
789
|
+
if (children == null || children === "" || children === false) return null;
|
|
790
|
+
return /* @__PURE__ */ jsx6(
|
|
791
|
+
"p",
|
|
792
|
+
{
|
|
793
|
+
className: className ? `ba-error ${className}` : "ba-error",
|
|
794
|
+
role: "alert",
|
|
795
|
+
"aria-live": "assertive",
|
|
796
|
+
"data-testid": testId,
|
|
797
|
+
children
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/components/organization/model.ts
|
|
803
|
+
function organizationSlugFromName(name) {
|
|
804
|
+
return name.normalize("NFKD").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
805
|
+
}
|
|
806
|
+
function organizationRoles(role) {
|
|
807
|
+
return role.split(",").map((value) => value.trim()).filter(Boolean);
|
|
808
|
+
}
|
|
809
|
+
function hasOrganizationRole(member, role) {
|
|
810
|
+
return member ? organizationRoles(member.role).includes(role) : false;
|
|
811
|
+
}
|
|
812
|
+
function canManageOrganization(member) {
|
|
813
|
+
return hasOrganizationRole(member, "owner") || hasOrganizationRole(member, "admin");
|
|
814
|
+
}
|
|
815
|
+
function roleHasStatement(roles, heldRoles, resource, action) {
|
|
816
|
+
return roles.some((role) => {
|
|
817
|
+
if (!heldRoles.has(role.role) || typeof role.permission !== "object" || role.permission === null) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
const actions = role.permission[resource];
|
|
821
|
+
return Array.isArray(actions) && actions.includes(action);
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function teamManagementCapabilities(member, dynamicRoles) {
|
|
825
|
+
const heldRoles = new Set(organizationRoles(member.role));
|
|
826
|
+
if (heldRoles.has("owner") || heldRoles.has("admin")) {
|
|
827
|
+
return {
|
|
828
|
+
createTeam: true,
|
|
829
|
+
updateTeam: true,
|
|
830
|
+
deleteTeam: true,
|
|
831
|
+
addTeamMember: true,
|
|
832
|
+
removeTeamMember: true
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
createTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "create"),
|
|
837
|
+
updateTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "update"),
|
|
838
|
+
deleteTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "delete"),
|
|
839
|
+
addTeamMember: roleHasStatement(dynamicRoles, heldRoles, "member", "update"),
|
|
840
|
+
removeTeamMember: roleHasStatement(dynamicRoles, heldRoles, "member", "delete")
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/components/organization/use-organization-roles.ts
|
|
845
|
+
import * as React7 from "react";
|
|
846
|
+
var BUILTIN_ROLES = ["owner", "admin", "member"];
|
|
847
|
+
function useOrganizationRoles(organizationId) {
|
|
848
|
+
const api = useAuthClient().organization;
|
|
849
|
+
const apiRef = React7.useRef(api);
|
|
850
|
+
apiRef.current = api;
|
|
851
|
+
const [dynamicRoles, setDynamicRoles] = React7.useState([]);
|
|
852
|
+
const requestRef = React7.useRef(0);
|
|
853
|
+
React7.useEffect(() => {
|
|
854
|
+
const token = ++requestRef.current;
|
|
855
|
+
setDynamicRoles([]);
|
|
856
|
+
if (!organizationId) return;
|
|
857
|
+
void (async () => {
|
|
858
|
+
try {
|
|
859
|
+
const result = await apiRef.current.listRoles({ organizationId });
|
|
860
|
+
if (token !== requestRef.current) return;
|
|
861
|
+
setDynamicRoles((result.data ?? []).filter((entry) => entry.role.trim().length > 0));
|
|
862
|
+
} catch {
|
|
863
|
+
}
|
|
864
|
+
})();
|
|
865
|
+
return () => {
|
|
866
|
+
requestRef.current += 1;
|
|
867
|
+
};
|
|
868
|
+
}, [organizationId]);
|
|
869
|
+
const roles = React7.useMemo(() => {
|
|
870
|
+
const seen = /* @__PURE__ */ new Set();
|
|
871
|
+
const out = [];
|
|
872
|
+
for (const candidate of [...BUILTIN_ROLES, ...dynamicRoles.map((entry) => entry.role)]) {
|
|
873
|
+
const key = candidate.trim();
|
|
874
|
+
if (!key || seen.has(key)) continue;
|
|
875
|
+
seen.add(key);
|
|
876
|
+
out.push(key);
|
|
877
|
+
}
|
|
878
|
+
return out;
|
|
879
|
+
}, [dynamicRoles]);
|
|
880
|
+
return { roles, dynamicRoles };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
export {
|
|
884
|
+
DEFAULT_BRAND_COLOR,
|
|
885
|
+
useAuthClient,
|
|
886
|
+
useAccount,
|
|
887
|
+
usePublicConfig,
|
|
888
|
+
useSession,
|
|
889
|
+
useUser,
|
|
890
|
+
useAuth,
|
|
891
|
+
useOrganization,
|
|
892
|
+
useOrganizationInvitation,
|
|
893
|
+
useSignIn,
|
|
894
|
+
usePasskeys,
|
|
895
|
+
useMFA,
|
|
896
|
+
usePasswordReset,
|
|
897
|
+
useEmailVerification,
|
|
898
|
+
useConsent,
|
|
899
|
+
useSignUp,
|
|
900
|
+
useWaitlist,
|
|
901
|
+
useSignOut,
|
|
902
|
+
useLocale,
|
|
903
|
+
useT,
|
|
904
|
+
richMessage,
|
|
905
|
+
Bidi,
|
|
906
|
+
useServerError,
|
|
907
|
+
ModalSurface,
|
|
908
|
+
InvitationPrompt,
|
|
909
|
+
AuthOwlProvider,
|
|
910
|
+
useAuthOwlContext,
|
|
911
|
+
useSubmitAction,
|
|
912
|
+
Spinner,
|
|
913
|
+
Busy,
|
|
914
|
+
FormError,
|
|
915
|
+
organizationSlugFromName,
|
|
916
|
+
organizationRoles,
|
|
917
|
+
hasOrganizationRole,
|
|
918
|
+
canManageOrganization,
|
|
919
|
+
teamManagementCapabilities,
|
|
920
|
+
useOrganizationRoles
|
|
921
|
+
};
|