@authowl/react 0.23.0 → 0.24.1

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.
@@ -0,0 +1,204 @@
1
+ "use client";
2
+ import {
3
+ Busy,
4
+ FormError,
5
+ useAuthClient,
6
+ useLocale,
7
+ usePublicConfig,
8
+ useServerError,
9
+ useT,
10
+ useUser
11
+ } from "./chunk-TOVDZ2ZE.js";
12
+
13
+ // src/components/PrivacyCenterContent.tsx
14
+ import * as React from "react";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ var RIGHT_KEYS = {
17
+ access: "privacy.right.access",
18
+ correction: "privacy.right.correction",
19
+ portability: "privacy.right.portability",
20
+ erasure: "privacy.right.erasure",
21
+ restriction: "privacy.right.restriction",
22
+ objection: "privacy.right.objection",
23
+ consent_withdrawal: "privacy.right.consentWithdrawal"
24
+ };
25
+ var STATE_KEYS = {
26
+ received: "privacy.state.received",
27
+ identity_pending: "privacy.state.identityPending",
28
+ in_progress: "privacy.state.inProgress",
29
+ restricted: "privacy.state.restricted",
30
+ completed: "privacy.state.completed",
31
+ denied: "privacy.state.denied",
32
+ withdrawn: "privacy.state.withdrawn"
33
+ };
34
+ function PrivacyCenterContent({ className } = {}) {
35
+ const t = useT();
36
+ const locale = useLocale();
37
+ const toServerError = useServerError();
38
+ const client = useAuthClient();
39
+ const { config } = usePublicConfig();
40
+ const { isLoaded, isSignedIn, user } = useUser();
41
+ const [preferences, setPreferences] = React.useState([]);
42
+ const [requests, setRequests] = React.useState([]);
43
+ const [loading, setLoading] = React.useState(true);
44
+ const [error, setError] = React.useState(null);
45
+ const [pendingPurpose, setPendingPurpose] = React.useState(null);
46
+ const [pendingRight, setPendingRight] = React.useState(null);
47
+ const refresh = React.useCallback(async () => {
48
+ if (!isSignedIn) return;
49
+ setLoading(true);
50
+ setError(null);
51
+ const result = await loadPrivacy(client);
52
+ if (result.error) setError(toServerError(result.error, t("privacy.error.load")));
53
+ else {
54
+ setPreferences(result.preferences);
55
+ setRequests(result.requests);
56
+ }
57
+ setLoading(false);
58
+ }, [client, isSignedIn, t, toServerError]);
59
+ React.useEffect(() => {
60
+ if (isLoaded && isSignedIn) void refresh();
61
+ else if (isLoaded) setLoading(false);
62
+ }, [isLoaded, isSignedIn, refresh, user?.id]);
63
+ if (!isLoaded || loading) {
64
+ return /* @__PURE__ */ jsxs("section", { className: classes("ba-profile-section ba-privacy-center", className), "aria-busy": "true", children: [
65
+ /* @__PURE__ */ jsx("div", { className: "ba-skeleton" }),
66
+ /* @__PURE__ */ jsx("div", { className: "ba-skeleton" }),
67
+ /* @__PURE__ */ jsx("div", { className: "ba-skeleton" })
68
+ ] });
69
+ }
70
+ if (!isSignedIn) return /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("privacy.signedOut") });
71
+ const privacy = config?.privacy;
72
+ const preferenceByCode = new Map(preferences.map((item) => [item.code, item]));
73
+ async function updateConsent(purposeCode, granted) {
74
+ const purpose = privacy?.consentPurposes.find((item) => item.code === purposeCode);
75
+ const notice = privacy?.notices.find((item) => item.purposeCodes.includes(purposeCode));
76
+ if (!purpose || !notice) {
77
+ setError(t("privacy.error.unavailable"));
78
+ return;
79
+ }
80
+ setPendingPurpose(purposeCode);
81
+ setError(null);
82
+ const result = await client.privacy.recordConsent({
83
+ purposeCode,
84
+ purposeVersionId: purpose.purposeVersionId,
85
+ noticeVersionId: notice.noticeVersionId,
86
+ decision: granted ? "granted" : "withdrawn",
87
+ locale
88
+ });
89
+ if (result.error) setError(toServerError(result.error, t("privacy.error.save")));
90
+ else await refresh();
91
+ setPendingPurpose(null);
92
+ }
93
+ async function createRequest(rightType) {
94
+ setPendingRight(rightType);
95
+ setError(null);
96
+ const result = await client.privacy.createRightsRequest({ rightType, locale });
97
+ if (result.error) setError(toServerError(result.error, t("privacy.error.request")));
98
+ else await refresh();
99
+ setPendingRight(null);
100
+ }
101
+ return /* @__PURE__ */ jsxs("section", { className: classes("ba-profile-section ba-privacy-center", className), "aria-labelledby": "ba-privacy-title", children: [
102
+ /* @__PURE__ */ jsxs("header", { className: "ba-profile-section-header ba-privacy-center-header", children: [
103
+ /* @__PURE__ */ jsx("p", { className: "ba-eyebrow", children: t("privacy.dataUse") }),
104
+ /* @__PURE__ */ jsx("h2", { className: "ba-title", id: "ba-privacy-title", children: t("privacy.title") }),
105
+ /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("privacy.description") })
106
+ ] }),
107
+ /* @__PURE__ */ jsx(FormError, { children: error }),
108
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block", children: [
109
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block-heading", children: [
110
+ /* @__PURE__ */ jsx("h3", { children: t("privacy.choices.title") }),
111
+ /* @__PURE__ */ jsx("p", { children: t("privacy.choices.description") })
112
+ ] }),
113
+ (privacy?.consentPurposes.length ?? 0) === 0 ? /* @__PURE__ */ jsx("p", { className: "ba-privacy-empty", children: t("privacy.choices.empty") }) : /* @__PURE__ */ jsx("div", { className: "ba-privacy-center-choices", children: privacy.consentPurposes.map((purpose) => {
114
+ const granted = preferenceByCode.get(purpose.code)?.state === "granted";
115
+ const pending = pendingPurpose === purpose.code;
116
+ return /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-choice", children: [
117
+ /* @__PURE__ */ jsxs("span", { children: [
118
+ /* @__PURE__ */ jsx("strong", { children: purpose.title[locale] }),
119
+ /* @__PURE__ */ jsx("small", { children: purpose.description[locale] })
120
+ ] }),
121
+ /* @__PURE__ */ jsxs(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ className: "ba-privacy-toggle",
126
+ role: "switch",
127
+ "aria-checked": granted,
128
+ "aria-label": purpose.title[locale],
129
+ disabled: pending,
130
+ onClick: () => void updateConsent(purpose.code, !granted),
131
+ children: [
132
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
133
+ /* @__PURE__ */ jsx("b", { children: pending ? t("common.working") : granted ? t("privacy.on") : t("privacy.off") })
134
+ ]
135
+ }
136
+ )
137
+ ] }, purpose.purposeVersionId);
138
+ }) })
139
+ ] }),
140
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block", children: [
141
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block-heading", children: [
142
+ /* @__PURE__ */ jsx("h3", { children: t("privacy.rights.title") }),
143
+ /* @__PURE__ */ jsx("p", { children: t("privacy.rights.description") })
144
+ ] }),
145
+ /* @__PURE__ */ jsx("div", { className: "ba-privacy-right-grid", children: Object.keys(RIGHT_KEYS).map((right) => /* @__PURE__ */ jsx(
146
+ "button",
147
+ {
148
+ type: "button",
149
+ className: right === "erasure" ? "ba-privacy-right ba-privacy-right-danger" : "ba-privacy-right",
150
+ disabled: pendingRight !== null,
151
+ onClick: () => void createRequest(right),
152
+ children: /* @__PURE__ */ jsx(Busy, { busy: pendingRight === right, label: t("common.working"), children: t(RIGHT_KEYS[right]) })
153
+ },
154
+ right
155
+ )) })
156
+ ] }),
157
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block", children: [
158
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block-heading", children: [
159
+ /* @__PURE__ */ jsx("h3", { children: t("privacy.requests.title") }),
160
+ /* @__PURE__ */ jsx("p", { children: t("privacy.requests.description") })
161
+ ] }),
162
+ requests.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ba-privacy-empty", children: t("privacy.requests.empty") }) : /* @__PURE__ */ jsx("ol", { className: "ba-privacy-request-list", children: requests.map((request) => /* @__PURE__ */ jsxs("li", { children: [
163
+ /* @__PURE__ */ jsxs("span", { children: [
164
+ /* @__PURE__ */ jsx("strong", { children: t(RIGHT_KEYS[request.rightType]) }),
165
+ /* @__PURE__ */ jsx("small", { children: formatCairo(request.receivedAt, locale) })
166
+ ] }),
167
+ /* @__PURE__ */ jsx("b", { "data-state": request.state, children: t(STATE_KEYS[request.state]) })
168
+ ] }, request.id)) })
169
+ ] }),
170
+ (privacy?.notices.length ?? 0) > 0 && /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block", children: [
171
+ /* @__PURE__ */ jsxs("div", { className: "ba-privacy-center-block-heading", children: [
172
+ /* @__PURE__ */ jsx("h3", { children: t("privacy.notices.title") }),
173
+ /* @__PURE__ */ jsx("p", { children: t("privacy.notices.description") })
174
+ ] }),
175
+ /* @__PURE__ */ jsx("div", { className: "ba-privacy-notices", children: privacy.notices.map((notice) => /* @__PURE__ */ jsxs("details", { className: "ba-privacy-notice", children: [
176
+ /* @__PURE__ */ jsx("summary", { children: notice.title[locale] }),
177
+ /* @__PURE__ */ jsx("p", { children: notice.body[locale] })
178
+ ] }, notice.noticeVersionId)) })
179
+ ] })
180
+ ] });
181
+ }
182
+ async function loadPrivacy(client) {
183
+ const [consent, rights] = await Promise.all([
184
+ client.privacy.listConsentPreferences({ retry: 2 }),
185
+ client.privacy.listRightsRequests({ retry: 2 })
186
+ ]);
187
+ return {
188
+ preferences: consent.data?.preferences ?? [],
189
+ requests: rights.data?.requests ?? [],
190
+ error: consent.error ?? rights.error
191
+ };
192
+ }
193
+ function formatCairo(date, locale) {
194
+ return new Intl.DateTimeFormat(locale === "ar" ? "ar-EG" : "en-EG", {
195
+ dateStyle: "medium",
196
+ timeZone: "Africa/Cairo"
197
+ }).format(date);
198
+ }
199
+ function classes(...values) {
200
+ return values.filter(Boolean).join(" ");
201
+ }
202
+ export {
203
+ PrivacyCenterContent
204
+ };
@@ -1,15 +1,19 @@
1
1
  "use client";
