@better-auth-ui/heroui 1.6.3 → 1.6.5

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 (204) hide show
  1. package/dist/components/auth/additional-field.d.ts +4 -0
  2. package/dist/components/auth/additional-field.js +270 -0
  3. package/dist/components/auth/auth-provider.d.ts +4 -6
  4. package/dist/components/auth/auth.d.ts +1 -1
  5. package/dist/components/auth/auth.js +54 -28
  6. package/dist/components/{settings/security → auth/delete-user}/danger-zone.d.ts +1 -1
  7. package/dist/components/{settings/security → auth/delete-user}/danger-zone.js +1 -1
  8. package/dist/components/auth/delete-user/delete-user.js +95 -0
  9. package/dist/components/{email → auth/email}/email-changed-email.d.ts +2 -2
  10. package/dist/components/{email → auth/email}/email-changed-email.js +7 -7
  11. package/dist/components/{email → auth/email}/email-verification-email.d.ts +2 -2
  12. package/dist/components/{email → auth/email}/email-verification-email.js +7 -7
  13. package/dist/components/auth/email/magic-link-email.d.ts +3 -0
  14. package/dist/components/{email → auth/email}/magic-link-email.js +7 -7
  15. package/dist/components/auth/email/new-device-email.d.ts +3 -0
  16. package/dist/components/{email → auth/email}/new-device-email.js +7 -7
  17. package/dist/components/auth/email/otp-email.d.ts +3 -0
  18. package/dist/components/{email → auth/email}/otp-email.js +6 -6
  19. package/dist/components/{email → auth/email}/password-changed-email.d.ts +2 -2
  20. package/dist/components/{email → auth/email}/password-changed-email.js +7 -7
  21. package/dist/components/{email → auth/email}/reset-password-email.d.ts +2 -2
  22. package/dist/components/{email → auth/email}/reset-password-email.js +7 -7
  23. package/dist/components/auth/forgot-password.d.ts +1 -1
  24. package/dist/components/auth/forgot-password.js +70 -48
  25. package/dist/components/auth/magic-link/magic-link-button.d.ts +11 -0
  26. package/dist/components/auth/magic-link/magic-link-button.js +20 -0
  27. package/dist/components/auth/magic-link/magic-link.d.ts +16 -0
  28. package/dist/components/auth/magic-link/magic-link.js +87 -0
  29. package/dist/components/{settings/account → auth/multi-session}/manage-account.d.ts +2 -6
  30. package/dist/components/auth/multi-session/manage-account.js +49 -0
  31. package/dist/components/auth/multi-session/manage-accounts.js +34 -0
  32. package/dist/components/{user/switch-account-menu.d.ts → auth/multi-session/switch-account-submenu-content.d.ts} +3 -3
  33. package/dist/components/auth/multi-session/switch-account-submenu-content.js +29 -0
  34. package/dist/components/{user/switch-account-item.d.ts → auth/multi-session/switch-account-submenu-item.d.ts} +2 -2
  35. package/dist/components/auth/multi-session/switch-account-submenu-item.js +20 -0
  36. package/dist/components/auth/multi-session/switch-account-submenu.d.ts +13 -0
  37. package/dist/components/auth/multi-session/switch-account-submenu.js +24 -0
  38. package/dist/components/auth/passkey/passkey-button.d.ts +6 -0
  39. package/dist/components/auth/passkey/passkey-button.js +24 -0
  40. package/dist/components/auth/passkey/passkey.js +45 -0
  41. package/dist/components/auth/passkey/passkeys.js +106 -0
  42. package/dist/components/auth/provider-button.d.ts +6 -8
  43. package/dist/components/auth/provider-button.js +16 -20
  44. package/dist/components/auth/provider-buttons.d.ts +3 -4
  45. package/dist/components/auth/provider-buttons.js +9 -10
  46. package/dist/components/auth/reset-password.js +110 -107
  47. package/dist/components/{settings → auth/settings}/account/account-settings.d.ts +5 -2
  48. package/dist/components/auth/settings/account/account-settings.js +20 -0
  49. package/dist/components/auth/settings/account/change-avatar.js +89 -0
  50. package/dist/components/{settings → auth/settings}/account/change-email.js +17 -17
  51. package/dist/components/{settings → auth/settings}/account/user-profile.d.ts +2 -3
  52. package/dist/components/auth/settings/account/user-profile.js +86 -0
  53. package/dist/components/auth/settings/security/active-session.js +59 -0
  54. package/dist/components/{settings → auth/settings}/security/active-sessions.js +4 -4
  55. package/dist/components/auth/settings/security/change-password.js +189 -0
  56. package/dist/components/auth/settings/security/linked-account.js +56 -0
  57. package/dist/components/{settings → auth/settings}/security/linked-accounts.js +5 -5
  58. package/dist/components/{settings → auth/settings}/security/security-settings.d.ts +1 -1
  59. package/dist/components/auth/settings/security/security-settings.js +22 -0
  60. package/dist/components/auth/settings/settings.js +45 -0
  61. package/dist/components/auth/sign-in.js +66 -85
  62. package/dist/components/auth/sign-out.js +11 -11
  63. package/dist/components/auth/sign-up.js +128 -148
  64. package/dist/components/{settings/account → auth/theme}/appearance.d.ts +3 -6
  65. package/dist/components/auth/theme/appearance.js +72 -0
  66. package/dist/components/auth/theme/theme-toggle-item.d.ts +5 -0
  67. package/dist/components/auth/theme/theme-toggle-item.js +58 -0
  68. package/dist/components/auth/user/user-avatar.js +29 -0
  69. package/dist/components/{user → auth/user}/user-button.d.ts +2 -6
  70. package/dist/components/auth/user/user-button.js +55 -0
  71. package/dist/components/auth/user/user-view.js +37 -0
  72. package/dist/components/auth/username/sign-in-username.d.ts +14 -0
  73. package/dist/components/auth/username/sign-in-username.js +148 -0
  74. package/dist/components/auth/username/username-field.d.ts +8 -0
  75. package/dist/components/auth/username/username-field.js +53 -0
  76. package/dist/email.d.ts +7 -0
  77. package/dist/email.js +9 -0
  78. package/dist/index.d.ts +12 -27
  79. package/dist/index.js +20 -35
  80. package/dist/lib/auth/auth-plugin.d.ts +44 -0
  81. package/dist/lib/auth/delete-user-plugin.d.ts +17 -0
  82. package/dist/lib/auth/delete-user-plugin.js +10 -0
  83. package/dist/lib/auth/magic-link-plugin.d.ts +31 -0
  84. package/dist/lib/auth/magic-link-plugin.js +13 -0
  85. package/dist/lib/auth/multi-session-plugin.d.ts +18 -0
  86. package/dist/lib/auth/multi-session-plugin.js +12 -0
  87. package/dist/lib/auth/passkey-plugin.d.ts +20 -0
  88. package/dist/lib/auth/passkey-plugin.js +12 -0
  89. package/dist/lib/auth/theme-plugin.d.ts +68 -0
  90. package/dist/lib/auth/theme-plugin.js +23 -0
  91. package/dist/lib/auth/username-plugin.d.ts +63 -0
  92. package/dist/lib/auth/username-plugin.js +18 -0
  93. package/dist/plugins.d.ts +20 -0
  94. package/dist/plugins.js +21 -0
  95. package/package.json +19 -21
  96. package/src/components/auth/additional-field.tsx +554 -0
  97. package/src/components/auth/auth-provider.tsx +5 -6
  98. package/src/components/auth/auth.tsx +99 -35
  99. package/src/components/{settings/security → auth/delete-user}/danger-zone.tsx +1 -1
  100. package/src/components/{settings/security → auth/delete-user}/delete-user.tsx +30 -20
  101. package/src/components/{email → auth/email}/email-changed-email.tsx +2 -2
  102. package/src/components/{email → auth/email}/email-verification-email.tsx +2 -2
  103. package/src/components/{email → auth/email}/magic-link-email.tsx +2 -2
  104. package/src/components/{email → auth/email}/new-device-email.tsx +2 -2
  105. package/src/components/{email → auth/email}/otp-email.tsx +2 -2
  106. package/src/components/{email → auth/email}/password-changed-email.tsx +2 -2
  107. package/src/components/{email → auth/email}/reset-password-email.tsx +2 -2
  108. package/src/components/auth/forgot-password.tsx +42 -17
  109. package/src/components/auth/magic-link/magic-link-button.tsx +61 -0
  110. package/src/components/auth/{magic-link.tsx → magic-link/magic-link.tsx} +55 -40
  111. package/src/components/{settings/account → auth/multi-session}/manage-account.tsx +16 -13
  112. package/src/components/{settings/account → auth/multi-session}/manage-accounts.tsx +14 -5
  113. package/src/components/{user/switch-account-menu.tsx → auth/multi-session/switch-account-submenu-content.tsx} +18 -11
  114. package/src/components/{user/switch-account-item.tsx → auth/multi-session/switch-account-submenu-item.tsx} +17 -5
  115. package/src/components/auth/multi-session/switch-account-submenu.tsx +50 -0
  116. package/src/components/auth/passkey/passkey-button.tsx +56 -0
  117. package/src/components/{settings/security → auth/passkey}/passkey.tsx +20 -6
  118. package/src/components/auth/passkey/passkeys.tsx +196 -0
  119. package/src/components/auth/provider-button.tsx +27 -29
  120. package/src/components/auth/provider-buttons.tsx +6 -11
  121. package/src/components/auth/reset-password.tsx +13 -7
  122. package/src/components/{settings → auth/settings}/account/account-settings.tsx +15 -13
  123. package/src/components/{settings → auth/settings}/account/change-avatar.tsx +4 -3
  124. package/src/components/{settings → auth/settings}/account/change-email.tsx +3 -3
  125. package/src/components/auth/settings/account/user-profile.tsx +171 -0
  126. package/src/components/{settings → auth/settings}/security/active-session.tsx +8 -5
  127. package/src/components/{settings → auth/settings}/security/active-sessions.tsx +3 -3
  128. package/src/components/{settings → auth/settings}/security/change-password.tsx +14 -9
  129. package/src/components/{settings → auth/settings}/security/linked-account.tsx +12 -8
  130. package/src/components/{settings → auth/settings}/security/linked-accounts.tsx +2 -2
  131. package/src/components/{settings → auth/settings}/security/security-settings.tsx +7 -6
  132. package/src/components/{settings → auth/settings}/settings.tsx +2 -2
  133. package/src/components/auth/sign-in.tsx +50 -61
  134. package/src/components/auth/sign-out.tsx +2 -2
  135. package/src/components/auth/sign-up.tsx +119 -131
  136. package/src/components/{settings/account → auth/theme}/appearance.tsx +17 -24
  137. package/src/components/auth/theme/theme-toggle-item.tsx +110 -0
  138. package/src/components/{user → auth/user}/user-avatar.tsx +12 -4
  139. package/src/components/{user → auth/user}/user-button.tsx +15 -88
  140. package/src/components/{user → auth/user}/user-view.tsx +12 -4
  141. package/src/components/auth/username/sign-in-username.tsx +299 -0
  142. package/src/components/auth/username/username-field.tsx +124 -0
  143. package/src/email.ts +9 -0
  144. package/src/index.tsx +12 -27
  145. package/src/lib/auth/auth-plugin.ts +67 -0
  146. package/src/lib/auth/delete-user-plugin.ts +15 -0
  147. package/src/lib/auth/magic-link-plugin.ts +26 -0
  148. package/src/lib/auth/multi-session-plugin.ts +17 -0
  149. package/src/lib/auth/passkey-plugin.ts +17 -0
  150. package/src/lib/auth/theme-plugin.ts +86 -0
  151. package/src/lib/auth/username-plugin.tsx +30 -0
  152. package/src/plugins.ts +23 -0
  153. package/dist/components/auth/magic-link-button.d.ts +0 -13
  154. package/dist/components/auth/magic-link-button.js +0 -16
  155. package/dist/components/auth/magic-link.d.ts +0 -17
  156. package/dist/components/auth/magic-link.js +0 -99
  157. package/dist/components/auth/passkey-button.d.ts +0 -4
  158. package/dist/components/auth/passkey-button.js +0 -21
  159. package/dist/components/email/magic-link-email.d.ts +0 -3
  160. package/dist/components/email/new-device-email.d.ts +0 -3
  161. package/dist/components/email/otp-email.d.ts +0 -3
  162. package/dist/components/settings/account/account-settings.js +0 -23
  163. package/dist/components/settings/account/appearance.js +0 -71
  164. package/dist/components/settings/account/change-avatar.js +0 -89
  165. package/dist/components/settings/account/manage-account.js +0 -48
  166. package/dist/components/settings/account/manage-accounts.js +0 -33
  167. package/dist/components/settings/account/user-profile.js +0 -103
  168. package/dist/components/settings/security/active-session.js +0 -59
  169. package/dist/components/settings/security/change-password.js +0 -189
  170. package/dist/components/settings/security/delete-user.js +0 -92
  171. package/dist/components/settings/security/linked-account.js +0 -56
  172. package/dist/components/settings/security/passkey.js +0 -43
  173. package/dist/components/settings/security/passkeys.js +0 -53
  174. package/dist/components/settings/security/security-settings.js +0 -25
  175. package/dist/components/settings/settings.js +0 -45
  176. package/dist/components/user/switch-account-item.js +0 -20
  177. package/dist/components/user/switch-account-menu.js +0 -28
  178. package/dist/components/user/user-avatar.js +0 -29
  179. package/dist/components/user/user-button.js +0 -91
  180. package/dist/components/user/user-view.js +0 -37
  181. package/dist/core.d.ts +0 -1
  182. package/dist/core.js +0 -1
  183. package/dist/react-exports.d.ts +0 -1
  184. package/dist/react-exports.js +0 -1
  185. package/src/components/auth/magic-link-button.tsx +0 -43
  186. package/src/components/auth/passkey-button.tsx +0 -35
  187. package/src/components/settings/account/user-profile.tsx +0 -217
  188. package/src/components/settings/security/passkeys.tsx +0 -96
  189. package/src/core.ts +0 -1
  190. package/src/react-exports.ts +0 -1
  191. /package/dist/components/{settings/security → auth/delete-user}/delete-user.d.ts +0 -0
  192. /package/dist/components/{settings/account → auth/multi-session}/manage-accounts.d.ts +0 -0
  193. /package/dist/components/{settings/security → auth/passkey}/passkey.d.ts +0 -0
  194. /package/dist/components/{settings/security → auth/passkey}/passkeys.d.ts +0 -0
  195. /package/dist/components/{settings → auth/settings}/account/change-avatar.d.ts +0 -0
  196. /package/dist/components/{settings → auth/settings}/account/change-email.d.ts +0 -0
  197. /package/dist/components/{settings → auth/settings}/security/active-session.d.ts +0 -0
  198. /package/dist/components/{settings → auth/settings}/security/active-sessions.d.ts +0 -0
  199. /package/dist/components/{settings → auth/settings}/security/change-password.d.ts +0 -0
  200. /package/dist/components/{settings → auth/settings}/security/linked-account.d.ts +0 -0
  201. /package/dist/components/{settings → auth/settings}/security/linked-accounts.d.ts +0 -0
  202. /package/dist/components/{settings → auth/settings}/settings.d.ts +0 -0
  203. /package/dist/components/{user → auth/user}/user-avatar.d.ts +0 -0
  204. /package/dist/components/{user → auth/user}/user-view.d.ts +0 -0
