@better-auth-ui/heroui 1.6.2 → 1.6.4

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 (213) 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-provider.js +12 -11
  5. package/dist/components/auth/auth.d.ts +1 -1
  6. package/dist/components/auth/auth.js +54 -28
  7. package/dist/components/{settings/security → auth/delete-user}/danger-zone.d.ts +3 -4
  8. package/dist/components/{settings/security → auth/delete-user}/danger-zone.js +1 -1
  9. package/dist/components/{settings/security → auth/delete-user}/delete-user.d.ts +1 -1
  10. package/dist/components/auth/delete-user/delete-user.js +95 -0
  11. package/dist/components/{email → auth/email}/email-changed-email.d.ts +2 -2
  12. package/dist/components/{email → auth/email}/email-changed-email.js +7 -7
  13. package/dist/components/{email → auth/email}/email-verification-email.d.ts +2 -2
  14. package/dist/components/{email → auth/email}/email-verification-email.js +7 -7
  15. package/dist/components/auth/email/magic-link-email.d.ts +3 -0
  16. package/dist/components/{email → auth/email}/magic-link-email.js +7 -7
  17. package/dist/components/auth/email/new-device-email.d.ts +3 -0
  18. package/dist/components/{email → auth/email}/new-device-email.js +7 -7
  19. package/dist/components/auth/email/otp-email.d.ts +3 -0
  20. package/dist/components/{email → auth/email}/otp-email.js +6 -6
  21. package/dist/components/{email → auth/email}/password-changed-email.d.ts +2 -2
  22. package/dist/components/{email → auth/email}/password-changed-email.js +7 -7
  23. package/dist/components/{email → auth/email}/reset-password-email.d.ts +2 -2
  24. package/dist/components/{email → auth/email}/reset-password-email.js +7 -7
  25. package/dist/components/auth/error-toaster.d.ts +1 -0
  26. package/dist/components/auth/error-toaster.js +17 -0
  27. package/dist/components/auth/forgot-password.d.ts +2 -2
  28. package/dist/components/auth/forgot-password.js +42 -42
  29. package/dist/components/auth/magic-link/magic-link-button.d.ts +11 -0
  30. package/dist/components/auth/magic-link/magic-link-button.js +20 -0
  31. package/dist/components/auth/magic-link/magic-link.d.ts +16 -0
  32. package/dist/components/auth/magic-link/magic-link.js +87 -0
  33. package/dist/components/{settings/account → auth/multi-session}/manage-account.d.ts +2 -6
  34. package/dist/components/auth/multi-session/manage-account.js +49 -0
  35. package/dist/components/{settings/account → auth/multi-session}/manage-accounts.d.ts +1 -1
  36. package/dist/components/auth/multi-session/manage-accounts.js +34 -0
  37. package/dist/components/{user/switch-account-menu.d.ts → auth/multi-session/switch-account-submenu-content.d.ts} +3 -3
  38. package/dist/components/auth/multi-session/switch-account-submenu-content.js +29 -0
  39. package/dist/components/{user/switch-account-item.d.ts → auth/multi-session/switch-account-submenu-item.d.ts} +2 -2
  40. package/dist/components/auth/multi-session/switch-account-submenu-item.js +20 -0
  41. package/dist/components/auth/multi-session/switch-account-submenu.d.ts +13 -0
  42. package/dist/components/auth/multi-session/switch-account-submenu.js +24 -0
  43. package/dist/components/auth/passkey/passkey-button.d.ts +6 -0
  44. package/dist/components/auth/passkey/passkey-button.js +24 -0
  45. package/dist/components/auth/passkey/passkey.js +45 -0
  46. package/dist/components/{settings/security → auth/passkey}/passkeys.d.ts +1 -1
  47. package/dist/components/auth/passkey/passkeys.js +106 -0
  48. package/dist/components/auth/provider-button.d.ts +13 -0
  49. package/dist/components/auth/provider-button.js +25 -0
  50. package/dist/components/auth/provider-buttons.d.ts +3 -10
  51. package/dist/components/auth/provider-buttons.js +14 -22
  52. package/dist/components/auth/reset-password.d.ts +1 -1
  53. package/dist/components/auth/reset-password.js +110 -110
  54. package/dist/components/{settings → auth/settings}/account/account-settings.d.ts +5 -2
  55. package/dist/components/auth/settings/account/account-settings.js +20 -0
  56. package/dist/components/auth/settings/account/change-avatar.js +89 -0
  57. package/dist/components/{settings → auth/settings}/account/change-email.d.ts +1 -1
  58. package/dist/components/{settings → auth/settings}/account/change-email.js +17 -20
  59. package/dist/components/{settings → auth/settings}/account/user-profile.d.ts +3 -4
  60. package/dist/components/auth/settings/account/user-profile.js +86 -0
  61. package/dist/components/auth/settings/security/active-session.js +59 -0
  62. package/dist/components/{settings → auth/settings}/security/active-sessions.d.ts +1 -1
  63. package/dist/components/auth/settings/security/active-sessions.js +34 -0
  64. package/dist/components/{settings → auth/settings}/security/change-password.d.ts +1 -1
  65. package/dist/components/auth/settings/security/change-password.js +189 -0
  66. package/dist/components/auth/settings/security/linked-account.js +56 -0
  67. package/dist/components/{settings → auth/settings}/security/linked-accounts.d.ts +1 -1
  68. package/dist/components/auth/settings/security/linked-accounts.js +45 -0
  69. package/dist/components/{settings → auth/settings}/security/security-settings.d.ts +1 -1
  70. package/dist/components/auth/settings/security/security-settings.js +22 -0
  71. package/dist/components/auth/settings/settings.js +45 -0
  72. package/dist/components/auth/sign-in.d.ts +1 -1
  73. package/dist/components/auth/sign-in.js +64 -100
  74. package/dist/components/auth/sign-out.js +11 -11
  75. package/dist/components/auth/sign-up.d.ts +1 -1
  76. package/dist/components/auth/sign-up.js +123 -157
  77. package/dist/components/{settings/account → auth/theme}/appearance.d.ts +3 -6
  78. package/dist/components/auth/theme/appearance.js +72 -0
  79. package/dist/components/auth/theme/theme-toggle-item.d.ts +5 -0
  80. package/dist/components/auth/theme/theme-toggle-item.js +58 -0
  81. package/dist/components/auth/user/user-avatar.js +29 -0
  82. package/dist/components/{user → auth/user}/user-button.d.ts +2 -6
  83. package/dist/components/auth/user/user-button.js +55 -0
  84. package/dist/components/auth/user/user-view.js +37 -0
  85. package/dist/components/auth/username/sign-in-username.d.ts +14 -0
  86. package/dist/components/auth/username/sign-in-username.js +142 -0
  87. package/dist/components/auth/username/username-field.d.ts +8 -0
  88. package/dist/components/auth/username/username-field.js +51 -0
  89. package/dist/email.d.ts +7 -0
  90. package/dist/email.js +9 -0
  91. package/dist/index.d.ts +13 -27
  92. package/dist/index.js +22 -36
  93. package/dist/lib/auth/auth-plugin.d.ts +44 -0
  94. package/dist/lib/auth/delete-user-plugin.d.ts +17 -0
  95. package/dist/lib/auth/delete-user-plugin.js +10 -0
  96. package/dist/lib/auth/magic-link-plugin.d.ts +31 -0
  97. package/dist/lib/auth/magic-link-plugin.js +13 -0
  98. package/dist/lib/auth/multi-session-plugin.d.ts +18 -0
  99. package/dist/lib/auth/multi-session-plugin.js +12 -0
  100. package/dist/lib/auth/passkey-plugin.d.ts +20 -0
  101. package/dist/lib/auth/passkey-plugin.js +12 -0
  102. package/dist/lib/auth/theme-plugin.d.ts +68 -0
  103. package/dist/lib/auth/theme-plugin.js +23 -0
  104. package/dist/lib/auth/username-plugin.d.ts +63 -0
  105. package/dist/lib/auth/username-plugin.js +18 -0
  106. package/dist/plugins.d.ts +20 -0
  107. package/dist/plugins.js +21 -0
  108. package/package.json +22 -22
  109. package/src/components/auth/additional-field.tsx +554 -0
  110. package/src/components/auth/auth-provider.tsx +8 -5
  111. package/src/components/auth/auth.tsx +100 -36
  112. package/src/components/{settings/security → auth/delete-user}/danger-zone.tsx +3 -10
  113. package/src/components/{settings/security → auth/delete-user}/delete-user.tsx +31 -24
  114. package/src/components/{email → auth/email}/email-changed-email.tsx +2 -2
  115. package/src/components/{email → auth/email}/email-verification-email.tsx +2 -2
  116. package/src/components/{email → auth/email}/magic-link-email.tsx +2 -2
  117. package/src/components/{email → auth/email}/new-device-email.tsx +2 -2
  118. package/src/components/{email → auth/email}/otp-email.tsx +2 -2
  119. package/src/components/{email → auth/email}/password-changed-email.tsx +2 -2
  120. package/src/components/{email → auth/email}/reset-password-email.tsx +2 -2
  121. package/src/components/auth/error-toaster.tsx +25 -0
  122. package/src/components/auth/forgot-password.tsx +15 -13
  123. package/src/components/auth/magic-link/magic-link-button.tsx +61 -0
  124. package/src/components/auth/{magic-link.tsx → magic-link/magic-link.tsx} +52 -57
  125. package/src/components/{settings/account → auth/multi-session}/manage-account.tsx +15 -15
  126. package/src/components/{settings/account → auth/multi-session}/manage-accounts.tsx +16 -12
  127. package/src/components/{user/switch-account-menu.tsx → auth/multi-session/switch-account-submenu-content.tsx} +19 -17
  128. package/src/components/{user/switch-account-item.tsx → auth/multi-session/switch-account-submenu-item.tsx} +18 -8
  129. package/src/components/auth/multi-session/switch-account-submenu.tsx +50 -0
  130. package/src/components/auth/passkey/passkey-button.tsx +56 -0
  131. package/src/components/{settings/security → auth/passkey}/passkey.tsx +21 -9
  132. package/src/components/auth/passkey/passkeys.tsx +196 -0
  133. package/src/components/auth/provider-button.tsx +65 -0
  134. package/src/components/auth/provider-buttons.tsx +23 -45
  135. package/src/components/auth/reset-password.tsx +14 -9
  136. package/src/components/{settings → auth/settings}/account/account-settings.tsx +15 -13
  137. package/src/components/{settings → auth/settings}/account/change-avatar.tsx +4 -5
  138. package/src/components/{settings → auth/settings}/account/change-email.tsx +5 -6
  139. package/src/components/auth/settings/account/user-profile.tsx +171 -0
  140. package/src/components/{settings → auth/settings}/security/active-session.tsx +8 -6
  141. package/src/components/{settings → auth/settings}/security/active-sessions.tsx +5 -10
  142. package/src/components/{settings → auth/settings}/security/change-password.tsx +22 -14
  143. package/src/components/{settings → auth/settings}/security/linked-account.tsx +10 -16
  144. package/src/components/{settings → auth/settings}/security/linked-accounts.tsx +4 -9
  145. package/src/components/{settings → auth/settings}/security/security-settings.tsx +7 -6
  146. package/src/components/{settings → auth/settings}/settings.tsx +2 -2
  147. package/src/components/auth/sign-in.tsx +39 -80
  148. package/src/components/auth/sign-out.tsx +2 -2
  149. package/src/components/auth/sign-up.tsx +110 -147
  150. package/src/components/{settings/account → auth/theme}/appearance.tsx +18 -25
  151. package/src/components/auth/theme/theme-toggle-item.tsx +110 -0
  152. package/src/components/{user → auth/user}/user-avatar.tsx +12 -4
  153. package/src/components/{user → auth/user}/user-button.tsx +15 -88
  154. package/src/components/{user → auth/user}/user-view.tsx +12 -4
  155. package/src/components/auth/username/sign-in-username.tsx +283 -0
  156. package/src/components/auth/username/username-field.tsx +122 -0
  157. package/src/email.ts +9 -0
  158. package/src/index.tsx +13 -27
  159. package/src/lib/auth/auth-plugin.ts +67 -0
  160. package/src/lib/auth/delete-user-plugin.ts +15 -0
  161. package/src/lib/auth/magic-link-plugin.ts +26 -0
  162. package/src/lib/auth/multi-session-plugin.ts +17 -0
  163. package/src/lib/auth/passkey-plugin.ts +17 -0
  164. package/src/lib/auth/theme-plugin.ts +86 -0
  165. package/src/lib/auth/username-plugin.tsx +30 -0
  166. package/src/plugins.ts +23 -0
  167. package/dist/components/auth/magic-link-button.d.ts +0 -13
  168. package/dist/components/auth/magic-link-button.js +0 -16
  169. package/dist/components/auth/magic-link.d.ts +0 -17
  170. package/dist/components/auth/magic-link.js +0 -111
  171. package/dist/components/auth/passkey-button.d.ts +0 -4
  172. package/dist/components/auth/passkey-button.js +0 -24
  173. package/dist/components/email/magic-link-email.d.ts +0 -3
  174. package/dist/components/email/new-device-email.d.ts +0 -3
  175. package/dist/components/email/otp-email.d.ts +0 -3
  176. package/dist/components/settings/account/account-settings.js +0 -23
  177. package/dist/components/settings/account/appearance.js +0 -71
  178. package/dist/components/settings/account/change-avatar.js +0 -89
  179. package/dist/components/settings/account/manage-account.js +0 -51
  180. package/dist/components/settings/account/manage-accounts.js +0 -33
  181. package/dist/components/settings/account/user-profile.js +0 -106
  182. package/dist/components/settings/security/active-session.js +0 -62
  183. package/dist/components/settings/security/active-sessions.js +0 -34
  184. package/dist/components/settings/security/change-password.js +0 -192
  185. package/dist/components/settings/security/delete-user.js +0 -97
  186. package/dist/components/settings/security/linked-account.js +0 -62
  187. package/dist/components/settings/security/linked-accounts.js +0 -45
  188. package/dist/components/settings/security/passkey.js +0 -43
  189. package/dist/components/settings/security/passkeys.js +0 -53
  190. package/dist/components/settings/security/security-settings.js +0 -25
  191. package/dist/components/settings/settings.js +0 -45
  192. package/dist/components/user/switch-account-item.js +0 -20
  193. package/dist/components/user/switch-account-menu.js +0 -28
  194. package/dist/components/user/user-avatar.js +0 -29
  195. package/dist/components/user/user-button.js +0 -91
  196. package/dist/components/user/user-view.js +0 -37
  197. package/dist/core.d.ts +0 -1
  198. package/dist/core.js +0 -1
  199. package/dist/react-exports.d.ts +0 -1
  200. package/dist/react-exports.js +0 -1
  201. package/src/components/auth/magic-link-button.tsx +0 -43
  202. package/src/components/auth/passkey-button.tsx +0 -36
  203. package/src/components/settings/account/user-profile.tsx +0 -218
  204. package/src/components/settings/security/passkeys.tsx +0 -104
  205. package/src/core.ts +0 -1
  206. package/src/react-exports.ts +0 -1
  207. /package/dist/components/{settings/security → auth/passkey}/passkey.d.ts +0 -0
  208. /package/dist/components/{settings → auth/settings}/account/change-avatar.d.ts +0 -0
  209. /package/dist/components/{settings → auth/settings}/security/active-session.d.ts +0 -0
  210. /package/dist/components/{settings → auth/settings}/security/linked-account.d.ts +0 -0
  211. /package/dist/components/{settings → auth/settings}/settings.d.ts +0 -0
  212. /package/dist/components/{user → auth/user}/user-avatar.d.ts +0 -0
  213. /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
  *
