@better-auth-ui/heroui 1.6.8 → 1.6.9

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 (270) hide show
  1. package/dist/components/auth/additional-field.d.ts +2 -2
  2. package/dist/components/auth/additional-field.js +133 -266
  3. package/dist/components/auth/api-key/api-key-skeleton.js +4 -20
  4. package/dist/components/auth/api-key/api-key.d.ts +6 -2
  5. package/dist/components/auth/api-key/api-key.js +16 -54
  6. package/dist/components/auth/api-key/api-keys-empty.d.ts +3 -1
  7. package/dist/components/auth/api-key/api-keys-empty.js +8 -34
  8. package/dist/components/auth/api-key/api-keys.d.ts +10 -2
  9. package/dist/components/auth/api-key/api-keys.js +21 -40
  10. package/dist/components/auth/api-key/create-api-key-dialog.d.ts +3 -1
  11. package/dist/components/auth/api-key/create-api-key-dialog.js +43 -73
  12. package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +4 -2
  13. package/dist/components/auth/api-key/delete-api-key-dialog.js +16 -50
  14. package/dist/components/auth/api-key/new-api-key-dialog.js +23 -59
  15. package/dist/components/auth/api-key/organization-api-keys.d.ts +14 -0
  16. package/dist/components/auth/api-key/organization-api-keys.js +22 -0
  17. package/dist/components/auth/auth-provider.d.ts +1 -1
  18. package/dist/components/auth/auth-provider.js +10 -16
  19. package/dist/components/auth/auth.d.ts +3 -3
  20. package/dist/components/auth/auth.js +86 -61
  21. package/dist/components/auth/delete-user/danger-zone.d.ts +3 -3
  22. package/dist/components/auth/delete-user/danger-zone.js +11 -17
  23. package/dist/components/auth/delete-user/delete-account.d.ts +9 -0
  24. package/dist/components/auth/delete-user/delete-account.js +50 -0
  25. package/dist/components/auth/email/email-changed-email.d.ts +2 -2
  26. package/dist/components/auth/email/email-changed-email.js +22 -29
  27. package/dist/components/auth/email/email-verification-email.d.ts +2 -2
  28. package/dist/components/auth/email/email-verification-email.js +22 -29
  29. package/dist/components/auth/email/magic-link-email.d.ts +2 -2
  30. package/dist/components/auth/email/magic-link-email.js +22 -29
  31. package/dist/components/auth/email/new-device-email.d.ts +2 -2
  32. package/dist/components/auth/email/new-device-email.js +22 -29
  33. package/dist/components/auth/email/organization-invitation-email.d.ts +3 -0
  34. package/dist/components/auth/email/organization-invitation-email.js +23 -0
  35. package/dist/components/auth/email/otp-email.d.ts +2 -2
  36. package/dist/components/auth/email/otp-email.js +21 -28
  37. package/dist/components/auth/email/password-changed-email.d.ts +2 -2
  38. package/dist/components/auth/email/password-changed-email.js +22 -29
  39. package/dist/components/auth/email/reset-password-email.d.ts +2 -2
  40. package/dist/components/auth/email/reset-password-email.js +22 -29
  41. package/dist/components/auth/error-toaster.js +32 -16
  42. package/dist/components/auth/field-separator.d.ts +1 -1
  43. package/dist/components/auth/field-separator.js +4 -17
  44. package/dist/components/auth/forgot-password.d.ts +1 -1
  45. package/dist/components/auth/forgot-password.js +36 -88
  46. package/dist/components/auth/magic-link/magic-link-button.d.ts +1 -1
  47. package/dist/components/auth/magic-link/magic-link-button.js +33 -19
  48. package/dist/components/auth/magic-link/magic-link.d.ts +2 -2
  49. package/dist/components/auth/magic-link/magic-link.js +39 -86
  50. package/dist/components/auth/multi-session/manage-account.d.ts +1 -1
  51. package/dist/components/auth/multi-session/manage-account.js +32 -48
  52. package/dist/components/auth/multi-session/manage-accounts.d.ts +1 -1
  53. package/dist/components/auth/multi-session/manage-accounts.js +32 -33
  54. package/dist/components/auth/multi-session/switch-account-submenu-content.d.ts +3 -1
  55. package/dist/components/auth/multi-session/switch-account-submenu-content.js +24 -27
  56. package/dist/components/auth/multi-session/switch-account-submenu-item.d.ts +3 -2
  57. package/dist/components/auth/multi-session/switch-account-submenu-item.js +16 -19
  58. package/dist/components/auth/multi-session/switch-account-submenu.d.ts +2 -1
  59. package/dist/components/auth/multi-session/switch-account-submenu.js +23 -23
  60. package/dist/components/auth/organization/change-organization-logo.d.ts +4 -0
  61. package/dist/components/auth/organization/change-organization-logo.js +69 -0
  62. package/dist/components/auth/organization/create-organization-dialog.d.ts +13 -0
  63. package/dist/components/auth/organization/create-organization-dialog.js +37 -0
  64. package/dist/components/auth/organization/delete-organization-dialog.d.ts +7 -0
  65. package/dist/components/auth/organization/delete-organization-dialog.js +25 -0
  66. package/dist/components/auth/organization/delete-organization-skeleton.d.ts +4 -0
  67. package/dist/components/auth/organization/delete-organization-skeleton.js +8 -0
  68. package/dist/components/auth/organization/delete-organization.d.ts +5 -0
  69. package/dist/components/auth/organization/delete-organization.js +27 -0
  70. package/dist/components/auth/organization/invite-member-dialog.d.ts +13 -0
  71. package/dist/components/auth/organization/invite-member-dialog.js +47 -0
  72. package/dist/components/auth/organization/leave-organization-dialog.d.ts +7 -0
  73. package/dist/components/auth/organization/leave-organization-dialog.js +21 -0
  74. package/dist/components/auth/organization/leave-organization.d.ts +4 -0
  75. package/dist/components/auth/organization/leave-organization.js +16 -0
  76. package/dist/components/auth/organization/organization-danger-zone.d.ts +16 -0
  77. package/dist/components/auth/organization/organization-danger-zone.js +23 -0
  78. package/dist/components/auth/organization/organization-invitation-row-skeleton.d.ts +5 -0
  79. package/dist/components/auth/organization/organization-invitation-row-skeleton.js +9 -0
  80. package/dist/components/auth/organization/organization-invitation-row.d.ts +5 -0
  81. package/dist/components/auth/organization/organization-invitation-row.js +31 -0
  82. package/dist/components/auth/organization/organization-invitations-empty.d.ts +8 -0
  83. package/dist/components/auth/organization/organization-invitations-empty.js +13 -0
  84. package/dist/components/auth/organization/organization-invitations.d.ts +9 -0
  85. package/dist/components/auth/organization/organization-invitations.js +66 -0
  86. package/dist/components/auth/organization/organization-logo.d.ts +15 -0
  87. package/dist/components/auth/organization/organization-logo.js +14 -0
  88. package/dist/components/auth/organization/organization-member-row-skeleton.d.ts +4 -0
  89. package/dist/components/auth/organization/organization-member-row-skeleton.js +9 -0
  90. package/dist/components/auth/organization/organization-member-row.d.ts +9 -0
  91. package/dist/components/auth/organization/organization-member-row.js +34 -0
  92. package/dist/components/auth/organization/organization-members.d.ts +9 -0
  93. package/dist/components/auth/organization/organization-members.js +67 -0
  94. package/dist/components/auth/organization/organization-people.d.ts +10 -0
  95. package/dist/components/auth/organization/organization-people.js +11 -0
  96. package/dist/components/auth/organization/organization-profile.d.ts +9 -0
  97. package/dist/components/auth/organization/organization-profile.js +34 -0
  98. package/dist/components/auth/organization/organization-row.d.ts +8 -0
  99. package/dist/components/auth/organization/organization-row.js +31 -0
  100. package/dist/components/auth/organization/organization-settings.d.ts +12 -0
  101. package/dist/components/auth/organization/organization-settings.js +13 -0
  102. package/dist/components/auth/organization/organization-switcher.d.ts +20 -0
  103. package/dist/components/auth/organization/organization-switcher.js +48 -0
  104. package/dist/components/auth/organization/organization-view-skeleton.d.ts +6 -0
  105. package/dist/components/auth/organization/organization-view-skeleton.js +9 -0
  106. package/dist/components/auth/organization/organization-view.d.ts +15 -0
  107. package/dist/components/auth/organization/organization-view.js +31 -0
  108. package/dist/components/auth/organization/organization.d.ts +17 -0
  109. package/dist/components/auth/organization/organization.js +60 -0
  110. package/dist/components/auth/organization/organizations-empty.d.ts +4 -0
  111. package/dist/components/auth/organization/organizations-empty.js +9 -0
  112. package/dist/components/auth/organization/organizations-settings.d.ts +17 -0
  113. package/dist/components/auth/organization/organizations-settings.js +17 -0
  114. package/dist/components/auth/organization/organizations.d.ts +10 -0
  115. package/dist/components/auth/organization/organizations.js +21 -0
  116. package/dist/components/auth/organization/remove-member-dialog.d.ts +9 -0
  117. package/dist/components/auth/organization/remove-member-dialog.js +20 -0
  118. package/dist/components/auth/organization/slug-field.d.ts +19 -0
  119. package/dist/components/auth/organization/slug-field.js +38 -0
  120. package/dist/components/auth/organization/user-invitation-row-skeleton.d.ts +4 -0
  121. package/dist/components/auth/organization/user-invitation-row-skeleton.js +8 -0
  122. package/dist/components/auth/organization/user-invitation-row.d.ts +10 -0
  123. package/dist/components/auth/organization/user-invitation-row.js +22 -0
  124. package/dist/components/auth/organization/user-invitations-empty.d.ts +4 -0
  125. package/dist/components/auth/organization/user-invitations-empty.js +12 -0
  126. package/dist/components/auth/organization/user-invitations.d.ts +10 -0
  127. package/dist/components/auth/organization/user-invitations.js +18 -0
  128. package/dist/components/auth/passkey/add-passkey-dialog.js +21 -64
  129. package/dist/components/auth/passkey/delete-passkey-dialog.js +13 -46
  130. package/dist/components/auth/passkey/passkey-button.d.ts +1 -1
  131. package/dist/components/auth/passkey/passkey-button.js +24 -23
  132. package/dist/components/auth/passkey/passkey-skeleton.js +4 -16
  133. package/dist/components/auth/passkey/passkey.d.ts +1 -1
  134. package/dist/components/auth/passkey/passkey.js +16 -47
  135. package/dist/components/auth/passkey/passkeys-empty.js +8 -34
  136. package/dist/components/auth/passkey/passkeys.d.ts +1 -1
  137. package/dist/components/auth/passkey/passkeys.js +15 -41
  138. package/dist/components/auth/provider-button.d.ts +2 -2
  139. package/dist/components/auth/provider-button.js +28 -24
  140. package/dist/components/auth/provider-buttons.js +28 -18
  141. package/dist/components/auth/reset-password.d.ts +1 -1
  142. package/dist/components/auth/reset-password.js +56 -146
  143. package/dist/components/auth/settings/account/account-settings.d.ts +2 -2
  144. package/dist/components/auth/settings/account/account-settings.js +21 -19
  145. package/dist/components/auth/settings/account/change-avatar.js +53 -88
  146. package/dist/components/auth/settings/account/change-email.d.ts +1 -1
  147. package/dist/components/auth/settings/account/change-email.js +27 -53
  148. package/dist/components/auth/settings/account/user-profile.d.ts +1 -1
  149. package/dist/components/auth/settings/account/user-profile.js +64 -85
  150. package/dist/components/auth/settings/security/active-session.d.ts +1 -1
  151. package/dist/components/auth/settings/security/active-session.js +50 -57
  152. package/dist/components/auth/settings/security/active-sessions.d.ts +1 -1
  153. package/dist/components/auth/settings/security/active-sessions.js +21 -32
  154. package/dist/components/auth/settings/security/change-password.d.ts +1 -1
  155. package/dist/components/auth/settings/security/change-password.js +76 -186
  156. package/dist/components/auth/settings/security/linked-account.d.ts +1 -1
  157. package/dist/components/auth/settings/security/linked-account.js +37 -55
  158. package/dist/components/auth/settings/security/linked-accounts.d.ts +1 -1
  159. package/dist/components/auth/settings/security/linked-accounts.js +34 -43
  160. package/dist/components/auth/settings/security/security-settings.d.ts +2 -2
  161. package/dist/components/auth/settings/security/security-settings.js +19 -21
  162. package/dist/components/auth/settings/settings.d.ts +4 -4
  163. package/dist/components/auth/settings/settings.js +52 -44
  164. package/dist/components/auth/sign-in.d.ts +2 -2
  165. package/dist/components/auth/sign-in.js +63 -132
  166. package/dist/components/auth/sign-out.js +32 -26
  167. package/dist/components/auth/sign-up.d.ts +2 -2
  168. package/dist/components/auth/sign-up.js +97 -211
  169. package/dist/components/auth/theme/appearance.d.ts +1 -1
  170. package/dist/components/auth/theme/appearance.js +23 -71
  171. package/dist/components/auth/theme/theme-toggle-item.js +56 -61
  172. package/dist/components/auth/user/user-avatar.d.ts +4 -4
  173. package/dist/components/auth/user/user-avatar.js +31 -28
  174. package/dist/components/auth/user/user-button.d.ts +6 -7
  175. package/dist/components/auth/user/user-button.js +38 -72
  176. package/dist/components/auth/user/user-view.d.ts +13 -7
  177. package/dist/components/auth/user/user-view.js +26 -36
  178. package/dist/components/auth/username/sign-in-username.d.ts +2 -2
  179. package/dist/components/auth/username/sign-in-username.js +87 -147
  180. package/dist/components/auth/username/username-field.d.ts +1 -1
  181. package/dist/components/auth/username/username-field.js +44 -52
  182. package/dist/email.d.ts +8 -7
  183. package/dist/email.js +8 -8
  184. package/dist/index.d.ts +21 -21
  185. package/dist/index.js +21 -22
  186. package/dist/lib/auth/api-key-plugin.d.ts +7 -4
  187. package/dist/lib/auth/api-key-plugin.js +12 -10
  188. package/dist/lib/auth/auth-plugin.d.ts +10 -6
  189. package/dist/lib/auth/auth-plugin.js +1 -0
  190. package/dist/lib/auth/delete-user-plugin.d.ts +6 -6
  191. package/dist/lib/auth/delete-user-plugin.js +6 -9
  192. package/dist/lib/auth/magic-link-plugin.d.ts +5 -5
  193. package/dist/lib/auth/magic-link-plugin.js +17 -12
  194. package/dist/lib/auth/multi-session-plugin.d.ts +5 -5
  195. package/dist/lib/auth/multi-session-plugin.js +8 -11
  196. package/dist/lib/auth/organization-plugin.d.ts +37 -0
  197. package/dist/lib/auth/organization-plugin.js +19 -0
  198. package/dist/lib/auth/passkey-plugin.d.ts +5 -5
  199. package/dist/lib/auth/passkey-plugin.js +8 -11
  200. package/dist/lib/auth/theme-plugin.d.ts +6 -6
  201. package/dist/lib/auth/theme-plugin.js +25 -22
  202. package/dist/lib/auth/username-plugin.d.ts +11 -11
  203. package/dist/lib/auth/username-plugin.js +18 -17
  204. package/dist/plugins.d.ts +46 -22
  205. package/dist/plugins.js +47 -22
  206. package/package.json +25 -19
  207. package/src/components/auth/api-key/api-key.tsx +25 -16
  208. package/src/components/auth/api-key/api-keys-empty.tsx +8 -4
  209. package/src/components/auth/api-key/api-keys.tsx +52 -14
  210. package/src/components/auth/api-key/create-api-key-dialog.tsx +15 -2
  211. package/src/components/auth/api-key/delete-api-key-dialog.tsx +10 -2
  212. package/src/components/auth/api-key/organization-api-keys.tsx +55 -0
  213. package/src/components/auth/delete-user/danger-zone.tsx +3 -3
  214. package/src/components/auth/delete-user/{delete-user.tsx → delete-account.tsx} +9 -9
  215. package/src/components/auth/email/organization-invitation-email.tsx +38 -0
  216. package/src/components/auth/error-toaster.tsx +28 -10
  217. package/src/components/auth/forgot-password.tsx +10 -2
  218. package/src/components/auth/multi-session/manage-accounts.tsx +1 -1
  219. package/src/components/auth/multi-session/switch-account-submenu-content.tsx +7 -2
  220. package/src/components/auth/multi-session/switch-account-submenu-item.tsx +4 -2
  221. package/src/components/auth/multi-session/switch-account-submenu.tsx +8 -4
  222. package/src/components/auth/organization/change-organization-logo.tsx +171 -0
  223. package/src/components/auth/organization/create-organization-dialog.tsx +133 -0
  224. package/src/components/auth/organization/delete-organization-dialog.tsx +97 -0
  225. package/src/components/auth/organization/delete-organization-skeleton.tsx +17 -0
  226. package/src/components/auth/organization/delete-organization.tsx +77 -0
  227. package/src/components/auth/organization/invite-member-dialog.tsx +177 -0
  228. package/src/components/auth/organization/leave-organization-dialog.tsx +95 -0
  229. package/src/components/auth/organization/leave-organization.tsx +59 -0
  230. package/src/components/auth/organization/organization-danger-zone.tsx +68 -0
  231. package/src/components/auth/organization/organization-invitation-row-skeleton.tsx +29 -0
  232. package/src/components/auth/organization/organization-invitation-row.tsx +97 -0
  233. package/src/components/auth/organization/organization-invitations-empty.tsx +39 -0
  234. package/src/components/auth/organization/organization-invitations.tsx +351 -0
  235. package/src/components/auth/organization/organization-logo.tsx +64 -0
  236. package/src/components/auth/organization/organization-member-row-skeleton.tsx +24 -0
  237. package/src/components/auth/organization/organization-member-row.tsx +157 -0
  238. package/src/components/auth/organization/organization-members.tsx +284 -0
  239. package/src/components/auth/organization/organization-people.tsx +26 -0
  240. package/src/components/auth/organization/organization-profile.tsx +141 -0
  241. package/src/components/auth/organization/organization-row.tsx +66 -0
  242. package/src/components/auth/organization/organization-settings.tsx +38 -0
  243. package/src/components/auth/organization/organization-switcher.tsx +233 -0
  244. package/src/components/auth/organization/organization-view-skeleton.tsx +36 -0
  245. package/src/components/auth/organization/organization-view.tsx +109 -0
  246. package/src/components/auth/organization/organization.tsx +162 -0
  247. package/src/components/auth/organization/organizations-empty.tsx +35 -0
  248. package/src/components/auth/organization/organizations-settings.tsx +36 -0
  249. package/src/components/auth/organization/organizations.tsx +80 -0
  250. package/src/components/auth/organization/remove-member-dialog.tsx +95 -0
  251. package/src/components/auth/organization/slug-field.tsx +111 -0
  252. package/src/components/auth/organization/user-invitation-row-skeleton.tsx +17 -0
  253. package/src/components/auth/organization/user-invitation-row.tsx +90 -0
  254. package/src/components/auth/organization/user-invitations-empty.tsx +30 -0
  255. package/src/components/auth/organization/user-invitations.tsx +59 -0
  256. package/src/components/auth/settings/account/change-email.tsx +1 -1
  257. package/src/components/auth/settings/account/user-profile.tsx +5 -5
  258. package/src/components/auth/settings/security/active-sessions.tsx +6 -4
  259. package/src/components/auth/settings/security/linked-accounts.tsx +2 -2
  260. package/src/components/auth/settings/settings.tsx +77 -14
  261. package/src/components/auth/user/user-avatar.tsx +4 -1
  262. package/src/components/auth/user/user-button.tsx +14 -11
  263. package/src/components/auth/user/user-view.tsx +32 -12
  264. package/src/email.ts +1 -0
  265. package/src/lib/auth/api-key-plugin.ts +10 -4
  266. package/src/lib/auth/auth-plugin.ts +6 -1
  267. package/src/lib/auth/organization-plugin.tsx +32 -0
  268. package/src/plugins.ts +25 -1
  269. package/dist/components/auth/delete-user/delete-user.d.ts +0 -9
  270. package/dist/components/auth/delete-user/delete-user.js +0 -95