@@ -1,8 +1,8 @@
1
1
  import type { AuthView } from "@better-auth-ui/core"
2
2
  import { useAuth } from "@better-auth-ui/react"
3
3
  import type { CardProps } from "@heroui/react"
4
+ import { type ComponentType, useEffect } from "react"
4
5
  import { ForgotPassword } from "./forgot-password"
5
- import { MagicLink } from "./magic-link"
6
6
  import type { SocialLayout } from "./provider-buttons"
7
7
  import { ResetPassword } from "./reset-password"
8
8
  import { SignIn } from "./sign-in"
@@ -19,6 +19,21 @@ export type AuthProps = {
19
19
  view?: AuthView
20
20
  }
21
21
 
22
+ /**
23
+ * Built-in views that only make sense when email + password auth is enabled.
24
+ * When it's disabled, the `<Auth>` router redirects these to `signIn` so a
25
+ * plugin's `fallbackViews.auth.signIn` (e.g. magic link) takes over.
26
+ */
27
+ const PASSWORD_ONLY_VIEWS = ["signUp", "forgotPassword", "resetPassword"]
28
+
29
+ const AUTH_VIEWS: Partial<Record<AuthView, ComponentType<AuthProps>>> = {
30
+ signIn: SignIn,
31
+ signOut: SignOut,
32
+ signUp: SignUp,
33
+ forgotPassword: ForgotPassword,
34
+ resetPassword: ResetPassword
35
+ }
36
+
22
37
  /**
23
38
  * Render the appropriate authentication view based on the provided `view` or `path`.
24
39
  *
@@ -36,52 +51,101 @@ export function Auth({
36
51
  view,
37
52
  ...props
38
53
  }: AuthProps & Omit<CardProps, "children">) {
39
- const { viewPaths } = useAuth()
54
+ const { basePaths, emailAndPassword, plugins, viewPaths, navigate } =
55
+ useAuth()
40
56
 
41
57
  if (!view && !path) {
42
58
  throw new Error("[Better Auth UI] Either `view` or `path` must be provided")
43
59
  }
44
60
 
45
- const authPathViews = Object.fromEntries(
46
- Object.entries(viewPaths.auth).map(([k, v]) => [v, k])
47
- ) as Record<string, AuthView>
61
+ const authView =
62
+ view ||
63
+ (Object.keys(viewPaths.auth) as AuthView[]).find(
64
+ (key) => viewPaths.auth[key] === path
65
+ )
48
66
 
49
- const currentView = view || (path ? authPathViews[path] : undefined)
67
+ // When email + password auth is disabled, password-only views (signUp,
68
+ // forgotPassword, resetPassword) have no meaning. Redirect them to signIn,
69
+ // where a plugin's `fallbackViews.auth.signIn` (e.g. magic link) takes
70
+ // over as the primary entry point.
71
+ const shouldRedirectToSignIn =
72
+ !emailAndPassword?.enabled &&
73
+ authView &&
74
+ PASSWORD_ONLY_VIEWS.includes(authView)
50
75
 
51
- switch (currentView) {
52
- case "signIn":
53
- return (
54
- <SignIn
55
- socialLayout={socialLayout}
56
- socialPosition={socialPosition}
57
- {...props}
58
- />
59
- )
60
- case "signUp":
61
- return (
62
- <SignUp
63
- socialLayout={socialLayout}
64
- socialPosition={socialPosition}
65
- {...props}
66
- />
67
- )
68
- case "magicLink":
76
+ useEffect(() => {
77
+ if (shouldRedirectToSignIn) {
78
+ navigate({
79
+ to: `${basePaths.auth}/${viewPaths.auth.signIn}`,
80
+ replace: true
81
+ })
82
+ }
83
+ }, [shouldRedirectToSignIn, navigate, basePaths.auth, viewPaths.auth.signIn])
84
+
85
+ if (shouldRedirectToSignIn) {
86
+ return null
87
+ }
88
+
89
+ // 1. Plugin overrides (`views.auth[currentView]`) — first plugin wins,
90
+ // including over built-in views. Resolves the view key from `view`,
91
+ // then `authView` (built-in path match), then plugin-introduced paths
92
+ // (e.g. `magicLink` → `/auth/magic-link`).
93
+ for (const plugin of plugins) {
94
+ const pluginAuthPaths = plugin.viewPaths?.auth
95
+
96
+ const pluginView =
97
+ view ??
98
+ authView ??
99
+ (pluginAuthPaths &&
100
+ Object.keys(pluginAuthPaths).find(
101
+ (key) => pluginAuthPaths[key] === path
102
+ ))
103
+ if (!pluginView) continue
104
+
105
+ const PluginView = plugin.views?.auth?.[pluginView]
106
+ if (!PluginView) continue
107
+
108
+ return (
109
+ <PluginView
110
+ socialLayout={socialLayout}
111
+ socialPosition={socialPosition}
112
+ {...props}
113
+ />
114
+ )
115
+ }
116
+
117
+ // 2. Plugin fallbacks — only when the built-in `signIn` isn't viable
118
+ // (password auth is off). Used by `magicLinkPlugin` to render the
119
+ // magic-link form as the primary passwordless sign-in surface.
120
+ if (authView === "signIn" && !emailAndPassword?.enabled) {
121
+ const Fallback = plugins.find(
122
+ (plugin) => plugin.fallbackViews?.auth?.signIn
123
+ )?.fallbackViews?.auth?.signIn
124
+
125
+ if (Fallback) {
69
126
  return (
70
- <MagicLink
127
+ <Fallback
71
128
  socialLayout={socialLayout}
72
129
  socialPosition={socialPosition}
73
130
  {...props}
74
131
  />
75
132
  )
76
- case "forgotPassword":
77
- return <ForgotPassword {...props} />
78
- case "resetPassword":
79
- return <ResetPassword {...props} />
80
- case "signOut":
81
- return <SignOut {...props} />
82
- default:
83
- throw new Error(
84
- `[Better Auth UI] Valid views are: ${Object.keys(viewPaths.auth).join(", ")}`
85
- )
133
+ }
86
134
  }
135
+
136
+ const AuthView = authView ? AUTH_VIEWS[authView] : undefined
137
+
138
+ if (!AuthView) {
139
+ throw new Error(
140
+ `[Better Auth UI] Unknown view "${authView}". Valid views are: ${Object.keys(AUTH_VIEWS).join(", ")}`
141
+ )
142
+ }
143
+
144
+ return (
145
+ <AuthView
146
+ socialLayout={socialLayout}
147
+ socialPosition={socialPosition}
148
+ {...props}
149
+ />
150
+ )
87
151
  }
@@ -11,7 +11,7 @@ export type DangerZoneProps = {
11
11
 
12
12
  /**
13
13
  * Renders the danger zone heading and {@link DeleteUser}.
14
- * Gate with `deleteUser.enabled` at the call site (e.g. {@link SecuritySettings}).
14
+ * Registered as a `securityCard` by `deleteUserPlugin()`; gate by registering the plugin.
15
15
  */
