@better-auth-ui/heroui 1.6.9 → 1.6.10

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.
Files changed (191) hide show
  1. package/dist/components/auth/additional-field.d.ts +2 -2
  2. package/dist/components/auth/api-key/api-key.d.ts +1 -1
  3. package/dist/components/auth/api-key/api-keys.d.ts +1 -1
  4. package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +1 -1
  5. package/dist/components/auth/api-key/organization-api-keys.d.ts +1 -1
  6. package/dist/components/auth/auth-provider.d.ts +1 -1
  7. package/dist/components/auth/auth.d.ts +3 -3
  8. package/dist/components/auth/delete-user/danger-zone.d.ts +2 -2
  9. package/dist/components/auth/delete-user/delete-account.d.ts +1 -1
  10. package/dist/components/auth/email/email-changed-email.d.ts +2 -2
  11. package/dist/components/auth/email/email-verification-email.d.ts +2 -2
  12. package/dist/components/auth/email/magic-link-email.d.ts +2 -2
  13. package/dist/components/auth/email/new-device-email.d.ts +2 -2
  14. package/dist/components/auth/email/organization-invitation-email.d.ts +2 -2
  15. package/dist/components/auth/email/otp-email.d.ts +2 -2
  16. package/dist/components/auth/email/password-changed-email.d.ts +2 -2
  17. package/dist/components/auth/email/reset-password-email.d.ts +2 -2
  18. package/dist/components/auth/field-separator.d.ts +1 -1
  19. package/dist/components/auth/forgot-password.d.ts +1 -1
  20. package/dist/components/auth/magic-link/magic-link-button.d.ts +1 -1
  21. package/dist/components/auth/magic-link/magic-link.d.ts +2 -2
  22. package/dist/components/auth/multi-session/manage-account.d.ts +1 -1
  23. package/dist/components/auth/multi-session/manage-accounts.d.ts +1 -1
  24. package/dist/components/auth/multi-session/switch-account-submenu-item.d.ts +1 -1
  25. package/dist/components/auth/organization/delete-organization-dialog.d.ts +1 -1
  26. package/dist/components/auth/organization/leave-organization-dialog.d.ts +1 -1
  27. package/dist/components/auth/organization/organization-danger-zone.d.ts +2 -2
  28. package/dist/components/auth/organization/organization-invitation-row.d.ts +1 -1
  29. package/dist/components/auth/organization/organization-invitations.d.ts +1 -1
  30. package/dist/components/auth/organization/organization-logo.d.ts +3 -3
  31. package/dist/components/auth/organization/organization-member-row.d.ts +1 -1
  32. package/dist/components/auth/organization/organization-members.d.ts +1 -1
  33. package/dist/components/auth/organization/organization-people.d.ts +1 -1
  34. package/dist/components/auth/organization/organization-profile.d.ts +1 -1
  35. package/dist/components/auth/organization/organization-row.d.ts +1 -1
  36. package/dist/components/auth/organization/organization-settings.d.ts +2 -2
  37. package/dist/components/auth/organization/organization-switcher.d.ts +3 -3
  38. package/dist/components/auth/organization/organization-view-skeleton.d.ts +2 -2
  39. package/dist/components/auth/organization/organization-view.d.ts +3 -3
  40. package/dist/components/auth/organization/organization.d.ts +3 -3
  41. package/dist/components/auth/organization/organizations-settings.d.ts +2 -2
  42. package/dist/components/auth/organization/organizations.d.ts +1 -1
  43. package/dist/components/auth/organization/remove-member-dialog.d.ts +1 -1
  44. package/dist/components/auth/organization/slug-field.d.ts +1 -1
  45. package/dist/components/auth/organization/user-invitation-row.d.ts +1 -1
  46. package/dist/components/auth/organization/user-invitations.d.ts +1 -1
  47. package/dist/components/auth/passkey/passkey-button.d.ts +1 -1
  48. package/dist/components/auth/passkey/passkey.d.ts +1 -1
  49. package/dist/components/auth/passkey/passkeys.d.ts +1 -1
  50. package/dist/components/auth/provider-button.d.ts +2 -2
  51. package/dist/components/auth/reset-password.d.ts +1 -1
  52. package/dist/components/auth/settings/account/account-settings.d.ts +2 -2
  53. package/dist/components/auth/settings/account/change-email.d.ts +1 -1
  54. package/dist/components/auth/settings/account/user-profile.d.ts +1 -1
  55. package/dist/components/auth/settings/security/active-session.d.ts +1 -1
  56. package/dist/components/auth/settings/security/active-sessions.d.ts +1 -1
  57. package/dist/components/auth/settings/security/change-password.d.ts +1 -1
  58. package/dist/components/auth/settings/security/linked-account.d.ts +1 -1
  59. package/dist/components/auth/settings/security/linked-accounts.d.ts +1 -1
  60. package/dist/components/auth/settings/security/security-settings.d.ts +2 -2
  61. package/dist/components/auth/settings/settings.d.ts +3 -3
  62. package/dist/components/auth/sign-in.d.ts +2 -2
  63. package/dist/components/auth/sign-up.d.ts +2 -2
  64. package/dist/components/auth/theme/appearance.d.ts +1 -1
  65. package/dist/components/auth/user/user-avatar.d.ts +3 -3
  66. package/dist/components/auth/user/user-button.d.ts +2 -2
  67. package/dist/components/auth/user/user-view.d.ts +3 -3
  68. package/dist/components/auth/username/sign-in-username.d.ts +2 -2
  69. package/dist/components/auth/username/username-field.d.ts +1 -1
  70. package/dist/email.d.ts +8 -8
  71. package/dist/email.js +211 -8
  72. package/dist/index.d.ts +21 -21
  73. package/dist/index.js +1682 -21
  74. package/dist/lib/auth/api-key-plugin.d.ts +3 -3
  75. package/dist/lib/auth/auth-plugin.d.ts +5 -5
  76. package/dist/lib/auth/delete-user-plugin.d.ts +2 -2
  77. package/dist/lib/auth/magic-link-plugin.d.ts +3 -3
  78. package/dist/lib/auth/multi-session-plugin.d.ts +3 -3
  79. package/dist/lib/auth/organization-plugin.d.ts +2 -2
  80. package/dist/lib/auth/passkey-plugin.d.ts +3 -3
  81. package/dist/lib/auth/theme-plugin.d.ts +3 -3
  82. package/dist/lib/auth/username-plugin.d.ts +3 -3
  83. package/dist/plugins.d.ts +46 -46
  84. package/dist/plugins.js +2824 -47
  85. package/dist/user-view--47tVtJ3.js +113 -0
  86. package/package.json +2 -3
  87. package/dist/components/auth/additional-field.js +0 -137
  88. package/dist/components/auth/api-key/api-key-skeleton.js +0 -5
  89. package/dist/components/auth/api-key/api-key.js +0 -17
  90. package/dist/components/auth/api-key/api-keys-empty.js +0 -9
  91. package/dist/components/auth/api-key/api-keys.js +0 -22
  92. package/dist/components/auth/api-key/create-api-key-dialog.js +0 -44
  93. package/dist/components/auth/api-key/delete-api-key-dialog.js +0 -17
  94. package/dist/components/auth/api-key/new-api-key-dialog.js +0 -24
  95. package/dist/components/auth/api-key/organization-api-keys.js +0 -22
  96. package/dist/components/auth/auth-provider.js +0 -11
  97. package/dist/components/auth/auth.js +0 -88
  98. package/dist/components/auth/delete-user/danger-zone.js +0 -12
  99. package/dist/components/auth/delete-user/delete-account.js +0 -50
  100. package/dist/components/auth/email/email-changed-email.js +0 -23
  101. package/dist/components/auth/email/email-verification-email.js +0 -23
  102. package/dist/components/auth/email/magic-link-email.js +0 -23
  103. package/dist/components/auth/email/new-device-email.js +0 -23
  104. package/dist/components/auth/email/organization-invitation-email.js +0 -23
  105. package/dist/components/auth/email/otp-email.js +0 -22
  106. package/dist/components/auth/email/password-changed-email.js +0 -23
  107. package/dist/components/auth/email/reset-password-email.js +0 -23
  108. package/dist/components/auth/error-toaster.js +0 -33
  109. package/dist/components/auth/field-separator.js +0 -5
  110. package/dist/components/auth/forgot-password.js +0 -37
  111. package/dist/components/auth/magic-link/magic-link-button.js +0 -34
  112. package/dist/components/auth/magic-link/magic-link.js +0 -40
  113. package/dist/components/auth/multi-session/manage-account.js +0 -33
  114. package/dist/components/auth/multi-session/manage-accounts.js +0 -33
  115. package/dist/components/auth/multi-session/switch-account-submenu-content.js +0 -25
  116. package/dist/components/auth/multi-session/switch-account-submenu-item.js +0 -17
  117. package/dist/components/auth/multi-session/switch-account-submenu.js +0 -24
  118. package/dist/components/auth/organization/change-organization-logo.js +0 -69
  119. package/dist/components/auth/organization/create-organization-dialog.js +0 -37
  120. package/dist/components/auth/organization/delete-organization-dialog.js +0 -25
  121. package/dist/components/auth/organization/delete-organization-skeleton.js +0 -8
  122. package/dist/components/auth/organization/delete-organization.js +0 -27
  123. package/dist/components/auth/organization/invite-member-dialog.js +0 -47
  124. package/dist/components/auth/organization/leave-organization-dialog.js +0 -21
  125. package/dist/components/auth/organization/leave-organization.js +0 -16
  126. package/dist/components/auth/organization/organization-danger-zone.js +0 -23
  127. package/dist/components/auth/organization/organization-invitation-row-skeleton.js +0 -9
  128. package/dist/components/auth/organization/organization-invitation-row.js +0 -31
  129. package/dist/components/auth/organization/organization-invitations-empty.js +0 -13
  130. package/dist/components/auth/organization/organization-invitations.js +0 -66
  131. package/dist/components/auth/organization/organization-logo.js +0 -14
  132. package/dist/components/auth/organization/organization-member-row-skeleton.js +0 -9
  133. package/dist/components/auth/organization/organization-member-row.js +0 -34
  134. package/dist/components/auth/organization/organization-members.js +0 -67
  135. package/dist/components/auth/organization/organization-people.js +0 -11
  136. package/dist/components/auth/organization/organization-profile.js +0 -34
  137. package/dist/components/auth/organization/organization-row.js +0 -31
  138. package/dist/components/auth/organization/organization-settings.js +0 -13
  139. package/dist/components/auth/organization/organization-switcher.js +0 -48
  140. package/dist/components/auth/organization/organization-view-skeleton.js +0 -9
  141. package/dist/components/auth/organization/organization-view.js +0 -31
  142. package/dist/components/auth/organization/organization.js +0 -60
  143. package/dist/components/auth/organization/organizations-empty.js +0 -9
  144. package/dist/components/auth/organization/organizations-settings.js +0 -17
  145. package/dist/components/auth/organization/organizations.js +0 -21
  146. package/dist/components/auth/organization/remove-member-dialog.js +0 -20
  147. package/dist/components/auth/organization/slug-field.js +0 -38
  148. package/dist/components/auth/organization/user-invitation-row-skeleton.js +0 -8
  149. package/dist/components/auth/organization/user-invitation-row.js +0 -22
  150. package/dist/components/auth/organization/user-invitations-empty.js +0 -12
  151. package/dist/components/auth/organization/user-invitations.js +0 -18
  152. package/dist/components/auth/passkey/add-passkey-dialog.js +0 -22
  153. package/dist/components/auth/passkey/delete-passkey-dialog.js +0 -14
  154. package/dist/components/auth/passkey/passkey-button.js +0 -25
  155. package/dist/components/auth/passkey/passkey-skeleton.js +0 -5
  156. package/dist/components/auth/passkey/passkey.js +0 -17
  157. package/dist/components/auth/passkey/passkeys-empty.js +0 -9
  158. package/dist/components/auth/passkey/passkeys.js +0 -16
  159. package/dist/components/auth/provider-button.js +0 -29
  160. package/dist/components/auth/provider-buttons.js +0 -29
  161. package/dist/components/auth/reset-password.js +0 -57
  162. package/dist/components/auth/settings/account/account-settings.js +0 -22
  163. package/dist/components/auth/settings/account/change-avatar.js +0 -54
  164. package/dist/components/auth/settings/account/change-email.js +0 -28
  165. package/dist/components/auth/settings/account/user-profile.js +0 -65
  166. package/dist/components/auth/settings/security/active-session.js +0 -52
  167. package/dist/components/auth/settings/security/active-sessions.js +0 -23
  168. package/dist/components/auth/settings/security/change-password.js +0 -79
  169. package/dist/components/auth/settings/security/linked-account.js +0 -38
  170. package/dist/components/auth/settings/security/linked-accounts.js +0 -36
  171. package/dist/components/auth/settings/security/security-settings.js +0 -20
  172. package/dist/components/auth/settings/settings.js +0 -53
  173. package/dist/components/auth/sign-in.js +0 -64
  174. package/dist/components/auth/sign-out.js +0 -33
  175. package/dist/components/auth/sign-up.js +0 -98
  176. package/dist/components/auth/theme/appearance.js +0 -24
  177. package/dist/components/auth/theme/theme-toggle-item.js +0 -57
  178. package/dist/components/auth/user/user-avatar.js +0 -32
  179. package/dist/components/auth/user/user-button.js +0 -40
  180. package/dist/components/auth/user/user-view.js +0 -27
  181. package/dist/components/auth/username/sign-in-username.js +0 -88
  182. package/dist/components/auth/username/username-field.js +0 -45
  183. package/dist/lib/auth/api-key-plugin.js +0 -12
  184. package/dist/lib/auth/auth-plugin.js +0 -1
  185. package/dist/lib/auth/delete-user-plugin.js +0 -7
  186. package/dist/lib/auth/magic-link-plugin.js +0 -18
  187. package/dist/lib/auth/multi-session-plugin.js +0 -9
  188. package/dist/lib/auth/organization-plugin.js +0 -19
  189. package/dist/lib/auth/passkey-plugin.js +0 -9
  190. package/dist/lib/auth/theme-plugin.js +0 -26
  191. package/dist/lib/auth/username-plugin.js +0 -19
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Skeleton } from "@heroui/react";
3
- /**
4
- * Placeholder matching {@link DeleteOrganization} while the delete permission resolves.
5
- */
6
- export function DeleteOrganizationSkeleton() {
7
- return (_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-3.5 w-40 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-64 rounded-lg" })] }), _jsx(Skeleton, { className: "h-8 w-36 shrink-0 rounded-full" })] }));
8
- }
@@ -1,27 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useActiveOrganization, useAuth, useAuthPlugin, useHasPermission } from "@better-auth-ui/react";
3
- import { AlertDialog, Button } from "@heroui/react";
4
- import { useState } from "react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- import { DeleteOrganizationDialog } from "./delete-organization-dialog";
7
- import { DeleteOrganizationSkeleton } from "./delete-organization-skeleton";
8
- /**
9
- * Danger-zone row to delete the active organization. Hidden for members without
10
- * the `organization:delete` permission.
11
- */
12
- export function DeleteOrganization() {
13
- const { authClient } = useAuth();
14
- const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
15
- const { data: activeOrganization } = useActiveOrganization(authClient);
16
- const { data: permission, isPending: permissionPending } = useHasPermission(authClient, {
17
- permissions: { organization: ["delete"] }
18
- });
19
- const [confirmOpen, setConfirmOpen] = useState(false);
20
- if (permissionPending) {
21
- return _jsx(DeleteOrganizationSkeleton, {});
22
- }
23
- if (!permission?.success) {
24
- return null;
25
- }
26
- return (_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium leading-tight", children: organizationLocalization.deleteOrganization }), _jsx("p", { className: "text-muted mt-0.5 text-xs", children: organizationLocalization.deleteOrganizationDescription })] }), _jsxs(AlertDialog, { children: [_jsx(Button, { isDisabled: !activeOrganization, size: "sm", variant: "danger-soft", onPress: () => setConfirmOpen(true), children: organizationLocalization.deleteOrganization }), activeOrganization && (_jsx(DeleteOrganizationDialog, { isOpen: confirmOpen, onOpenChange: setConfirmOpen, organization: activeOrganization }))] })] }));
27
- }
@@ -1,47 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useAuthPlugin, useInviteMember } from "@better-auth-ui/react";
3
- import { PersonPlus } from "@gravity-ui/icons";
4
- import { AlertDialog, Button, FieldError, Form, Input, Label, ListBox, Select, Spinner, TextField, toast } from "@heroui/react";
5
- import { useEffect, useState } from "react";
6
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
7
- const pickDefaultRole = (keys) => keys.includes("member") ? "member" : (keys.at(-1) ?? "");
8
- /**
9
- * Render a dialog for inviting a member to the organization.
10
- *
11
- * @param isOpen - Whether the dialog is open
12
- * @param onOpenChange - Callback for when the dialog open state changes
13
- * @returns The invite member dialog as a JSX element
14
- */
15
- export function InviteMemberDialog({ isOpen, onOpenChange }) {
16
- const { authClient, localization } = useAuth();
17
- const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
18
- const [role, setRole] = useState(() => pickDefaultRole(Object.keys(roles)));
19
- useEffect(() => {
20
- setRole((current) => {
21
- const keys = Object.keys(roles);
22
- return keys.includes(current) ? current : pickDefaultRole(keys);
23
- });
24
- }, [roles]);
25
- const { mutate: inviteMember, isPending: isInviting } = useInviteMember(authClient, {
26
- onSuccess: () => {
27
- onOpenChange(false);
28
- toast.success(organizationLocalization.inviteMemberSuccess);
29
- }
30
- });
31
- const isRoleValid = Object.keys(roles).includes(role);
32
- const handleSubmit = (e) => {
33
- e.preventDefault();
34
- if (!isRoleValid)
35
- return;
36
- const formData = new FormData(e.target);
37
- const email = formData.get("email");
38
- inviteMember({
39
- email: email.trim(),
40
- role: role
41
- });
42
- };
43
- return (_jsx(AlertDialog.Backdrop, { isOpen: isOpen, onOpenChange: onOpenChange, children: _jsx(AlertDialog.Container, { children: _jsx(AlertDialog.Dialog, { children: _jsxs(Form, { onSubmit: handleSubmit, children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "default", children: _jsx(PersonPlus, {}) }), _jsx(AlertDialog.Heading, { children: organizationLocalization.inviteMember })] }), _jsxs(AlertDialog.Body, { className: "flex flex-col gap-4 overflow-visible", children: [_jsx("p", { className: "text-muted text-sm", children: organizationLocalization.inviteMemberDescription }), _jsxs(TextField, { id: "email", name: "email", type: "email", isDisabled: isInviting, children: [_jsx(Label, { children: localization.auth.email }), _jsx(Input, { autoFocus: true, placeholder: localization.auth.email, variant: "secondary", required: true }), _jsx(FieldError, {})] }), _jsxs(Select, { name: "role", value: role, onChange: (value) => {
44
- if (typeof value === "string")
45
- setRole(value);
46
- }, isDisabled: isInviting, variant: "secondary", fullWidth: true, children: [_jsx(Label, { children: organizationLocalization.role }), _jsxs(Select.Trigger, { children: [_jsx(Select.Value, {}), _jsx(Select.Indicator, {})] }), _jsx(Select.Popover, { children: _jsx(ListBox, { children: Object.entries(roles).map(([key, label]) => (_jsxs(ListBox.Item, { id: key, textValue: label, children: [label, _jsx(ListBox.ItemIndicator, {})] }, key))) }) }), _jsx(FieldError, {})] })] }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "tertiary", isDisabled: isInviting, children: localization.settings.cancel }), _jsxs(Button, { type: "submit", isPending: isInviting, isDisabled: !isRoleValid, children: [isInviting && _jsx(Spinner, { color: "current", size: "sm" }), organizationLocalization.inviteMember] })] })] }) }) }) }));
47
- }
@@ -1,21 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useAuthPlugin, useLeaveOrganization } from "@better-auth-ui/react";
3
- import { ArrowRightFromSquare } from "@gravity-ui/icons";
4
- import { AlertDialog, Button, Card, Spinner, toast } from "@heroui/react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- import { OrganizationView } from "./organization-view";
7
- export function LeaveOrganizationDialog({ isOpen, onOpenChange, organization }) {
8
- const { authClient, basePaths, localization, navigate } = useAuth();
9
- const { localization: organizationLocalization, viewPaths: organizationPluginViewPaths } = useAuthPlugin(organizationPlugin);
10
- const { mutate: leaveOrganization, isPending } = useLeaveOrganization(authClient, {
11
- onSuccess: () => {
12
- onOpenChange(false);
13
- toast.success(organizationLocalization.leftOrganization);
14
- navigate({
15
- to: `${basePaths.settings}/${organizationPluginViewPaths.settings.organizations}`,
16
- replace: true
17
- });
18
- }
19
- });
20
- return (_jsx(AlertDialog.Backdrop, { isOpen: isOpen, onOpenChange: onOpenChange, children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger", children: _jsx(ArrowRightFromSquare, {}) }), _jsx(AlertDialog.Heading, { children: organizationLocalization.leaveOrganization })] }), _jsxs(AlertDialog.Body, { className: "flex flex-col gap-4 overflow-visible", children: [_jsx("p", { className: "text-muted text-sm", children: organizationLocalization.leaveOrganizationDescription }), _jsx(Card, { variant: "secondary", children: _jsx(Card.Content, { children: _jsx(OrganizationView, { organization: organization, hideRole: true }) }) })] }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "tertiary", isDisabled: isPending, children: localization.settings.cancel }), _jsxs(Button, { variant: "danger", isPending: isPending, onPress: () => leaveOrganization({ organizationId: organization.id }), children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), organizationLocalization.leaveOrganization] })] })] }) }) }));
21
- }
@@ -1,16 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useActiveOrganization, useAuth, useAuthPlugin } from "@better-auth-ui/react";
3
- import { AlertDialog, Button } from "@heroui/react";
4
- import { useState } from "react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- import { LeaveOrganizationDialog } from "./leave-organization-dialog";
7
- /**
8
- * Danger-zone row to leave the active organization.
9
- */
10
- export function LeaveOrganization() {
11
- const { authClient } = useAuth();
12
- const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
13
- const { data: activeOrganization } = useActiveOrganization(authClient);
14
- const [confirmOpen, setConfirmOpen] = useState(false);
15
- return (_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium leading-tight", children: organizationLocalization.leaveOrganization }), _jsx("p", { className: "text-muted mt-0.5 text-xs", children: organizationLocalization.leaveOrganizationDescription })] }), _jsxs(AlertDialog, { children: [_jsx(Button, { isDisabled: !activeOrganization, size: "sm", variant: "danger-soft", onPress: () => setConfirmOpen(true), children: organizationLocalization.leaveOrganization }), activeOrganization && (_jsx(LeaveOrganizationDialog, { isOpen: confirmOpen, onOpenChange: setConfirmOpen, organization: activeOrganization }))] })] }));
16
- }
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useHasPermission } from "@better-auth-ui/react";
3
- import { Card, cn } from "@heroui/react";
4
- import { DeleteOrganization } from "./delete-organization";
5
- import { DeleteOrganizationSkeleton } from "./delete-organization-skeleton";
6
- import { LeaveOrganization } from "./leave-organization";
7
- /**
8
- * Danger zone heading with {@link LeaveOrganization} and {@link DeleteOrganization}
9
- * for the active organization in a single card.
10
- *
11
- * Resolves the `organization:delete` permission before rendering anything to
12
- * avoid flashing {@link LeaveOrganization} (and a stray separator) before the
13
- * delete row appears or disappears. Inner {@link DeleteOrganization} also
14
- * self-gates so it stays safe to use standalone.
15
- */
16
- export function OrganizationDangerZone({ className, variant, ...props }) {
17
- const { authClient, localization } = useAuth();
18
- const { data: deletePermission, isPending: deletePermissionPending } = useHasPermission(authClient, {
19
- permissions: { organization: ["delete"] }
20
- });
21
- const canDelete = !!deletePermission?.success;
22
- return (_jsxs("div", { className: cn("flex w-full flex-col", className), ...props, children: [_jsx("h2", { className: cn("mb-3 text-sm font-semibold text-danger"), children: localization.settings.dangerZone }), _jsx(Card, { variant: variant, children: _jsx(Card.Content, { className: "gap-0", children: deletePermissionPending ? (_jsx(DeleteOrganizationSkeleton, {})) : (_jsxs(_Fragment, { children: [_jsx(LeaveOrganization, {}), canDelete && (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-b border-dashed -mx-4 my-4" }), _jsx(DeleteOrganization, {})] }))] })) }) })] }));
23
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Skeleton, Table } from "@heroui/react";
3
- /**
4
- * Placeholder row matching {@link OrganizationInvitationTableRow} while
5
- * invitations load.
6
- */
7
- export function OrganizationInvitationRowSkeleton() {
8
- return (_jsxs(Table.Row, { children: [_jsx(Table.Cell, { children: _jsx(Skeleton, { className: "h-4 w-48 rounded-lg" }) }), _jsx(Table.Cell, { children: _jsx(Skeleton, { className: "h-4 w-36 rounded-lg" }) }), _jsx(Table.Cell, { children: _jsx(Skeleton, { className: "h-4 w-16 rounded-lg" }) }), _jsx(Table.Cell, { children: _jsx(Skeleton, { className: "h-4 w-14 rounded-full" }) }), _jsx(Table.Cell, {})] }));
9
- }
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useAuthPlugin, useCancelInvitation, useHasPermission } from "@better-auth-ui/react";
3
- import { Xmark } from "@gravity-ui/icons";
4
- import { Button, Chip, Spinner, Table } from "@heroui/react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- import { OrganizationInvitationRowSkeleton } from "./organization-invitation-row-skeleton";
7
- export function OrganizationInvitationTableRow({ invitation }) {
8
- const { authClient } = useAuth();
9
- const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
10
- const { data: cancelInvitationPermission, isPending: cancelPermissionPending } = useHasPermission(authClient, {
11
- permissions: { invitation: ["cancel"] }
12
- });
13
- const { mutate: cancelInvitation, isPending: cancelPending } = useCancelInvitation(authClient);
14
- const roleLabel = roles?.[invitation.role] ?? invitation.role;
15
- const statusLabel = organizationLocalization[invitation.status] ?? invitation.status;
16
- const statusColor = invitation.status === "pending"
17
- ? "warning"
18
- : invitation.status === "accepted"
19
- ? "success"
20
- : invitation.status === "rejected"
21
- ? "danger"
22
- : "default";
23
- if (cancelPermissionPending) {
24
- return _jsx(OrganizationInvitationRowSkeleton, {});
25
- }
26
- return (_jsxs(Table.Row, { children: [_jsx(Table.Cell, { className: "font-medium text-sm", children: invitation.email }), _jsx(Table.Cell, { className: "text-muted text-xs tabular-nums whitespace-nowrap", children: new Date(invitation.createdAt).toLocaleString(undefined, {
27
- dateStyle: "short",
28
- timeStyle: "short"
29
- }) }), _jsx(Table.Cell, { className: "text-sm", children: roleLabel }), _jsx(Table.Cell, { className: "text-sm", children: _jsx(Chip, { color: statusColor, size: "sm", variant: "soft", children: statusLabel }) }), _jsx(Table.Cell, { className: "text-end", children: cancelInvitationPermission?.success &&
30
- invitation.status === "pending" && (_jsx(Button, { isIconOnly: true, size: "sm", variant: "danger-soft", isPending: cancelPending, onPress: () => cancelInvitation({ invitationId: invitation.id }), "aria-label": organizationLocalization.cancelInvitation, children: cancelPending ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(Xmark, {})) })) })] }));
31
- }
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuthPlugin } from "@better-auth-ui/react";
3
- import { PaperPlane } from "@gravity-ui/icons";
4
- import { Button, EmptyState } from "@heroui/react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- /**
7
- * Empty state for {@link OrganizationInvitations} — mirrors `ApiKeysEmpty`
8
- * (icon, title, description) with an "Invite member" call to action.
9
- */
10
- export function OrganizationInvitationsEmpty({ onInvitePress }) {
11
- const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
12
- return (_jsxs(EmptyState, { className: "flex flex-col items-center gap-4 text-center p-4", children: [_jsx(PaperPlane, { className: "size-6 text-muted" }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-semibold text-foreground", children: organizationLocalization.noInvitations }), _jsx("span", { className: "text-sm text-muted", children: organizationLocalization.organizationInvitationsEmptyDescription })] }), _jsx(Button, { size: "sm", onPress: onInvitePress, children: organizationLocalization.inviteMember })] }));
13
- }
@@ -1,66 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useAuthPlugin, useHasPermission, useListOrganizationInvitations } from "@better-auth-ui/react";
3
- import { ChevronUp, Funnel, Xmark } from "@gravity-ui/icons";
4
- import { Button, Chip, cn, Dropdown, Label, SearchField, Table } from "@heroui/react";
5
- import { useMemo, useState } from "react";
6
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
7
- import { InviteMemberDialog } from "./invite-member-dialog";
8
- import { OrganizationInvitationTableRow } from "./organization-invitation-row";
9
- import { OrganizationInvitationRowSkeleton } from "./organization-invitation-row-skeleton";
10
- import { OrganizationInvitationsEmpty } from "./organization-invitations-empty";
11
- /**
12
- * Organization invitations table with invite control and per-row actions.
13
- */
14
- export function OrganizationInvitations({ className, ...props }) {
15
- const { authClient, localization } = useAuth();
16
- const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
17
- const { data: invitations, isPending: invitationsPending } = useListOrganizationInvitations(authClient);
18
- const { isPending: invitationPermissionPending } = useHasPermission(authClient, {
19
- permissions: { invitation: ["cancel"] }
20
- });
21
- const isPending = invitationsPending || invitationPermissionPending;
22
- const [sortDescriptor, setSortDescriptor] = useState();
23
- const [roleFilter, setRoleFilter] = useState("all");
24
- const [statusFilter, setStatusFilter] = useState("all");
25
- const [search, setSearch] = useState("");
26
- const filteredInvitations = useMemo(() => {
27
- return invitations?.filter((invitation) => (roleFilter === "all" || invitation.role === roleFilter) &&
28
- (statusFilter === "all" || invitation.status === statusFilter) &&
29
- invitation.email.toLowerCase().includes(search.toLowerCase()));
30
- }, [search, invitations, roleFilter, statusFilter]);
31
- const sortedInvitations = useMemo(() => {
32
- if (!sortDescriptor)
33
- return filteredInvitations;
34
- if (!filteredInvitations)
35
- return filteredInvitations;
36
- return [...filteredInvitations].sort((a, b) => {
37
- const col = sortDescriptor.column;
38
- let cmp = 0;
39
- if (col === "createdAt") {
40
- cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
41
- }
42
- else {
43
- cmp = String(a[col]).localeCompare(String(b[col]));
44
- }
45
- if (sortDescriptor.direction === "descending") {
46
- cmp *= -1;
47
- }
48
- return cmp;
49
- });
50
- }, [sortDescriptor, filteredInvitations]);
51
- const [inviteOpen, setInviteOpen] = useState(false);
52
- return (_jsxs("div", { className: cn("flex flex-col gap-3", className), ...props, children: [_jsx("h3", { className: "truncate text-sm font-semibold", children: organizationLocalization.invitations }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(SearchField, { className: "min-w-0", "aria-label": organizationLocalization.search, value: search, onChange: setSearch, isDisabled: isPending, children: _jsxs(SearchField.Group, { children: [_jsx(SearchField.SearchIcon, {}), _jsx(SearchField.Input, { placeholder: organizationLocalization.search, className: "sm:w-[200px]" }), _jsx(SearchField.ClearButton, {})] }) }), _jsxs(Dropdown, { children: [_jsxs(Button, { size: "sm", variant: "secondary", isDisabled: isPending, children: [_jsx(Funnel, {}), organizationLocalization.role] }), _jsx(Dropdown.Popover, { children: _jsxs(Dropdown.Menu, { selectionMode: "single", selectedKeys: new Set([roleFilter]), onSelectionChange: (keys) => {
53
- const key = [...keys][0];
54
- setRoleFilter(key ?? "all");
55
- }, children: [_jsxs(Dropdown.Item, { id: "all", textValue: organizationLocalization.all, children: [_jsx(Label, { children: organizationLocalization.all }), _jsx(Dropdown.ItemIndicator, {})] }), Object.entries(roles).map(([key, label]) => (_jsxs(Dropdown.Item, { id: key, textValue: label, children: [_jsx(Label, { children: label }), _jsx(Dropdown.ItemIndicator, {})] }, key)))] }) })] }), _jsxs(Dropdown, { children: [_jsxs(Button, { size: "sm", variant: "secondary", isDisabled: isPending, children: [_jsx(Funnel, {}), organizationLocalization.status] }), _jsx(Dropdown.Popover, { children: _jsxs(Dropdown.Menu, { selectionMode: "single", selectedKeys: new Set([statusFilter]), onSelectionChange: (keys) => {
56
- const key = [...keys][0];
57
- setStatusFilter(key ?? "all");
58
- }, children: [_jsxs(Dropdown.Item, { id: "all", textValue: organizationLocalization.all, children: [_jsx(Label, { children: organizationLocalization.all }), _jsx(Dropdown.ItemIndicator, {})] }), ["pending", "accepted", "rejected", "canceled"].map((status) => (_jsxs(Dropdown.Item, { id: status, textValue: organizationLocalization[status] ?? status, children: [_jsx(Label, { children: organizationLocalization[status] ?? status }), _jsx(Dropdown.ItemIndicator, {})] }, status)))] }) })] })] }), (roleFilter !== "all" || statusFilter !== "all") && (_jsxs("div", { className: "flex flex-wrap gap-2", children: [roleFilter !== "all" && (_jsxs(Chip, { size: "sm", variant: "secondary", className: "w-fit", children: [_jsxs(Chip.Label, { children: [organizationLocalization.role, ":", " ", _jsx("span", { className: "capitalize", children: roles?.[roleFilter] ?? roleFilter })] }), _jsx("button", { type: "button", "aria-label": organizationLocalization.clear, className: "text-muted hover:text-foreground inline-flex cursor-pointer items-center", onClick: () => setRoleFilter("all"), children: _jsx(Xmark, { className: "size-3" }) })] })), statusFilter !== "all" && (_jsxs(Chip, { size: "sm", variant: "secondary", className: "w-fit", children: [_jsxs(Chip.Label, { children: [organizationLocalization.status, ":", " ", organizationLocalization[statusFilter] ?? statusFilter] }), _jsx("button", { type: "button", "aria-label": organizationLocalization.clear, className: "text-muted hover:text-foreground inline-flex cursor-pointer items-center", onClick: () => setStatusFilter("all"), children: _jsx(Xmark, { className: "size-3" }) })] }))] })), _jsx(Table, { children: _jsx(Table.ScrollContainer, { children: _jsxs(Table.Content, { "aria-label": organizationLocalization.invitations, sortDescriptor: sortDescriptor, onSortChange: (descriptor) => {
59
- const shouldReset = sortDescriptor?.column === descriptor.column &&
60
- descriptor.direction === "ascending";
61
- setSortDescriptor(shouldReset ? undefined : descriptor);
62
- }, children: [_jsxs(Table.Header, { children: [_jsx(Table.Column, { allowsSorting: true, isRowHeader: true, id: "email", children: ({ sortDirection }) => (_jsx(SortableColumnHeader, { sortDirection: sortDirection, children: localization.auth.email })) }), _jsx(Table.Column, { allowsSorting: true, id: "createdAt", children: ({ sortDirection }) => (_jsx(SortableColumnHeader, { sortDirection: sortDirection, children: organizationLocalization.invitedAt })) }), _jsx(Table.Column, { allowsSorting: true, id: "role", children: ({ sortDirection }) => (_jsx(SortableColumnHeader, { sortDirection: sortDirection, children: organizationLocalization.role })) }), _jsx(Table.Column, { allowsSorting: true, id: "status", children: ({ sortDirection }) => (_jsx(SortableColumnHeader, { sortDirection: sortDirection, children: organizationLocalization.status })) }), _jsx(Table.Column, { className: "text-end", children: organizationLocalization.actions })] }), _jsx(Table.Body, { renderEmptyState: () => (_jsx(OrganizationInvitationsEmpty, { onInvitePress: () => setInviteOpen(true) })), children: isPending ? (_jsx(OrganizationInvitationRowSkeleton, {})) : (sortedInvitations?.map((invitation) => (_jsx(OrganizationInvitationTableRow, { invitation: invitation }, invitation.id)))) })] }) }) })] }), _jsx(InviteMemberDialog, { isOpen: inviteOpen, onOpenChange: setInviteOpen })] }));
63
- }
64
- function SortableColumnHeader({ children, sortDirection }) {
65
- return (_jsxs("span", { className: "flex items-center justify-between", children: [children, !!sortDirection && (_jsx(ChevronUp, { className: cn("size-3 transform transition-transform duration-100 ease-out", sortDirection === "descending" ? "rotate-180" : "") }))] }));
66
- }
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Briefcase } from "@gravity-ui/icons";
3
- import { Avatar, cn, Skeleton } from "@heroui/react";
4
- /**
5
- * Renders an organization logo image, initials fallback, or a briefcase icon;
6
- * shows a circular skeleton while loading when `isPending` is set and `organization` is undefined.
7
- */
8
- export function OrganizationLogo({ className, fallback, isPending, organization, size = "sm", style, ...props }) {
9
- if (isPending && !organization) {
10
- return (_jsx(Skeleton, { className: cn("rounded-full", size === "sm" ? "size-8" : size === "md" ? "size-10" : "size-12", className), style: style }));
11
- }
12
- const initials = organization?.name?.slice(0, 2).toUpperCase();
13
- return (_jsxs(Avatar, { size: size, className: cn("rounded-full", className), style: style, ...props, children: [_jsx(Avatar.Image, { alt: organization?.name ?? "Organization", src: organization?.logo ?? undefined }), _jsx(Avatar.Fallback, { className: cn(size === "lg" ? "text-xl" : size === "md" ? "text-base" : "text-sm"), delayMs: organization?.logo ? 600 : undefined, children: fallback || initials || _jsx(Briefcase, { className: "size-4" }) })] }));
14
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Skeleton, Table } from "@heroui/react";
3
- import { UserView } from "../user/user-view";
4
- /**
5
- * Placeholder row matching {@link OrganizationMemberRow} while members load.
6
- */
7
- export function OrganizationMemberRowSkeleton() {
8
- return (_jsxs(Table.Row, { children: [_jsx(Table.Cell, { children: _jsx(UserView, { isPending: true }) }), _jsx(Table.Cell, { children: _jsx(Skeleton, { className: "h-4 w-18 rounded-lg" }) }), _jsx(Table.Cell, { className: "flex justify-end", children: _jsx(Skeleton, { className: "size-8 rounded-full" }) })] }));
9
- }
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useAuthPlugin, useHasPermission, useSession, useUpdateMemberRole } from "@better-auth-ui/react";
3
- import { ArrowRightFromSquare, Pencil, TrashBin } from "@gravity-ui/icons";
4
- import { Button, Dropdown, Label, Spinner, Table, toast } from "@heroui/react";
5
- import { useState } from "react";
6
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
7
- import { UserView } from "../user/user-view";
8
- import { LeaveOrganizationDialog } from "./leave-organization-dialog";
9
- import { OrganizationMemberRowSkeleton } from "./organization-member-row-skeleton";
10
- import { RemoveMemberDialog } from "./remove-member-dialog";
11
- export function OrganizationMemberRow({ member, isOwner, organization }) {
12
- const { authClient } = useAuth();
13
- const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
14
- const { data: session } = useSession(authClient);
15
- const { data: hasUpdatePermission, isPending: updatePermissionPending } = useHasPermission(authClient, {
16
- permissions: { member: ["update"] }
17
- });
18
- const { data: hasDeletePermission, isPending: deletePermissionPending } = useHasPermission(authClient, {
19
- permissions: { member: ["delete"] }
20
- });
21
- const isPending = updatePermissionPending || deletePermissionPending;
22
- const { mutate: updateMemberRole, isPending: isUpdatingRole } = useUpdateMemberRole(authClient, {
23
- onSuccess: () => toast.success(organizationLocalization.memberRoleUpdated)
24
- });
25
- const roleLabel = roles?.[member.role] ?? member.role;
26
- const assignableRoles = Object.entries(roles).filter(([key]) => isOwner || key !== "owner");
27
- const isCurrentUser = session?.user.id === member.userId;
28
- const [removeOpen, setRemoveOpen] = useState(false);
29
- const [leaveOpen, setLeaveOpen] = useState(false);
30
- if (isPending) {
31
- return _jsx(OrganizationMemberRowSkeleton, {});
32
- }
33
- return (_jsxs(Table.Row, { children: [_jsx(Table.Cell, { children: _jsx(UserView, { user: member.user }) }), _jsx(Table.Cell, { children: roleLabel }), _jsxs(Table.Cell, { children: [_jsxs("div", { className: "flex items-center justify-end gap-1", children: [hasUpdatePermission?.success && (_jsxs(Dropdown, { children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "tertiary", isDisabled: isUpdatingRole, "aria-label": organizationLocalization.changeMemberRole, children: isUpdatingRole ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(Pencil, {})) }), _jsx(Dropdown.Popover, { className: "min-w-fit", children: _jsx(Dropdown.Menu, { children: assignableRoles.map(([role, label]) => (_jsx(Dropdown.Item, { textValue: label, isDisabled: member.role === role, onAction: () => updateMemberRole({ memberId: member.id, role }), children: _jsx(Label, { children: label }) }, role))) }) })] })), isCurrentUser ? (_jsx(Button, { isIconOnly: true, size: "sm", variant: "danger-soft", "aria-label": organizationLocalization.leaveOrganization, onPress: () => setLeaveOpen(true), children: _jsx(ArrowRightFromSquare, {}) })) : (hasDeletePermission?.success && (_jsx(Button, { isIconOnly: true, size: "sm", variant: "danger-soft", "aria-label": organizationLocalization.removeMember, onPress: () => setRemoveOpen(true), children: _jsx(TrashBin, {}) })))] }), isCurrentUser && organization ? (_jsx(LeaveOrganizationDialog, { isOpen: leaveOpen, onOpenChange: setLeaveOpen, organization: organization })) : (hasDeletePermission?.success && (_jsx(RemoveMemberDialog, { isOpen: removeOpen, onOpenChange: setRemoveOpen, member: member })))] })] }));
34
- }
@@ -1,67 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useActiveOrganization, useAuth, useAuthPlugin, useHasPermission, useListOrganizationMembers, useSession } from "@better-auth-ui/react";
3
- import { ChevronUp, Funnel, Xmark } from "@gravity-ui/icons";
4
- import { Button, Chip, cn, Dropdown, Label, SearchField, Table } from "@heroui/react";
5
- import { useMemo, useState } from "react";
6
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
7
- import { InviteMemberDialog } from "./invite-member-dialog";
8
- import { OrganizationMemberRow } from "./organization-member-row";
9
- import { OrganizationMemberRowSkeleton } from "./organization-member-row-skeleton";
10
- /**
11
- * Organization members table with title, invite control, and per-row actions.
12
- */
13
- export function OrganizationMembers({ className, ...props }) {
14
- const { authClient } = useAuth();
15
- const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
16
- const { data: session } = useSession(authClient);
17
- const { data: activeOrganization, isPending: activeOrganizationPending } = useActiveOrganization(authClient);
18
- const { data: membersData, isPending: membersPending } = useListOrganizationMembers(authClient);
19
- const { isPending: updatePermissionPending } = useHasPermission(authClient, {
20
- permissions: { member: ["update"] }
21
- });
22
- const { isPending: deletePermissionPending } = useHasPermission(authClient, {
23
- permissions: { member: ["delete"] }
24
- });
25
- const isPending = activeOrganizationPending ||
26
- membersPending ||
27
- updatePermissionPending ||
28
- deletePermissionPending;
29
- const [sortDescriptor, setSortDescriptor] = useState();
30
- const [roleFilter, setRoleFilter] = useState("all");
31
- const [search, setSearch] = useState("");
32
- const filteredMembers = useMemo(() => {
33
- return membersData?.members.filter((member) => (roleFilter === "all" || member.role === roleFilter) &&
34
- (member.user.name.toLowerCase().includes(search.toLowerCase()) ||
35
- member.user.email.toLowerCase().includes(search.toLowerCase())));
36
- }, [search, membersData?.members, roleFilter]);
37
- const sortedMembers = useMemo(() => {
38
- if (!sortDescriptor)
39
- return filteredMembers;
40
- if (!filteredMembers)
41
- return filteredMembers;
42
- return [...filteredMembers].sort((a, b) => {
43
- const col = sortDescriptor.column;
44
- const first = col === "user" ? a.user.name || a.user.email : String(a[col]);
45
- const second = col === "user" ? b.user.name || b.user.email : String(b[col]);
46
- let cmp = first.localeCompare(second);
47
- if (sortDescriptor.direction === "descending") {
48
- cmp *= -1;
49
- }
50
- return cmp;
51
- });
52
- }, [sortDescriptor, filteredMembers]);
53
- const [inviteOpen, setInviteOpen] = useState(false);
54
- const isOwner = membersData?.members.some((member) => member.role === "owner" && member.userId === session?.user.id);
55
- return (_jsxs("div", { className: cn("flex flex-col gap-3", className), ...props, children: [_jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("h3", { className: "truncate text-sm font-semibold", children: organizationLocalization.members }), _jsx(Button, { className: "shrink-0", size: "sm", isDisabled: isPending, onPress: () => setInviteOpen(true), children: organizationLocalization.inviteMember })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(SearchField, { className: "min-w-0", "aria-label": organizationLocalization.search, value: search, onChange: setSearch, isDisabled: isPending, children: _jsxs(SearchField.Group, { children: [_jsx(SearchField.SearchIcon, {}), _jsx(SearchField.Input, { placeholder: organizationLocalization.search, className: "sm:w-[200px]" }), _jsx(SearchField.ClearButton, {})] }) }), _jsxs(Dropdown, { children: [_jsxs(Button, { size: "sm", variant: "secondary", isDisabled: isPending, children: [_jsx(Funnel, {}), organizationLocalization.role] }), _jsx(Dropdown.Popover, { children: _jsxs(Dropdown.Menu, { selectionMode: "single", selectedKeys: new Set([roleFilter]), onSelectionChange: (keys) => {
56
- const key = [...keys][0];
57
- setRoleFilter(key ?? "all");
58
- }, children: [_jsxs(Dropdown.Item, { id: "all", textValue: organizationLocalization.all, children: [_jsx(Label, { children: organizationLocalization.all }), _jsx(Dropdown.ItemIndicator, {})] }), Object.entries(roles).map(([role, label]) => (_jsxs(Dropdown.Item, { id: role, textValue: label, children: [_jsx(Label, { children: label }), _jsx(Dropdown.ItemIndicator, {})] }, role)))] }) })] })] }), roleFilter !== "all" && (_jsxs(Chip, { size: "sm", variant: "secondary", className: "w-fit", children: [_jsxs(Chip.Label, { children: [organizationLocalization.role, ":", " ", _jsx("span", { className: "capitalize", children: roles?.[roleFilter] ?? roleFilter })] }), _jsx("button", { type: "button", "aria-label": organizationLocalization.clear, className: "text-muted hover:text-foreground inline-flex cursor-pointer items-center", onClick: () => setRoleFilter("all"), children: _jsx(Xmark, { className: "size-3" }) })] })), _jsx(Table, { children: _jsx(Table.ScrollContainer, { children: _jsxs(Table.Content, { "aria-label": organizationLocalization.members, sortDescriptor: sortDescriptor, onSortChange: (descriptor) => {
59
- const shouldReset = sortDescriptor?.column === descriptor.column &&
60
- descriptor.direction === "ascending";
61
- setSortDescriptor(shouldReset ? undefined : descriptor);
62
- }, children: [_jsxs(Table.Header, { children: [_jsx(Table.Column, { allowsSorting: true, isRowHeader: true, id: "user", children: ({ sortDirection }) => (_jsx(SortableColumnHeader, { sortDirection: sortDirection, children: organizationLocalization.member })) }), _jsx(Table.Column, { allowsSorting: true, id: "role", children: ({ sortDirection }) => (_jsx(SortableColumnHeader, { sortDirection: sortDirection, children: organizationLocalization.role })) }), _jsx(Table.Column, { className: "text-end", children: organizationLocalization.actions })] }), _jsx(Table.Body, { children: isPending ? (_jsx(OrganizationMemberRowSkeleton, {})) : (!!activeOrganization &&
63
- sortedMembers?.map((member) => (_jsx(OrganizationMemberRow, { member: member, isOwner: isOwner, organization: activeOrganization }, member.id)))) })] }) }) })] }), _jsx(InviteMemberDialog, { isOpen: inviteOpen, onOpenChange: setInviteOpen })] }));
64
- }
65
- function SortableColumnHeader({ children, sortDirection }) {
66
- return (_jsxs("span", { className: "flex items-center justify-between", children: [children, !!sortDirection && (_jsx(ChevronUp, { className: cn("size-3 transform transition-transform duration-100 ease-out", sortDirection === "descending" ? "rotate-180" : "") }))] }));
67
- }
@@ -1,11 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn } from "@heroui/react";
3
- import { OrganizationInvitations } from "./organization-invitations";
4
- import { OrganizationMembers } from "./organization-members";
5
- /**
6
- * Organization people UI: members table (see {@link OrganizationMembers}), then org
7
- * invitations.
8
- */
9
- export function OrganizationPeople({ className, ...props }) {
10
- return (_jsxs("div", { className: cn("flex flex-col gap-4 md:gap-6", className), ...props, children: [_jsx(OrganizationMembers, {}), _jsx(OrganizationInvitations, {})] }));
11
- }
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useActiveOrganization, useAuth, useAuthPlugin, useUpdateOrganization } from "@better-auth-ui/react";
3
- import { Button, Card, cn, FieldError, Form, Input, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
4
- import { useEffect, useState } from "react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- import { ChangeOrganizationLogo } from "./change-organization-logo";
7
- import { SlugField } from "./slug-field";
8
- /**
9
- * Profile card for the active organization: logo (when enabled), display name, and slug.
10
- */
11
- export function OrganizationProfile({ className, variant, ...props }) {
12
- const { authClient, localization } = useAuth();
13
- const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
14
- const { data: activeOrganization } = useActiveOrganization(authClient);
15
- const [slug, setSlug] = useState(activeOrganization?.slug ?? "");
16
- useEffect(() => {
17
- setSlug(activeOrganization?.slug ?? "");
18
- }, [activeOrganization?.slug]);
19
- const { mutate: commitOrganizationUpdate, isPending } = useUpdateOrganization(authClient, {
20
- onSuccess: () => toast.success(organizationLocalization.organizationUpdatedSuccess)
21
- });
22
- function handleSubmit(e) {
23
- e.preventDefault();
24
- if (!activeOrganization)
25
- return;
26
- const formData = new FormData(e.currentTarget);
27
- const name = formData.get("name");
28
- commitOrganizationUpdate({
29
- data: { name, slug }
30
- });
31
- }
32
- const inputVariant = variant === "transparent" ? "primary" : "secondary";
33
- return (_jsxs("div", { children: [_jsx("h2", { className: cn("mb-3 text-sm font-semibold"), children: organizationLocalization.organizationProfile }), _jsx(Card, { className: cn(className), variant: variant, ...props, children: _jsx(Card.Content, { children: _jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsx(ChangeOrganizationLogo, {}), _jsxs(TextField, { name: "name", defaultValue: activeOrganization?.name, isDisabled: isPending || !activeOrganization, children: [_jsx(Label, { children: organizationLocalization.name }), _jsx(Input, { className: cn(!activeOrganization && "hidden"), autoComplete: "organization", placeholder: organizationLocalization.namePlaceholder, variant: inputVariant }), !activeOrganization && (_jsx(Skeleton, { className: "h-10 w-full rounded-xl md:h-9" })), _jsx(FieldError, {})] }, `${activeOrganization?.id}-${activeOrganization?.name}-name`), activeOrganization ? (_jsx(SlugField, { value: slug, onChange: setSlug, currentSlug: activeOrganization.slug, isDisabled: isPending, variant: inputVariant })) : (_jsxs(TextField, { isDisabled: true, children: [_jsx(Label, { children: organizationLocalization.slug }), _jsx(Skeleton, { className: "h-10 w-full rounded-xl md:h-9" })] })), _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !activeOrganization, size: "sm", className: "mt-1", children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.settings.saveChanges] })] }) }) })] }));
34
- }
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth, useAuthPlugin, useSetActiveOrganization } from "@better-auth-ui/react";
3
- import { Gear } from "@gravity-ui/icons";
4
- import { Button, Spinner } from "@heroui/react";
5
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
6
- import { OrganizationView } from "./organization-view";
7
- /**
8
- * Single organization row: logo and labels via {@link OrganizationView}, plus Manage.
9
- */
10
- export function OrganizationRow({ organization }) {
11
- const { authClient, basePaths, navigate } = useAuth();
12
- const { localization: organizationLocalization, viewPaths: organizationViewPaths, slug } = useAuthPlugin(organizationPlugin);
13
- const { mutate: setActiveOrganization, isPending: setActivePending } = useSetActiveOrganization(authClient, {
14
- onSuccess: () => {
15
- navigate({
16
- to: `${basePaths.organization}/${organizationViewPaths.organization.settings}`
17
- });
18
- }
19
- });
20
- function manageOrganization() {
21
- if (slug !== undefined) {
22
- navigate({
23
- to: `${basePaths.organization}/${organization.slug}/${organizationViewPaths.organization.settings}`
24
- });
25
- }
26
- else {
27
- setActiveOrganization({ organizationId: organization.id });
28
- }
29
- }
30
- return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(OrganizationView, { organization: organization }), _jsxs(Button, { className: "ml-auto shrink-0", variant: "outline", size: "sm", isPending: setActivePending, onPress: manageOrganization, "aria-label": organizationLocalization.manage, children: [setActivePending ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Gear, {}), organizationLocalization.manage] })] }));
31
- }
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAuth } from "@better-auth-ui/react";
3
- import { cn } from "@heroui/react";
4
- import { OrganizationDangerZone } from "./organization-danger-zone";
5
- import { OrganizationProfile } from "./organization-profile";
6
- /**
7
- * Organization settings UI: profile card, plugin-contributed cards
8
- * (`organizationCards`), then danger zone.
9
- */
10
- export function OrganizationSettings({ className, variant, ...props }) {
11
- const { plugins } = useAuth();
12
- return (_jsxs("div", { className: cn("flex flex-col gap-4 md:gap-6", className), ...props, children: [_jsx(OrganizationProfile, { variant: variant }), plugins.flatMap((plugin) => plugin.organizationCards?.map((Card, index) => (_jsx(Card, { variant: variant }, `${plugin.id}-${index.toString()}`)))), _jsx(OrganizationDangerZone, { variant: variant })] }));
13
- }
@@ -1,48 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useActiveOrganization, useAuth, useAuthPlugin, useListOrganizations, useSession, useSetActiveOrganization } from "@better-auth-ui/react";
3
- import { ChevronsExpandVertical, CirclePlus, Gear } from "@gravity-ui/icons";
4
- import { Button, cn, Dropdown, Label, Link } from "@heroui/react";
5
- import { buttonVariants } from "@heroui/styles";
6
- import { useState } from "react";
7
- import { organizationPlugin } from "../../../lib/auth/organization-plugin";
8
- import { UserView } from "../user/user-view";
9
- import { CreateOrganizationDialog } from "./create-organization-dialog";
10
- import { OrganizationView } from "./organization-view";
11
- /**
12
- * Renders an organizations dropdown with a trigger button,
13
- * header summary, and a menu of organizations to switch to.
14
- */
15
- export function OrganizationSwitcher({ className, hideCreate, hidePersonal, hideSettings, hideSlug = true, setActive, placement, variant = "ghost", size = "md", trigger, ...props }) {
16
- const { authClient, navigate, basePaths, localization, viewPaths } = useAuth();
17
- const { data: session, isPending: sessionPending } = useSession(authClient);
18
- const { localization: organizationLocalization, viewPaths: organizationViewPaths, slug } = useAuthPlugin(organizationPlugin);
19
- const { data: activeOrganization, isPending: activeOrganizationPending } = useActiveOrganization(authClient);
20
- const { data: organizations, isPending: organizationsPending } = useListOrganizations(authClient);
21
- const { mutate: setActiveOrganization } = useSetActiveOrganization(authClient);
22
- const isPending = sessionPending ||
23
- (!!session && (organizationsPending || activeOrganizationPending));
24
- const [createOpen, setCreateOpen] = useState(false);
25
- const [dropdownOpen, setDropdownOpen] = useState(false);
26
- function handleSetActive(organization) {
27
- if (setActive) {
28
- setActive(organization);
29
- }
30
- else if (slug !== undefined) {
31
- navigate({
32
- to: organization
33
- ? `${basePaths.organization}/${organization.slug}/${organizationViewPaths.organization.settings}`
34
- : `${basePaths.settings}/${viewPaths.settings.account}`
35
- });
36
- }
37
- else {
38
- setActiveOrganization({ organizationId: organization?.id ?? null });
39
- }
40
- }
41
- return (_jsxs(_Fragment, { children: [_jsxs(Dropdown, { isOpen: dropdownOpen, onOpenChange: setDropdownOpen, children: [trigger ? (_jsx(Dropdown.Trigger, { children: trigger })) : (_jsxs(Button, { variant: variant, className: cn("h-auto px-2 py-2 text-left", className), isDisabled: !session || isPending, "aria-label": organizationLocalization.organization, ...props, children: [isPending ? (_jsx(OrganizationView, { size: size, isPending: true, hideRole: true, hideSlug: hideSlug })) : activeOrganization ? (_jsx(OrganizationView, { size: size, hideRole: true, hideSlug: hideSlug })) : session && !hidePersonal ? (_jsx(UserView, { size: size, hideSubtitle: hideSlug })) : (_jsx(OrganizationView, { size: size, hideRole: true, hideSlug: hideSlug, organization: {
42
- name: organizationLocalization.organization
43
- } })), _jsx(ChevronsExpandVertical, { className: "size-3 shrink-0 text-muted" })] })), _jsxs(Dropdown.Popover, { placement: placement, className: "max-w-svw", children: [activeOrganization ? (_jsxs("div", { className: "flex items-center justify-between gap-4 px-4 pt-3", children: [_jsx(OrganizationView, { hideRole: true, hideSlug: hideSlug, organization: activeOrganization }), !hideSettings && (_jsxs(Link, { href: slug
44
- ? `${basePaths.organization}/${slug}/${organizationViewPaths.organization.settings}`
45
- : `${basePaths.organization}/${organizationViewPaths.organization.settings}`, className: cn(buttonVariants({ variant: "outline", size: "sm" }), "shrink-0 gap-2"), onPress: () => setDropdownOpen(false), children: [_jsx(Gear, { className: "text-muted" }), organizationLocalization.manage] }))] })) : !isPending && session?.user && !hidePersonal ? (_jsxs("div", { className: "flex items-center justify-between gap-4 px-4 pt-3", children: [_jsx(UserView, { hideSubtitle: hideSlug }), !hideSettings && (_jsxs(Link, { href: `${basePaths.settings}/${viewPaths.settings.account}`, className: cn(buttonVariants({ variant: "outline", size: "sm" }), "shrink-0 gap-2"), onPress: () => setDropdownOpen(false), children: [_jsx(Gear, { className: "text-muted" }), localization.settings.settings] }))] })) : null, _jsxs(Dropdown.Menu, { children: [!!activeOrganization && !hidePersonal && (_jsx(Dropdown.Item, { textValue: organizationLocalization.personalAccount, onPress: () => handleSetActive(null), children: _jsx(UserView, { hideSubtitle: hideSlug }) })), organizations
46
- ?.filter((organization) => organization.id !== activeOrganization?.id)
47
- ?.map((organization) => (_jsx(Dropdown.Item, { textValue: organization.name, onPress: () => handleSetActive(organization), children: _jsx(OrganizationView, { hideRole: true, hideSlug: hideSlug, organization: organization }) }, organization.id))), !hideCreate && (_jsxs(Dropdown.Item, { textValue: organizationLocalization.createOrganization, onPress: () => setCreateOpen(true), children: [_jsx(CirclePlus, { className: "text-muted" }), _jsx(Label, { children: organizationLocalization.createOrganization })] }))] })] })] }), _jsx(CreateOrganizationDialog, { isOpen: createOpen, onOpenChange: setCreateOpen })] }));
48
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn, Skeleton } from "@heroui/react";
3
- import { OrganizationLogo } from "./organization-logo";
4
- /**
5
- * Placeholder matching {@link OrganizationView} while organization data loads.
6
- */
7
- export function OrganizationViewSkeleton({ className, hideSlug, size = "md", ...props }) {
8
- return (_jsxs("div", { className: cn("flex min-w-0 items-center gap-2", className), ...props, children: [_jsx(OrganizationLogo, { isPending: true, className: size === "sm" ? "size-5" : undefined, size: size === "lg" ? "md" : "sm" }), _jsxs("div", { className: "flex flex-col min-w-0 gap-1", children: [_jsx(Skeleton, { className: "h-3.5 w-20 rounded-lg" }), !hideSlug && (_jsx(Skeleton, { className: "h-3 w-28 rounded-lg mt-[0.5px] mb-0.5" }))] })] }));
9
- }