2
+ import {
3
+ useOrganizationRoles
4
+ } from "./chunk-ZYCPC5DH.js";
5
+ import {
6
+ teamManagementCapabilities,
7
+ useSubmitAction
8
+ } from "./chunk-SAVEQ6RC.js";
2
9
  import {
3
10
  Bidi,
4
11
  Busy,
5
12
  FormError,
6
- teamManagementCapabilities,
7
13
  useAuthClient,
8
- useOrganizationRoles,
9
14
  useServerError,
10
- useSubmitAction,
11
15
  useT
12
- } from "./chunk-E5ZOQHXY.js";
16
+ } from "./chunk-TOVDZ2ZE.js";
13
17
 
14
18
  // src/components/organization/TeamsSection.tsx
15
19
  import * as React4 from "react";
@@ -0,0 +1,139 @@
1
+ import {
2
+ organizationSlugFromName,
3
+ useSubmitAction
4
+ } from "./chunk-SAVEQ6RC.js";
5
+ import {
6
+ Busy,
7
+ FormError,
8
+ useAuthClient,
9
+ usePublicConfig,
10
+ useT,
11
+ useUser
12
+ } from "./chunk-TOVDZ2ZE.js";
13
+
14
+ // src/components/CreateOrganization.tsx
15
+ import * as React from "react";
16
+ import { jsx, jsxs } from "react/jsx-runtime";
17
+ function CreateOrganization({ onCreated, title } = {}) {
18
+ const t = useT();
19
+ const { config, isLoading: configLoading } = usePublicConfig();
20
+ const { isLoaded, isSignedIn } = useUser();
21
+ const api = useAuthClient().organization;
22
+ const { pending, error, run } = useSubmitAction();
23
+ const [name, setName] = React.useState("");
24
+ const [slug, setSlug] = React.useState("");
25
+ const [logo, setLogo] = React.useState("");
26
+ const [slugTouched, setSlugTouched] = React.useState(false);
27
+ if (configLoading || !isLoaded) {
28
+ return /* @__PURE__ */ jsx("div", { className: "ba-skeleton", "aria-label": t("organization.loading") });
29
+ }
30
+ if (config?.organizations !== true) return null;
31
+ if (!isSignedIn) return /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("organization.signedOut") });
32
+ const submit = (event) => {
33
+ event.preventDefault();
34
+ const normalizedName = name.trim();
35
+ const normalizedSlug = slug.trim();
36
+ if (!normalizedName || !normalizedSlug) return;
37
+ void run(
38
+ () => api.create({
39
+ name: normalizedName,
40
+ slug: normalizedSlug,
41
+ logo: logo.trim() || null
42
+ }),
43
+ {
44
+ failure: t("organization.create.error"),
45
+ onSuccess: (result) => {
46
+ if (!result.data) return;
47
+ setName("");
48
+ setSlug("");
49
+ setLogo("");
50
+ setSlugTouched(false);
51
+ onCreated?.(result.data);
52
+ }
53
+ }
54
+ );
55
+ };
56
+ return /* @__PURE__ */ jsxs("section", { className: "ba-organization-create", children: [
57
+ title !== null && /* @__PURE__ */ jsxs("header", { className: "ba-organization-section-header", children: [
58
+ /* @__PURE__ */ jsx("h2", { className: "ba-title", children: title ?? t("organization.create.title") }),
59
+ /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("organization.create.description") })
60
+ ] }),
61
+ /* @__PURE__ */ jsxs("form", { method: "post", className: "ba-fields", onSubmit: submit, children: [
62
+ /* @__PURE__ */ jsxs("label", { className: "ba-label", children: [
63
+ t("organization.create.name"),
64
+ /* @__PURE__ */ jsx(
65
+ "input",
66
+ {
67
+ className: "ba-input",
68
+ value: name,
69
+ onChange: (event) => {
70
+ const next = event.target.value;
71
+ setName(next);
72
+ if (!slugTouched) setSlug(organizationSlugFromName(next));
73
+ },
74
+ autoComplete: "organization",
75
+ required: true
76
+ }
77
+ )
78
+ ] }),
79
+ /* @__PURE__ */ jsxs("label", { className: "ba-label", children: [
80
+ t("organization.create.slug"),
81
+ /* @__PURE__ */ jsx(
82
+ "input",
83
+ {
84
+ className: "ba-input",
85
+ dir: "ltr",
86
+ value: slug,
87
+ onChange: (event) => {
88
+ setSlugTouched(true);
89
+ setSlug(organizationSlugFromName(event.target.value));
90
+ },
91
+ pattern: "[a-z0-9]+(?:-[a-z0-9]+)*",
92
+ required: true
93
+ }
94
+ )
95
+ ] }),
96
+ /* @__PURE__ */ jsxs("label", { className: "ba-label", children: [
97
+ t("organization.create.logo"),
98
+ /* @__PURE__ */ jsx(
99
+ "input",
100
+ {
101
+ className: "ba-input",
102
+ type: "url",
103
+ dir: "ltr",
104
+ value: logo,
105
+ onChange: (event) => setLogo(event.target.value),
106
+ placeholder: "https://"
107
+ }
108
+ )
109
+ ] }),
110
+ /* @__PURE__ */ jsx(FormError, { children: error }),
111
+ /* @__PURE__ */ jsx(
112
+ "button",
113
+ {
114
+ className: "ba-button ba-profile-submit",
115
+ type: "submit",
116
+ disabled: pending || !name.trim() || !slug.trim(),
117
+ "aria-busy": pending || void 0,
118
+ children: /* @__PURE__ */ jsx(Busy, { busy: pending, label: t("common.working"), children: t("organization.create.submit") })
119
+ }
120
+ )
121
+ ] })
122
+ ] });
123
+ }
124
+
125
+ // src/components/OrganizationProfile.tsx
126
+ import * as React2 from "react";
127
+ import { jsx as jsx2 } from "react/jsx-runtime";
128
+ var OrganizationProfileContent = React2.lazy(async () => {
129
+ const module = await import("./OrganizationProfileContent-HJT33DMJ.js");
130
+ return { default: module.OrganizationProfileContent };
131
+ });
132
+ function OrganizationProfile(props = {}) {
133
+ return /* @__PURE__ */ jsx2(React2.Suspense, { fallback: /* @__PURE__ */ jsx2("div", { className: "ba-skeleton", "aria-busy": "true" }), children: /* @__PURE__ */ jsx2(OrganizationProfileContent, { ...props }) });
134
+ }
135
+
136
+ export {
137
+ CreateOrganization,
138
+ OrganizationProfile
139
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ organizationRoles
3
+ } from "./chunk-SAVEQ6RC.js";
4
+
5
+ // src/components/organization/role-label.ts
6
+ function organizationRoleLabel(role, t) {
7
+ const roles = organizationRoles(role);
8
+ return (roles.length > 0 ? roles : [role]).map((roleKey) => {
9
+ if (roleKey === "owner") return t("organization.role.owner");
10
+ if (roleKey === "admin") return t("organization.role.admin");
11
+ if (roleKey === "member") return t("organization.role.member");
12
+ const label = roleKey.split(/[-_]+/).filter(Boolean).map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(" ");
13
+ return t("organization.role.custom", { role: label });
14
+ }).join(t("organization.role.separator"));
15
+ }
16
+
17
+ export {
18
+ organizationRoleLabel
19
+ };
@@ -0,0 +1,106 @@
1
+ import {
2
+ ModalSurface,
3
+ useAuthClient,
4
+ useServerError,
5
+ useT,
6
+ useUser
7
+ } from "./chunk-TOVDZ2ZE.js";
8
+
9
+ // src/components/organization/OrganizationModal.tsx
10
+ import * as React from "react";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ function OrganizationModal({
13
+ title,
14
+ onClose,
15
+ returnFocusRef,
16
+ children
17
+ }) {
18
+ const t = useT();
19
+ const titleId = React.useId();
20
+ return /* @__PURE__ */ jsxs(
21
+ ModalSurface,
22
+ {
23
+ overlayClassName: "ba-organization-overlay",
24
+ panelClassName: "ba-organization-modal",
25
+ labelledBy: titleId,
26
+ returnFocusRef,
27
+ onClose,
28
+ children: [
29
+ /* @__PURE__ */ jsxs("div", { className: "ba-organization-modal-header", children: [
30
+ /* @__PURE__ */ jsx("h2", { id: titleId, className: "ba-title", children: title }),
31
+ /* @__PURE__ */ jsx(
32
+ "button",
33
+ {
34
+ className: "ba-profile-close",
35
+ type: "button",
36
+ "aria-label": t("organization.close"),
37
+ onClick: onClose,
38
+ autoFocus: true,
39
+ children: "\xD7"
40
+ }
41
+ )
42
+ ] }),
43
+ /* @__PURE__ */ jsx("div", { className: "ba-organization-modal-body", children })
44
+ ]
45
+ }
46
+ );
47
+ }
48
+
49
+ // src/components/organization/use-organizations-resource.ts
50
+ import * as React2 from "react";
51
+ function useOrganizationsResource(enabled = true) {
52
+ const api = useAuthClient().organization;
53
+ const apiRef = React2.useRef(api);
54
+ apiRef.current = api;
55
+ const { user, isLoaded, isSignedIn } = useUser();
56
+ const t = useT();
57
+ const toServerError = useServerError();
58
+ const [organizations, setOrganizations] = React2.useState(null);
59
+ const [error, setError] = React2.useState(null);
60
+ const requestRef = React2.useRef(0);
61
+ const identity = user?.id ?? null;
62
+ const refresh = React2.useCallback(async () => {
63
+ const token = ++requestRef.current;
64
+ if (!enabled || !identity || !isSignedIn) {
65
+ setOrganizations([]);
66
+ setError(null);
67
+ return;
68
+ }
69
+ try {
70
+ const result = await apiRef.current.list();
71
+ if (token !== requestRef.current) return;
72
+ if (result.error) {
73
+ setError(toServerError(result.error, t("organization.error.load")));
74
+ return;
75
+ }
76
+ setError(null);
77
+ setOrganizations(result.data ?? []);
78
+ } catch {
79
+ if (token === requestRef.current) setError(t("organization.error.load"));
80
+ }
81
+ }, [enabled, identity, isSignedIn, t, toServerError]);
82
+ React2.useEffect(() => {
83
+ setOrganizations(null);
84
+ setError(null);
85
+ if (!isLoaded) return;
86
+ void refresh();
87
+ return () => {
88
+ requestRef.current += 1;
89
+ };
90
+ }, [identity, isLoaded, refresh]);
91
+ React2.useEffect(
92
+ () => api.subscribe(() => void refresh()),
93
+ [api, refresh]
94
+ );
95
+ return {
96
+ organizations,
97
+ isLoading: !isLoaded || organizations === null && error === null,
98
+ error,
99
+ refresh
100
+ };
101
+ }
102
+
103
+ export {
104
+ OrganizationModal,
105
+ useOrganizationsResource
106
+ };
@@ -0,0 +1,83 @@
1
+ import {
2
+ useServerError
3
+ } from "./chunk-TOVDZ2ZE.js";
4
+
5
+ // src/components/use-submit-action.ts
6
+ import * as React from "react";
7
+ function useSubmitAction() {
8
+ const [pending, setPending] = React.useState(false);
9
+ const [error, setError] = React.useState(null);
10
+ const toMessage = useServerError();
11
+ const run = React.useCallback(
12
+ async (action, { failure, onSuccess, mapError, keepPendingOnSuccess }) => {
13
+ setError(null);
14
+ setPending(true);
15
+ try {
16
+ const res = await action();
17
+ if (res?.error) {
18
+ setError(mapError?.(res.error) ?? toMessage(res.error, failure));
19
+ setPending(false);
20
+ return;
21
+ }
22
+ await onSuccess?.(res ?? { data: null, error: null });
23
+ if (!keepPendingOnSuccess) setPending(false);
24
+ } catch {
25
+ setError(failure);
26
+ setPending(false);
27
+ }
28
+ },
29
+ [toMessage]
30
+ );
31
+ return { pending, error, setError, run };
32
+ }
33
+
34
+ // src/components/organization/model.ts
35
+ function organizationSlugFromName(name) {
36
+ return name.normalize("NFKD").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
37
+ }
38
+ function organizationRoles(role) {
39
+ return role.split(",").map((value) => value.trim()).filter(Boolean);
40
+ }
41
+ function hasOrganizationRole(member, role) {
42
+ return member ? organizationRoles(member.role).includes(role) : false;
43
+ }
44
+ function canManageOrganization(member) {
45
+ return hasOrganizationRole(member, "owner") || hasOrganizationRole(member, "admin");
46
+ }
47
+ function roleHasStatement(roles, heldRoles, resource, action) {
48
+ return roles.some((role) => {
49
+ if (!heldRoles.has(role.role) || typeof role.permission !== "object" || role.permission === null) {
50
+ return false;
51
+ }
52
+ const actions = role.permission[resource];
53
+ return Array.isArray(actions) && actions.includes(action);
54
+ });
55
+ }
56
+ function teamManagementCapabilities(member, dynamicRoles) {
57
+ const heldRoles = new Set(organizationRoles(member.role));
58
+ if (heldRoles.has("owner") || heldRoles.has("admin")) {
59
+ return {
60
+ createTeam: true,
61
+ updateTeam: true,
62
+ deleteTeam: true,
63
+ addTeamMember: true,
64
+ removeTeamMember: true
65
+ };
66
+ }
67
+ return {
68
+ createTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "create"),
69
+ updateTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "update"),
70
+ deleteTeam: roleHasStatement(dynamicRoles, heldRoles, "team", "delete"),
71
+ addTeamMember: roleHasStatement(dynamicRoles, heldRoles, "member", "update"),
72
+ removeTeamMember: roleHasStatement(dynamicRoles, heldRoles, "member", "delete")
73
+ };
74
+ }
75
+
76
+ export {
77
+ useSubmitAction,
78
+ organizationSlugFromName,
79
+ organizationRoles,
80
+ hasOrganizationRole,
81
+ canManageOrganization,
82
+ teamManagementCapabilities
83
+ };