16
16
  export function DangerZone({
17
17
  className,
@@ -1,4 +1,10 @@
1
- import { useAuth, useDeleteUser, useListAccounts } from "@better-auth-ui/react"
1
+ import { authQueryKeys } from "@better-auth-ui/core"
2
+ import {
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useDeleteUser,
6
+ useListAccounts
7
+ } from "@better-auth-ui/react"
2
8
  import { TriangleExclamation } from "@gravity-ui/icons"
3
9
  import {
4
10
  AlertDialog,
@@ -14,8 +20,11 @@ import {
14
20
  TextField,
15
21
  toast
16
22
  } from "@heroui/react"
23
+ import { useQueryClient } from "@tanstack/react-query"
17
24
  import { type SyntheticEvent, useState } from "react"
18
25
 
26
+ import { deleteUserPlugin } from "../../../lib/auth/delete-user-plugin"
27
+
19
28
  export type DeleteUserProps = {
20
29
  className?: string
21
30
  variant?: CardProps["variant"]
@@ -29,15 +38,16 @@ export function DeleteUser({
29
38
  variant,
30
39
  ...props
31
40
  }: DeleteUserProps & Omit<CardProps, "children">) {
41
+ const { authClient, basePaths, localization, navigate, viewPaths } = useAuth()
42
+
32
43
  const {
33
- basePaths,
34
- deleteUser: deleteUserConfig,
35
- localization,
36
- navigate,
37
- viewPaths
38
- } = useAuth()
44
+ localization: deleteUserLocalization,
45
+ sendDeleteAccountVerification
46
+ } = useAuthPlugin(deleteUserPlugin)
47
+
48
+ const { data: accounts } = useListAccounts(authClient)
39
49
 
40
- const { data: accounts } = useListAccounts()
50
+ const queryClient = useQueryClient()
41
51
 
42
52
  const [confirmOpen, setConfirmOpen] = useState(false)
43
53
  const [password, setPassword] = useState("")
@@ -45,10 +55,9 @@ export function DeleteUser({
45
55
  const hasCredentialAccount = accounts?.some(
46
56
  (account) => account.providerId === "credential"
47
57
  )
48
- const needsPassword =
49
- !deleteUserConfig?.sendDeleteAccountVerification && hasCredentialAccount
58
+ const needsPassword = !sendDeleteAccountVerification && hasCredentialAccount
50
59
 
51
- const { mutate: deleteUser, isPending } = useDeleteUser()
60
+ const { mutate: deleteUser, isPending } = useDeleteUser(authClient)
52
61
 
53
62
  const handleDialogOpenChange = (open: boolean) => {
54
63
  setConfirmOpen(open)
@@ -67,10 +76,11 @@ export function DeleteUser({
67
76
  setConfirmOpen(false)
68
77
  setPassword("")
69
78
 
70
- if (deleteUserConfig?.sendDeleteAccountVerification) {
71
- toast.success(localization.settings.deleteUserVerificationSent)
79
+ if (sendDeleteAccountVerification) {
80
+ toast.success(deleteUserLocalization.deleteUserVerificationSent)
72
81
  } else {
73
- toast.success(localization.settings.deleteUserSuccess)
82
+ toast.success(deleteUserLocalization.deleteUserSuccess)
83
+ queryClient.removeQueries({ queryKey: authQueryKeys.all })
74
84
  navigate({
75
85
  to: `${basePaths.auth}/${viewPaths.auth.signIn}`,
76
86
  replace: true
@@ -89,11 +99,11 @@ export function DeleteUser({
89
99
  <Card.Content className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
90
100
  <div>
91
101
  <p className="text-sm font-medium leading-tight">
92
- {localization.settings.deleteUser}
102
+ {deleteUserLocalization.deleteUser}
93
103
  </p>
94
104
 
95
105
  <p className="text-muted text-xs mt-0.5">
96
- {localization.settings.deleteUserDescription}
106
+ {deleteUserLocalization.deleteUserDescription}
97
107
  </p>
98
108
  </div>
99
109
 
@@ -104,7 +114,7 @@ export function DeleteUser({
104
114
  variant="danger"
105
115
  onPress={() => setConfirmOpen(true)}
106
116
  >
107
- {localization.settings.deleteUser}
117
+ {deleteUserLocalization.deleteUser}
108
118
  </Button>
109
119
 
110
120
  <AlertDialog.Backdrop
@@ -122,13 +132,13 @@ export function DeleteUser({
122
132
  </AlertDialog.Icon>
123
133
 
124
134
  <AlertDialog.Heading>
125
- {localization.settings.deleteUser}
135
+ {deleteUserLocalization.deleteUser}
126
136
  </AlertDialog.Heading>
127
137
  </AlertDialog.Header>
128
138
 
129
139
  <AlertDialog.Body className="overflow-visible">
130
140
  <p className="text-muted text-sm">
131
- {localization.settings.deleteUserDescription}
141
+ {deleteUserLocalization.deleteUserDescription}
132
142
  </p>
133
143
 
134
144
  {needsPassword && (
@@ -170,7 +180,7 @@ export function DeleteUser({
170
180
  >
171
181
  {isPending && <Spinner color="current" size="sm" />}
172
182
 
173
- {localization.settings.deleteUser}
183
+ {deleteUserLocalization.deleteUser}
174
184
  </Button>
175
185
  </AlertDialog.Footer>
176
186
  </Form>
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  EmailChangedEmail as EmailChangedEmailPrimitive,
3
3
  type EmailChangedEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { EmailChangedEmailProps } from "@better-auth-ui/react"
7
+ export type { EmailChangedEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function EmailChangedEmail({
10
10
  colors,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  EmailVerificationEmail as EmailVerificationEmailPrimitive,
3
3
  type EmailVerificationEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { EmailVerificationEmailProps } from "@better-auth-ui/react"
7
+ export type { EmailVerificationEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function EmailVerificationEmail({
10
10
  colors,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MagicLinkEmail as MagicLinkEmailPrimitive,
3
3
  type MagicLinkEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { MagicLinkEmailProps } from "@better-auth-ui/react"
7
+ export type { MagicLinkEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function MagicLinkEmail({
10
10
  colors,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  NewDeviceEmail as NewDeviceEmailPrimitive,
3
3
  type NewDeviceEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { NewDeviceEmailProps } from "@better-auth-ui/react"
7
+ export type { NewDeviceEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function NewDeviceEmail({
10
10
  colors,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  OtpEmail as OtpEmailPrimitive,
3
3
  type OtpEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { OtpEmailProps } from "@better-auth-ui/react"
7
+ export type { OtpEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function OtpEmail({ colors, classNames, ...props }: OtpEmailProps) {
10
10
  return (
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  PasswordChangedEmail as PasswordChangedEmailPrimitive,
3
3
  type PasswordChangedEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { PasswordChangedEmailProps } from "@better-auth-ui/react"
7
+ export type { PasswordChangedEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function PasswordChangedEmail({
10
10
  colors,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ResetPasswordEmail as ResetPasswordEmailPrimitive,
3
3
  type ResetPasswordEmailProps
4
- } from "@better-auth-ui/react"
4
+ } from "@better-auth-ui/react/email"
5
5
  import { cn } from "@heroui/react"
6
6
 
7
- export type { ResetPasswordEmailProps } from "@better-auth-ui/react"
7
+ export type { ResetPasswordEmailProps } from "@better-auth-ui/react/email"
8
8
 
9
9
  export function ResetPasswordEmail({
10
10
  colors,
@@ -1,4 +1,8 @@
1
- import { useAuth, useRequestPasswordReset } from "@better-auth-ui/react"
1
+ import {
2
+ useAuth,
3
+ useFetchOptions,
4
+ useRequestPasswordReset
5
+ } from "@better-auth-ui/react"
2
6
  import {
3
7
  Button,
4
8
  Card,
@@ -25,7 +29,7 @@ export type ForgotPasswordProps = {
25
29
  * Render a card-based "Forgot Password" form that sends a password-reset email.
26
30
  *
27
31
  * The form displays an email input, submit button, and a link back to sign-in.
28
- * Toasts are displayed on success or error via the `useForgotPassword` hook.
32
+ * Toasts are displayed on success or error via the `useRequestPasswordReset` hook.
29
33
  *
30
34
  * @param className - Optional additional CSS class names applied to the card
31
35
  * @returns The forgot-password form UI as a JSX element
@@ -35,25 +39,42 @@ export function ForgotPassword({
35
39
  variant,
36
40
  ...props
37
41
  }: ForgotPasswordProps & Omit<CardProps, "children">) {
38
- const { basePaths, localization, viewPaths, navigate } = useAuth()
42
+ const { authClient, basePaths, localization, viewPaths, navigate, plugins } =
43
+ useAuth()
44
+
45
+ const { fetchOptions, resetFetchOptions } = useFetchOptions()
39
46
 
40
- const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset({
41
- onSuccess: () => {
42
- toast.success(localization.auth.passwordResetEmailSent)
43
- navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
47
+ const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset(
48
+ authClient,
49
+ {
50
+ onError: (error) => {
51
+ toast.danger(error.error?.message || error.message)
52
+ resetFetchOptions()
53
+ },
54
+ onSuccess: () => {
55
+ toast.success(localization.auth.passwordResetEmailSent)
56
+ navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
57
+ }
44
58
  }
45
- })
59
+ )
46
60
 
47
61
  function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
48
62
  e.preventDefault()
49
63
 
50
64
  const formData = new FormData(e.currentTarget)
51
- requestPasswordReset({ email: formData.get("email") as string })
65
+ requestPasswordReset({
66
+ email: formData.get("email") as string,
67
+ fetchOptions
68
+ })
52
69
  }
53
70
 
71
+ const Captcha = plugins.find(
72
+ (plugin) => plugin.captchaComponent
73
+ )?.captchaComponent
74
+
54
75
  return (
55
76
  <Card
56
- className={cn("w-full max-w-sm p-4 md:p-6", className)}
77
+ className={cn("w-full max-w-sm gap-4 md:p-6", className)}
57
78
  variant={variant}
58
79
  {...props}
59
80
  >
@@ -63,7 +84,7 @@ export function ForgotPassword({
63
84
  </Card.Title>
64
85
  </Card.Header>
65
86
 
66
- <Card.Content>
87
+ <Card.Content className="gap-4">
67
88
  <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
68
89
  <TextField
69
90
  name="email"
@@ -82,20 +103,24 @@ export function ForgotPassword({
82
103
  <FieldError />
83
104
  </TextField>
84
105
 
85
- <Button type="submit" className="w-full" isPending={isPending}>
86
- {isPending && <Spinner color="current" size="sm" />}
106
+ {Captcha && <div className="flex justify-center">{Captcha}</div>}
107
+
108
+ <div className="flex flex-col gap-3">
109
+ <Button type="submit" className="w-full" isPending={isPending}>
110
+ {isPending && <Spinner color="current" size="sm" />}
87
111
 
88
- {localization.auth.sendResetLink}
89
- </Button>
112
+ {localization.auth.sendResetLink}
113
+ </Button>
114
+ </div>
90
115
  </Form>
91
116
  </Card.Content>
92
117
 
93
- <Card.Footer className="flex-col">
118
+ <Card.Footer className="flex-col gap-3">
94
119
  <Description className="text-sm">
95
120
  {localization.auth.rememberYourPassword}{" "}
96
121
  <Link
97
122
  href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
98
- className="text-accent decoration-accent no-underline hover:underline"
123
+ className="text-accent no-underline hover:underline decoration-accent-hover"
99
124
  >
100
125
  {localization.auth.signIn}
101
126
  </Link>
@@ -0,0 +1,61 @@
1
+ import { type AuthView, authMutationKeys } from "@better-auth-ui/core"
2
+ import { useAuth, useAuthPlugin } from "@better-auth-ui/react"
3
+ import { Envelope, Lock } from "@gravity-ui/icons"
4
+ import { cn, Link } from "@heroui/react"
5
+ import { buttonVariants } from "@heroui/styles"
6
+ import { useIsMutating } from "@tanstack/react-query"
7
+
8
+ import { magicLinkPlugin } from "../../../lib/auth/magic-link-plugin"
9
+
10
+ export type MagicLinkButtonProps = {
11
+ /** @remarks `AuthView` */
12
+ view?: AuthView
13
+ }
14
+
15
+ /**
16
+ * Toggle button between the password sign-in and magic-link routes.
17
+ *
18
+ * @param view - Current auth view. On `"magicLink"` this links back to password sign-in.
19
+ */
20
+ export function MagicLinkButton({ view }: MagicLinkButtonProps) {
21
+ const { basePaths, emailAndPassword, viewPaths, localization } = useAuth()
22
+ const signInMutating = useIsMutating({
23
+ mutationKey: authMutationKeys.signIn.all
24
+ })
25
+ const signUpMutating = useIsMutating({
26
+ mutationKey: authMutationKeys.signUp.all
27
+ })
28
+ const isPending = signInMutating + signUpMutating > 0
29
+
30
+ const { localization: magicLinkLocalization, viewPaths: magicLinkViewPaths } =
31
+ useAuthPlugin(magicLinkPlugin)
32
+
33
+ const isMagicLinkView = view === "magicLink"
34
+
35
+ // On the magic-link view this button switches back to password sign-in.
36
+ // With password auth disabled there's nowhere to switch to, so hide it.
37
+ // (Other views — e.g. a phone-number plugin's surface — still get a
38
+ // "Continue with Magic Link" link.)
39
+ if (isMagicLinkView && !emailAndPassword?.enabled) return null
40
+
41
+ return (
42
+ <Link
43
+ href={`${basePaths.auth}/${isMagicLinkView ? viewPaths.auth.signIn : magicLinkViewPaths.auth.magicLink}`}
44
+ isDisabled={isPending}
45
+ className={cn(
46
+ buttonVariants({ variant: "tertiary" }),
47
+ "w-full gap-2",
48
+ isPending && "status-disabled"
49
+ )}
50
+ >
51
+ {isMagicLinkView ? <Lock /> : <Envelope />}
52
+
53
+ {localization.auth.continueWith.replace(
54
+ "{{provider}}",
55
+ isMagicLinkView
56
+ ? localization.auth.password
57
+ : magicLinkLocalization.magicLink
58
+ )}
59
+ </Link>
60
+ )
61
+ }