@better-auth-ui/heroui 1.6.7 → 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 (280) 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.d.ts +5 -0
  129. package/dist/components/auth/passkey/add-passkey-dialog.js +22 -0
  130. package/dist/components/auth/passkey/delete-passkey-dialog.d.ts +11 -0
  131. package/dist/components/auth/passkey/delete-passkey-dialog.js +14 -0
  132. package/dist/components/auth/passkey/passkey-button.d.ts +1 -1
  133. package/dist/components/auth/passkey/passkey-button.js +24 -23
  134. package/dist/components/auth/passkey/passkey-skeleton.d.ts +1 -0
  135. package/dist/components/auth/passkey/passkey-skeleton.js +5 -0
  136. package/dist/components/auth/passkey/passkey.d.ts +2 -5
  137. package/dist/components/auth/passkey/passkey.js +16 -44
  138. package/dist/components/auth/passkey/passkeys-empty.d.ts +4 -0
  139. package/dist/components/auth/passkey/passkeys-empty.js +9 -0
  140. package/dist/components/auth/passkey/passkeys.d.ts +1 -1
  141. package/dist/components/auth/passkey/passkeys.js +15 -105
  142. package/dist/components/auth/provider-button.d.ts +2 -2
  143. package/dist/components/auth/provider-button.js +28 -24
  144. package/dist/components/auth/provider-buttons.js +28 -18
  145. package/dist/components/auth/reset-password.d.ts +1 -1
  146. package/dist/components/auth/reset-password.js +56 -146
  147. package/dist/components/auth/settings/account/account-settings.d.ts +2 -2
  148. package/dist/components/auth/settings/account/account-settings.js +21 -19
  149. package/dist/components/auth/settings/account/change-avatar.js +53 -88
  150. package/dist/components/auth/settings/account/change-email.d.ts +1 -1
  151. package/dist/components/auth/settings/account/change-email.js +27 -53
  152. package/dist/components/auth/settings/account/user-profile.d.ts +1 -1
  153. package/dist/components/auth/settings/account/user-profile.js +64 -85
  154. package/dist/components/auth/settings/security/active-session.d.ts +1 -1
  155. package/dist/components/auth/settings/security/active-session.js +50 -57
  156. package/dist/components/auth/settings/security/active-sessions.d.ts +1 -1
  157. package/dist/components/auth/settings/security/active-sessions.js +21 -32
  158. package/dist/components/auth/settings/security/change-password.d.ts +1 -1
  159. package/dist/components/auth/settings/security/change-password.js +76 -186
  160. package/dist/components/auth/settings/security/linked-account.d.ts +1 -1
  161. package/dist/components/auth/settings/security/linked-account.js +37 -55
  162. package/dist/components/auth/settings/security/linked-accounts.d.ts +1 -1
  163. package/dist/components/auth/settings/security/linked-accounts.js +34 -43
  164. package/dist/components/auth/settings/security/security-settings.d.ts +2 -2
  165. package/dist/components/auth/settings/security/security-settings.js +19 -21
  166. package/dist/components/auth/settings/settings.d.ts +4 -4
  167. package/dist/components/auth/settings/settings.js +52 -44
  168. package/dist/components/auth/sign-in.d.ts +2 -2
  169. package/dist/components/auth/sign-in.js +63 -132
  170. package/dist/components/auth/sign-out.js +32 -26
  171. package/dist/components/auth/sign-up.d.ts +2 -2
  172. package/dist/components/auth/sign-up.js +97 -211
  173. package/dist/components/auth/theme/appearance.d.ts +1 -1
  174. package/dist/components/auth/theme/appearance.js +23 -71
  175. package/dist/components/auth/theme/theme-toggle-item.js +56 -61
  176. package/dist/components/auth/user/user-avatar.d.ts +4 -4
  177. package/dist/components/auth/user/user-avatar.js +31 -28
  178. package/dist/components/auth/user/user-button.d.ts +6 -7
  179. package/dist/components/auth/user/user-button.js +38 -72
  180. package/dist/components/auth/user/user-view.d.ts +13 -7
  181. package/dist/components/auth/user/user-view.js +26 -36
  182. package/dist/components/auth/username/sign-in-username.d.ts +2 -2
  183. package/dist/components/auth/username/sign-in-username.js +87 -147
  184. package/dist/components/auth/username/username-field.d.ts +1 -1
  185. package/dist/components/auth/username/username-field.js +44 -52
  186. package/dist/email.d.ts +8 -7
  187. package/dist/email.js +8 -8
  188. package/dist/index.d.ts +21 -21
  189. package/dist/index.js +21 -22
  190. package/dist/lib/auth/api-key-plugin.d.ts +7 -4
  191. package/dist/lib/auth/api-key-plugin.js +12 -10
  192. package/dist/lib/auth/auth-plugin.d.ts +10 -6
  193. package/dist/lib/auth/auth-plugin.js +1 -0
  194. package/dist/lib/auth/delete-user-plugin.d.ts +6 -6
  195. package/dist/lib/auth/delete-user-plugin.js +6 -9
  196. package/dist/lib/auth/magic-link-plugin.d.ts +5 -5
  197. package/dist/lib/auth/magic-link-plugin.js +17 -12
  198. package/dist/lib/auth/multi-session-plugin.d.ts +5 -5
  199. package/dist/lib/auth/multi-session-plugin.js +8 -11
  200. package/dist/lib/auth/organization-plugin.d.ts +37 -0
  201. package/dist/lib/auth/organization-plugin.js +19 -0
  202. package/dist/lib/auth/passkey-plugin.d.ts +9 -6
  203. package/dist/lib/auth/passkey-plugin.js +8 -11
  204. package/dist/lib/auth/theme-plugin.d.ts +6 -6
  205. package/dist/lib/auth/theme-plugin.js +25 -22
  206. package/dist/lib/auth/username-plugin.d.ts +11 -11
  207. package/dist/lib/auth/username-plugin.js +18 -17
  208. package/dist/plugins.d.ts +46 -22
  209. package/dist/plugins.js +47 -22
  210. package/package.json +25 -19
  211. package/src/components/auth/api-key/api-key.tsx +25 -16
  212. package/src/components/auth/api-key/api-keys-empty.tsx +8 -4
  213. package/src/components/auth/api-key/api-keys.tsx +52 -14
  214. package/src/components/auth/api-key/create-api-key-dialog.tsx +17 -4
  215. package/src/components/auth/api-key/delete-api-key-dialog.tsx +10 -2
  216. package/src/components/auth/api-key/organization-api-keys.tsx +55 -0
  217. package/src/components/auth/delete-user/danger-zone.tsx +3 -3
  218. package/src/components/auth/delete-user/{delete-user.tsx → delete-account.tsx} +9 -9
  219. package/src/components/auth/email/organization-invitation-email.tsx +38 -0
  220. package/src/components/auth/error-toaster.tsx +28 -10
  221. package/src/components/auth/forgot-password.tsx +10 -2
  222. package/src/components/auth/multi-session/manage-accounts.tsx +1 -1
  223. package/src/components/auth/multi-session/switch-account-submenu-content.tsx +7 -2
  224. package/src/components/auth/multi-session/switch-account-submenu-item.tsx +4 -2
  225. package/src/components/auth/multi-session/switch-account-submenu.tsx +8 -4
  226. package/src/components/auth/organization/change-organization-logo.tsx +171 -0
  227. package/src/components/auth/organization/create-organization-dialog.tsx +133 -0
  228. package/src/components/auth/organization/delete-organization-dialog.tsx +97 -0
  229. package/src/components/auth/organization/delete-organization-skeleton.tsx +17 -0
  230. package/src/components/auth/organization/delete-organization.tsx +77 -0
  231. package/src/components/auth/organization/invite-member-dialog.tsx +177 -0
  232. package/src/components/auth/organization/leave-organization-dialog.tsx +95 -0
  233. package/src/components/auth/organization/leave-organization.tsx +59 -0
  234. package/src/components/auth/organization/organization-danger-zone.tsx +68 -0
  235. package/src/components/auth/organization/organization-invitation-row-skeleton.tsx +29 -0
  236. package/src/components/auth/organization/organization-invitation-row.tsx +97 -0
  237. package/src/components/auth/organization/organization-invitations-empty.tsx +39 -0
  238. package/src/components/auth/organization/organization-invitations.tsx +351 -0
  239. package/src/components/auth/organization/organization-logo.tsx +64 -0
  240. package/src/components/auth/organization/organization-member-row-skeleton.tsx +24 -0
  241. package/src/components/auth/organization/organization-member-row.tsx +157 -0
  242. package/src/components/auth/organization/organization-members.tsx +284 -0
  243. package/src/components/auth/organization/organization-people.tsx +26 -0
  244. package/src/components/auth/organization/organization-profile.tsx +141 -0
  245. package/src/components/auth/organization/organization-row.tsx +66 -0
  246. package/src/components/auth/organization/organization-settings.tsx +38 -0
  247. package/src/components/auth/organization/organization-switcher.tsx +233 -0
  248. package/src/components/auth/organization/organization-view-skeleton.tsx +36 -0
  249. package/src/components/auth/organization/organization-view.tsx +109 -0
  250. package/src/components/auth/organization/organization.tsx +162 -0
  251. package/src/components/auth/organization/organizations-empty.tsx +35 -0
  252. package/src/components/auth/organization/organizations-settings.tsx +36 -0
  253. package/src/components/auth/organization/organizations.tsx +80 -0
  254. package/src/components/auth/organization/remove-member-dialog.tsx +95 -0
  255. package/src/components/auth/organization/slug-field.tsx +111 -0
  256. package/src/components/auth/organization/user-invitation-row-skeleton.tsx +17 -0
  257. package/src/components/auth/organization/user-invitation-row.tsx +90 -0
  258. package/src/components/auth/organization/user-invitations-empty.tsx +30 -0
  259. package/src/components/auth/organization/user-invitations.tsx +59 -0
  260. package/src/components/auth/passkey/add-passkey-dialog.tsx +109 -0
  261. package/src/components/auth/passkey/delete-passkey-dialog.tsx +95 -0
  262. package/src/components/auth/passkey/passkey-skeleton.tsx +16 -0
  263. package/src/components/auth/passkey/passkey.tsx +23 -22
  264. package/src/components/auth/passkey/passkeys-empty.tsx +35 -0
  265. package/src/components/auth/passkey/passkeys.tsx +33 -154
  266. package/src/components/auth/settings/account/change-email.tsx +1 -1
  267. package/src/components/auth/settings/account/user-profile.tsx +5 -5
  268. package/src/components/auth/settings/security/active-sessions.tsx +6 -4
  269. package/src/components/auth/settings/security/linked-accounts.tsx +2 -2
  270. package/src/components/auth/settings/settings.tsx +77 -14
  271. package/src/components/auth/user/user-avatar.tsx +4 -1
  272. package/src/components/auth/user/user-button.tsx +14 -11
  273. package/src/components/auth/user/user-view.tsx +32 -12
  274. package/src/email.ts +1 -0
  275. package/src/lib/auth/api-key-plugin.ts +10 -4
  276. package/src/lib/auth/auth-plugin.ts +6 -1
  277. package/src/lib/auth/organization-plugin.tsx +32 -0
  278. package/src/plugins.ts +25 -1
  279. package/dist/components/auth/delete-user/delete-user.d.ts +0 -9
  280. package/dist/components/auth/delete-user/delete-user.js +0 -95
