@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,16 @@
1
+ import { Skeleton } from "@heroui/react"
2
+
3
+ export function PasskeySkeleton() {
4
+ return (
5
+ <div className="flex items-center justify-between">
6
+ <div className="flex items-center gap-3">
7
+ <Skeleton className="size-10 rounded-xl" />
8
+
9
+ <div className="flex flex-col gap-1">
10
+ <Skeleton className="h-4 w-28 rounded-lg" />
11
+ <Skeleton className="h-3 w-32 rounded-lg" />
12
+ </div>
13
+ </div>
14
+ </div>
15
+ )
16
+ }
@@ -1,29 +1,22 @@
1
- import {
2
- type PasskeyAuthClient,
3
- useAuth,
4
- useAuthPlugin,
5
- useDeletePasskey
6
- } from "@better-auth-ui/react"
1
+ import { useAuth, useAuthPlugin } from "@better-auth-ui/react"
7
2
  import { Fingerprint, Xmark } from "@gravity-ui/icons"
8
- import { Button, Spinner } from "@heroui/react"
3
+ import { Button } from "@heroui/react"
4
+ import { useState } from "react"
9
5
 
10
6
  import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
7
+ import {
8
+ DeletePasskeyDialog,
9
+ type ListedPasskey
10
+ } from "./delete-passkey-dialog"
11
11
 
12
12
  export type PasskeyProps = {
13
- passkey: {
14
- id: string
15
- name?: string | null
16
- createdAt: Date
17
- }
13
+ passkey: ListedPasskey
18
14
  }
19
15
 