@@ -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
+ }
@@ -0,0 +1,233 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useActiveOrganization,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useListOrganizations,
7
+ useSession,
8
+ useSetActiveOrganization
9
+ } from "@better-auth-ui/react"
10
+ import { ChevronsExpandVertical, CirclePlus, Gear } from "@gravity-ui/icons"
11
+ import {
12
+ Button,
13
+ type ButtonProps,
14
+ cn,
15
+ Dropdown,
16
+ type DropdownPopoverProps,
17
+ Label,
18
+ Link
19
+ } from "@heroui/react"
20
+ import { buttonVariants } from "@heroui/styles"
21
+ import type { Organization } from "better-auth/client"
22
+ import { type ReactNode, useState } from "react"
23
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
24
+ import { UserView } from "../user/user-view"
25
+ import { CreateOrganizationDialog } from "./create-organization-dialog"
26
+ import { OrganizationView } from "./organization-view"
27
+
28
+ /** Props for the {@link OrganizationSwitcher} component. */
29
+ export type OrganizationSwitcherProps = {
30
+ className?: string
31
+ placement?: DropdownPopoverProps["placement"]
32
+ variant?: ButtonProps["variant"]
33
+ trigger?: ReactNode
34
+ hideCreate?: boolean
35
+ hidePersonal?: boolean
36
+ hideSettings?: boolean
37
+ hideSlug?: boolean
38
+ setActive?: (organization: Organization | null) => void
39
+ }
40
+
41
+ /**
42
+ * Renders an organizations dropdown with a trigger button,
43
+ * header summary, and a menu of organizations to switch to.
44
+ */
45
+ export function OrganizationSwitcher({
46
+ className,
47
+ hideCreate,
48
+ hidePersonal,
49
+ hideSettings,
50
+ hideSlug = true,
51
+ setActive,
52
+ placement,
53
+ variant = "ghost",
54
+ size = "md",
55
+ trigger,
56
+ ...props
57
+ }: OrganizationSwitcherProps & ButtonProps) {
58
+ const { authClient, navigate, basePaths, localization, viewPaths } = useAuth()
59
+ const { data: session, isPending: sessionPending } = useSession(authClient)
60
+ const {
61
+ localization: organizationLocalization,
62
+ viewPaths: organizationViewPaths,
63
+ slug
64
+ } = useAuthPlugin(organizationPlugin)
65
+
66
+ const { data: activeOrganization, isPending: activeOrganizationPending } =
67
+ useActiveOrganization(authClient as OrganizationAuthClient)
68
+
69
+ const { data: organizations, isPending: organizationsPending } =
70
+ useListOrganizations(authClient as OrganizationAuthClient)
71
+
72
+ const { mutate: setActiveOrganization } = useSetActiveOrganization(
73
+ authClient as OrganizationAuthClient
74
+ )
75
+
76
+ const isPending =
77
+ sessionPending ||
78
+ (!!session && (organizationsPending || activeOrganizationPending))
79
+
80
+ const [createOpen, setCreateOpen] = useState(false)
81
+ const [dropdownOpen, setDropdownOpen] = useState(false)
82
+
83
+ function handleSetActive(organization: Organization | null) {
84
+ if (setActive) {
85
+ setActive(organization)
86
+ } else if (slug !== undefined) {
87
+ navigate({
88
+ to: organization
89
+ ? `${basePaths.organization}/${organization.slug}/${organizationViewPaths.organization.settings}`
90
+ : `${basePaths.settings}/${viewPaths.settings.account}`
91
+ })
92
+ } else {
93
+ setActiveOrganization({ organizationId: organization?.id ?? null })
94
+ }
95
+ }
96
+
97
+ return (
98
+ <>
99
+ <Dropdown isOpen={dropdownOpen} onOpenChange={setDropdownOpen}>
100
+ {trigger ? (
101
+ <Dropdown.Trigger>{trigger}</Dropdown.Trigger>
102
+ ) : (
103
+ <Button
104
+ variant={variant}
105
+ className={cn("h-auto px-2 py-2 text-left", className)}
106
+ isDisabled={!session || isPending}
107
+ aria-label={organizationLocalization.organization}
108
+ {...props}
109
+ >
110
+ {isPending ? (
111
+ <OrganizationView
112
+ size={size}
113
+ isPending
114
+ hideRole
115
+ hideSlug={hideSlug}
116
+ />
117
+ ) : activeOrganization ? (
118
+ <OrganizationView size={size} hideRole hideSlug={hideSlug} />
119
+ ) : session && !hidePersonal ? (
120
+ <UserView size={size} hideSubtitle={hideSlug} />
121
+ ) : (
122
+ <OrganizationView
123
+ size={size}
124
+ hideRole
125
+ hideSlug={hideSlug}
126
+ organization={{
127
+ name: organizationLocalization.organization
128
+ }}
129
+ />
130
+ )}
131
+
132
+ <ChevronsExpandVertical className="size-3 shrink-0 text-muted" />
133
+ </Button>
134
+ )}
135
+
136
+ <Dropdown.Popover placement={placement} className="max-w-svw">
137
+ {activeOrganization ? (
138
+ <div className="flex items-center justify-between gap-4 px-4 pt-3">
139
+ <OrganizationView
140
+ hideRole
141
+ hideSlug={hideSlug}
142
+ organization={activeOrganization}
143
+ />
144
+
145
+ {!hideSettings && (
146
+ <Link
147
+ href={
148
+ slug
149
+ ? `${basePaths.organization}/${slug}/${organizationViewPaths.organization.settings}`
150
+ : `${basePaths.organization}/${organizationViewPaths.organization.settings}`
151
+ }
152
+ className={cn(
153
+ buttonVariants({ variant: "outline", size: "sm" }),
154
+ "shrink-0 gap-2"
155
+ )}
156
+ onPress={() => setDropdownOpen(false)}
157
+ >
158
+ <Gear className="text-muted" />
159
+
160
+ {organizationLocalization.manage}
161
+ </Link>
162
+ )}
163
+ </div>
164
+ ) : !isPending && session?.user && !hidePersonal ? (
165
+ <div className="flex items-center justify-between gap-4 px-4 pt-3">
166
+ <UserView hideSubtitle={hideSlug} />
167
+
168
+ {!hideSettings && (
169
+ <Link
170
+ href={`${basePaths.settings}/${viewPaths.settings.account}`}
171
+ className={cn(
172
+ buttonVariants({ variant: "outline", size: "sm" }),
173
+ "shrink-0 gap-2"
174
+ )}
175
+ onPress={() => setDropdownOpen(false)}
176
+ >
177
+ <Gear className="text-muted" />
178
+
179
+ {localization.settings.settings}
180
+ </Link>
181
+ )}
182
+ </div>
183
+ ) : null}
184
+
185
+ <Dropdown.Menu>
186
+ {!!activeOrganization && !hidePersonal && (
187
+ <Dropdown.Item
188
+ textValue={organizationLocalization.personalAccount}
189
+ onPress={() => handleSetActive(null)}
190
+ >
191
+ <UserView hideSubtitle={hideSlug} />
192
+ </Dropdown.Item>
193
+ )}
194
+
195
+ {organizations
196
+ ?.filter(
197
+ (organization) => organization.id !== activeOrganization?.id
198
+ )
199
+ ?.map((organization) => (
200
+ <Dropdown.Item
201
+ key={organization.id}
202
+ textValue={organization.name}
203
+ onPress={() => handleSetActive(organization)}
204
+ >
205
+ <OrganizationView
206
+ hideRole
207
+ hideSlug={hideSlug}
208
+ organization={organization}
209
+ />
210
+ </Dropdown.Item>
211
+ ))}
212
+
213
+ {!hideCreate && (
214
+ <Dropdown.Item
215
+ textValue={organizationLocalization.createOrganization}
216
+ onPress={() => setCreateOpen(true)}
217
+ >
218
+ <CirclePlus className="text-muted" />
219
+
220
+ <Label>{organizationLocalization.createOrganization}</Label>
221
+ </Dropdown.Item>
222
+ )}
223
+ </Dropdown.Menu>
224
+ </Dropdown.Popover>
225
+ </Dropdown>
226
+
227
+ <CreateOrganizationDialog
228
+ isOpen={createOpen}
229
+ onOpenChange={setCreateOpen}
230
+ />
231
+ </>
232
+ )
233
+ }
@@ -0,0 +1,36 @@
1
+ import { cn, Skeleton } from "@heroui/react"
2
+ import type { ComponentProps } from "react"
3
+
4
+ import { OrganizationLogo } from "./organization-logo"
5
+ import type { OrganizationViewProps } from "./organization-view"
6
+
7
+ /**
8
+ * Placeholder matching {@link OrganizationView} while organization data loads.
9
+ */
10
+ export function OrganizationViewSkeleton({
11
+ className,
12
+ hideSlug,
13
+ size = "md",
14
+ ...props
15
+ }: OrganizationViewProps & ComponentProps<"div">) {
16
+ return (
17
+ <div
18
+ className={cn("flex min-w-0 items-center gap-2", className)}
19
+ {...props}
20
+ >
21
+ <OrganizationLogo
22
+ isPending
23
+ className={size === "sm" ? "size-5" : undefined}
24
+ size={size === "lg" ? "md" : "sm"}
25
+ />
26
+
27
+ <div className="flex flex-col min-w-0 gap-1">
28
+ <Skeleton className="h-3.5 w-20 rounded-lg" />
29
+
30
+ {!hideSlug && (
31
+ <Skeleton className="h-3 w-28 rounded-lg mt-[0.5px] mb-0.5" />
32
+ )}
33
+ </div>
34
+ </div>
35
+ )
36
+ }
@@ -0,0 +1,109 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useActiveOrganization,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useListOrganizationMembers,
7
+ useSession
8
+ } from "@better-auth-ui/react"
9
+ import { type AvatarProps, Chip, cn } from "@heroui/react"
10
+ import type { Organization } from "better-auth/client"
11
+ import type { ComponentProps } from "react"
12
+
13
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
14
+ import { OrganizationLogo } from "./organization-logo"
15
+ import { OrganizationViewSkeleton } from "./organization-view-skeleton"
16
+
17
+ export type OrganizationViewProps = {
18
+ className?: string
19
+ isPending?: boolean
20
+ size?: AvatarProps["size"]
21
+ hideRole?: boolean
22
+ hideSlug?: boolean
23
+ organization?: Partial<Organization>
24
+ }
25
+
26
+ /**
27
+ * Compact organization row: logo, primary name, secondary slug — analogous to {@link UserView}.
28
+ */
29
+ export function OrganizationView({
30
+ className,
31
+ isPending,
32
+ size = "md",
33
+ hideSlug,
34
+ hideRole,
35
+ organization,
36
+ ...props
37
+ }: OrganizationViewProps & ComponentProps<"div">) {
38
+ const { authClient } = useAuth()
39
+ const { roles } = useAuthPlugin(organizationPlugin)
40
+
41
+ const { data: session } = useSession(authClient)
42
+
43
+ const { data: activeOrganization, isPending: activeOrganizationPending } =
44
+ useActiveOrganization(authClient as OrganizationAuthClient, {
45
+ enabled: !organization && !isPending
46
+ })
47
+
48
+ const resolvedOrganization = organization ?? activeOrganization
49
+
50
+ const { data: membersList, isPending: membersPending } =
51
+ useListOrganizationMembers(authClient as OrganizationAuthClient, {
52
+ query: {
53
+ organizationId: resolvedOrganization?.id
54
+ },
55
+ enabled: !!resolvedOrganization?.id && !hideRole
56
+ })
57
+
58
+ const membership = membersList?.members?.find(
59
+ (member) => member.userId === session?.user.id
60
+ )
61
+
62
+ if (
63
+ isPending ||
64
+ (!organization && activeOrganizationPending) ||
65
+ (!hideRole && !!resolvedOrganization?.id && membersPending)
66
+ ) {
67
+ return (
68
+ <OrganizationViewSkeleton
69
+ className={className}
70
+ hideSlug={hideSlug}
71
+ size={size}
72
+ {...props}
73
+ />
74
+ )
75
+ }
76
+
77
+ return (
78
+ <div
79
+ className={cn("flex min-w-0 items-center gap-2", className)}
80
+ {...props}
81
+ >
82
+ <OrganizationLogo
83
+ organization={resolvedOrganization}
84
+ className={size === "sm" ? "size-5 [&>span]:text-xs" : undefined}
85
+ size={size === "lg" ? "md" : "sm"}
86
+ />
87
+
88
+ <div className="flex flex-col min-w-0">
89
+ <div className="flex min-w-0 items-center gap-2">
90
+ <p className="text-foreground text-sm font-medium truncate leading-tight">
91
+ {resolvedOrganization?.name}
92
+ </p>
93
+
94
+ {!hideRole && !!membership && (
95
+ <Chip className="shrink-0 -my-0.5" size="sm">
96
+ {roles?.[membership.role] ?? membership.role}
97
+ </Chip>
98
+ )}
99
+ </div>
100
+
101
+ {!hideSlug && (
102
+ <p className="text-muted text-xs truncate overflow-x-hidden font-mono leading-tight">
103
+ {resolvedOrganization?.slug}
104
+ </p>
105
+ )}
106
+ </div>
107
+ </div>
108
+ )
109
+ }
@@ -0,0 +1,162 @@
1
+ import type { OrganizationView } from "@better-auth-ui/core/plugins"
2
+ import {
3
+ type OrganizationAuthClient,
4
+ useActiveOrganization,
5
+ useAuth,
6
+ useAuthenticate,
7
+ useAuthPlugin
8
+ } from "@better-auth-ui/react"
9
+ import { Gear, Person } from "@gravity-ui/icons"
10
+ import { type CardProps, cn, Tabs } from "@heroui/react"
11
+ import { type ComponentProps, useEffect, useMemo } from "react"
12
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
13
+ import { OrganizationPeople } from "./organization-people"
14
+ import { OrganizationSettings } from "./organization-settings"
15
+
16
+ export type OrganizationProps = {
17
+ className?: string
18
+ hideNav?: boolean
19
+ path?: string
20
+ variant?: CardProps["variant"]
21
+ /** @remarks `OrganizationView` */
22
+ view?: OrganizationView
23
+ }
24
+
25
+ /**
26
+ * Organization management shell: tabs for profile / danger zone and for
27
+ * people (members / invitations). Path segments come from
28
+ * `useAuthPlugin(organizationPlugin).viewPaths.organization`.
29
+ */
30
+ export function Organization({
31
+ className,
32
+ hideNav,
33
+ path,
34
+ variant,
35
+ view,
36
+ ...props
37
+ }: OrganizationProps & ComponentProps<"div">) {
38
+ if (!view && !path) {
39
+ throw new Error("[Better Auth UI] Either `view` or `path` must be provided")
40
+ }
41
+
42
+ const { authClient, basePaths, localization, navigate } = useAuth()
43
+ useAuthenticate(authClient)
44
+
45
+ const {
46
+ localization: organizationLocalization,
47
+ viewPaths: organizationViewPaths,
48
+ slug
49
+ } = useAuthPlugin(organizationPlugin)
50
+
51
+ const { data: activeOrganization, isPending } = useActiveOrganization(
52
+ authClient as OrganizationAuthClient
53
+ )
54
+
55
+ useEffect(() => {
56
+ if (!isPending && !activeOrganization) {
57
+ navigate({
58
+ to: `${basePaths.settings}/${organizationViewPaths.settings?.organizations}`,
59
+ replace: true
60
+ })
61
+ }
62
+ }, [
63
+ basePaths.settings,
64
+ isPending,
65
+ navigate,
66
+ organizationViewPaths.settings?.organizations,
67
+ activeOrganization
68
+ ])
69
+
70
+ const currentView = useMemo(() => {
71
+ if (view) return view
72
+
73
+ const match = Object.entries(organizationViewPaths.organization).find(
74
+ ([, segment]) => segment === path
75
+ )
76
+
77
+ return match?.[0] as OrganizationView | undefined
78
+ }, [view, path, organizationViewPaths.organization])
79
+
80
+ if (!currentView) {
81
+ const validPaths = Object.values(organizationViewPaths.organization).join(
82
+ ", "
83
+ )
84
+ throw new Error(
85
+ `[Better Auth UI] Unknown organization path "${path}". Valid paths are: ${validPaths}`
86
+ )
87
+ }
88
+
89
+ if (!isPending && !activeOrganization) {
90
+ return null
91
+ }
92
+
93
+ return (
94
+ <Tabs
95
+ className={cn(className)}
96
+ orientation="horizontal"
97
+ selectedKey={currentView}
98
+ {...props}
99
+ >
100
+ {!hideNav && (
101
+ <Tabs.ListContainer>
102
+ <Tabs.List
103
+ aria-label={localization.settings.settings}
104
+ className="max-w-fit overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
105
+ >
106
+ <Tabs.Tab
107
+ id="settings"
108
+ href={
109
+ slug
110
+ ? `${basePaths.organization}/${slug}/${organizationViewPaths.organization.settings}`
111
+ : `${basePaths.organization}/${organizationViewPaths.organization.settings}`
112
+ }
113
+ className="gap-2"
114
+ onPress={(e) =>
115
+ e.target.scrollIntoView({
116
+ behavior: "smooth",
117
+ inline: "center"
118
+ })
119
+ }
120
+ >
121
+ <Gear className="text-muted" />
122
+
123
+ {localization.settings.settings}
124
+
125
+ <Tabs.Indicator />
126
+ </Tabs.Tab>
127
+
128
+ <Tabs.Tab
129
+ id="people"
130
+ href={
131
+ slug
132
+ ? `${basePaths.organization}/${slug}/${organizationViewPaths.organization.people}`
133
+ : `${basePaths.organization}/${organizationViewPaths.organization.people}`
134
+ }
135
+ className="gap-2"
136
+ onPress={(e) =>
137
+ e.target.scrollIntoView({
138
+ behavior: "smooth",
139
+ inline: "center"
140
+ })
141
+ }
142
+ >
143
+ <Person className="text-muted" />
144
+
145
+ {organizationLocalization.people}
146
+
147
+ <Tabs.Indicator />
148
+ </Tabs.Tab>
149
+ </Tabs.List>
150
+ </Tabs.ListContainer>
151
+ )}
152
+
153
+ <Tabs.Panel id="settings" className="px-0">
154
+ <OrganizationSettings variant={variant} />
155
+ </Tabs.Panel>
156
+
157
+ <Tabs.Panel id="people" className="px-0">
158
+ <OrganizationPeople />
159
+ </Tabs.Panel>
160
+ </Tabs>
161
+ )
162
+ }
@@ -0,0 +1,35 @@
1
+ import { useAuthPlugin } from "@better-auth-ui/react"
2
+ import { Briefcase } from "@gravity-ui/icons"
3
+ import { Button, EmptyState } from "@heroui/react"
4
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
5
+
6
+ export type OrganizationsEmptyProps = {
7
+ onCreatePress: () => void
8
+ }
9
+
10
+ export function OrganizationsEmpty({ onCreatePress }: OrganizationsEmptyProps) {
11
+ const { localization: organizationLocalization } =
12
+ useAuthPlugin(organizationPlugin)
13
+
14
+ return (
15
+ <EmptyState className="flex flex-col items-center gap-4 text-center">
16
+ <div className="flex size-12 items-center justify-center rounded-full bg-surface-secondary">
17
+ <Briefcase className="size-5" />
18
+ </div>
19
+
20
+ <div className="flex flex-col gap-2">
21
+ <p className="text-sm font-semibold text-foreground">
22
+ {organizationLocalization.noOrganizations}
23
+ </p>
24
+
25
+ <span className="text-sm text-muted">
26
+ {organizationLocalization.organizationsDescription}
27
+ </span>
28
+ </div>
29
+
30
+ <Button size="sm" onPress={onCreatePress}>
31
+ {organizationLocalization.createOrganization}
32
+ </Button>
33
+ </EmptyState>
34
+ )
35
+ }
@@ -0,0 +1,36 @@
1
+ import { type CardProps, cn } from "@heroui/react"
2
+ import type { ComponentProps } from "react"
3
+
4
+ import { Organizations } from "./organizations"
5
+ import { UserInvitations } from "./user-invitations"
6
+
7
+ export type OrganizationsSettingsProps = {
8
+ className?: string
9
+ variant?: CardProps["variant"]
10
+ }
11
+
12
+ /**
13
+ * Renders the organizations settings panel.
14
+ *
15
+ * Displays all organizations the user belongs to with an empty state and
16
+ * create button, followed by a card for invitations to the user.
17
+ *
18
+ * @param className - Optional additional CSS class names for the outer container.
19
+ * @param variant - Card variant forwarded to each card.
20
+ * @returns The organizations settings UI as a JSX element.
21
+ */
22
+ export function OrganizationsSettings({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: OrganizationsSettingsProps & ComponentProps<"div">) {
27
+ return (
28
+ <div
29
+ className={cn("flex w-full flex-col gap-4 md:gap-6", className)}
30
+ {...props}
31
+ >
32
+ <Organizations variant={variant} />
33
+ <UserInvitations variant={variant} />
34
+ </div>
35
+ )
36
+ }