@authowl/react 0.22.0 → 0.24.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,451 @@
1
+ "use client";
2
+ import {
3
+ organizationRoleLabel
4
+ } from "./chunk-7WIBXVGK.js";
5
+ import {
6
+ useOrganizationRoles
7
+ } from "./chunk-ZYCPC5DH.js";
8
+ import {
9
+ canManageOrganization,
10
+ hasOrganizationRole,
11
+ organizationRoles,
12
+ organizationSlugFromName,
13
+ useSubmitAction
14
+ } from "./chunk-SAVEQ6RC.js";
15
+ import {
16
+ Bidi,
17
+ Busy,
18
+ FormError,
19
+ useAuthClient,
20
+ usePublicConfig,
21
+ useServerError,
22
+ useSession,
23
+ useT,
24
+ useUser
25
+ } from "./chunk-TOVDZ2ZE.js";
26
+
27
+ // src/components/OrganizationProfileContent.tsx
28
+ import * as React4 from "react";
29
+
30
+ // src/components/organization/DangerSection.tsx
31
+ import * as React from "react";
32
+ import { jsx, jsxs } from "react/jsx-runtime";
33
+ function DangerSection({
34
+ organization,
35
+ membership,
36
+ onDeleted,
37
+ onLeft
38
+ }) {
39
+ const t = useT();
40
+ const api = useAuthClient().organization;
41
+ const session = useSession();
42
+ const { pending, error, run } = useSubmitAction();
43
+ const [confirmation, setConfirmation] = React.useState("");
44
+ const isOwner = hasOrganizationRole(membership, "owner");
45
+ const confirmed = confirmation.trim().toLowerCase() === organization.slug.toLowerCase();
46
+ const leave = () => {
47
+ if (!window.confirm(t("organization.profile.danger.leaveConfirm", { name: organization.name }))) return;
48
+ void run(
49
+ () => api.leave({ organizationId: organization.id }),
50
+ {
51
+ failure: t("organization.profile.danger.leaveError"),
52
+ onSuccess: async () => {
53
+ await session.refetch({ query: { disableCookieCache: true } });
54
+ onLeft?.(organization);
55
+ }
56
+ }
57
+ );
58
+ };
59
+ const remove = (event) => {
60
+ event.preventDefault();
61
+ if (!confirmed) return;
62
+ void run(
63
+ () => api.delete({ organizationId: organization.id }),
64
+ {
65
+ failure: t("organization.profile.danger.deleteError"),
66
+ onSuccess: async () => {
67
+ await session.refetch({ query: { disableCookieCache: true } });
68
+ onDeleted?.(organization);
69
+ }
70
+ }
71
+ );
72
+ };
73
+ return /* @__PURE__ */ jsxs("section", { className: "ba-organization-section ba-organization-danger", children: [
74
+ /* @__PURE__ */ jsxs("header", { className: "ba-organization-section-header", children: [
75
+ /* @__PURE__ */ jsx("h3", { className: "ba-title", children: t("organization.profile.danger.title") }),
76
+ /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("organization.profile.danger.description") })
77
+ ] }),
78
+ /* @__PURE__ */ jsxs("div", { className: "ba-organization-danger-action", children: [
79
+ /* @__PURE__ */ jsx("strong", { children: t("organization.profile.danger.leaveTitle") }),
80
+ /* @__PURE__ */ jsx("p", { className: "ba-muted", children: isOwner ? t("organization.profile.danger.ownerLeaveHint") : t("organization.profile.danger.leaveHint") }),
81
+ /* @__PURE__ */ jsx("button", { className: "ba-button ba-button-secondary", type: "button", disabled: pending, onClick: leave, children: t("organization.profile.danger.leave") })
82
+ ] }),
83
+ isOwner && /* @__PURE__ */ jsxs("form", { method: "post", className: "ba-organization-danger-action ba-organization-delete", onSubmit: remove, children: [
84
+ /* @__PURE__ */ jsx("strong", { children: t("organization.profile.danger.deleteTitle") }),
85
+ /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("organization.profile.danger.deleteHint") }),
86
+ /* @__PURE__ */ jsxs("label", { className: "ba-label", children: [
87
+ t("organization.profile.danger.deleteConfirm"),
88
+ " ",
89
+ /* @__PURE__ */ jsx(Bidi, { children: organization.slug }),
90
+ /* @__PURE__ */ jsx("input", { className: "ba-input", dir: "ltr", value: confirmation, onChange: (event) => setConfirmation(event.target.value), autoComplete: "off", required: true })
91
+ ] }),
92
+ /* @__PURE__ */ jsx(
93
+ "button",
94
+ {
95
+ className: "ba-button ba-danger-button",
96
+ type: "submit",
97
+ disabled: pending || !confirmed,
98
+ "aria-busy": pending || void 0,
99
+ children: /* @__PURE__ */ jsx(Busy, { busy: pending, label: t("common.working"), children: t("organization.profile.danger.delete") })
100
+ }
101
+ )
102
+ ] }),
103
+ /* @__PURE__ */ jsx(FormError, { children: error })
104
+ ] });
105
+ }
106
+
107
+ // src/components/organization/GeneralSection.tsx
108
+ import * as React2 from "react";
109
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
110
+ function GeneralSection({
111
+ organization,
112
+ canManage,
113
+ onChanged
114
+ }) {
115
+ const t = useT();
116
+ const api = useAuthClient().organization;
117
+ const { pending, error, run } = useSubmitAction();
118
+ const [name, setName] = React2.useState(organization.name);
119
+ const [slug, setSlug] = React2.useState(organization.slug);
120
+ const [logo, setLogo] = React2.useState(organization.logo ?? "");
121
+ React2.useEffect(() => {
122
+ setName(organization.name);
123
+ setSlug(organization.slug);
124
+ setLogo(organization.logo ?? "");
125
+ }, [organization.id, organization.logo, organization.name, organization.slug]);
126
+ if (!canManage) {
127
+ return /* @__PURE__ */ jsxs2("section", { className: "ba-organization-section", children: [
128
+ /* @__PURE__ */ jsx2("p", { className: "ba-muted", children: t("organization.profile.general.readOnly") }),
129
+ /* @__PURE__ */ jsxs2("dl", { className: "ba-organization-facts", children: [
130
+ /* @__PURE__ */ jsxs2("div", { children: [
131
+ /* @__PURE__ */ jsx2("dt", { children: t("organization.create.name") }),
132
+ /* @__PURE__ */ jsx2("dd", { children: organization.name })
133
+ ] }),
134
+ /* @__PURE__ */ jsxs2("div", { children: [
135
+ /* @__PURE__ */ jsx2("dt", { children: t("organization.create.slug") }),
136
+ /* @__PURE__ */ jsx2("dd", { children: /* @__PURE__ */ jsx2(Bidi, { children: organization.slug }) })
137
+ ] })
138
+ ] })
139
+ ] });
140
+ }
141
+ const submit = (event) => {
142
+ event.preventDefault();
143
+ void run(
144
+ () => api.update({
145
+ organizationId: organization.id,
146
+ data: { name: name.trim(), slug: slug.trim(), logo: logo.trim() || null }
147
+ }),
148
+ {
149
+ failure: t("organization.profile.general.error"),
150
+ onSuccess: onChanged
151
+ }
152
+ );
153
+ };
154
+ return /* @__PURE__ */ jsxs2("section", { className: "ba-organization-section", children: [
155
+ /* @__PURE__ */ jsxs2("header", { className: "ba-organization-section-header", children: [
156
+ /* @__PURE__ */ jsx2("h3", { className: "ba-title", children: t("organization.profile.general.title") }),
157
+ /* @__PURE__ */ jsx2("p", { className: "ba-muted", children: t("organization.profile.general.description") })
158
+ ] }),
159
+ /* @__PURE__ */ jsxs2("form", { method: "post", className: "ba-fields", onSubmit: submit, children: [
160
+ /* @__PURE__ */ jsxs2("label", { className: "ba-label", children: [
161
+ t("organization.create.name"),
162
+ /* @__PURE__ */ jsx2("input", { className: "ba-input", value: name, onChange: (event) => setName(event.target.value), required: true })
163
+ ] }),
164
+ /* @__PURE__ */ jsxs2("label", { className: "ba-label", children: [
165
+ t("organization.create.slug"),
166
+ /* @__PURE__ */ jsx2(
167
+ "input",
168
+ {
169
+ className: "ba-input",
170
+ dir: "ltr",
171
+ value: slug,
172
+ onChange: (event) => setSlug(organizationSlugFromName(event.target.value)),
173
+ pattern: "[a-z0-9]+(?:-[a-z0-9]+)*",
174
+ required: true
175
+ }
176
+ )
177
+ ] }),
178
+ /* @__PURE__ */ jsxs2("label", { className: "ba-label", children: [
179
+ t("organization.create.logo"),
180
+ /* @__PURE__ */ jsx2("input", { className: "ba-input", type: "url", dir: "ltr", value: logo, onChange: (event) => setLogo(event.target.value), placeholder: "https://" })
181
+ ] }),
182
+ /* @__PURE__ */ jsx2(FormError, { children: error }),
183
+ /* @__PURE__ */ jsx2(
184
+ "button",
185
+ {
186
+ className: "ba-button ba-profile-submit",
187
+ type: "submit",
188
+ disabled: pending || !name.trim() || !slug.trim(),
189
+ "aria-busy": pending || void 0,
190
+ children: /* @__PURE__ */ jsx2(Busy, { busy: pending, label: t("common.working"), children: t("organization.profile.general.save") })
191
+ }
192
+ )
193
+ ] })
194
+ ] });
195
+ }
196
+
197
+ // src/components/organization/InvitationsSection.tsx
198
+ import * as React3 from "react";
199
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
200
+ function InvitationsSection({
201
+ organization,
202
+ onChanged
203
+ }) {
204
+ const t = useT();
205
+ const api = useAuthClient().organization;
206
+ const { pending, error, run } = useSubmitAction();
207
+ const { roles } = useOrganizationRoles(organization.id);
208
+ const [email, setEmail] = React3.useState("");
209
+ const [role, setRole] = React3.useState("member");
210
+ const invitations = organization.invitations.filter((invitation) => invitation.status === "pending");
211
+ const invite = (event) => {
212
+ event.preventDefault();
213
+ void run(
214
+ () => api.inviteMember({ organizationId: organization.id, email: email.trim(), role }),
215
+ {
216
+ failure: t("organization.profile.invitations.inviteError"),
217
+ onSuccess: async () => {
218
+ setEmail("");
219
+ await onChanged();
220
+ }
221
+ }
222
+ );
223
+ };
224
+ const cancel = (invitation) => {
225
+ if (!window.confirm(t("organization.profile.invitations.cancelConfirm", { email: invitation.email }))) return;
226
+ void run(
227
+ () => api.cancelInvitation({ invitationId: invitation.id }),
228
+ { failure: t("organization.profile.invitations.cancelError"), onSuccess: onChanged }
229
+ );
230
+ };
231
+ return /* @__PURE__ */ jsxs3("section", { className: "ba-organization-section", children: [
232
+ /* @__PURE__ */ jsxs3("header", { className: "ba-organization-section-header", children: [
233
+ /* @__PURE__ */ jsx3("h3", { className: "ba-title", children: t("organization.profile.invitations.title") }),
234
+ /* @__PURE__ */ jsx3("p", { className: "ba-muted", children: t("organization.profile.invitations.description") })
235
+ ] }),
236
+ /* @__PURE__ */ jsxs3("form", { method: "post", className: "ba-organization-invite-form", onSubmit: invite, children: [
237
+ /* @__PURE__ */ jsxs3("label", { className: "ba-label", children: [
238
+ t("organization.profile.invitations.email"),
239
+ /* @__PURE__ */ jsx3("input", { className: "ba-input", type: "email", dir: "ltr", value: email, onChange: (event) => setEmail(event.target.value), required: true })
240
+ ] }),
241
+ /* @__PURE__ */ jsxs3("label", { className: "ba-label", children: [
242
+ t("organization.profile.members.role"),
243
+ /* @__PURE__ */ jsx3("select", { className: "ba-input", value: role, onChange: (event) => setRole(event.target.value), children: roles.map((option) => /* @__PURE__ */ jsx3("option", { value: option, children: organizationRoleLabel(option, t) }, option)) })
244
+ ] }),
245
+ /* @__PURE__ */ jsx3(
246
+ "button",
247
+ {
248
+ className: "ba-button",
249
+ type: "submit",
250
+ disabled: pending || !email.trim(),
251
+ "aria-busy": pending || void 0,
252
+ children: /* @__PURE__ */ jsx3(Busy, { busy: pending, label: t("common.working"), children: t("organization.profile.invitations.invite") })
253
+ }
254
+ )
255
+ ] }),
256
+ /* @__PURE__ */ jsx3(FormError, { children: error }),
257
+ invitations.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "ba-muted", children: t("organization.profile.invitations.empty") }) : /* @__PURE__ */ jsx3("ul", { className: "ba-organization-list", children: invitations.map((invitation) => /* @__PURE__ */ jsxs3("li", { className: "ba-organization-invitation", children: [
258
+ /* @__PURE__ */ jsxs3("span", { children: [
259
+ /* @__PURE__ */ jsx3("strong", { children: /* @__PURE__ */ jsx3(Bidi, { children: invitation.email }) }),
260
+ /* @__PURE__ */ jsx3("small", { children: organizationRoleLabel(invitation.role, t) })
261
+ ] }),
262
+ /* @__PURE__ */ jsx3("button", { className: "ba-link-button ba-danger", type: "button", disabled: pending, onClick: () => cancel(invitation), children: t("organization.profile.invitations.cancel") })
263
+ ] }, invitation.id)) })
264
+ ] });
265
+ }
266
+
267
+ // src/components/organization/MembersSection.tsx
268
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
269
+ function MembersSection({
270
+ organization,
271
+ userId,
272
+ canManage,
273
+ onChanged
274
+ }) {
275
+ const t = useT();
276
+ const api = useAuthClient().organization;
277
+ const { pending, error, run } = useSubmitAction();
278
+ const { roles } = useOrganizationRoles(organization.id);
279
+ const updateRole = (member, role) => {
280
+ const current = organizationRoles(member.role)[0] ?? member.role;
281
+ if (current === role) return;
282
+ if (!window.confirm(t("organization.profile.members.roleConfirm", { name: member.user.name, role: organizationRoleLabel(role, t) }))) return;
283
+ void run(
284
+ () => api.updateMemberRole({ organizationId: organization.id, memberId: member.id, role }),
285
+ { failure: t("organization.profile.members.roleError"), onSuccess: onChanged }
286
+ );
287
+ };
288
+ const remove = (member) => {
289
+ if (!window.confirm(t("organization.profile.members.removeConfirm", { name: member.user.name }))) return;
290
+ void run(
291
+ () => api.removeMember({ organizationId: organization.id, memberIdOrEmail: member.id }),
292
+ { failure: t("organization.profile.members.removeError"), onSuccess: onChanged }
293
+ );
294
+ };
295
+ return /* @__PURE__ */ jsxs4("section", { className: "ba-organization-section", children: [
296
+ /* @__PURE__ */ jsxs4("header", { className: "ba-organization-section-header", children: [
297
+ /* @__PURE__ */ jsx4("h3", { className: "ba-title", children: t("organization.profile.members.title") }),
298
+ /* @__PURE__ */ jsx4("p", { className: "ba-muted", children: t("organization.profile.members.description") })
299
+ ] }),
300
+ /* @__PURE__ */ jsx4(FormError, { children: error }),
301
+ /* @__PURE__ */ jsx4("ul", { className: "ba-organization-list", children: organization.members.map((member) => {
302
+ const primaryRole = organizationRoles(member.role)[0] ?? member.role;
303
+ const isCurrent = member.userId === userId;
304
+ return /* @__PURE__ */ jsxs4("li", { className: "ba-organization-member", children: [
305
+ /* @__PURE__ */ jsx4("span", { className: "ba-organization-avatar", "aria-hidden": "true", children: member.user.name.trim().charAt(0).toUpperCase() || "?" }),
306
+ /* @__PURE__ */ jsxs4("span", { className: "ba-organization-member-copy", children: [
307
+ /* @__PURE__ */ jsxs4("strong", { children: [
308
+ member.user.name,
309
+ isCurrent ? ` ${t("organization.profile.members.you")}` : ""
310
+ ] }),
311
+ /* @__PURE__ */ jsx4("small", { children: /* @__PURE__ */ jsx4(Bidi, { children: member.user.email }) })
312
+ ] }),
313
+ canManage && !isCurrent ? /* @__PURE__ */ jsxs4("span", { className: "ba-organization-member-actions", children: [
314
+ /* @__PURE__ */ jsx4("label", { className: "ba-sr-only", htmlFor: `organization-role-${member.id}`, children: t("organization.profile.members.role") }),
315
+ /* @__PURE__ */ jsx4(
316
+ "select",
317
+ {
318
+ id: `organization-role-${member.id}`,
319
+ className: "ba-input ba-organization-role",
320
+ value: primaryRole,
321
+ disabled: pending,
322
+ onChange: (event) => updateRole(member, event.target.value),
323
+ children: (roles.includes(primaryRole) ? roles : [primaryRole, ...roles]).map((role) => /* @__PURE__ */ jsx4("option", { value: role, children: organizationRoleLabel(role, t) }, role))
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsx4("button", { className: "ba-link-button ba-danger", type: "button", disabled: pending, onClick: () => remove(member), children: t("organization.profile.members.remove") })
327
+ ] }) : /* @__PURE__ */ jsx4("span", { className: "ba-organization-role-label", children: /* @__PURE__ */ jsx4(Bidi, { children: organizationRoleLabel(member.role, t) }) })
328
+ ] }, member.id);
329
+ }) })
330
+ ] });
331
+ }
332
+
333
+ // src/components/OrganizationProfileContent.tsx
334
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
335
+ var SECTION_KEYS = {
336
+ general: "organization.profile.nav.general",
337
+ members: "organization.profile.nav.members",
338
+ teams: "organization.profile.nav.teams",
339
+ invitations: "organization.profile.nav.invitations",
340
+ danger: "organization.profile.nav.danger"
341
+ };
342
+ var TeamsSection = React4.lazy(() => import("./TeamsSection-VJTKS4KP.js"));
343
+ function OrganizationProfileContent({ organizationId, defaultSection = "general", onDeleted, onLeft } = {}) {
344
+ const t = useT();
345
+ const toServerError = useServerError();
346
+ const { config, isLoading: configLoading } = usePublicConfig();
347
+ const { user, isLoaded, isSignedIn } = useUser();
348
+ const session = useSession();
349
+ const api = useAuthClient().organization;
350
+ const apiRef = React4.useRef(api);
351
+ apiRef.current = api;
352
+ const activeOrganizationId = session.data?.session.activeOrganizationId ?? null;
353
+ const requestedId = organizationId ?? activeOrganizationId;
354
+ const enabled = config?.organizations === true && isSignedIn;
355
+ const [organization, setOrganization] = React4.useState(null);
356
+ const [error, setError] = React4.useState(null);
357
+ const [wasRemoved, setWasRemoved] = React4.useState(false);
358
+ const [section, setSection] = React4.useState(defaultSection);
359
+ const requestRef = React4.useRef(0);
360
+ const load = React4.useCallback(async () => {
361
+ const token = ++requestRef.current;
362
+ if (!enabled || !requestedId) {
363
+ setOrganization(null);
364
+ setError(null);
365
+ return;
366
+ }
367
+ try {
368
+ const result = await apiRef.current.get({ organizationId: requestedId });
369
+ if (token !== requestRef.current) return;
370
+ if (result.error) {
371
+ setError(toServerError(result.error, t("organization.profile.loadError")));
372
+ return;
373
+ }
374
+ setError(null);
375
+ setOrganization(result.data ?? null);
376
+ } catch {
377
+ if (token === requestRef.current) setError(t("organization.profile.loadError"));
378
+ }
379
+ }, [enabled, requestedId, t, toServerError]);
380
+ React4.useEffect(() => {
381
+ setOrganization(null);
382
+ setError(null);
383
+ setWasRemoved(false);
384
+ void load();
385
+ return () => {
386
+ requestRef.current += 1;
387
+ };
388
+ }, [load]);
389
+ if (configLoading || !isLoaded) return /* @__PURE__ */ jsx5("div", { className: "ba-skeleton", "aria-label": t("organization.loading") });
390
+ if (config?.organizations !== true) return null;
391
+ if (!isSignedIn || !user) return /* @__PURE__ */ jsx5("p", { className: "ba-muted", children: t("organization.signedOut") });
392
+ if (wasRemoved) return /* @__PURE__ */ jsx5("p", { className: "ba-muted", children: t("organization.profile.noActive") });
393
+ if (!requestedId) return /* @__PURE__ */ jsx5("p", { className: "ba-muted", children: t("organization.profile.noActive") });
394
+ if (error) {
395
+ return /* @__PURE__ */ jsxs5("div", { className: "ba-inline-error", children: [
396
+ /* @__PURE__ */ jsx5(FormError, { children: error }),
397
+ /* @__PURE__ */ jsx5("button", { className: "ba-link-button", type: "button", onClick: () => void load(), children: t("organization.retry") })
398
+ ] });
399
+ }
400
+ if (!organization) return /* @__PURE__ */ jsx5("div", { className: "ba-skeleton", "aria-label": t("organization.loading") });
401
+ const membership = organization.members.find((member) => member.userId === user.id);
402
+ if (!membership) return /* @__PURE__ */ jsx5("p", { className: "ba-muted", children: t("organization.profile.notMember") });
403
+ const canManage = canManageOrganization(membership);
404
+ const visibleSections = canManage ? ["general", "members", "teams", "invitations", "danger"] : ["general", "members", "teams", "danger"];
405
+ const activeSection = visibleSections.includes(section) ? section : "general";
406
+ const handleRemoval = (callback) => (removedOrganization) => {
407
+ setWasRemoved(true);
408
+ setOrganization(null);
409
+ callback?.(removedOrganization);
410
+ };
411
+ return /* @__PURE__ */ jsxs5("article", { className: "ba-organization-profile", children: [
412
+ /* @__PURE__ */ jsxs5("header", { className: "ba-organization-profile-heading", children: [
413
+ /* @__PURE__ */ jsx5("span", { className: "ba-organization-avatar ba-organization-avatar-large", "aria-hidden": "true", children: organization.name.trim().charAt(0).toUpperCase() || "?" }),
414
+ /* @__PURE__ */ jsxs5("span", { children: [
415
+ /* @__PURE__ */ jsx5("h2", { className: "ba-title", children: organization.name }),
416
+ /* @__PURE__ */ jsx5("p", { className: "ba-muted", children: organization.slug })
417
+ ] })
418
+ ] }),
419
+ /* @__PURE__ */ jsxs5("div", { className: "ba-organization-profile-layout", children: [
420
+ /* @__PURE__ */ jsx5("nav", { className: "ba-organization-profile-nav", "aria-label": t("organization.profile.nav.label"), children: visibleSections.map((item) => /* @__PURE__ */ jsx5(
421
+ "button",
422
+ {
423
+ className: "ba-profile-nav-item",
424
+ type: "button",
425
+ "aria-current": activeSection === item ? "page" : void 0,
426
+ onClick: () => setSection(item),
427
+ children: t(SECTION_KEYS[item])
428
+ },
429
+ item
430
+ )) }),
431
+ /* @__PURE__ */ jsxs5("div", { className: "ba-organization-profile-content", children: [
432
+ activeSection === "general" && /* @__PURE__ */ jsx5(GeneralSection, { organization, canManage, onChanged: load }),
433
+ activeSection === "members" && /* @__PURE__ */ jsx5(MembersSection, { organization, userId: user.id, canManage, onChanged: load }),
434
+ activeSection === "teams" && /* @__PURE__ */ jsx5(React4.Suspense, { fallback: null, children: /* @__PURE__ */ jsx5(TeamsSection, { organization, membership }) }),
435
+ activeSection === "invitations" && /* @__PURE__ */ jsx5(InvitationsSection, { organization, onChanged: load }),
436
+ activeSection === "danger" && /* @__PURE__ */ jsx5(
437
+ DangerSection,
438
+ {
439
+ organization,
440
+ membership,
441
+ onDeleted: handleRemoval(onDeleted),
442
+ onLeft: handleRemoval(onLeft)
443
+ }
444
+ )
445
+ ] })
446
+ ] })
447
+ ] });
448
+ }
449
+ export {
450
+ OrganizationProfileContent
451
+ };
@@ -0,0 +1,176 @@
1
+ "use client";
2
+ import {
3
+ OrganizationModal,
4
+ useOrganizationsResource
5
+ } from "./chunk-IDMSHQ6V.js";
6
+ import {
7
+ CreateOrganization,
8
+ OrganizationProfile
9
+ } from "./chunk-2HNQYORD.js";
10
+ import {
11
+ useSubmitAction
12
+ } from "./chunk-SAVEQ6RC.js";
13
+ import {
14
+ FormError,
15
+ useAuthClient,
16
+ usePublicConfig,
17
+ useSession,
18
+ useT,
19
+ useUser
20
+ } from "./chunk-TOVDZ2ZE.js";
21
+
22
+ // src/components/OrganizationSwitcherContent.tsx
23
+ import * as React from "react";
24
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
25
+ function OrganizationSwitcherContent({ showPersonalWorkspace = true, onOrganizationChange } = {}) {
26
+ const t = useT();
27
+ const { config, isLoading: configLoading } = usePublicConfig();
28
+ const { isLoaded, isSignedIn } = useUser();
29
+ const session = useSession();
30
+ const api = useAuthClient().organization;
31
+ const enabled = config?.organizations === true && isSignedIn;
32
+ const { organizations, isLoading, error: loadError, refresh } = useOrganizationsResource(enabled);
33
+ const { pending, error, run } = useSubmitAction();
34
+ const [open, setOpen] = React.useState(false);
35
+ const [dialog, setDialog] = React.useState(null);
36
+ const rootRef = React.useRef(null);
37
+ const triggerRef = React.useRef(null);
38
+ const menuRef = React.useRef(null);
39
+ const menuId = React.useId();
40
+ const activeId = session.data?.session.activeOrganizationId ?? null;
41
+ const active = organizations?.find((organization) => organization.id === activeId) ?? null;
42
+ React.useEffect(() => {
43
+ if (!open) return;
44
+ const onPointerDown = (event) => {
45
+ if (!rootRef.current?.contains(event.target)) setOpen(false);
46
+ };
47
+ const onKeyDown = (event) => {
48
+ if (event.key === "Escape") setOpen(false);
49
+ };
50
+ document.addEventListener("pointerdown", onPointerDown);
51
+ document.addEventListener("keydown", onKeyDown);
52
+ return () => {
53
+ document.removeEventListener("pointerdown", onPointerDown);
54
+ document.removeEventListener("keydown", onKeyDown);
55
+ };
56
+ }, [open]);
57
+ if (configLoading || !isLoaded) return /* @__PURE__ */ jsx("div", { className: "ba-skeleton ba-organization-switcher-skeleton", "aria-label": t("organization.loading") });
58
+ if (config?.organizations !== true) return null;
59
+ if (!isSignedIn) return /* @__PURE__ */ jsx("p", { className: "ba-muted", children: t("organization.signedOut") });
60
+ const select = (organization) => {
61
+ void run(
62
+ () => api.setActive({ organizationId: organization?.id ?? null }),
63
+ {
64
+ failure: t("organization.switcher.error"),
65
+ onSuccess: async () => {
66
+ await session.refetch({ query: { disableCookieCache: true } });
67
+ setOpen(false);
68
+ onOrganizationChange?.(organization);
69
+ }
70
+ }
71
+ );
72
+ };
73
+ const afterProfileRemoval = async () => {
74
+ await refresh();
75
+ await session.refetch({ query: { disableCookieCache: true } });
76
+ setDialog(null);
77
+ };
78
+ const focusMenuItem = (position) => {
79
+ window.requestAnimationFrame(() => {
80
+ const items = menuRef.current?.querySelectorAll('[role="menuitem"]:not([disabled])');
81
+ if (!items?.length) return;
82
+ items[position === "first" ? 0 : items.length - 1]?.focus();
83
+ });
84
+ };
85
+ const onMenuKeyDown = (event) => {
86
+ const items = [...menuRef.current?.querySelectorAll('[role="menuitem"]:not([disabled])') ?? []];
87
+ if (event.key === "Escape") {
88
+ event.preventDefault();
89
+ event.stopPropagation();
90
+ setOpen(false);
91
+ triggerRef.current?.focus();
92
+ return;
93
+ }
94
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key) || items.length === 0) return;
95
+ event.preventDefault();
96
+ const current = items.indexOf(document.activeElement);
97
+ if (event.key === "Home") items[0]?.focus();
98
+ else if (event.key === "End") items.at(-1)?.focus();
99
+ else if (event.key === "ArrowDown") items[(current + 1 + items.length) % items.length]?.focus();
100
+ else items[(current - 1 + items.length) % items.length]?.focus();
101
+ };
102
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
103
+ /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "ba-organization-switcher", children: [
104
+ /* @__PURE__ */ jsxs(
105
+ "button",
106
+ {
107
+ ref: triggerRef,
108
+ className: "ba-organization-switcher-trigger",
109
+ type: "button",
110
+ "aria-haspopup": "menu",
111
+ "aria-expanded": open,
112
+ "aria-controls": open ? menuId : void 0,
113
+ onClick: () => setOpen((value) => !value),
114
+ onKeyDown: (event) => {
115
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
116
+ event.preventDefault();
117
+ setOpen(true);
118
+ focusMenuItem(event.key === "ArrowDown" ? "first" : "last");
119
+ },
120
+ children: [
121
+ /* @__PURE__ */ jsx("span", { className: "ba-organization-avatar", "aria-hidden": "true", children: active?.name.trim().charAt(0).toUpperCase() || "P" }),
122
+ /* @__PURE__ */ jsx("span", { children: active?.name ?? t("organization.personal") }),
123
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2304" })
124
+ ]
125
+ }
126
+ ),
127
+ open && /* @__PURE__ */ jsxs("div", { ref: menuRef, id: menuId, className: "ba-organization-menu", role: "menu", "aria-label": t("organization.switcher.label"), onKeyDown: onMenuKeyDown, children: [
128
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "ba-skeleton" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
129
+ showPersonalWorkspace && /* @__PURE__ */ jsxs("button", { className: "ba-organization-menu-item", type: "button", role: "menuitem", "aria-current": activeId === null ? "true" : void 0, disabled: pending, onClick: () => select(null), children: [
130
+ /* @__PURE__ */ jsx("span", { className: "ba-organization-avatar", "aria-hidden": "true", children: "P" }),
131
+ /* @__PURE__ */ jsx("span", { children: t("organization.personal") })
132
+ ] }),
133
+ organizations?.map((organization) => /* @__PURE__ */ jsxs("button", { className: "ba-organization-menu-item", type: "button", role: "menuitem", "aria-current": activeId === organization.id ? "true" : void 0, disabled: pending, onClick: () => select(organization), children: [
134
+ /* @__PURE__ */ jsx("span", { className: "ba-organization-avatar", "aria-hidden": "true", children: organization.name.trim().charAt(0).toUpperCase() || "?" }),
135
+ /* @__PURE__ */ jsxs("span", { children: [
136
+ organization.name,
137
+ /* @__PURE__ */ jsx("small", { children: organization.slug })
138
+ ] })
139
+ ] }, organization.id))
140
+ ] }),
141
+ (loadError || error) && /* @__PURE__ */ jsxs("div", { className: "ba-inline-error", children: [
142
+ /* @__PURE__ */ jsx(FormError, { children: loadError ?? error }),
143
+ loadError && /* @__PURE__ */ jsx("button", { className: "ba-link-button", type: "button", onClick: () => void refresh(), children: t("organization.retry") })
144
+ ] }),
145
+ /* @__PURE__ */ jsxs("div", { className: "ba-organization-menu-actions", children: [
146
+ active && /* @__PURE__ */ jsx("button", { className: "ba-menu-item", type: "button", role: "menuitem", onClick: () => {
147
+ setOpen(false);
148
+ setDialog("profile");
149
+ }, children: t("organization.switcher.manage") }),
150
+ /* @__PURE__ */ jsx("button", { className: "ba-menu-item", type: "button", role: "menuitem", onClick: () => {
151
+ setOpen(false);
152
+ setDialog("create");
153
+ }, children: t("organization.switcher.create") })
154
+ ] })
155
+ ] })
156
+ ] }),
157
+ dialog === "create" && /* @__PURE__ */ jsx(OrganizationModal, { title: t("organization.create.title"), returnFocusRef: triggerRef, onClose: () => setDialog(null), children: /* @__PURE__ */ jsx(
158
+ CreateOrganization,
159
+ {
160
+ title: null,
161
+ onCreated: (organization) => {
162
+ void (async () => {
163
+ await refresh();
164
+ await session.refetch({ query: { disableCookieCache: true } });
165
+ setDialog(null);
166
+ onOrganizationChange?.(organization);
167
+ })();
168
+ }
169
+ }
170
+ ) }),
171
+ dialog === "profile" && active && /* @__PURE__ */ jsx(OrganizationModal, { title: t("organization.profile.title"), returnFocusRef: triggerRef, onClose: () => setDialog(null), children: /* @__PURE__ */ jsx(OrganizationProfile, { organizationId: active.id, onDeleted: () => void afterProfileRemoval(), onLeft: () => void afterProfileRemoval() }) })
172
+ ] });
173
+ }
174
+ export {
175
+ OrganizationSwitcherContent
176
+ };