@@ -35,53 +50,102 @@ export function Auth({
35
50
  socialPosition,
36
51
  view,
37
52
  ...props
38
- }: AuthProps & CardProps) {
39
- const { viewPaths } = useAuth()
53
+ }: AuthProps & Omit<CardProps, "children">) {
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
  }
@@ -1,23 +1,16 @@
1
1
  import { useAuth } from "@better-auth-ui/react"
2
2
  import { type CardProps, cn } from "@heroui/react"
3
3
  import type { ComponentProps } from "react"
4
-
5
4
  import { DeleteUser } from "./delete-user"
6
-
7
- export type DangerZoneProps = {
8
- className?: string
5
+ export type DangerZoneProps = Omit<ComponentProps<"div">, "children"> & {
9
6
  variant?: CardProps["variant"]
10
7
  }
11
8
 
12
9
  /**
13
10
  * Renders the danger zone heading and {@link DeleteUser}.
14
- * Gate with `deleteUser.enabled` at the call site (e.g. {@link SecuritySettings}).
11
+ * Registered as a `securityCard` by `deleteUserPlugin()`; gate by registering the plugin.
15
12
  */
16
- export function DangerZone({
17
- className,
18
- variant,
19
- ...props
20
- }: DangerZoneProps & ComponentProps<"div">) {
13
+ export function DangerZone({ className, variant, ...props }: DangerZoneProps) {
21
14
  const { localization } = useAuth()
22
15
 
23
16
  return (
@@ -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"]
@@ -28,16 +37,17 @@ export function DeleteUser({
28
37
  className,
29
38
  variant,
30
39
  ...props
31
- }: DeleteUserProps & CardProps) {
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,18 +76,16 @@ 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
77
87
  })
78
88
  }
79
- },
80
- onError: (error) => {
81
- toast.danger(error.error?.message || error.message)
82
89
  }
83
90
  })
