@better-auth-ui/heroui 1.6.8 → 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 (195) hide show
  1. package/dist/components/auth/api-key/api-key.d.ts +5 -1
  2. package/dist/components/auth/api-key/api-keys-empty.d.ts +3 -1
  3. package/dist/components/auth/api-key/api-keys.d.ts +9 -1
  4. package/dist/components/auth/api-key/create-api-key-dialog.d.ts +3 -1
  5. package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +3 -1
  6. package/dist/components/auth/api-key/organization-api-keys.d.ts +14 -0
  7. package/dist/components/auth/delete-user/danger-zone.d.ts +1 -1
  8. package/dist/components/auth/delete-user/{delete-user.d.ts → delete-account.d.ts} +2 -2
  9. package/dist/components/auth/email/organization-invitation-email.d.ts +3 -0
  10. package/dist/components/auth/multi-session/switch-account-submenu-content.d.ts +3 -1
  11. package/dist/components/auth/multi-session/switch-account-submenu-item.d.ts +2 -1
  12. package/dist/components/auth/multi-session/switch-account-submenu.d.ts +2 -1
  13. package/dist/components/auth/organization/change-organization-logo.d.ts +4 -0
  14. package/dist/components/auth/organization/create-organization-dialog.d.ts +13 -0
  15. package/dist/components/auth/organization/delete-organization-dialog.d.ts +7 -0
  16. package/dist/components/auth/organization/delete-organization-skeleton.d.ts +4 -0
  17. package/dist/components/auth/organization/delete-organization.d.ts +5 -0
  18. package/dist/components/auth/organization/invite-member-dialog.d.ts +13 -0
  19. package/dist/components/auth/organization/leave-organization-dialog.d.ts +7 -0
  20. package/dist/components/auth/organization/leave-organization.d.ts +4 -0
  21. package/dist/components/auth/organization/organization-danger-zone.d.ts +16 -0
  22. package/dist/components/auth/organization/organization-invitation-row-skeleton.d.ts +5 -0
  23. package/dist/components/auth/organization/organization-invitation-row.d.ts +5 -0
  24. package/dist/components/auth/organization/organization-invitations-empty.d.ts +8 -0
  25. package/dist/components/auth/organization/organization-invitations.d.ts +9 -0
  26. package/dist/components/auth/organization/organization-logo.d.ts +15 -0
  27. package/dist/components/auth/organization/organization-member-row-skeleton.d.ts +4 -0
  28. package/dist/components/auth/organization/organization-member-row.d.ts +9 -0
  29. package/dist/components/auth/organization/organization-members.d.ts +9 -0
  30. package/dist/components/auth/organization/organization-people.d.ts +10 -0
  31. package/dist/components/auth/organization/organization-profile.d.ts +9 -0
  32. package/dist/components/auth/organization/organization-row.d.ts +8 -0
  33. package/dist/components/auth/organization/organization-settings.d.ts +12 -0
  34. package/dist/components/auth/organization/organization-switcher.d.ts +20 -0
  35. package/dist/components/auth/organization/organization-view-skeleton.d.ts +6 -0
  36. package/dist/components/auth/organization/organization-view.d.ts +15 -0
  37. package/dist/components/auth/organization/organization.d.ts +17 -0
  38. package/dist/components/auth/organization/organizations-empty.d.ts +4 -0
  39. package/dist/components/auth/organization/organizations-settings.d.ts +17 -0
  40. package/dist/components/auth/organization/organizations.d.ts +10 -0
  41. package/dist/components/auth/organization/remove-member-dialog.d.ts +9 -0
  42. package/dist/components/auth/organization/slug-field.d.ts +19 -0
  43. package/dist/components/auth/organization/user-invitation-row-skeleton.d.ts +4 -0
  44. package/dist/components/auth/organization/user-invitation-row.d.ts +10 -0
  45. package/dist/components/auth/organization/user-invitations-empty.d.ts +4 -0
  46. package/dist/components/auth/organization/user-invitations.d.ts +10 -0
  47. package/dist/components/auth/settings/settings.d.ts +1 -1
  48. package/dist/components/auth/user/user-avatar.d.ts +1 -1
  49. package/dist/components/auth/user/user-button.d.ts +4 -5
  50. package/dist/components/auth/user/user-view.d.ts +10 -4
  51. package/dist/email.d.ts +1 -0
  52. package/dist/email.js +211 -8
  53. package/dist/index.js +1682 -22
  54. package/dist/lib/auth/api-key-plugin.d.ts +5 -2
  55. package/dist/lib/auth/auth-plugin.d.ts +6 -2
  56. package/dist/lib/auth/delete-user-plugin.d.ts +4 -4
  57. package/dist/lib/auth/magic-link-plugin.d.ts +2 -2
  58. package/dist/lib/auth/multi-session-plugin.d.ts +2 -2
  59. package/dist/lib/auth/organization-plugin.d.ts +37 -0
  60. package/dist/lib/auth/passkey-plugin.d.ts +2 -2
  61. package/dist/lib/auth/theme-plugin.d.ts +3 -3
  62. package/dist/lib/auth/username-plugin.d.ts +8 -8
  63. package/dist/plugins.d.ts +25 -1
  64. package/dist/plugins.js +2824 -22
  65. package/dist/user-view--47tVtJ3.js +113 -0
  66. package/package.json +23 -18
  67. package/src/components/auth/api-key/api-key.tsx +25 -16
  68. package/src/components/auth/api-key/api-keys-empty.tsx +8 -4
  69. package/src/components/auth/api-key/api-keys.tsx +52 -14
  70. package/src/components/auth/api-key/create-api-key-dialog.tsx +15 -2
  71. package/src/components/auth/api-key/delete-api-key-dialog.tsx +10 -2
  72. package/src/components/auth/api-key/organization-api-keys.tsx +55 -0
  73. package/src/components/auth/delete-user/danger-zone.tsx +3 -3
  74. package/src/components/auth/delete-user/{delete-user.tsx → delete-account.tsx} +9 -9
  75. package/src/components/auth/email/organization-invitation-email.tsx +38 -0
  76. package/src/components/auth/error-toaster.tsx +28 -10
  77. package/src/components/auth/forgot-password.tsx +10 -2
  78. package/src/components/auth/multi-session/manage-accounts.tsx +1 -1
  79. package/src/components/auth/multi-session/switch-account-submenu-content.tsx +7 -2
  80. package/src/components/auth/multi-session/switch-account-submenu-item.tsx +4 -2
  81. package/src/components/auth/multi-session/switch-account-submenu.tsx +8 -4
  82. package/src/components/auth/organization/change-organization-logo.tsx +171 -0
  83. package/src/components/auth/organization/create-organization-dialog.tsx +133 -0
  84. package/src/components/auth/organization/delete-organization-dialog.tsx +97 -0
  85. package/src/components/auth/organization/delete-organization-skeleton.tsx +17 -0
  86. package/src/components/auth/organization/delete-organization.tsx +77 -0
  87. package/src/components/auth/organization/invite-member-dialog.tsx +177 -0
  88. package/src/components/auth/organization/leave-organization-dialog.tsx +95 -0
  89. package/src/components/auth/organization/leave-organization.tsx +59 -0
  90. package/src/components/auth/organization/organization-danger-zone.tsx +68 -0
  91. package/src/components/auth/organization/organization-invitation-row-skeleton.tsx +29 -0
  92. package/src/components/auth/organization/organization-invitation-row.tsx +97 -0
  93. package/src/components/auth/organization/organization-invitations-empty.tsx +39 -0
  94. package/src/components/auth/organization/organization-invitations.tsx +351 -0
  95. package/src/components/auth/organization/organization-logo.tsx +64 -0
  96. package/src/components/auth/organization/organization-member-row-skeleton.tsx +24 -0
  97. package/src/components/auth/organization/organization-member-row.tsx +157 -0
  98. package/src/components/auth/organization/organization-members.tsx +284 -0
  99. package/src/components/auth/organization/organization-people.tsx +26 -0
  100. package/src/components/auth/organization/organization-profile.tsx +141 -0
  101. package/src/components/auth/organization/organization-row.tsx +66 -0
  102. package/src/components/auth/organization/organization-settings.tsx +38 -0
  103. package/src/components/auth/organization/organization-switcher.tsx +233 -0
  104. package/src/components/auth/organization/organization-view-skeleton.tsx +36 -0
  105. package/src/components/auth/organization/organization-view.tsx +109 -0
  106. package/src/components/auth/organization/organization.tsx +162 -0
  107. package/src/components/auth/organization/organizations-empty.tsx +35 -0
  108. package/src/components/auth/organization/organizations-settings.tsx +36 -0
  109. package/src/components/auth/organization/organizations.tsx +80 -0
  110. package/src/components/auth/organization/remove-member-dialog.tsx +95 -0
  111. package/src/components/auth/organization/slug-field.tsx +111 -0
  112. package/src/components/auth/organization/user-invitation-row-skeleton.tsx +17 -0
  113. package/src/components/auth/organization/user-invitation-row.tsx +90 -0
  114. package/src/components/auth/organization/user-invitations-empty.tsx +30 -0
  115. package/src/components/auth/organization/user-invitations.tsx +59 -0
  116. package/src/components/auth/settings/account/change-email.tsx +1 -1
  117. package/src/components/auth/settings/account/user-profile.tsx +5 -5
  118. package/src/components/auth/settings/security/active-sessions.tsx +6 -4
  119. package/src/components/auth/settings/security/linked-accounts.tsx +2 -2
  120. package/src/components/auth/settings/settings.tsx +77 -14
  121. package/src/components/auth/user/user-avatar.tsx +4 -1
  122. package/src/components/auth/user/user-button.tsx +14 -11
  123. package/src/components/auth/user/user-view.tsx +32 -12
  124. package/src/email.ts +1 -0
  125. package/src/lib/auth/api-key-plugin.ts +10 -4
  126. package/src/lib/auth/auth-plugin.ts +6 -1
  127. package/src/lib/auth/organization-plugin.tsx +32 -0
  128. package/src/plugins.ts +25 -1
  129. package/dist/components/auth/additional-field.js +0 -270
  130. package/dist/components/auth/api-key/api-key-skeleton.js +0 -21
  131. package/dist/components/auth/api-key/api-key.js +0 -55
  132. package/dist/components/auth/api-key/api-keys-empty.js +0 -35
  133. package/dist/components/auth/api-key/api-keys.js +0 -41
  134. package/dist/components/auth/api-key/create-api-key-dialog.js +0 -74
  135. package/dist/components/auth/api-key/delete-api-key-dialog.js +0 -51
  136. package/dist/components/auth/api-key/new-api-key-dialog.js +0 -60
  137. package/dist/components/auth/auth-provider.js +0 -17
  138. package/dist/components/auth/auth.js +0 -63
  139. package/dist/components/auth/delete-user/danger-zone.js +0 -18
  140. package/dist/components/auth/delete-user/delete-user.js +0 -95
  141. package/dist/components/auth/email/email-changed-email.js +0 -30
  142. package/dist/components/auth/email/email-verification-email.js +0 -30
  143. package/dist/components/auth/email/magic-link-email.js +0 -30
  144. package/dist/components/auth/email/new-device-email.js +0 -30
  145. package/dist/components/auth/email/otp-email.js +0 -29
  146. package/dist/components/auth/email/password-changed-email.js +0 -30
  147. package/dist/components/auth/email/reset-password-email.js +0 -30
  148. package/dist/components/auth/error-toaster.js +0 -17
  149. package/dist/components/auth/field-separator.js +0 -18
  150. package/dist/components/auth/forgot-password.js +0 -89
  151. package/dist/components/auth/magic-link/magic-link-button.js +0 -20
  152. package/dist/components/auth/magic-link/magic-link.js +0 -87
  153. package/dist/components/auth/multi-session/manage-account.js +0 -49
  154. package/dist/components/auth/multi-session/manage-accounts.js +0 -34
  155. package/dist/components/auth/multi-session/switch-account-submenu-content.js +0 -28
  156. package/dist/components/auth/multi-session/switch-account-submenu-item.js +0 -20
  157. package/dist/components/auth/multi-session/switch-account-submenu.js +0 -24
  158. package/dist/components/auth/passkey/add-passkey-dialog.js +0 -65
  159. package/dist/components/auth/passkey/delete-passkey-dialog.js +0 -47
  160. package/dist/components/auth/passkey/passkey-button.js +0 -24
  161. package/dist/components/auth/passkey/passkey-skeleton.js +0 -17
  162. package/dist/components/auth/passkey/passkey.js +0 -48
  163. package/dist/components/auth/passkey/passkeys-empty.js +0 -35
  164. package/dist/components/auth/passkey/passkeys.js +0 -42
  165. package/dist/components/auth/provider-button.js +0 -25
  166. package/dist/components/auth/provider-buttons.js +0 -19
  167. package/dist/components/auth/reset-password.js +0 -147
  168. package/dist/components/auth/settings/account/account-settings.js +0 -20
  169. package/dist/components/auth/settings/account/change-avatar.js +0 -89
  170. package/dist/components/auth/settings/account/change-email.js +0 -54
  171. package/dist/components/auth/settings/account/user-profile.js +0 -86
  172. package/dist/components/auth/settings/security/active-session.js +0 -59
  173. package/dist/components/auth/settings/security/active-sessions.js +0 -34
  174. package/dist/components/auth/settings/security/change-password.js +0 -189
  175. package/dist/components/auth/settings/security/linked-account.js +0 -56
  176. package/dist/components/auth/settings/security/linked-accounts.js +0 -45
  177. package/dist/components/auth/settings/security/security-settings.js +0 -22
  178. package/dist/components/auth/settings/settings.js +0 -45
  179. package/dist/components/auth/sign-in.js +0 -133
  180. package/dist/components/auth/sign-out.js +0 -27
  181. package/dist/components/auth/sign-up.js +0 -212
  182. package/dist/components/auth/theme/appearance.js +0 -72
  183. package/dist/components/auth/theme/theme-toggle-item.js +0 -62
  184. package/dist/components/auth/user/user-avatar.js +0 -29
  185. package/dist/components/auth/user/user-button.js +0 -74
  186. package/dist/components/auth/user/user-view.js +0 -37
  187. package/dist/components/auth/username/sign-in-username.js +0 -148
  188. package/dist/components/auth/username/username-field.js +0 -53
  189. package/dist/lib/auth/api-key-plugin.js +0 -10
  190. package/dist/lib/auth/delete-user-plugin.js +0 -10
  191. package/dist/lib/auth/magic-link-plugin.js +0 -13
  192. package/dist/lib/auth/multi-session-plugin.js +0 -12
  193. package/dist/lib/auth/passkey-plugin.js +0 -12
  194. package/dist/lib/auth/theme-plugin.js +0 -23
  195. package/dist/lib/auth/username-plugin.js +0 -18
