@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.
- package/README.md +11 -2
- package/dist/OrganizationListContent-BSGDHYAC.js +185 -0
- package/dist/OrganizationProfileContent-HJT33DMJ.js +451 -0
- package/dist/OrganizationSwitcherContent-2TJ76HZM.js +176 -0
- package/dist/PrivacyCenterContent-EQEXXSLA.js +204 -0
- package/dist/{TeamsSection-NV64O5I4.js → TeamsSection-VJTKS4KP.js} +8 -4
- package/dist/chunk-2HNQYORD.js +139 -0
- package/dist/chunk-7WIBXVGK.js +19 -0
- package/dist/chunk-IDMSHQ6V.js +106 -0
- package/dist/chunk-SAVEQ6RC.js +83 -0
- package/dist/{chunk-E5ZOQHXY.js → chunk-TOVDZ2ZE.js} +6 -119
- package/dist/chunk-ZYCPC5DH.js +46 -0
- package/dist/index.cjs +5213 -4748
- package/dist/index.d.cts +26 -5
- package/dist/index.d.ts +26 -5
- package/dist/index.js +489 -1305
- package/dist/styles.css +58 -0
- package/package.json +2 -2
|
@@ -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-
|
|
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
|
+
};
|