@authowl/react 0.19.1 → 0.21.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.
@@ -0,0 +1,295 @@
1
+ "use client";
2
+ import {
3
+ Bidi,
4
+ Busy,
5
+ FormError,
6
+ teamManagementCapabilities,
7
+ useAuthClient,
8
+ useOrganizationRoles,
9
+ useServerError,
10
+ useSubmitAction,
11
+ useT
12
+ } from "./chunk-K67DJJHP.js";
13
+
14
+ // src/components/organization/TeamsSection.tsx
15
+ import * as React4 from "react";
16
+
17
+ // src/components/organization/TeamMembersPanel.tsx
18
+ import * as React3 from "react";
19
+
20
+ // src/components/organization/use-team-resources.ts
21
+ import * as React2 from "react";
22
+
23
+ // src/components/organization/use-organization-list-resource.ts
24
+ import * as React from "react";
25
+ function useOrganizationListResource({
26
+ enabled,
27
+ resourceKey,
28
+ request,
29
+ fallback,
30
+ inactiveData = null
31
+ }) {
32
+ const toServerError = useServerError();
33
+ const requestFn = React.useRef(request);
34
+ requestFn.current = request;
35
+ const [data, setData] = React.useState(null);
36
+ const [error, setError] = React.useState(null);
37
+ const requestToken = React.useRef(0);
38
+ const refresh = React.useCallback(async () => {
39
+ const token = ++requestToken.current;
40
+ if (!enabled || !resourceKey) {
41
+ setData(inactiveData);
42
+ setError(null);
43
+ return;
44
+ }
45
+ setError(null);
46
+ try {
47
+ const result = await requestFn.current();
48
+ if (token !== requestToken.current) return;
49
+ if (result.error) {
50
+ setError(toServerError(result.error, fallback));
51
+ return;
52
+ }
53
+ setData(result.data ?? []);
54
+ } catch {
55
+ if (token === requestToken.current) setError(fallback);
56
+ }
57
+ }, [enabled, fallback, inactiveData, resourceKey, toServerError]);
58
+ React.useEffect(() => {
59
+ setData(enabled && resourceKey ? null : inactiveData);
60
+ setError(null);
61
+ void refresh();
62
+ return () => {
63
+ requestToken.current += 1;
64
+ };
65
+ }, [enabled, inactiveData, refresh, resourceKey]);
66
+ return { data, isLoading: data === null && error === null, error, refresh };
67
+ }
68
+
69
+ // src/components/organization/use-team-resources.ts
70
+ function useTeamsResource(organizationId) {
71
+ const api = useAuthClient().organization;
72
+ const apiRef = React2.useRef(api);
73
+ apiRef.current = api;
74
+ const t = useT();
75
+ const resource = useOrganizationListResource({
76
+ enabled: true,
77
+ resourceKey: organizationId,
78
+ request: () => apiRef.current.listTeams({ organizationId }),
79
+ fallback: t("organization.profile.loadError")
80
+ });
81
+ return { teams: resource.data, error: resource.error, reload: resource.refresh };
82
+ }
83
+ function useTeamMembersResource(selectedTeamId) {
84
+ const api = useAuthClient().organization;
85
+ const apiRef = React2.useRef(api);
86
+ apiRef.current = api;
87
+ const t = useT();
88
+ const resource = useOrganizationListResource({
89
+ enabled: selectedTeamId !== null,
90
+ resourceKey: selectedTeamId,
91
+ request: () => apiRef.current.listTeamMembers({ teamId: selectedTeamId }),
92
+ fallback: t("organization.profile.loadError")
93
+ });
94
+ return { members: resource.data, error: resource.error, reload: resource.refresh };
95
+ }
96
+
97
+ // src/components/organization/TeamMembersPanel.tsx
98
+ import { jsx, jsxs } from "react/jsx-runtime";
99
+ function TeamMembersPanel({
100
+ organization,
101
+ team,
102
+ canAdd,
103
+ canRemove
104
+ }) {
105
+ const t = useT();
106
+ const api = useAuthClient().organization;
107
+ const { pending, error: actionError, run } = useSubmitAction();
108
+ const [userId, setUserId] = React3.useState("");
109
+ const { members, error, reload } = useTeamMembersResource(team.id);
110
+ const assignedUserIds = new Set(members?.map((member) => member.userId));
111
+ const availableMembers = organization.members.filter((member) => !assignedUserIds.has(member.userId));
112
+ const addableUserId = availableMembers.some((member) => member.userId === userId) ? userId : availableMembers[0]?.userId ?? "";
113
+ const addMember = (event) => {
114
+ event.preventDefault();
115
+ if (!addableUserId) return;
116
+ void run(
117
+ () => api.addTeamMember({
118
+ teamId: team.id,
119
+ userId: addableUserId,
120
+ organizationId: organization.id
121
+ }),
122
+ {
123
+ failure: t("organization.profile.general.error"),
124
+ onSuccess: async () => {
125
+ setUserId("");
126
+ await reload();
127
+ }
128
+ }
129
+ );
130
+ };
131
+ const removeMember = (member) => {
132
+ const organizationMember = organization.members.find((entry) => entry.userId === member.userId);
133
+ const displayName = organizationMember?.user.name || member.userId;
134
+ if (!window.confirm(t("organization.profile.teams.removeMemberConfirm", { name: displayName }))) return;
135
+ void run(
136
+ () => api.removeTeamMember({
137
+ teamId: team.id,
138
+ userId: member.userId,
139
+ organizationId: organization.id
140
+ }),
141
+ {
142
+ failure: t("organization.profile.general.error"),
143
+ onSuccess: reload
144
+ }
145
+ );
146
+ };
147
+ return /* @__PURE__ */ jsxs("div", { className: "ba-organization-user-invitations", children: [
148
+ /* @__PURE__ */ jsx("h4", { children: t("organization.profile.teams.manageMembers") }),
149
+ canAdd && availableMembers.length > 0 && members !== null && /* @__PURE__ */ jsxs("form", { method: "post", className: "ba-organization-invite-form", onSubmit: addMember, children: [
150
+ /* @__PURE__ */ jsxs("label", { className: "ba-label", children: [
151
+ t("organization.profile.teams.addMember"),
152
+ /* @__PURE__ */ jsx("select", { className: "ba-input", value: addableUserId, onChange: (event) => setUserId(event.target.value), children: availableMembers.map((member) => /* @__PURE__ */ jsx("option", { value: member.userId, children: member.user.name || member.user.email || member.userId }, member.userId)) })
153
+ ] }),
154
+ /* @__PURE__ */ jsx("button", { className: "ba-button", type: "submit", disabled: pending || !addableUserId, children: t("organization.profile.teams.addMember") })
155
+ ] }),
156
+ /* @__PURE__ */ jsx(FormError, { children: actionError }),
157
+ error && /* @__PURE__ */ jsxs("div", { className: "ba-inline-error", children: [
158
+ /* @__PURE__ */ jsx(FormError, { children: error }),
159
+ /* @__PURE__ */ jsx("button", { className: "ba-link-button", type: "button", onClick: () => void reload(), children: t("organization.retry") })
160
+ ] }),
161
+ members?.length ? /* @__PURE__ */ jsx("ul", { className: "ba-organization-list", children: members.map((member) => {
162
+ const organizationMember = organization.members.find((entry) => entry.userId === member.userId);
163
+ const displayName = organizationMember?.user.name || member.userId;
164
+ return /* @__PURE__ */ jsxs("li", { className: "ba-organization-member", children: [
165
+ /* @__PURE__ */ jsxs("span", { className: "ba-organization-member-copy", children: [
166
+ /* @__PURE__ */ jsx("strong", { children: displayName }),
167
+ organizationMember?.user.email && /* @__PURE__ */ jsx("small", { children: /* @__PURE__ */ jsx(Bidi, { children: organizationMember.user.email }) })
168
+ ] }),
169
+ canRemove && /* @__PURE__ */ jsx("button", { className: "ba-link-button ba-danger", type: "button", disabled: pending, onClick: () => removeMember(member), children: t("organization.profile.teams.removeMember") })
170
+ ] }, member.id);
171
+ }) }) : error ? null : members === null ? /* @__PURE__ */ jsx("div", { className: "ba-skeleton", "aria-label": t("common.loading") }) : members.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("organization.profile.teams.membersEmpty") }) : null
172
+ ] });
173
+ }
174
+
175
+ // src/components/organization/TeamsSection.tsx
176
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
177
+ function TeamsSection({
178
+ organization,
179
+ membership
180
+ }) {
181
+ const t = useT();
182
+ const api = useAuthClient().organization;
183
+ const { pending, error: actionError, run } = useSubmitAction();
184
+ const { teams, error: teamsError, reload } = useTeamsResource(organization.id);
185
+ const { dynamicRoles } = useOrganizationRoles(organization.id);
186
+ const capabilities = teamManagementCapabilities(membership, dynamicRoles);
187
+ const [name, setName] = React4.useState("");
188
+ const [selectedTeamId, setSelectedTeamId] = React4.useState(null);
189
+ const [editingTeamId, setEditingTeamId] = React4.useState(null);
190
+ const [editingName, setEditingName] = React4.useState("");
191
+ React4.useEffect(() => {
192
+ setSelectedTeamId(null);
193
+ setEditingTeamId(null);
194
+ setEditingName("");
195
+ }, [organization.id]);
196
+ const create = (event) => {
197
+ event.preventDefault();
198
+ void run(
199
+ () => api.createTeam({ organizationId: organization.id, name: name.trim() }),
200
+ {
201
+ failure: t("organization.profile.general.error"),
202
+ onSuccess: async () => {
203
+ setName("");
204
+ await reload();
205
+ }
206
+ }
207
+ );
208
+ };
209
+ const beginRename = (team) => {
210
+ setEditingTeamId(team.id);
211
+ setEditingName(team.name);
212
+ };
213
+ const rename = (event, team) => {
214
+ event.preventDefault();
215
+ const nextName = editingName.trim();
216
+ if (!nextName || nextName === team.name) return;
217
+ void run(
218
+ () => api.updateTeam({
219
+ teamId: team.id,
220
+ data: { name: nextName, organizationId: organization.id }
221
+ }),
222
+ {
223
+ failure: t("organization.profile.general.error"),
224
+ onSuccess: async () => {
225
+ setEditingTeamId(null);
226
+ setEditingName("");
227
+ await reload();
228
+ }
229
+ }
230
+ );
231
+ };
232
+ const remove = (team) => {
233
+ if (!window.confirm(t("organization.profile.teams.removeTeamConfirm", { name: team.name }))) return;
234
+ void run(
235
+ () => api.removeTeam({ teamId: team.id, organizationId: organization.id }),
236
+ {
237
+ failure: t("organization.profile.general.error"),
238
+ onSuccess: async () => {
239
+ if (selectedTeamId === team.id) setSelectedTeamId(null);
240
+ if (editingTeamId === team.id) setEditingTeamId(null);
241
+ await reload();
242
+ }
243
+ }
244
+ );
245
+ };
246
+ const selectedTeam = teams?.find((team) => team.id === selectedTeamId);
247
+ return /* @__PURE__ */ jsxs2("section", { className: "ba-organization-section", children: [
248
+ /* @__PURE__ */ jsxs2("header", { className: "ba-organization-section-header", children: [
249
+ /* @__PURE__ */ jsx2("h3", { className: "ba-title", children: t("organization.profile.teams.title") }),
250
+ /* @__PURE__ */ jsx2("p", { className: "ba-muted", children: t("organization.profile.teams.description") })
251
+ ] }),
252
+ capabilities.createTeam ? /* @__PURE__ */ jsxs2("form", { method: "post", className: "ba-organization-invite-form", onSubmit: create, children: [
253
+ /* @__PURE__ */ jsxs2("label", { className: "ba-label", children: [
254
+ t("organization.profile.teams.create"),
255
+ /* @__PURE__ */ jsx2("input", { className: "ba-input", value: name, onChange: (event) => setName(event.target.value), required: true })
256
+ ] }),
257
+ /* @__PURE__ */ jsx2("button", { className: "ba-button", type: "submit", disabled: pending || !name.trim(), "aria-busy": pending || void 0, children: /* @__PURE__ */ jsx2(Busy, { busy: pending, label: t("common.working"), children: t("organization.profile.teams.create") }) })
258
+ ] }) : null,
259
+ /* @__PURE__ */ jsx2(FormError, { children: actionError }),
260
+ teamsError && /* @__PURE__ */ jsxs2("div", { className: "ba-inline-error", children: [
261
+ /* @__PURE__ */ jsx2(FormError, { children: teamsError }),
262
+ /* @__PURE__ */ jsx2("button", { className: "ba-link-button", type: "button", onClick: () => void reload(), children: t("organization.retry") })
263
+ ] }),
264
+ teams?.length ? /* @__PURE__ */ jsx2("ul", { className: "ba-organization-list", children: teams.map((team) => /* @__PURE__ */ jsxs2("li", { className: "ba-organization-member", children: [
265
+ /* @__PURE__ */ jsx2("span", { className: "ba-organization-avatar", "aria-hidden": "true", children: team.name.trim().charAt(0).toUpperCase() || "?" }),
266
+ editingTeamId === team.id ? /* @__PURE__ */ jsxs2("form", { method: "post", className: "ba-organization-invite-form", onSubmit: (event) => rename(event, team), children: [
267
+ /* @__PURE__ */ jsxs2("label", { className: "ba-label", children: [
268
+ t("organization.profile.teams.rename"),
269
+ /* @__PURE__ */ jsx2("input", { className: "ba-input", value: editingName, onChange: (event) => setEditingName(event.target.value), required: true, autoFocus: true })
270
+ ] }),
271
+ /* @__PURE__ */ jsx2("button", { className: "ba-button", type: "submit", disabled: pending || !editingName.trim(), children: t("organization.profile.teams.rename") }),
272
+ /* @__PURE__ */ jsx2("button", { className: "ba-link-button", type: "button", disabled: pending, onClick: () => setEditingTeamId(null), children: t("organization.profile.teams.cancel") })
273
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
274
+ /* @__PURE__ */ jsx2("span", { className: "ba-organization-member-copy", children: /* @__PURE__ */ jsx2("strong", { children: team.name }) }),
275
+ /* @__PURE__ */ jsxs2("span", { className: "ba-organization-member-actions", children: [
276
+ /* @__PURE__ */ jsx2("button", { className: "ba-link-button", type: "button", disabled: pending, "aria-expanded": selectedTeamId === team.id, onClick: () => setSelectedTeamId(selectedTeamId === team.id ? null : team.id), children: t("organization.profile.teams.manageMembers") }),
277
+ capabilities.updateTeam && /* @__PURE__ */ jsx2("button", { className: "ba-link-button", type: "button", disabled: pending, onClick: () => beginRename(team), children: t("organization.profile.teams.rename") }),
278
+ capabilities.deleteTeam && /* @__PURE__ */ jsx2("button", { className: "ba-link-button ba-danger", type: "button", disabled: pending, onClick: () => remove(team), children: t("organization.profile.teams.removeTeam") })
279
+ ] })
280
+ ] })
281
+ ] }, team.id)) }) : teamsError ? null : teams === null ? /* @__PURE__ */ jsx2("div", { className: "ba-skeleton", "aria-label": t("common.loading") }) : teams.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "ba-muted", children: t("organization.profile.teams.empty") }) : null,
282
+ selectedTeam && /* @__PURE__ */ jsx2(
283
+ TeamMembersPanel,
284
+ {
285
+ organization,
286
+ team: selectedTeam,
287
+ canAdd: capabilities.addTeamMember,
288
+ canRemove: capabilities.removeTeamMember
289
+ }
290
+ )
291
+ ] });
292
+ }
293
+ export {
294
+ TeamsSection as default
295
+ };