@@ -0,0 +1,284 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useActiveOrganization,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useHasPermission,
7
+ useListOrganizationMembers,
8
+ useSession
9
+ } from "@better-auth-ui/react"
10
+ import { ChevronUp, Funnel, Xmark } from "@gravity-ui/icons"
11
+ import {
12
+ Button,
13
+ Chip,
14
+ cn,
15
+ Dropdown,
16
+ Label,
17
+ SearchField,
18
+ type SortDescriptor,
19
+ Table
20
+ } from "@heroui/react"
21
+ import type { Member } from "better-auth/client"
22
+ import { type ComponentProps, type ReactNode, useMemo, useState } from "react"
23
+
24
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
25
+ import { InviteMemberDialog } from "./invite-member-dialog"
26
+ import { OrganizationMemberRow } from "./organization-member-row"
27
+ import { OrganizationMemberRowSkeleton } from "./organization-member-row-skeleton"
28
+
29
+ /** Props for the {@link OrganizationMembers} component. */
30
+ export type OrganizationMembersProps = {
31
+ className?: string
32
+ }
33
+
34
+ /**
35
+ * Organization members table with title, invite control, and per-row actions.
36
+ */
37
+ export function OrganizationMembers({
38
+ className,
39
+ ...props
40
+ }: OrganizationMembersProps & ComponentProps<"div">) {
41
+ const { authClient } = useAuth()
42
+ const { localization: organizationLocalization, roles } =
43
+ useAuthPlugin(organizationPlugin)
44
+
45
+ const { data: session } = useSession(authClient)
46
+ const { data: activeOrganization, isPending: activeOrganizationPending } =
47
+ useActiveOrganization(authClient as OrganizationAuthClient)
48
+ const { data: membersData, isPending: membersPending } =
49
+ useListOrganizationMembers(authClient as OrganizationAuthClient)
50
+
51
+ const { isPending: updatePermissionPending } = useHasPermission(
52
+ authClient as OrganizationAuthClient,
53
+ {
54
+ permissions: { member: ["update"] }
55
+ }
56
+ )
57
+ const { isPending: deletePermissionPending } = useHasPermission(
58
+ authClient as OrganizationAuthClient,
59
+ {
60
+ permissions: { member: ["delete"] }
61
+ }
62
+ )
63
+
64
+ const isPending =
65
+ activeOrganizationPending ||
66
+ membersPending ||
67
+ updatePermissionPending ||
68
+ deletePermissionPending
69
+
70
+ const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>()
71
+ const [roleFilter, setRoleFilter] = useState("all")
72
+ const [search, setSearch] = useState("")
73
+
74
+ const filteredMembers = useMemo(() => {
75
+ return membersData?.members.filter(
76
+ (member) =>
77
+ (roleFilter === "all" || member.role === roleFilter) &&
78
+ (member.user.name.toLowerCase().includes(search.toLowerCase()) ||
79
+ member.user.email.toLowerCase().includes(search.toLowerCase()))
80
+ )
81
+ }, [search, membersData?.members, roleFilter])
82
+
83
+ const sortedMembers = useMemo(() => {
84
+ if (!sortDescriptor) return filteredMembers
85
+ if (!filteredMembers) return filteredMembers
86
+
87
+ return [...filteredMembers].sort((a, b) => {
88
+ const col = sortDescriptor.column as keyof Member | "user"
89
+ const first =
90
+ col === "user" ? a.user.name || a.user.email : String(a[col])
91
+ const second =
92
+ col === "user" ? b.user.name || b.user.email : String(b[col])
93
+
94
+ let cmp = first.localeCompare(second)
95
+ if (sortDescriptor.direction === "descending") {
96
+ cmp *= -1
97
+ }
98
+
99
+ return cmp
100
+ })
101
+ }, [sortDescriptor, filteredMembers])
102
+
103
+ const [inviteOpen, setInviteOpen] = useState(false)
104
+
105
+ const isOwner = membersData?.members.some(
106
+ (member) => member.role === "owner" && member.userId === session?.user.id
107
+ )
108
+
109
+ return (
110
+ <div className={cn("flex flex-col gap-3", className)} {...props}>
111
+ <div className="flex items-end justify-between gap-3">
112
+ <h3 className="truncate text-sm font-semibold">
113
+ {organizationLocalization.members}
114
+ </h3>
115
+
116
+ <Button
117
+ className="shrink-0"
118
+ size="sm"
119
+ isDisabled={isPending}
120
+ onPress={() => setInviteOpen(true)}
121
+ >
122
+ {organizationLocalization.inviteMember}
123
+ </Button>
124
+ </div>
125
+
126
+ <div className="flex flex-col gap-4">
127
+ <div className="flex items-center gap-3">
128
+ <SearchField
129
+ className="min-w-0"
130
+ aria-label={organizationLocalization.search}
131
+ value={search}
132
+ onChange={setSearch}
133
+ isDisabled={isPending}
134
+ >
135
+ <SearchField.Group>
136
+ <SearchField.SearchIcon />
137
+
138
+ <SearchField.Input
139
+ placeholder={organizationLocalization.search}
140
+ className="sm:w-[200px]"
141
+ />
142
+
143
+ <SearchField.ClearButton />
144
+ </SearchField.Group>
145
+ </SearchField>
146
+
147
+ <Dropdown>
148
+ <Button size="sm" variant="secondary" isDisabled={isPending}>
149
+ <Funnel />
150
+
151
+ {organizationLocalization.role}
152
+ </Button>
153
+
154
+ <Dropdown.Popover>
155
+ <Dropdown.Menu
156
+ selectionMode="single"
157
+ selectedKeys={new Set([roleFilter])}
158
+ onSelectionChange={(keys) => {
159
+ const key = [...keys][0] as string | undefined
160
+ setRoleFilter(key ?? "all")
161
+ }}
162
+ >
163
+ <Dropdown.Item
164
+ id="all"
165
+ textValue={organizationLocalization.all}
166
+ >
167
+ <Label>{organizationLocalization.all}</Label>
168
+
169
+ <Dropdown.ItemIndicator />
170
+ </Dropdown.Item>
171
+
172
+ {Object.entries(roles).map(([role, label]) => (
173
+ <Dropdown.Item key={role} id={role} textValue={label}>
174
+ <Label>{label}</Label>
175
+
176
+ <Dropdown.ItemIndicator />
177
+ </Dropdown.Item>
178
+ ))}
179
+ </Dropdown.Menu>
180
+ </Dropdown.Popover>
181
+ </Dropdown>
182
+ </div>
183
+
184
+ {roleFilter !== "all" && (
185
+ <Chip size="sm" variant="secondary" className="w-fit">
186
+ <Chip.Label>
187
+ {organizationLocalization.role}:{" "}
188
+ <span className="capitalize">
189
+ {roles?.[roleFilter] ?? roleFilter}
190
+ </span>
191
+ </Chip.Label>
192
+
193
+ <button
194
+ type="button"
195
+ aria-label={organizationLocalization.clear}
196
+ className="text-muted hover:text-foreground inline-flex cursor-pointer items-center"
197
+ onClick={() => setRoleFilter("all")}
198
+ >
199
+ <Xmark className="size-3" />
200
+ </button>
201
+ </Chip>
202
+ )}
203
+
204
+ <Table>
205
+ <Table.ScrollContainer>
206
+ <Table.Content
207
+ aria-label={organizationLocalization.members}
208
+ sortDescriptor={sortDescriptor}
209
+ onSortChange={(descriptor) => {
210
+ const shouldReset =
211
+ sortDescriptor?.column === descriptor.column &&
212
+ descriptor.direction === "ascending"
213
+ setSortDescriptor(shouldReset ? undefined : descriptor)
214
+ }}
215
+ >
216
+ <Table.Header>
217
+ <Table.Column allowsSorting isRowHeader id="user">
218
+ {({ sortDirection }) => (
219
+ <SortableColumnHeader sortDirection={sortDirection}>
220
+ {organizationLocalization.member}
221
+ </SortableColumnHeader>
222
+ )}
223
+ </Table.Column>
224
+
225
+ <Table.Column allowsSorting id="role">
226
+ {({ sortDirection }) => (
227
+ <SortableColumnHeader sortDirection={sortDirection}>
228
+ {organizationLocalization.role}
229
+ </SortableColumnHeader>
230
+ )}
231
+ </Table.Column>
232
+
233
+ <Table.Column className="text-end">
234
+ {organizationLocalization.actions}
235
+ </Table.Column>
236
+ </Table.Header>
237
+
238
+ <Table.Body>
239
+ {isPending ? (
240
+ <OrganizationMemberRowSkeleton />
241
+ ) : (
242
+ !!activeOrganization &&
243
+ sortedMembers?.map((member) => (
244
+ <OrganizationMemberRow
245
+ key={member.id}
246
+ member={member}
247
+ isOwner={isOwner}
248
+ organization={activeOrganization}
249
+ />
250
+ ))
251
+ )}
252
+ </Table.Body>
253
+ </Table.Content>
254
+ </Table.ScrollContainer>
255
+ </Table>
256
+ </div>
257
+
258
+ <InviteMemberDialog isOpen={inviteOpen} onOpenChange={setInviteOpen} />
259
+ </div>
260
+ )
261
+ }
262
+
263
+ function SortableColumnHeader({
264
+ children,
265
+ sortDirection
266
+ }: {
267
+ children: ReactNode
268
+ sortDirection?: "ascending" | "descending"
269
+ }) {
270
+ return (
271
+ <span className="flex items-center justify-between">
272
+ {children}
273
+
274
+ {!!sortDirection && (
275
+ <ChevronUp
276
+ className={cn(
277
+ "size-3 transform transition-transform duration-100 ease-out",
278
+ sortDirection === "descending" ? "rotate-180" : ""
279
+ )}
280
+ />
281
+ )}
282
+ </span>
283
+ )
284
+ }
@@ -0,0 +1,26 @@
1
+ import { cn } from "@heroui/react"
2
+ import type { ComponentProps } from "react"
3
+
4
+ import { OrganizationInvitations } from "./organization-invitations"
5
+ import { OrganizationMembers } from "./organization-members"
6
+
7
+ /** Props for the {@link OrganizationPeople} component. */
8
+ export type OrganizationPeopleProps = {
9
+ className?: string
10
+ }
11
+
12
+ /**
13
+ * Organization people UI: members table (see {@link OrganizationMembers}), then org
14
+ * invitations.
15
+ */
16
+ export function OrganizationPeople({
17
+ className,
18
+ ...props
19
+ }: OrganizationPeopleProps & ComponentProps<"div">) {
20
+ return (
21
+ <div className={cn("flex flex-col gap-4 md:gap-6", className)} {...props}>
22
+ <OrganizationMembers />
23
+ <OrganizationInvitations />
24
+ </div>
25
+ )
26
+ }
@@ -0,0 +1,141 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useActiveOrganization,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useUpdateOrganization
7
+ } from "@better-auth-ui/react"
8
+ import {
9
+ Button,
10
+ Card,
11
+ type CardProps,
12
+ cn,
13
+ FieldError,
14
+ Form,
15
+ Input,
16
+ Label,
17
+ Skeleton,
18
+ Spinner,
19
+ TextField,
20
+ toast
21
+ } from "@heroui/react"
22
+ import { type SyntheticEvent, useEffect, useState } from "react"
23
+
24
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
25
+ import { ChangeOrganizationLogo } from "./change-organization-logo"
26
+ import { SlugField } from "./slug-field"
27
+
28
+ export type OrganizationProfileProps = {
29
+ className?: string
30
+ variant?: CardProps["variant"]
31
+ }
32
+
33
+ /**
34
+ * Profile card for the active organization: logo (when enabled), display name, and slug.
35
+ */
36
+ export function OrganizationProfile({
37
+ className,
38
+ variant,
39
+ ...props
40
+ }: OrganizationProfileProps & Omit<CardProps, "children">) {
41
+ const { authClient, localization } = useAuth()
42
+ const { localization: organizationLocalization } =
43
+ useAuthPlugin(organizationPlugin)
44
+
45
+ const { data: activeOrganization } = useActiveOrganization(
46
+ authClient as OrganizationAuthClient
47
+ )
48
+
49
+ const [slug, setSlug] = useState(activeOrganization?.slug ?? "")
50
+
51
+ useEffect(() => {
52
+ setSlug(activeOrganization?.slug ?? "")
53
+ }, [activeOrganization?.slug])
54
+
55
+ const { mutate: commitOrganizationUpdate, isPending } = useUpdateOrganization(
56
+ authClient as OrganizationAuthClient,
57
+ {
58
+ onSuccess: () =>
59
+ toast.success(organizationLocalization.organizationUpdatedSuccess)
60
+ }
61
+ )
62
+
63
+ function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
64
+ e.preventDefault()
65
+ if (!activeOrganization) return
66
+
67
+ const formData = new FormData(e.currentTarget)
68
+ const name = formData.get("name") as string
69
+
70
+ commitOrganizationUpdate({
71
+ data: { name, slug }
72
+ })
73
+ }
74
+
75
+ const inputVariant = variant === "transparent" ? "primary" : "secondary"
76
+
77
+ return (
78
+ <div>
79
+ <h2 className={cn("mb-3 text-sm font-semibold")}>
80
+ {organizationLocalization.organizationProfile}
81
+ </h2>
82
+
83
+ <Card className={cn(className)} variant={variant} {...props}>
84
+ <Card.Content>
85
+ <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
86
+ <ChangeOrganizationLogo />
87
+
88
+ <TextField
89
+ key={`${activeOrganization?.id}-${activeOrganization?.name}-name`}
90
+ name="name"
91
+ defaultValue={activeOrganization?.name}
92
+ isDisabled={isPending || !activeOrganization}
93
+ >
94
+ <Label>{organizationLocalization.name}</Label>
95
+
96
+ <Input
97
+ className={cn(!activeOrganization && "hidden")}
98
+ autoComplete="organization"
99
+ placeholder={organizationLocalization.namePlaceholder}
100
+ variant={inputVariant}
101
+ />
102
+
103
+ {!activeOrganization && (
104
+ <Skeleton className="h-10 w-full rounded-xl md:h-9" />
105
+ )}
106
+
107
+ <FieldError />
108
+ </TextField>
109
+
110
+ {activeOrganization ? (
111
+ <SlugField
112
+ value={slug}
113
+ onChange={setSlug}
114
+ currentSlug={activeOrganization.slug}
115
+ isDisabled={isPending}
116
+ variant={inputVariant}
117
+ />
118
+ ) : (
119
+ <TextField isDisabled>
120
+ <Label>{organizationLocalization.slug}</Label>
121
+ <Skeleton className="h-10 w-full rounded-xl md:h-9" />
122
+ </TextField>
123
+ )}
124
+
125
+ <Button
126
+ type="submit"
127
+ isPending={isPending}
128
+ isDisabled={!activeOrganization}
129
+ size="sm"
130
+ className="mt-1"
131
+ >
132
+ {isPending && <Spinner color="current" size="sm" />}
133
+
134
+ {localization.settings.saveChanges}
135
+ </Button>
136
+ </Form>
137
+ </Card.Content>
138
+ </Card>
139
+ </div>
140
+ )
141
+ }
@@ -0,0 +1,66 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useSetActiveOrganization
6
+ } from "@better-auth-ui/react"
7
+ import { Gear } from "@gravity-ui/icons"
8
+ import { Button, Spinner } from "@heroui/react"
9
+ import type { Organization } from "better-auth/client"
10
+
11
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
12
+ import { OrganizationView } from "./organization-view"
13
+
14
+ export type OrganizationRowProps = {
15
+ organization: Organization
16
+ }
17
+
18
+ /**
19
+ * Single organization row: logo and labels via {@link OrganizationView}, plus Manage.
20
+ */
21
+ export function OrganizationRow({ organization }: OrganizationRowProps) {
22
+ const { authClient, basePaths, navigate } = useAuth()
23
+ const {
24
+ localization: organizationLocalization,
25
+ viewPaths: organizationViewPaths,
26
+ slug
27
+ } = useAuthPlugin(organizationPlugin)
28
+
29
+ const { mutate: setActiveOrganization, isPending: setActivePending } =
30
+ useSetActiveOrganization(authClient as OrganizationAuthClient, {
31
+ onSuccess: () => {
32
+ navigate({
33
+ to: `${basePaths.organization}/${organizationViewPaths.organization.settings}`
34
+ })
35
+ }
36
+ })
37
+
38
+ function manageOrganization() {
39
+ if (slug !== undefined) {
40
+ navigate({
41
+ to: `${basePaths.organization}/${organization.slug}/${organizationViewPaths.organization.settings}`
42
+ })
43
+ } else {
44
+ setActiveOrganization({ organizationId: organization.id })
45
+ }
46
+ }
47
+
48
+ return (
49
+ <div className="flex items-center gap-3">
50
+ <OrganizationView organization={organization} />
51
+
52
+ <Button
53
+ className="ml-auto shrink-0"
54
+ variant="outline"
55
+ size="sm"
56
+ isPending={setActivePending}
57
+ onPress={manageOrganization}
58
+ aria-label={organizationLocalization.manage}
59
+ >
60
+ {setActivePending ? <Spinner color="current" size="sm" /> : <Gear />}
61
+
62
+ {organizationLocalization.manage}
63
+ </Button>
64
+ </div>
65
+ )
66
+ }
@@ -0,0 +1,38 @@
1
+ import { useAuth } from "@better-auth-ui/react"
2
+ import { type CardProps, cn } from "@heroui/react"
3
+ import type { ComponentProps } from "react"
4
+
5
+ import { OrganizationDangerZone } from "./organization-danger-zone"
6
+ import { OrganizationProfile } from "./organization-profile"
7
+
8
+ /** Props for the {@link OrganizationSettings} component. */
9
+ export type OrganizationSettingsProps = {
10
+ className?: string
11
+ variant?: CardProps["variant"]
12
+ }
13
+
14
+ /**
15
+ * Organization settings UI: profile card, plugin-contributed cards
16
+ * (`organizationCards`), then danger zone.
17
+ */
18
+ export function OrganizationSettings({
19
+ className,
20
+ variant,
21
+ ...props
22
+ }: OrganizationSettingsProps & ComponentProps<"div">) {
23
+ const { plugins } = useAuth()
24
+
25
+ return (
26
+ <div className={cn("flex flex-col gap-4 md:gap-6", className)} {...props}>
27
+ <OrganizationProfile variant={variant} />
28
+
29
+ {plugins.flatMap((plugin) =>
30
+ plugin.organizationCards?.map((Card, index) => (
31
+ <Card key={`${plugin.id}-${index.toString()}`} variant={variant} />
32
+ ))
33
+ )}
34
+
35
+ <OrganizationDangerZone variant={variant} />
36
+ </div>
37
+ )
38
+ }