84
91
  }
@@ -92,11 +99,11 @@ export function DeleteUser({
92
99
  <Card.Content className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
93
100
  <div>
94
101
  <p className="text-sm font-medium leading-tight">
95
- {localization.settings.deleteUser}
102
+ {deleteUserLocalization.deleteUser}
96
103
  </p>
97
104
 
98
105
  <p className="text-muted text-xs mt-0.5">
99
- {localization.settings.deleteUserDescription}
106
+ {deleteUserLocalization.deleteUserDescription}
100
107
  </p>
101
108
  </div>
102
109
 
@@ -107,7 +114,7 @@ export function DeleteUser({
107
114
  variant="danger"
108
115
  onPress={() => setConfirmOpen(true)}
109
116
  >
110
- {localization.settings.deleteUser}
117
+ {deleteUserLocalization.deleteUser}
111
118
  </Button>
112
119
 
113
120
  <AlertDialog.Backdrop
@@ -125,13 +132,13 @@ export function DeleteUser({
125
132
  </AlertDialog.Icon>
126
133
 
127
134
  <AlertDialog.Heading>
128
- {localization.settings.deleteUser}
135
+ {deleteUserLocalization.deleteUser}
129
136
  </AlertDialog.Heading>
130
137
  </AlertDialog.Header>
131
138
 
132
139
  <AlertDialog.Body className="overflow-visible">
133
140
  <p className="text-muted text-sm">
134
- {localization.settings.deleteUserDescription}
141
+ {deleteUserLocalization.deleteUserDescription}
135
142
  </p>
136
143
 
137
144
  {needsPassword && (
@@ -173,7 +180,7 @@ export function DeleteUser({
173
180
  >
174
181
  {isPending && <Spinner color="current" size="sm" />}
175
182
 
176
- {localization.settings.deleteUser}
183
+ {deleteUserLocalization.deleteUser}
177
184
  </Button>
178
185
  </AlertDialog.Footer>
179
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,
@@ -0,0 +1,25 @@
1
+ import { toast } from "@heroui/react"
2
+ import { useQueryClient } from "@tanstack/react-query"
3
+ import type { BetterFetchError } from "better-auth/react"
4
+ import { useEffect } from "react"
5
+
6
+ export function ErrorToaster() {
7
+ const queryClient = useQueryClient()
8
+
9
+ useEffect(() => {
10
+ queryClient.getQueryCache().config.onError = (error) => {
11
+ const err = error as BetterFetchError
12
+ if (err?.error) toast.danger(err.error.message)
13
+ }
14
+
15
+ queryClient.setMutationDefaults([], {
16
+ onError: (error) => {
17
+ toast.danger(
18
+ (error as BetterFetchError)?.error?.message || error.message
19
+ )
20
+ }
21
+ })
22
+ }, [queryClient])
23
+
24
+ return null
25
+ }
@@ -25,7 +25,7 @@ export type ForgotPasswordProps = {
25
25
  * Render a card-based "Forgot Password" form that sends a password-reset email.
26
26
  *
27
27
  * 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.
28
+ * Toasts are displayed on success or error via the `useRequestPasswordReset` hook.
29
29
  *
30
30
  * @param className - Optional additional CSS class names applied to the card
31
31
  * @returns The forgot-password form UI as a JSX element
@@ -34,16 +34,18 @@ export function ForgotPassword({
34
34
  className,
35
35
  variant,
36
36
  ...props
37
- }: ForgotPasswordProps & CardProps) {
38
- const { basePaths, localization, viewPaths, navigate } = useAuth()
37
+ }: ForgotPasswordProps & Omit<CardProps, "children">) {
38
+ const { authClient, basePaths, localization, viewPaths, navigate } = useAuth()
39
39
 
40
- const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset({
41
- onError: (error) => toast.danger(error.error?.message || error.message),
42
- onSuccess: () => {
43
- toast.success(localization.auth.passwordResetEmailSent)
44
- navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
40
+ const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset(
41
+ authClient,
42
+ {
43
+ onSuccess: () => {
44
+ toast.success(localization.auth.passwordResetEmailSent)
45
+ navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
46
+ }
45
47
  }
46
- })
48
+ )
47
49
 
48
50
  function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
49
51
  e.preventDefault()
@@ -54,7 +56,7 @@ export function ForgotPassword({
54
56
 
55
57
  return (
56
58
  <Card
57
- className={cn("w-full max-w-sm p-4 md:p-6", className)}
59
+ className={cn("w-full max-w-sm gap-4 md:p-6", className)}
58
60
  variant={variant}
59
61
  {...props}
60
62
  >
@@ -64,7 +66,7 @@ export function ForgotPassword({
64
66
  </Card.Title>
65
67
  </Card.Header>
66
68
 
67
- <Card.Content>
69
+ <Card.Content className="gap-4">
68
70
  <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
69
71
  <TextField
70
72
  name="email"
@@ -91,12 +93,12 @@ export function ForgotPassword({
91
93
  </Form>
92
94
  </Card.Content>
93
95
 
94
- <Card.Footer className="flex-col">
96
+ <Card.Footer className="flex-col gap-3">
95
97
  <Description className="text-sm">
96
98
  {localization.auth.rememberYourPassword}{" "}
97
99
  <Link
98
100
  href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
99
- className="text-accent decoration-accent no-underline hover:underline"
101
+ className="text-accent no-underline hover:underline decoration-accent-hover"
100
102
  >
101
103
  {localization.auth.signIn}
102
104
  </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
+ }