20
16
  export function Passkey({ passkey }: PasskeyProps) {
21
- const { authClient, localization } = useAuth()
17
+ const { localization } = useAuth()
22
18
  const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
23
-
24
- const { mutate: deletePasskey, isPending } = useDeletePasskey(
25
- authClient as PasskeyAuthClient
26
- )
19
+ const [deleteOpen, setDeleteOpen] = useState(false)
27
20
 
28
21
  const passkeyName = passkey.name || passkeyLocalization.passkey
29
22
 
@@ -33,8 +26,10 @@ export function Passkey({ passkey }: PasskeyProps) {
33
26
  <Fingerprint className="size-4.5" />
34
27
  </div>
35
28
 
36
- <div className="flex flex-col min-w-0">
37
- <span className="text-sm font-medium leading-tight">{passkeyName}</span>
29
+ <div className="flex min-w-0 flex-col">
30
+ <span className="truncate text-sm font-medium leading-tight">
31
+ {passkeyName}
32
+ </span>
38
33
 
39
34
  <span className="text-xs text-muted">
40
35
  {new Date(passkey.createdAt).toLocaleString(undefined, {
@@ -48,16 +43,22 @@ export function Passkey({ passkey }: PasskeyProps) {
48
43
  className="ml-auto shrink-0"
49
44
  variant="outline"
50
45
  size="sm"
51
- isPending={isPending}
52
- onPress={() => deletePasskey({ id: passkey.id })}
46
+ onPress={() => setDeleteOpen(true)}
53
47
  aria-label={passkeyLocalization.deletePasskey.replace(
54
48
  "{{name}}",
55
49
  passkeyName
56
50
  )}
57
51
  >
58
- {isPending ? <Spinner color="current" size="sm" /> : <Xmark />}
52
+ <Xmark />
53
+
59
54
  {localization.settings.delete}
60
55
  </Button>
56
+
57
+ <DeletePasskeyDialog
58
+ isOpen={deleteOpen}
59
+ onOpenChange={setDeleteOpen}
60
+ passkey={passkey}
61
+ />
61
62
  </div>
62
63
  )
63
64
  }
@@ -0,0 +1,35 @@
1
+ import { useAuthPlugin } from "@better-auth-ui/react"
2
+ import { Fingerprint } from "@gravity-ui/icons"
3
+ import { Button } from "@heroui/react"
4
+
5
+ import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
6
+
7
+ export type PasskeysEmptyProps = {
8
+ onAddPress: () => void
9
+ }
10
+
11
+ export function PasskeysEmpty({ onAddPress }: PasskeysEmptyProps) {
12
+ const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
13
+
14
+ return (
15
+ <div className="flex flex-col items-center justify-center gap-4">
16
+ <div className="flex size-10 items-center justify-center rounded-xl bg-surface-secondary">
17
+ <Fingerprint className="size-4.5" />
18
+ </div>
19
+
20
+ <div className="flex flex-col items-center justify-center gap-1 text-center">
21
+ <p className="text-sm font-semibold">
22
+ {passkeyLocalization.noPasskeys}
23
+ </p>
24
+
25
+ <p className="text-muted text-xs">
26
+ {passkeyLocalization.passkeysDescription}
27
+ </p>
28
+ </div>
29
+
30
+ <Button size="sm" onPress={onAddPress}>
31
+ {passkeyLocalization.addPasskey}
32
+ </Button>
33
+ </div>
34
+ )
35
+ }
@@ -1,29 +1,17 @@
1
1
  import {
2
2
  type PasskeyAuthClient,
3
- useAddPasskey,
4
3
  useAuth,
5
4
  useAuthPlugin,
6
5
  useListPasskeys
7
6
  } from "@better-auth-ui/react"
8
- import { Fingerprint } from "@gravity-ui/icons"
9
- import {
10
- AlertDialog,
11
- Button,
12
- Card,
13
- type CardProps,
14
- cn,
15
- FieldError,
16
- Form,
17
- Input,
18
- Label,
19
- Skeleton,
20
- Spinner,
21
- TextField
22
- } from "@heroui/react"
23
- import { type SyntheticEvent, useState } from "react"
7
+ import { Button, Card, type CardProps, cn } from "@heroui/react"
8
+ import { useState } from "react"
24
9
 
25
10
  import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
11
+ import { AddPasskeyDialog } from "./add-passkey-dialog"
26
12
  import { Passkey } from "./passkey"
13
+ import { PasskeySkeleton } from "./passkey-skeleton"
14
+ import { PasskeysEmpty } from "./passkeys-empty"
27
15
 
28
16
  export type PasskeysProps = {
29
17
  className?: string
@@ -35,162 +23,53 @@ export function Passkeys({
35
23
  variant,
36
24
  ...props
37
25
  }: PasskeysProps & Omit<CardProps, "children">) {
38
- const { authClient, localization } = useAuth()
26
+ const { authClient } = useAuth()
39
27
  const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
40
28
 
41
29
  const { data: passkeys, isPending } = useListPasskeys(
42
30
  authClient as PasskeyAuthClient
43
31
  )
44
- const { mutate: addPasskey, isPending: isAdding } = useAddPasskey(
45
- authClient as PasskeyAuthClient
46
- )
47
-
48
- const [nameOpen, setNameOpen] = useState(false)
49
- const [name, setName] = useState("")
50
32
 
51
- const handleDialogOpenChange = (open: boolean) => {
52
- setNameOpen(open)
53
- setName("")
54
- }
55
-
56
- const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
57
- e.preventDefault()
58
-
59
- addPasskey(
60
- { name: name.trim() || undefined },
61
- {
62
- onSuccess: () => {
63
- setNameOpen(false)
64
- setName("")
65
- }
66
- }
67
- )
68
- }
33
+ const [addOpen, setAddOpen] = useState(false)
69
34
 
70
35
  return (
71
- <div>
72
- <h2 className={cn("text-sm font-semibold mb-3")}>
73
- {passkeyLocalization.passkeys}
74
- </h2>
75
-
76
- <Card className={cn(className)} variant={variant} {...props}>
77
- <Card.Content className="gap-0">
78
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
79
- <div>
80
- <p className="text-sm font-medium leading-tight">
81
- {passkeyLocalization.passkeysDescription}
82
- </p>
83
-
84
- <p className="text-muted text-xs mt-0.5">
85
- {passkeyLocalization.passkeysInstructions}
86
- </p>
87
- </div>
88
-
89
- <AlertDialog>
90
- <Button
91
- className="shrink-0"
92
- size="sm"
93
- isDisabled={isPending}
94
- onPress={() => setNameOpen(true)}
95
- >
96
- {passkeyLocalization.addPasskey}
97
- </Button>
98
-
99
- <AlertDialog.Backdrop
100
- isOpen={nameOpen}
101
- onOpenChange={handleDialogOpenChange}
102
- >
103
- <AlertDialog.Container>
104
- <AlertDialog.Dialog>
105
- <Form onSubmit={handleSubmit}>
106
- <AlertDialog.CloseTrigger />
107
-
108
- <AlertDialog.Header>
109
- <AlertDialog.Icon status="default">
110
- <Fingerprint />
111
- </AlertDialog.Icon>
112
-
113
- <AlertDialog.Heading>
114
- {passkeyLocalization.addPasskey}
115
- </AlertDialog.Heading>
116
- </AlertDialog.Header>
117
-
118
- <AlertDialog.Body className="overflow-visible">
119
- <p className="text-muted text-sm">
120
- {passkeyLocalization.passkeysInstructions}
121
- </p>
122
-
123
- <TextField
124
- className="mt-4"
125
- name="passkey-name"
126
- isDisabled={isAdding}
127
- value={name}
128
- onChange={setName}
129
- >
130
- <Label>{passkeyLocalization.passkey}</Label>
131
-
132
- <Input
133
- autoFocus
134
- placeholder={localization.settings.optional}
135
- variant="secondary"
136
- />
137
-
138
- <FieldError />
139
- </TextField>
140
- </AlertDialog.Body>
141
-
142
- <AlertDialog.Footer>
143
- <Button
144
- slot="close"
145
- variant="tertiary"
146
- isDisabled={isAdding}
147
- >
148
- {localization.settings.cancel}
149
- </Button>
150
-
151
- <Button type="submit" isPending={isAdding}>
152
- {isAdding && <Spinner color="current" size="sm" />}
153
-
154
- {passkeyLocalization.addPasskey}
155
- </Button>
156
- </AlertDialog.Footer>
157
- </Form>
158
- </AlertDialog.Dialog>
159
- </AlertDialog.Container>
160
- </AlertDialog.Backdrop>
161
- </AlertDialog>
162
- </div>
36
+ <div className={cn("flex flex-col gap-3", className)}>
37
+ <div className="flex items-end justify-between gap-3">
38
+ <h2 className="text-sm font-semibold truncate">
39
+ {passkeyLocalization.passkeys}
40
+ </h2>
41
+
42
+ <Button
43
+ className="shrink-0"
44
+ size="sm"
45
+ isDisabled={isPending}
46
+ onPress={() => setAddOpen(true)}
47
+ >
48
+ {passkeyLocalization.addPasskey}
49
+ </Button>
50
+ </div>
163
51
 
52
+ <Card variant={variant} {...props}>
53
+ <Card.Content>
164
54
  {isPending ? (
165
- <>
166
- <div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
167
- <PasskeySkeleton />
168
- </>
55
+ <PasskeySkeleton />
56
+ ) : !passkeys?.length ? (
57
+ <PasskeysEmpty onAddPress={() => setAddOpen(true)} />
169
58
  ) : (
170
- passkeys?.map((passkey) => (
59
+ passkeys.map((passkey, index) => (
171
60
  <div key={passkey.id}>
172
- <div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
61
+ {index > 0 && (
62
+ <div className="border-b border-dashed -mx-4 my-4" />
63
+ )}
64
+
173
65
  <Passkey passkey={passkey} />
174
66
  </div>
175
67
  ))
176
68
  )}
177
69
  </Card.Content>
178
70
  </Card>
179
- </div>
180
- )
181
- }
182
71
 
183
- function PasskeySkeleton() {
184
- return (
185
- <div className="flex items-center justify-between">
186
- <div className="flex items-center gap-3">
187
- <Skeleton className="size-10 rounded-xl" />
188
-
189
- <div className="flex flex-col gap-1">
190
- <Skeleton className="h-4 w-24 rounded-lg" />
191
- <Skeleton className="h-3 w-20 rounded-lg" />
192
- </div>
193
- </div>
72
+ <AddPasskeyDialog isOpen={addOpen} onOpenChange={setAddOpen} />
194
73
  </div>
195
74
  )
196
75
  }
@@ -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
@@ -1,8 +1,8 @@
1
1
  import type { SettingsView } from "@better-auth-ui/core"
2
2
  import { useAuth, useAuthenticate } from "@better-auth-ui/react"
3
+ import { Person, Shield } from "@gravity-ui/icons"
3
4
  import { type CardProps, cn, Tabs } from "@heroui/react"
4
5
  import { type ComponentProps, useMemo } from "react"
5
-
6
6
  import { AccountSettings } from "./account/account-settings"
7
7
  import { SecuritySettings } from "./security/security-settings"
8
8
 
@@ -25,7 +25,7 @@ export type SettingsProps = {
25
25
  * @param variant - Card variant forwarded to each settings section card
26
26
  * @returns A DOM tree containing the responsive settings tabs and the currently selected settings panel
27
27
  *
28
- * @throws Error if neither `view` nor `path` is provided
28
+ * @throws Error if neither `view` nor `path` is provided, or if `path` does not match any settings view
29
29
  */
30
30
  export function Settings({
31
31
  className,
@@ -35,22 +35,39 @@ export function Settings({
35
35
  view,
36
36
  ...props
37
37
  }: SettingsProps & ComponentProps<"div">) {
38
- const { authClient, basePaths, localization, viewPaths } = useAuth()
39
- useAuthenticate(authClient)
40
-
41
38
  if (!view && !path) {
42
39
  throw new Error("[Better Auth UI] Either `view` or `path` must be provided")
43
40
  }
44
41
 
45
- const settingsPathViews = useMemo(
46
- () =>
47
- Object.fromEntries(
48
- Object.entries(viewPaths.settings).map(([k, v]) => [v, k])
49
- ) as Record<string, SettingsView>,
50
- [viewPaths.settings]
51
- )
42
+ const { authClient, basePaths, localization, viewPaths, plugins } = useAuth()
43
+ useAuthenticate(authClient)
44
+
45
+ // Built-ins first, then plugin segments
46
+ const currentView = useMemo(() => {
47
+ if (view) return view
48
+ if (!path) return undefined
52
49
 
53
- const currentView = view || (path ? settingsPathViews[path] : undefined)
50
+ const match = [
51
+ viewPaths.settings,
52
+ ...plugins.map((plugin) => plugin.viewPaths?.settings)
53
+ ]
54
+ .flatMap((source) => Object.entries(source ?? {}))
55
+ .find(([, segment]) => segment === path)
56
+
57
+ return match?.[0] as SettingsView | undefined
58
+ }, [view, path, viewPaths.settings, plugins])
59
+
60
+ if (!currentView) {
61
+ const validPaths = [
62
+ viewPaths.settings,
63
+ ...plugins.map((plugin) => plugin.viewPaths?.settings)
64
+ ]
65
+ .flatMap((source) => Object.values(source ?? {}))
66
+ .join(", ")
67
+ throw new Error(
68
+ `[Better Auth UI] Unknown settings path "${path}". Valid paths are: ${validPaths}`
69
+ )
70
+ }
54
71
 
55
72
  return (
56
73
  <Tabs
@@ -62,12 +79,18 @@ export function Settings({
62
79
  <Tabs.ListContainer>
63
80
  <Tabs.List
64
81
  aria-label={localization.settings.settings}
65
- className="overflow-auto w-fit"
82
+ className="max-w-fit overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
66
83
  >
67
84
  <Tabs.Tab
68
85
  id="account"
69
86
  href={`${basePaths.settings}/${viewPaths.settings.account}`}
87
+ className="gap-2"
88
+ onPress={(e) =>
89
+ e.target.scrollIntoView({ behavior: "smooth", inline: "center" })
90
+ }
70
91
  >
92
+ <Person className="text-muted" />
93
+
71
94
  {localization.settings.account}
72
95
 
73
96
  <Tabs.Indicator />
@@ -76,11 +99,39 @@ export function Settings({
76
99
  <Tabs.Tab
77
100
  id="security"
78
101
  href={`${basePaths.settings}/${viewPaths.settings.security}`}
102
+ className="gap-2"
103
+ onPress={(e) =>
104
+ e.target.scrollIntoView({ behavior: "smooth", inline: "center" })
105
+ }
79
106
  >
107
+ <Shield className="text-muted" />
108
+
80
109
  {localization.settings.security}
81
110
 
82
111
  <Tabs.Indicator />
83
112
  </Tabs.Tab>
113
+
114
+ {plugins.flatMap(
115
+ (plugin) =>
116
+ plugin.settingsTabs?.map((settingsTab, index) => (
117
+ <Tabs.Tab
118
+ key={`${plugin.id}-${index.toString()}`}
119
+ id={settingsTab.view}
120
+ href={`${basePaths.settings}/${plugin.viewPaths?.settings?.[settingsTab.view]}`}
121
+ className="gap-2"
122
+ onPress={(e) =>
123
+ e.target.scrollIntoView({
124
+ behavior: "smooth",
125
+ inline: "center"
126
+ })
127
+ }
128
+ >
129
+ {settingsTab.label}
130
+
131
+ <Tabs.Indicator />
132
+ </Tabs.Tab>
133
+ )) ?? []
134
+ )}
84
135
  </Tabs.List>
85
136
  </Tabs.ListContainer>
86
137
 
@@ -91,6 +142,18 @@ export function Settings({
91
142
  <Tabs.Panel id="security" className="px-0">
92
143
  <SecuritySettings variant={variant} />
93
144
  </Tabs.Panel>
145
+
146
+ {plugins.flatMap((plugin) =>
147
+ plugin.settingsTabs?.map((settingsTab, index) => (
148
+ <Tabs.Panel
149
+ key={`${plugin.id}-${index.toString()}`}
150
+ id={settingsTab.view}
151
+ className="px-0"
152
+ >
153
+ <settingsTab.component variant={variant} />
154
+ </Tabs.Panel>
155
+ ))
156
+ )}
94
157
  </Tabs>
95
158
  )
96
159
  }
@@ -13,7 +13,10 @@ export type UserAvatarProps = {
13
13
  fallback?: ReactNode
14
14
  isPending?: boolean
15
15
  /** @remarks `User` */
16
- user?: User & { username?: string | null; displayUsername?: string | null }
16
+ user?: Partial<User> & {
17
+ username?: string | null
18
+ displayUsername?: string | null
19
+ }
17
20
  size?: AvatarProps["size"]
18
21
  }
19
22
 
@@ -46,16 +46,15 @@ export type UserButtonLink = {
46
46
  export type UserButtonProps = {
47
47
  className?: string
48
48
  size?: "default" | "icon"
49
- /**
50
- * The placement of the element with respect to its anchor element.
51
- * @default "bottom"
52
- */
49
+ /** The placement of the element with respect to its anchor element. */
53
50
  placement?: DropdownPopoverProps["placement"]
54
51
  variant?: ButtonProps["variant"]
55
52
  /** Additional menu entries rendered above the built-in items. */
56
53
  links?: (UserButtonLink | ReactElement)[]
57
54
  /** Hide the built-in "Settings" link. Useful when replacing it via `links`. */
58
55
  hideSettings?: boolean
56
+ /** When true, the subtitle line (email when name/username is shown) is hidden. */
57
+ hideSubtitle?: boolean
59
58
  }
60
59
 
61
60
  function renderUserLink(
@@ -91,11 +90,12 @@ function renderUserLink(
91
90
  */
92
91
  export function UserButton({
93
92
  className,
94
- placement = "bottom",
93
+ placement,
95
94
  size = "default",
96
95
  variant = "ghost",
97
96
  links,
98
- hideSettings = false
97
+ hideSettings,
98
+ hideSubtitle
99
99
  }: UserButtonProps) {
100
100
  const { authClient, basePaths, viewPaths, localization, plugins } = useAuth()
101
101
 
@@ -104,7 +104,10 @@ export function UserButton({
104
104
  const userMenuItems = plugins.flatMap(
105
105
  (plugin) =>
106
106
  plugin.userMenuItems?.map((Item, index) => (
107
- <Item key={`${plugin.id}-${index.toString()}`} />
107
+ <Item
108
+ key={`${plugin.id}-${index.toString()}`}
109
+ hideSubtitle={hideSubtitle}
110
+ />
108
111
  )) ?? []
109
112
  )
110
113
 
@@ -127,12 +130,12 @@ export function UserButton({
127
130
  <Button
128
131
  variant={variant}
129
132
  className={cn(
130
- "h-auto font-normal justify-start px-3 py-2 text-left",
133
+ "h-auto font-normal justify-start px-2 py-2 text-left",
131
134
  className
132
135
  )}
133
136
  >
134
137
  {session || sessionPending ? (
135
- <UserView isPending={sessionPending} />
138
+ <UserView isPending={sessionPending} hideSubtitle={hideSubtitle} />
136
139
  ) : (
137
140
  <>
138
141
  <UserAvatar />
@@ -141,7 +144,7 @@ export function UserButton({
141
144
  </>
142
145
  )}
143
146
 
144
- <ChevronsExpandVertical className="ml-auto size-3.5" />
147
+ <ChevronsExpandVertical className="ml-auto size-3 text-muted" />
145
148
  </Button>
146
149
  )}
147
150
 
@@ -151,7 +154,7 @@ export function UserButton({
151
154
  >
152
155
  {session && (
153
156
  <div className="px-3 pt-3 pb-1">
154
- <UserView />
157
+ <UserView hideSubtitle={hideSubtitle} />
155
158
  </div>
156
159
  )}
157
160