@@ -0,0 +1,97 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useCancelInvitation,
6
+ useHasPermission
7
+ } from "@better-auth-ui/react"
8
+ import { Xmark } from "@gravity-ui/icons"
9
+ import { Button, Chip, Spinner, Table } from "@heroui/react"
10
+
11
+ import type { Invitation } from "better-auth/client"
12
+
13
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
14
+ import { OrganizationInvitationRowSkeleton } from "./organization-invitation-row-skeleton"
15
+
16
+ export type OrganizationInvitationTableRowProps = {
17
+ invitation: Invitation
18
+ }
19
+
20
+ export function OrganizationInvitationTableRow({
21
+ invitation
22
+ }: OrganizationInvitationTableRowProps) {
23
+ const { authClient } = useAuth()
24
+ const { localization: organizationLocalization, roles } =
25
+ useAuthPlugin(organizationPlugin)
26
+
27
+ const {
28
+ data: cancelInvitationPermission,
29
+ isPending: cancelPermissionPending
30
+ } = useHasPermission(authClient as OrganizationAuthClient, {
31
+ permissions: { invitation: ["cancel"] }
32
+ })
33
+
34
+ const { mutate: cancelInvitation, isPending: cancelPending } =
35
+ useCancelInvitation(authClient as OrganizationAuthClient)
36
+
37
+ const roleLabel = roles?.[invitation.role] ?? invitation.role
38
+
39
+ const statusLabel =
40
+ organizationLocalization[invitation.status] ?? invitation.status
41
+
42
+ const statusColor =
43
+ invitation.status === "pending"
44
+ ? "warning"
45
+ : invitation.status === "accepted"
46
+ ? "success"
47
+ : invitation.status === "rejected"
48
+ ? "danger"
49
+ : "default"
50
+
51
+ if (cancelPermissionPending) {
52
+ return <OrganizationInvitationRowSkeleton />
53
+ }
54
+
55
+ return (
56
+ <Table.Row>
57
+ <Table.Cell className="font-medium text-sm">
58
+ {invitation.email}
59
+ </Table.Cell>
60
+
61
+ <Table.Cell className="text-muted text-xs tabular-nums whitespace-nowrap">
62
+ {new Date(invitation.createdAt).toLocaleString(undefined, {
63
+ dateStyle: "short",
64
+ timeStyle: "short"
65
+ })}
66
+ </Table.Cell>
67
+
68
+ <Table.Cell className="text-sm">{roleLabel}</Table.Cell>
69
+
70
+ <Table.Cell className="text-sm">
71
+ <Chip color={statusColor} size="sm" variant="soft">
72
+ {statusLabel}
73
+ </Chip>
74
+ </Table.Cell>
75
+
76
+ <Table.Cell className="text-end">
77
+ {cancelInvitationPermission?.success &&
78
+ invitation.status === "pending" && (
79
+ <Button
80
+ isIconOnly
81
+ size="sm"
82
+ variant="danger-soft"
83
+ isPending={cancelPending}
84
+ onPress={() => cancelInvitation({ invitationId: invitation.id })}
85
+ aria-label={organizationLocalization.cancelInvitation}
86
+ >
87
+ {cancelPending ? (
88
+ <Spinner color="current" size="sm" />
89
+ ) : (
90
+ <Xmark />
91
+ )}
92
+ </Button>
93
+ )}
94
+ </Table.Cell>
95
+ </Table.Row>
96
+ )
97
+ }
@@ -0,0 +1,39 @@
1
+ import { useAuthPlugin } from "@better-auth-ui/react"
2
+ import { PaperPlane } from "@gravity-ui/icons"
3
+ import { Button, EmptyState } from "@heroui/react"
4
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
5
+
6
+ export type OrganizationInvitationsEmptyProps = {
7
+ onInvitePress: () => void
8
+ }
9
+
10
+ /**
11
+ * Empty state for {@link OrganizationInvitations} — mirrors `ApiKeysEmpty`
12
+ * (icon, title, description) with an "Invite member" call to action.
13
+ */
14
+ export function OrganizationInvitationsEmpty({
15
+ onInvitePress
16
+ }: OrganizationInvitationsEmptyProps) {
17
+ const { localization: organizationLocalization } =
18
+ useAuthPlugin(organizationPlugin)
19
+
20
+ return (
21
+ <EmptyState className="flex flex-col items-center gap-4 text-center p-4">
22
+ <PaperPlane className="size-6 text-muted" />
23
+
24
+ <div className="flex flex-col gap-2">
25
+ <p className="text-sm font-semibold text-foreground">
26
+ {organizationLocalization.noInvitations}
27
+ </p>
28
+
29
+ <span className="text-sm text-muted">
30
+ {organizationLocalization.organizationInvitationsEmptyDescription}
31
+ </span>
32
+ </div>
33
+
34
+ <Button size="sm" onPress={onInvitePress}>
35
+ {organizationLocalization.inviteMember}
36
+ </Button>
37
+ </EmptyState>
38
+ )
39
+ }
@@ -0,0 +1,351 @@
1
+ import type { OrganizationLocalization } from "@better-auth-ui/core/plugins"
2
+ import {
3
+ type OrganizationAuthClient,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useHasPermission,
7
+ useListOrganizationInvitations
8
+ } from "@better-auth-ui/react"
9
+ import { ChevronUp, Funnel, Xmark } from "@gravity-ui/icons"
10
+ import {
11
+ Button,
12
+ Chip,
13
+ cn,
14
+ Dropdown,
15
+ Label,
16
+ SearchField,
17
+ type SortDescriptor,
18
+ Table
19
+ } from "@heroui/react"
20
+ import { type ComponentProps, type ReactNode, useMemo, useState } from "react"
21
+
22
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
23
+ import { InviteMemberDialog } from "./invite-member-dialog"
24
+ import { OrganizationInvitationTableRow } from "./organization-invitation-row"
25
+ import { OrganizationInvitationRowSkeleton } from "./organization-invitation-row-skeleton"
26
+ import { OrganizationInvitationsEmpty } from "./organization-invitations-empty"
27
+
28
+ /** Props for the {@link OrganizationInvitations} component. */
29
+ export type OrganizationInvitationsProps = {
30
+ className?: string
31
+ }
32
+
33
+ /**
34
+ * Organization invitations table with invite control and per-row actions.
35
+ */
36
+ export function OrganizationInvitations({
37
+ className,
38
+ ...props
39
+ }: OrganizationInvitationsProps & ComponentProps<"div">) {
40
+ const { authClient, localization } = useAuth()
41
+ const { localization: organizationLocalization, roles } =
42
+ useAuthPlugin(organizationPlugin)
43
+
44
+ const { data: invitations, isPending: invitationsPending } =
45
+ useListOrganizationInvitations(authClient as OrganizationAuthClient)
46
+
47
+ const { isPending: invitationPermissionPending } = useHasPermission(
48
+ authClient as OrganizationAuthClient,
49
+ {
50
+ permissions: { invitation: ["cancel"] }
51
+ }
52
+ )
53
+
54
+ const isPending = invitationsPending || invitationPermissionPending
55
+
56
+ const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>()
57
+ const [roleFilter, setRoleFilter] = useState("all")
58
+ const [statusFilter, setStatusFilter] = useState("all")
59
+ const [search, setSearch] = useState("")
60
+
61
+ const filteredInvitations = useMemo(() => {
62
+ return invitations?.filter(
63
+ (invitation) =>
64
+ (roleFilter === "all" || invitation.role === roleFilter) &&
65
+ (statusFilter === "all" || invitation.status === statusFilter) &&
66
+ invitation.email.toLowerCase().includes(search.toLowerCase())
67
+ )
68
+ }, [search, invitations, roleFilter, statusFilter])
69
+
70
+ const sortedInvitations = useMemo(() => {
71
+ if (!sortDescriptor) return filteredInvitations
72
+ if (!filteredInvitations) return filteredInvitations
73
+
74
+ return [...filteredInvitations].sort((a, b) => {
75
+ const col = sortDescriptor.column as keyof typeof a
76
+ let cmp = 0
77
+
78
+ if (col === "createdAt") {
79
+ cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
80
+ } else {
81
+ cmp = String(a[col]).localeCompare(String(b[col]))
82
+ }
83
+
84
+ if (sortDescriptor.direction === "descending") {
85
+ cmp *= -1
86
+ }
87
+
88
+ return cmp
89
+ })
90
+ }, [sortDescriptor, filteredInvitations])
91
+
92
+ const [inviteOpen, setInviteOpen] = useState(false)
93
+
94
+ return (
95
+ <div className={cn("flex flex-col gap-3", className)} {...props}>
96
+ <h3 className="truncate text-sm font-semibold">
97
+ {organizationLocalization.invitations}
98
+ </h3>
99
+
100
+ <div className="flex flex-col gap-4">
101
+ <div className="flex items-center gap-3">
102
+ <SearchField
103
+ className="min-w-0"
104
+ aria-label={organizationLocalization.search}
105
+ value={search}
106
+ onChange={setSearch}
107
+ isDisabled={isPending}
108
+ >
109
+ <SearchField.Group>
110
+ <SearchField.SearchIcon />
111
+
112
+ <SearchField.Input
113
+ placeholder={organizationLocalization.search}
114
+ className="sm:w-[200px]"
115
+ />
116
+
117
+ <SearchField.ClearButton />
118
+ </SearchField.Group>
119
+ </SearchField>
120
+
121
+ <Dropdown>
122
+ <Button size="sm" variant="secondary" isDisabled={isPending}>
123
+ <Funnel />
124
+
125
+ {organizationLocalization.role}
126
+ </Button>
127
+
128
+ <Dropdown.Popover>
129
+ <Dropdown.Menu
130
+ selectionMode="single"
131
+ selectedKeys={new Set([roleFilter])}
132
+ onSelectionChange={(keys) => {
133
+ const key = [...keys][0] as string | undefined
134
+ setRoleFilter(key ?? "all")
135
+ }}
136
+ >
137
+ <Dropdown.Item
138
+ id="all"
139
+ textValue={organizationLocalization.all}
140
+ >
141
+ <Label>{organizationLocalization.all}</Label>
142
+
143
+ <Dropdown.ItemIndicator />
144
+ </Dropdown.Item>
145
+
146
+ {Object.entries(roles).map(([key, label]) => (
147
+ <Dropdown.Item key={key} id={key} textValue={label}>
148
+ <Label>{label}</Label>
149
+
150
+ <Dropdown.ItemIndicator />
151
+ </Dropdown.Item>
152
+ ))}
153
+ </Dropdown.Menu>
154
+ </Dropdown.Popover>
155
+ </Dropdown>
156
+
157
+ <Dropdown>
158
+ <Button size="sm" variant="secondary" isDisabled={isPending}>
159
+ <Funnel />
160
+
161
+ {organizationLocalization.status}
162
+ </Button>
163
+
164
+ <Dropdown.Popover>
165
+ <Dropdown.Menu
166
+ selectionMode="single"
167
+ selectedKeys={new Set([statusFilter])}
168
+ onSelectionChange={(keys) => {
169
+ const key = [...keys][0] as string | undefined
170
+ setStatusFilter(key ?? "all")
171
+ }}
172
+ >
173
+ <Dropdown.Item
174
+ id="all"
175
+ textValue={organizationLocalization.all}
176
+ >
177
+ <Label>{organizationLocalization.all}</Label>
178
+
179
+ <Dropdown.ItemIndicator />
180
+ </Dropdown.Item>
181
+
182
+ {["pending", "accepted", "rejected", "canceled"].map(
183
+ (status) => (
184
+ <Dropdown.Item
185
+ key={status}
186
+ id={status}
187
+ textValue={
188
+ organizationLocalization[
189
+ status as keyof OrganizationLocalization
190
+ ] ?? status
191
+ }
192
+ >
193
+ <Label>
194
+ {organizationLocalization[
195
+ status as keyof OrganizationLocalization
196
+ ] ?? status}
197
+ </Label>
198
+
199
+ <Dropdown.ItemIndicator />
200
+ </Dropdown.Item>
201
+ )
202
+ )}
203
+ </Dropdown.Menu>
204
+ </Dropdown.Popover>
205
+ </Dropdown>
206
+ </div>
207
+
208
+ {(roleFilter !== "all" || statusFilter !== "all") && (
209
+ <div className="flex flex-wrap gap-2">
210
+ {roleFilter !== "all" && (
211
+ <Chip size="sm" variant="secondary" className="w-fit">
212
+ <Chip.Label>
213
+ {organizationLocalization.role}:{" "}
214
+ <span className="capitalize">
215
+ {roles?.[roleFilter] ?? roleFilter}
216
+ </span>
217
+ </Chip.Label>
218
+
219
+ <button
220
+ type="button"
221
+ aria-label={organizationLocalization.clear}
222
+ className="text-muted hover:text-foreground inline-flex cursor-pointer items-center"
223
+ onClick={() => setRoleFilter("all")}
224
+ >
225
+ <Xmark className="size-3" />
226
+ </button>
227
+ </Chip>
228
+ )}
229
+
230
+ {statusFilter !== "all" && (
231
+ <Chip size="sm" variant="secondary" className="w-fit">
232
+ <Chip.Label>
233
+ {organizationLocalization.status}:{" "}
234
+ {organizationLocalization[
235
+ statusFilter as keyof OrganizationLocalization
236
+ ] ?? statusFilter}
237
+ </Chip.Label>
238
+
239
+ <button
240
+ type="button"
241
+ aria-label={organizationLocalization.clear}
242
+ className="text-muted hover:text-foreground inline-flex cursor-pointer items-center"
243
+ onClick={() => setStatusFilter("all")}
244
+ >
245
+ <Xmark className="size-3" />
246
+ </button>
247
+ </Chip>
248
+ )}
249
+ </div>
250
+ )}
251
+
252
+ <Table>
253
+ <Table.ScrollContainer>
254
+ <Table.Content
255
+ aria-label={organizationLocalization.invitations}
256
+ sortDescriptor={sortDescriptor}
257
+ onSortChange={(descriptor) => {
258
+ const shouldReset =
259
+ sortDescriptor?.column === descriptor.column &&
260
+ descriptor.direction === "ascending"
261
+ setSortDescriptor(shouldReset ? undefined : descriptor)
262
+ }}
263
+ >
264
+ <Table.Header>
265
+ <Table.Column allowsSorting isRowHeader id="email">
266
+ {({ sortDirection }) => (
267
+ <SortableColumnHeader sortDirection={sortDirection}>
268
+ {localization.auth.email}
269
+ </SortableColumnHeader>
270
+ )}
271
+ </Table.Column>
272
+
273
+ <Table.Column allowsSorting id="createdAt">
274
+ {({ sortDirection }) => (
275
+ <SortableColumnHeader sortDirection={sortDirection}>
276
+ {organizationLocalization.invitedAt}
277
+ </SortableColumnHeader>
278
+ )}
279
+ </Table.Column>
280
+
281
+ <Table.Column allowsSorting id="role">
282
+ {({ sortDirection }) => (
283
+ <SortableColumnHeader sortDirection={sortDirection}>
284
+ {organizationLocalization.role}
285
+ </SortableColumnHeader>
286
+ )}
287
+ </Table.Column>
288
+
289
+ <Table.Column allowsSorting id="status">
290
+ {({ sortDirection }) => (
291
+ <SortableColumnHeader sortDirection={sortDirection}>
292
+ {organizationLocalization.status}
293
+ </SortableColumnHeader>
294
+ )}
295
+ </Table.Column>
296
+
297
+ <Table.Column className="text-end">
298
+ {organizationLocalization.actions}
299
+ </Table.Column>
300
+ </Table.Header>
301
+
302
+ <Table.Body
303
+ renderEmptyState={() => (
304
+ <OrganizationInvitationsEmpty
305
+ onInvitePress={() => setInviteOpen(true)}
306
+ />
307
+ )}
308
+ >
309
+ {isPending ? (
310
+ <OrganizationInvitationRowSkeleton />
311
+ ) : (
312
+ sortedInvitations?.map((invitation) => (
313
+ <OrganizationInvitationTableRow
314
+ key={invitation.id}
315
+ invitation={invitation}
316
+ />
317
+ ))
318
+ )}
319
+ </Table.Body>
320
+ </Table.Content>
321
+ </Table.ScrollContainer>
322
+ </Table>
323
+ </div>
324
+
325
+ <InviteMemberDialog isOpen={inviteOpen} onOpenChange={setInviteOpen} />
326
+ </div>
327
+ )
328
+ }
329
+
330
+ function SortableColumnHeader({
331
+ children,
332
+ sortDirection
333
+ }: {
334
+ children: ReactNode
335
+ sortDirection?: "ascending" | "descending"
336
+ }) {
337
+ return (
338
+ <span className="flex items-center justify-between">
339
+ {children}
340
+
341
+ {!!sortDirection && (
342
+ <ChevronUp
343
+ className={cn(
344
+ "size-3 transform transition-transform duration-100 ease-out",
345
+ sortDirection === "descending" ? "rotate-180" : ""
346
+ )}
347
+ />
348
+ )}
349
+ </span>
350
+ )
351
+ }
@@ -0,0 +1,64 @@
1
+ import { Briefcase } from "@gravity-ui/icons"
2
+ import { Avatar, type AvatarProps, cn, Skeleton } from "@heroui/react"
3
+ import type { Organization } from "better-auth/client"
4
+ import type { ReactNode } from "react"
5
+
6
+ export type OrganizationLogoProps = {
7
+ className?: string
8
+ fallback?: ReactNode
9
+ isPending?: boolean
10
+ organization?: Partial<Organization>
11
+ size?: AvatarProps["size"]
12
+ }
13
+
14
+ /**
15
+ * Renders an organization logo image, initials fallback, or a briefcase icon;
16
+ * shows a circular skeleton while loading when `isPending` is set and `organization` is undefined.
17
+ */
18
+ export function OrganizationLogo({
19
+ className,
20
+ fallback,
21
+ isPending,
22
+ organization,
23
+ size = "sm",
24
+ style,
25
+ ...props
26
+ }: OrganizationLogoProps & AvatarProps) {
27
+ if (isPending && !organization) {
28
+ return (
29
+ <Skeleton
30
+ className={cn(
31
+ "rounded-full",
32
+ size === "sm" ? "size-8" : size === "md" ? "size-10" : "size-12",
33
+ className
34
+ )}
35
+ style={style}
36
+ />
37
+ )
38
+ }
39
+
40
+ const initials = organization?.name?.slice(0, 2).toUpperCase()
41
+
42
+ return (
43
+ <Avatar
44
+ size={size}
45
+ className={cn("rounded-full", className)}
46
+ style={style}
47
+ {...props}
48
+ >
49
+ <Avatar.Image
50
+ alt={organization?.name ?? "Organization"}
51
+ src={organization?.logo ?? undefined}
52
+ />
53
+
54
+ <Avatar.Fallback
55
+ className={cn(
56
+ size === "lg" ? "text-xl" : size === "md" ? "text-base" : "text-sm"
57
+ )}
58
+ delayMs={organization?.logo ? 600 : undefined}
59
+ >
60
+ {fallback || initials || <Briefcase className="size-4" />}
61
+ </Avatar.Fallback>
62
+ </Avatar>
63
+ )
64
+ }
@@ -0,0 +1,24 @@
1
+ import { Skeleton, Table } from "@heroui/react"
2
+
3
+ import { UserView } from "../user/user-view"
4
+
5
+ /**
6
+ * Placeholder row matching {@link OrganizationMemberRow} while members load.
7
+ */
8
+ export function OrganizationMemberRowSkeleton() {
9
+ return (
10
+ <Table.Row>
11
+ <Table.Cell>
12
+ <UserView isPending />
13
+ </Table.Cell>
14
+
15
+ <Table.Cell>
16
+ <Skeleton className="h-4 w-18 rounded-lg" />
17
+ </Table.Cell>
18
+
19
+ <Table.Cell className="flex justify-end">
20
+ <Skeleton className="size-8 rounded-full" />
21
+ </Table.Cell>
22
+ </Table.Row>
23
+ )
24
+ }