@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,80 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useListOrganizations
6
+ } from "@better-auth-ui/react"
7
+ import { Button, Card, type CardProps } from "@heroui/react"
8
+ import { useState } from "react"
9
+
10
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
11
+ import { CreateOrganizationDialog } from "./create-organization-dialog"
12
+ import { OrganizationRow } from "./organization-row"
13
+ import { OrganizationViewSkeleton } from "./organization-view-skeleton"
14
+ import { OrganizationsEmpty } from "./organizations-empty"
15
+
16
+ export type OrganizationsProps = {
17
+ variant?: CardProps["variant"]
18
+ }
19
+
20
+ /**
21
+ * Lists organizations the user belongs to (via {@link useListOrganizations}): loading skeleton,
22
+ * empty state with create, or a card of rows with a Manage control per organization.
23
+ * Owns {@link CreateOrganizationDialog} open state and the create actions.
24
+ */
25
+ export function Organizations({ variant }: OrganizationsProps) {
26
+ const { authClient } = useAuth()
27
+ const { localization: organizationLocalization } =
28
+ useAuthPlugin(organizationPlugin)
29
+
30
+ const [createOpen, setCreateOpen] = useState(false)
31
+
32
+ const { data: organizations, isPending: organizationsPending } =
33
+ useListOrganizations(authClient as OrganizationAuthClient)
34
+
35
+ return (
36
+ <>
37
+ <div className="flex flex-col gap-3">
38
+ <div className="flex items-end justify-between gap-3">
39
+ <h2 className="truncate text-sm font-semibold">
40
+ {organizationLocalization.organizations}
41
+ </h2>
42
+
43
+ <Button
44
+ className="shrink-0"
45
+ size="sm"
46
+ isDisabled={organizationsPending}
47
+ onPress={() => setCreateOpen(true)}
48
+ >
49
+ {organizationLocalization.createOrganization}
50
+ </Button>
51
+ </div>
52
+
53
+ <Card variant={variant}>
54
+ <Card.Content className="gap-0">
55
+ {organizationsPending ? (
56
+ <OrganizationViewSkeleton />
57
+ ) : !organizations?.length ? (
58
+ <OrganizationsEmpty onCreatePress={() => setCreateOpen(true)} />
59
+ ) : (
60
+ organizations.map((organization, index) => (
61
+ <div key={organization.id}>
62
+ {index > 0 && (
63
+ <div className="-mx-4 my-4 border-b border-dashed" />
64
+ )}
65
+
66
+ <OrganizationRow organization={organization} />
67
+ </div>
68
+ ))
69
+ )}
70
+ </Card.Content>
71
+ </Card>
72
+ </div>
73
+
74
+ <CreateOrganizationDialog
75
+ isOpen={createOpen}
76
+ onOpenChange={setCreateOpen}
77
+ />
78
+ </>
79
+ )
80
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useRemoveMember
6
+ } from "@better-auth-ui/react"
7
+ import { TrashBin } from "@gravity-ui/icons"
8
+ import { AlertDialog, Button, Card, Chip, Spinner, toast } from "@heroui/react"
9
+ import type { Member, User } from "better-auth/client"
10
+
11
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
12
+ import { UserView } from "../user/user-view"
13
+
14
+ export type RemoveMemberDialogProps = {
15
+ isOpen: boolean
16
+ onOpenChange: (open: boolean) => void
17
+ member: Member & { user: Partial<User> }
18
+ }
19
+
20
+ export function RemoveMemberDialog({
21
+ isOpen,
22
+ onOpenChange,
23
+ member
24
+ }: RemoveMemberDialogProps) {
25
+ const { authClient, localization } = useAuth()
26
+ const { localization: organizationLocalization, roles } =
27
+ useAuthPlugin(organizationPlugin)
28
+
29
+ const { mutate: removeMember, isPending } = useRemoveMember(
30
+ authClient as OrganizationAuthClient,
31
+ {
32
+ onSuccess: () => {
33
+ onOpenChange(false)
34
+ toast.success(organizationLocalization.memberRemoved)
35
+ }
36
+ }
37
+ )
38
+
39
+ return (
40
+ <AlertDialog.Backdrop isOpen={isOpen} onOpenChange={onOpenChange}>
41
+ <AlertDialog.Container>
42
+ <AlertDialog.Dialog>
43
+ <AlertDialog.CloseTrigger />
44
+
45
+ <AlertDialog.Header>
46
+ <AlertDialog.Icon status="danger">
47
+ <TrashBin />
48
+ </AlertDialog.Icon>
49
+
50
+ <AlertDialog.Heading>
51
+ {organizationLocalization.removeMember}
52
+ </AlertDialog.Heading>
53
+ </AlertDialog.Header>
54
+
55
+ <AlertDialog.Body className="flex flex-col gap-4 overflow-visible">
56
+ <p className="text-muted text-sm">
57
+ {organizationLocalization.removeMemberWarning}
58
+ </p>
59
+
60
+ <Card variant="secondary">
61
+ <Card.Content className="justify-between flex-row items-center gap-2">
62
+ <UserView user={member.user} />
63
+
64
+ <Chip size="sm" variant="tertiary">
65
+ <Chip.Label>{roles?.[member.role] ?? member.role}</Chip.Label>
66
+ </Chip>
67
+ </Card.Content>
68
+ </Card>
69
+ </AlertDialog.Body>
70
+
71
+ <AlertDialog.Footer>
72
+ <Button slot="close" variant="tertiary" isDisabled={isPending}>
73
+ {localization.settings.cancel}
74
+ </Button>
75
+
76
+ <Button
77
+ variant="danger"
78
+ isPending={isPending}
79
+ onPress={() =>
80
+ removeMember({
81
+ memberIdOrEmail: member.id,
82
+ organizationId: member.organizationId
83
+ })
84
+ }
85
+ >
86
+ {isPending && <Spinner color="current" size="sm" />}
87
+
88
+ {organizationLocalization.removeMember}
89
+ </Button>
90
+ </AlertDialog.Footer>
91
+ </AlertDialog.Dialog>
92
+ </AlertDialog.Container>
93
+ </AlertDialog.Backdrop>
94
+ )
95
+ }
@@ -0,0 +1,111 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useCheckSlug
6
+ } from "@better-auth-ui/react"
7
+ import { Check, Xmark } from "@gravity-ui/icons"
8
+ import {
9
+ FieldError,
10
+ InputGroup,
11
+ type InputProps,
12
+ Label,
13
+ Spinner,
14
+ TextField,
15
+ type TextFieldProps
16
+ } from "@heroui/react"
17
+ import { useDebouncer } from "@tanstack/react-pacer"
18
+ import { useEffect } from "react"
19
+
20
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
21
+
22
+ /** Props for the {@link SlugField} component. */
23
+ export type SlugFieldProps = {
24
+ value: string
25
+ onChange: (value: string) => void
26
+ currentSlug?: string
27
+ isDisabled?: boolean
28
+ variant?: InputProps["variant"]
29
+ }
30
+
31
+ /**
32
+ * Sanitize a slug value so it only contains lowercase alphanumeric characters
33
+ * and dashes. Runs of disallowed characters are collapsed to a single dash, but
34
+ * leading/trailing dashes are preserved while the user is still typing.
35
+ */
36
+ export function sanitizeSlug(value: string) {
37
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-")
38
+ }
39
+
40
+ /**
41
+ * Organization slug field with debounced availability checking.
42
+ */
43
+ export function SlugField({
44
+ value,
45
+ onChange,
46
+ currentSlug,
47
+ variant,
48
+ ...props
49
+ }: SlugFieldProps & TextFieldProps) {
50
+ const { authClient } = useAuth()
51
+ const { localization, checkSlug: checkSlugEnabled } =
52
+ useAuthPlugin(organizationPlugin)
53
+
54
+ const {
55
+ mutate: checkSlug,
56
+ data: checkSlugData,
57
+ error: checkSlugError,
58
+ reset: resetCheckSlug
59
+ } = useCheckSlug(authClient as OrganizationAuthClient)
60
+
61
+ const debouncer = useDebouncer(
62
+ (value) => {
63
+ if (!checkSlugEnabled || !value.trim() || value.trim() === currentSlug)
64
+ return
65
+
66
+ checkSlug({ slug: value.trim() })
67
+ },
68
+ { wait: 500 }
69
+ )
70
+
71
+ useEffect(() => {
72
+ if (!checkSlugEnabled) return
73
+
74
+ resetCheckSlug()
75
+ debouncer.maybeExecute(value)
76
+ }, [checkSlugEnabled, value, debouncer.maybeExecute, resetCheckSlug])
77
+
78
+ const handleChange = (next: string) => {
79
+ onChange(sanitizeSlug(next))
80
+ }
81
+
82
+ return (
83
+ <TextField
84
+ id="slug"
85
+ name="slug"
86
+ {...props}
87
+ value={value}
88
+ onChange={handleChange}
89
+ >
90
+ <Label>{localization.slug}</Label>
91
+
92
+ <InputGroup variant={variant}>
93
+ <InputGroup.Input placeholder={localization.slugPlaceholder} required />
94
+
95
+ {checkSlugEnabled && !!value.trim() && value.trim() !== currentSlug && (
96
+ <InputGroup.Suffix className="px-2">
97
+ {checkSlugData?.status ? (
98
+ <Check className="text-success" />
99
+ ) : checkSlugError ? (
100
+ <Xmark className="text-danger" />
101
+ ) : (
102
+ <Spinner size="sm" color="current" />
103
+ )}
104
+ </InputGroup.Suffix>
105
+ )}
106
+ </InputGroup>
107
+
108
+ <FieldError />
109
+ </TextField>
110
+ )
111
+ }
@@ -0,0 +1,17 @@
1
+ import { Skeleton } from "@heroui/react"
2
+
3
+ /**
4
+ * Placeholder row matching {@link UserInvitationRow} while invitations load.
5
+ */
6
+ export function UserInvitationRowSkeleton() {
7
+ return (
8
+ <div className="flex items-center gap-3">
9
+ <Skeleton className="size-10 shrink-0 rounded-xl" />
10
+
11
+ <div className="flex flex-col gap-1">
12
+ <Skeleton className="h-4 w-40 rounded-lg" />
13
+ <Skeleton className="h-3 w-28 rounded-lg" />
14
+ </div>
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAcceptInvitation,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useRejectInvitation
7
+ } from "@better-auth-ui/react"
8
+ import { Check, Clock, Xmark } from "@gravity-ui/icons"
9
+ import { Button, Chip, Spinner } from "@heroui/react"
10
+ import type { Invitation } from "better-auth/client"
11
+
12
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
13
+
14
+ export type UserInvitationRowProps = {
15
+ invitation: Invitation & { organizationName?: string }
16
+ }
17
+
18
+ /**
19
+ * Single invitation row with accept/reject actions for the current user.
20
+ */
21
+ export function UserInvitationRow({ invitation }: UserInvitationRowProps) {
22
+ const { authClient } = useAuth()
23
+ const { localization: organizationLocalization, roles } =
24
+ useAuthPlugin(organizationPlugin)
25
+
26
+ const { mutate: acceptInvitation, isPending: isAccepting } =
27
+ useAcceptInvitation(authClient as OrganizationAuthClient)
28
+
29
+ const { mutate: rejectInvitation, isPending: isRejecting } =
30
+ useRejectInvitation(authClient as OrganizationAuthClient)
31
+
32
+ return (
33
+ <div className="flex items-center gap-3">
34
+ <div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary">
35
+ <Clock className="size-4.5" />
36
+ </div>
37
+
38
+ <div className="flex flex-col">
39
+ <div className="flex items-center gap-1.5">
40
+ <span className="truncate text-sm font-medium leading-tight">
41
+ {invitation.organizationName}
42
+ </span>
43
+
44
+ <Chip size="sm">{roles?.[invitation.role] ?? invitation.role}</Chip>
45
+ </div>
46
+
47
+ <span className="truncate text-muted text-xs">
48
+ {new Date(invitation.createdAt).toLocaleString(undefined, {
49
+ dateStyle: "medium",
50
+ timeStyle: "short"
51
+ })}
52
+ </span>
53
+ </div>
54
+
55
+ <div className="ml-auto flex shrink-0 items-center gap-2">
56
+ <Button
57
+ variant="outline"
58
+ size="sm"
59
+ isPending={isAccepting}
60
+ isDisabled={isRejecting}
61
+ onPress={() =>
62
+ acceptInvitation({
63
+ invitationId: invitation.id
64
+ })
65
+ }
66
+ >
67
+ {isAccepting ? <Spinner color="current" size="sm" /> : <Check />}
68
+
69
+ {organizationLocalization.accept}
70
+ </Button>
71
+
72
+ <Button
73
+ variant="danger-soft"
74
+ size="sm"
75
+ isIconOnly
76
+ isPending={isRejecting}
77
+ isDisabled={isAccepting}
78
+ onPress={() =>
79
+ rejectInvitation({
80
+ invitationId: invitation.id
81
+ })
82
+ }
83
+ aria-label={organizationLocalization.rejectInvitation}
84
+ >
85
+ {isRejecting ? <Spinner color="current" size="sm" /> : <Xmark />}
86
+ </Button>
87
+ </div>
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,30 @@
1
+ import { useAuthPlugin } from "@better-auth-ui/react"
2
+ import { PaperPlane } from "@gravity-ui/icons"
3
+ import { EmptyState } from "@heroui/react"
4
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
5
+
6
+ /**
7
+ * Empty state for `UserInvitations`
8
+ */
9
+ export function UserInvitationsEmpty() {
10
+ const { localization: organizationLocalization } =
11
+ useAuthPlugin(organizationPlugin)
12
+
13
+ return (
14
+ <EmptyState className="flex flex-col items-center gap-4 text-center">
15
+ <div className="flex size-12 items-center justify-center rounded-full bg-surface-secondary">
16
+ <PaperPlane className="size-5" />
17
+ </div>
18
+
19
+ <div className="flex flex-col gap-2">
20
+ <p className="text-sm font-semibold text-foreground">
21
+ {organizationLocalization.noInvitations}
22
+ </p>
23
+
24
+ <span className="text-sm text-muted">
25
+ {organizationLocalization.userInvitationsEmptyDescription}
26
+ </span>
27
+ </div>
28
+ </EmptyState>
29
+ )
30
+ }
@@ -0,0 +1,59 @@
1
+ import {
2
+ type OrganizationAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useListUserInvitations
6
+ } from "@better-auth-ui/react"
7
+ import { Card, type CardProps } from "@heroui/react"
8
+
9
+ import { organizationPlugin } from "../../../lib/auth/organization-plugin"
10
+ import { UserInvitationRow } from "./user-invitation-row"
11
+ import { UserInvitationRowSkeleton } from "./user-invitation-row-skeleton"
12
+ import { UserInvitationsEmpty } from "./user-invitations-empty"
13
+
14
+ export type UserInvitationsProps = {
15
+ variant?: CardProps["variant"]
16
+ }
17
+
18
+ /**
19
+ * Organization invitations for the signed-in user (from
20
+ * {@link useListUserInvitations}). Always renders the section card; uses
21
+ * {@link UserInvitationsEmpty} when there are no pending invitations.
22
+ */
23
+ export function UserInvitations({ variant }: UserInvitationsProps) {
24
+ const { authClient } = useAuth()
25
+ const { localization: organizationLocalization } =
26
+ useAuthPlugin(organizationPlugin)
27
+
28
+ const { data: invitations, isPending } = useListUserInvitations(
29
+ authClient as OrganizationAuthClient
30
+ )
31
+
32
+ return (
33
+ <div className="flex flex-col gap-3">
34
+ <h2 className="truncate text-sm font-semibold">
35
+ {organizationLocalization.invitations}
36
+ </h2>
37
+
38
+ <Card variant={variant}>
39
+ <Card.Content>
40
+ {isPending ? (
41
+ <UserInvitationRowSkeleton />
42
+ ) : !invitations?.length ? (
43
+ <UserInvitationsEmpty />
44
+ ) : (
45
+ invitations?.map((invitation, index) => (
46
+ <div key={invitation.id}>
47
+ {index > 0 && (
48
+ <div className="border-b border-dashed -mx-4 my-4" />
49
+ )}
50
+
51
+ <UserInvitationRow invitation={invitation} />
52
+ </div>
53
+ ))
54
+ )}
55
+ </Card.Content>
56
+ </Card>
57
+ </div>
58
+ )
59
+ }
@@ -64,7 +64,7 @@ export function ChangeEmail({
64
64
  <Fieldset className="w-full gap-4">
65
65
  <Fieldset.Group>
66
66
  <TextField
67
- key={session?.user.email}
67
+ key={`${session?.user.id}-${session?.user.email}-email`}
68
68
  name="email"
69
69
  type="email"
70
70
  defaultValue={session?.user.email}
@@ -81,7 +81,7 @@ export function UserProfile({
81
81
  return (
82
82
  <div>
83
83
  <h2 className={cn("text-sm font-semibold mb-3")}>
84
- {localization.settings.profile}
84
+ {localization.settings.userProfile}
85
85
  </h2>
86
86
 
87
87
  <Card className={cn("p-4 gap-4", className)} variant={variant} {...props}>
@@ -90,7 +90,7 @@ export function UserProfile({
90
90
  <ChangeAvatar />
91
91
 
92
92
  <TextField
93
- key={session?.user?.name}
93
+ key={`${session?.user?.id}-${session?.user?.name}-name`}
94
94
  name="name"
95
95
  defaultValue={session?.user.name}
96
96
  isDisabled={isPending || !session}
@@ -131,9 +131,9 @@ export function UserProfile({
131
131
  field.name
132
132
  ]
133
133
 
134
- // Re-mount when the session value loads so the field's
135
- // uncontrolled `defaultValue` reflects the latest data.
136
- const key = `${field.name}:${
134
+ // Re-mount when the user or the session value changes so the
135
+ // field's uncontrolled `defaultValue` reflects the latest data.
136
+ const key = `${session.user.id}-${field.name}-${
137
137
  value instanceof Date
138
138
  ? value.toISOString()
139
139
  : String(value ?? "")
@@ -25,9 +25,11 @@ export function ActiveSessions({
25
25
 
26
26
  const { data: sessions, isPending } = useListSessions(authClient)
27
27
 
28
- const activeSessions = sessions?.toSorted((activeSession) =>
29
- activeSession.id === session?.session.id ? -1 : 1
30
- )
28
+ const activeSessions =
29
+ sessions &&
30
+ [...sessions].sort((a, b) =>
31
+ a.id === session?.session.id ? -1 : b.id === session?.session.id ? 1 : 0
32
+ )
31
33
 
32
34
  return (
33
35
  <div>
@@ -43,7 +45,7 @@ export function ActiveSessions({
43
45
  activeSessions?.map((activeSession, index) => (
44
46
  <div key={activeSession.id}>
45
47
  {index > 0 && (
46
- <div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
48
+ <div className="border-b border-dashed -mx-4 my-4" />
47
49
  )}
48
50
 
49
51
  <ActiveSession activeSession={activeSession} />
@@ -54,7 +54,7 @@ export function LinkedAccounts({
54
54
  ? socialProviders?.map((provider, index) => (
55
55
  <div key={provider}>
56
56
  {index > 0 && (
57
- <div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
57
+ <div className="border-b border-dashed -mx-4 my-4" />
58
58
  )}
59
59
  <AccountRowSkeleton />
60
60
  </div>
@@ -62,7 +62,7 @@ export function LinkedAccounts({
62
62
  : allRows.map((row, index) => (
63
63
  <div key={row.key}>
64
64
  {index > 0 && (
65
- <div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
65
+ <div className="border-b border-dashed -mx-4 my-4" />
66
66
  )}
67
67
 
68
68
  <LinkedAccount