@better-auth-ui/heroui 1.6.0

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 (253) hide show
  1. package/dist/components/account/account.d.ts +18 -0
  2. package/dist/components/account/account.js +24 -0
  3. package/dist/components/account/settings/account-settings.d.ts +5 -0
  4. package/dist/components/account/settings/account-settings.js +6 -0
  5. package/dist/components/account/settings/settings.d.ts +5 -0
  6. package/dist/components/account/settings/settings.js +5 -0
  7. package/dist/components/account/settings/user-profile.d.ts +5 -0
  8. package/dist/components/account/settings/user-profile.js +20 -0
  9. package/dist/components/auth/auth-provider.d.ts +9 -0
  10. package/dist/components/auth/auth-provider.js +12 -0
  11. package/dist/components/auth/auth.d.ts +23 -0
  12. package/dist/components/auth/auth.js +42 -0
  13. package/dist/components/auth/field-separator.d.ts +4 -0
  14. package/dist/components/auth/field-separator.js +5 -0
  15. package/dist/components/auth/forgot-password.d.ts +15 -0
  16. package/dist/components/auth/forgot-password.js +28 -0
  17. package/dist/components/auth/magic-link-button.d.ts +13 -0
  18. package/dist/components/auth/magic-link-button.js +19 -0
  19. package/dist/components/auth/magic-link.d.ts +17 -0
  20. package/dist/components/auth/magic-link.js +43 -0
  21. package/dist/components/auth/provider-buttons.d.ts +19 -0
  22. package/dist/components/auth/provider-buttons.js +34 -0
  23. package/dist/components/auth/reset-password.d.ts +11 -0
  24. package/dist/components/auth/reset-password.js +58 -0
  25. package/dist/components/auth/sign-in.d.ts +14 -0
  26. package/dist/components/auth/sign-in.js +64 -0
  27. package/dist/components/auth/sign-out.d.ts +9 -0
  28. package/dist/components/auth/sign-out.js +33 -0
  29. package/dist/components/auth/sign-up.d.ts +19 -0
  30. package/dist/components/auth/sign-up.js +71 -0
  31. package/dist/components/email/email-changed-email.d.ts +3 -0
  32. package/dist/components/email/email-changed-email.js +23 -0
  33. package/dist/components/email/email-verification-email.d.ts +3 -0
  34. package/dist/components/email/email-verification-email.js +23 -0
  35. package/dist/components/email/magic-link-email.d.ts +3 -0
  36. package/dist/components/email/magic-link-email.js +23 -0
  37. package/dist/components/email/new-device-email.d.ts +3 -0
  38. package/dist/components/email/new-device-email.js +23 -0
  39. package/dist/components/email/otp-email.d.ts +3 -0
  40. package/dist/components/email/otp-email.js +22 -0
  41. package/dist/components/email/password-changed-email.d.ts +3 -0
  42. package/dist/components/email/password-changed-email.js +23 -0
  43. package/dist/components/email/reset-password-email.d.ts +3 -0
  44. package/dist/components/email/reset-password-email.js +23 -0
  45. package/dist/components/settings/account/account-settings.d.ts +16 -0
  46. package/dist/components/settings/account/account-settings.js +20 -0
  47. package/dist/components/settings/account/accounts.d.ts +14 -0
  48. package/dist/components/settings/account/accounts.js +26 -0
  49. package/dist/components/settings/account/appearance.d.ts +16 -0
  50. package/dist/components/settings/account/appearance.js +28 -0
  51. package/dist/components/settings/account/change-avatar.d.ts +4 -0
  52. package/dist/components/settings/account/change-avatar.js +56 -0
  53. package/dist/components/settings/account/change-email.d.ts +15 -0
  54. package/dist/components/settings/account/change-email.js +29 -0
  55. package/dist/components/settings/account/manage-account.d.ts +19 -0
  56. package/dist/components/settings/account/manage-account.js +32 -0
  57. package/dist/components/settings/account/manage-accounts.d.ts +14 -0
  58. package/dist/components/settings/account/manage-accounts.js +37 -0
  59. package/dist/components/settings/account/theme-selector.d.ts +16 -0
  60. package/dist/components/settings/account/theme-selector.js +33 -0
  61. package/dist/components/settings/account/theme-settings.d.ts +16 -0
  62. package/dist/components/settings/account/theme-settings.js +31 -0
  63. package/dist/components/settings/account/user-profile.d.ts +11 -0
  64. package/dist/components/settings/account/user-profile.js +23 -0
  65. package/dist/components/settings/account-settings.d.ts +5 -0
  66. package/dist/components/settings/account-settings.js +6 -0
  67. package/dist/components/settings/account.d.ts +18 -0
  68. package/dist/components/settings/account.js +24 -0
  69. package/dist/components/settings/security/active-session.d.ts +14 -0
  70. package/dist/components/settings/security/active-session.js +53 -0
  71. package/dist/components/settings/security/active-sessions.d.ts +14 -0
  72. package/dist/components/settings/security/active-sessions.js +28 -0
  73. package/dist/components/settings/security/change-password.d.ts +15 -0
  74. package/dist/components/settings/security/change-password.js +79 -0
  75. package/dist/components/settings/security/connected-account.d.ts +16 -0
  76. package/dist/components/settings/security/connected-account.js +46 -0
  77. package/dist/components/settings/security/connected-accounts.d.ts +15 -0
  78. package/dist/components/settings/security/connected-accounts.js +31 -0
  79. package/dist/components/settings/security/danger-zone.d.ts +11 -0
  80. package/dist/components/settings/security/danger-zone.js +12 -0
  81. package/dist/components/settings/security/delete-user.d.ts +9 -0
  82. package/dist/components/settings/security/delete-user.js +47 -0
  83. package/dist/components/settings/security/linked-account.d.ts +16 -0
  84. package/dist/components/settings/security/linked-account.js +48 -0
  85. package/dist/components/settings/security/linked-accounts.d.ts +15 -0
  86. package/dist/components/settings/security/linked-accounts.js +42 -0
  87. package/dist/components/settings/security/security-settings.d.ts +17 -0
  88. package/dist/components/settings/security/security-settings.js +21 -0
  89. package/dist/components/settings/security/sessions.d.ts +14 -0
  90. package/dist/components/settings/security/sessions.js +40 -0
  91. package/dist/components/settings/settings/account-settings.d.ts +5 -0
  92. package/dist/components/settings/settings/account-settings.js +6 -0
  93. package/dist/components/settings/settings/user-profile.d.ts +5 -0
  94. package/dist/components/settings/settings/user-profile.js +20 -0
  95. package/dist/components/settings/settings.d.ts +24 -0
  96. package/dist/components/settings/settings.js +28 -0
  97. package/dist/components/settings/user-profile.d.ts +5 -0
  98. package/dist/components/settings/user-profile.js +20 -0
  99. package/dist/components/user/switch-account-item.d.ts +16 -0
  100. package/dist/components/user/switch-account-item.js +16 -0
  101. package/dist/components/user/switch-account-menu.d.ts +10 -0
  102. package/dist/components/user/switch-account-menu.js +29 -0
  103. package/dist/components/user/switch-account-popover.d.ts +10 -0
  104. package/dist/components/user/switch-account-popover.js +25 -0
  105. package/dist/components/user/switch-account-submenu.d.ts +10 -0
  106. package/dist/components/user/switch-account-submenu.js +25 -0
  107. package/dist/components/user/user-avatar.d.ts +25 -0
  108. package/dist/components/user/user-avatar.js +31 -0
  109. package/dist/components/user/user-button.d.ts +25 -0
  110. package/dist/components/user/user-button.js +24 -0
  111. package/dist/components/user/user-view.d.ts +22 -0
  112. package/dist/components/user/user-view.js +24 -0
  113. package/dist/core.d.ts +1 -0
  114. package/dist/core.js +1 -0
  115. package/dist/hooks/use-auth.d.ts +2 -0
  116. package/dist/hooks/use-auth.js +8 -0
  117. package/dist/hooks/use-redirect-to.d.ts +8 -0
  118. package/dist/hooks/use-redirect-to.js +19 -0
  119. package/dist/hooks/use-sign-in.d.ts +8 -0
  120. package/dist/hooks/use-sign-in.js +53 -0
  121. package/dist/index.d.ts +33 -0
  122. package/dist/index.js +34 -0
  123. package/dist/lib/utils.d.ts +2 -0
  124. package/dist/lib/utils.js +5 -0
  125. package/dist/react-exports.d.ts +1 -0
  126. package/dist/react-exports.js +1 -0
  127. package/dist/server.d.ts +1 -0
  128. package/dist/server.js +1 -0
  129. package/dist/src/components/auth/auth-provider.d.ts +8 -0
  130. package/dist/src/components/auth/auth-provider.js +13 -0
  131. package/dist/src/components/auth/auth.d.ts +22 -0
  132. package/dist/src/components/auth/auth.js +42 -0
  133. package/dist/src/components/auth/field-separator.d.ts +4 -0
  134. package/dist/src/components/auth/field-separator.js +5 -0
  135. package/dist/src/components/auth/forgot-password.d.ts +15 -0
  136. package/dist/src/components/auth/forgot-password.js +29 -0
  137. package/dist/src/components/auth/magic-link-button.d.ts +13 -0
  138. package/dist/src/components/auth/magic-link-button.js +20 -0
  139. package/dist/src/components/auth/magic-link.d.ts +17 -0
  140. package/dist/src/components/auth/magic-link.js +37 -0
  141. package/dist/src/components/auth/provider-buttons.d.ts +19 -0
  142. package/dist/src/components/auth/provider-buttons.js +35 -0
  143. package/dist/src/components/auth/reset-password.d.ts +11 -0
  144. package/dist/src/components/auth/reset-password.js +59 -0
  145. package/dist/src/components/auth/sign-in.d.ts +14 -0
  146. package/dist/src/components/auth/sign-in.js +58 -0
  147. package/dist/src/components/auth/sign-out.d.ts +11 -0
  148. package/dist/src/components/auth/sign-out.js +34 -0
  149. package/dist/src/components/auth/sign-up.d.ts +19 -0
  150. package/dist/src/components/auth/sign-up.js +65 -0
  151. package/dist/src/components/email/email-changed-email.d.ts +3 -0
  152. package/dist/src/components/email/email-changed-email.js +23 -0
  153. package/dist/src/components/email/email-verification-email.d.ts +3 -0
  154. package/dist/src/components/email/email-verification-email.js +23 -0
  155. package/dist/src/components/email/magic-link-email.d.ts +3 -0
  156. package/dist/src/components/email/magic-link-email.js +23 -0
  157. package/dist/src/components/email/new-device-email.d.ts +3 -0
  158. package/dist/src/components/email/new-device-email.js +23 -0
  159. package/dist/src/components/email/otp-email.d.ts +3 -0
  160. package/dist/src/components/email/otp-email.js +22 -0
  161. package/dist/src/components/email/password-changed-email.d.ts +3 -0
  162. package/dist/src/components/email/password-changed-email.js +23 -0
  163. package/dist/src/components/email/reset-password-email.d.ts +3 -0
  164. package/dist/src/components/email/reset-password-email.js +23 -0
  165. package/dist/src/components/settings/account/account-settings.d.ts +13 -0
  166. package/dist/src/components/settings/account/account-settings.js +19 -0
  167. package/dist/src/components/settings/account/appearance.d.ts +16 -0
  168. package/dist/src/components/settings/account/appearance.js +28 -0
  169. package/dist/src/components/settings/account/change-email.d.ts +15 -0
  170. package/dist/src/components/settings/account/change-email.js +35 -0
  171. package/dist/src/components/settings/account/manage-account.d.ts +19 -0
  172. package/dist/src/components/settings/account/manage-account.js +27 -0
  173. package/dist/src/components/settings/account/manage-accounts.d.ts +14 -0
  174. package/dist/src/components/settings/account/manage-accounts.js +27 -0
  175. package/dist/src/components/settings/account/user-profile.d.ts +13 -0
  176. package/dist/src/components/settings/account/user-profile.js +29 -0
  177. package/dist/src/components/settings/security/active-session.d.ts +14 -0
  178. package/dist/src/components/settings/security/active-session.js +35 -0
  179. package/dist/src/components/settings/security/active-sessions.d.ts +14 -0
  180. package/dist/src/components/settings/security/active-sessions.js +29 -0
  181. package/dist/src/components/settings/security/change-password.d.ts +15 -0
  182. package/dist/src/components/settings/security/change-password.js +57 -0
  183. package/dist/src/components/settings/security/linked-account.d.ts +16 -0
  184. package/dist/src/components/settings/security/linked-account.js +46 -0
  185. package/dist/src/components/settings/security/linked-accounts.d.ts +15 -0
  186. package/dist/src/components/settings/security/linked-accounts.js +31 -0
  187. package/dist/src/components/settings/security/security-settings.d.ts +15 -0
  188. package/dist/src/components/settings/security/security-settings.js +18 -0
  189. package/dist/src/components/settings/settings.d.ts +29 -0
  190. package/dist/src/components/settings/settings.js +39 -0
  191. package/dist/src/components/user/switch-account-item.d.ts +16 -0
  192. package/dist/src/components/user/switch-account-item.js +14 -0
  193. package/dist/src/components/user/switch-account-menu.d.ts +10 -0
  194. package/dist/src/components/user/switch-account-menu.js +23 -0
  195. package/dist/src/components/user/user-avatar.d.ts +24 -0
  196. package/dist/src/components/user/user-avatar.js +31 -0
  197. package/dist/src/components/user/user-button.d.ts +21 -0
  198. package/dist/src/components/user/user-button.js +24 -0
  199. package/dist/src/components/user/user-view.d.ts +21 -0
  200. package/dist/src/components/user/user-view.js +24 -0
  201. package/dist/src/core.d.ts +1 -0
  202. package/dist/src/core.js +1 -0
  203. package/dist/src/index.d.ts +31 -0
  204. package/dist/src/index.js +32 -0
  205. package/dist/src/lib/utils.d.ts +2 -0
  206. package/dist/src/lib/utils.js +5 -0
  207. package/dist/src/react-exports.d.ts +1 -0
  208. package/dist/src/react-exports.js +1 -0
  209. package/dist/styles.css +1 -0
  210. package/package.json +63 -0
  211. package/src/components/auth/auth-provider.tsx +27 -0
  212. package/src/components/auth/auth.tsx +87 -0
  213. package/src/components/auth/field-separator.tsx +14 -0
  214. package/src/components/auth/forgot-password.tsx +107 -0
  215. package/src/components/auth/magic-link-button.tsx +43 -0
  216. package/src/components/auth/magic-link.tsx +181 -0
  217. package/src/components/auth/provider-buttons.tsx +85 -0
  218. package/src/components/auth/reset-password.tsx +210 -0
  219. package/src/components/auth/sign-in.tsx +260 -0
  220. package/src/components/auth/sign-out.tsx +44 -0
  221. package/src/components/auth/sign-up.tsx +314 -0
  222. package/src/components/email/email-changed-email.tsx +38 -0
  223. package/src/components/email/email-verification-email.tsx +38 -0
  224. package/src/components/email/magic-link-email.tsx +38 -0
  225. package/src/components/email/new-device-email.tsx +38 -0
  226. package/src/components/email/otp-email.tsx +33 -0
  227. package/src/components/email/password-changed-email.tsx +38 -0
  228. package/src/components/email/reset-password-email.tsx +38 -0
  229. package/src/components/settings/account/account-settings.tsx +48 -0
  230. package/src/components/settings/account/appearance.tsx +157 -0
  231. package/src/components/settings/account/change-avatar.tsx +142 -0
  232. package/src/components/settings/account/change-email.tsx +111 -0
  233. package/src/components/settings/account/manage-account.tsx +123 -0
  234. package/src/components/settings/account/manage-accounts.tsx +78 -0
  235. package/src/components/settings/account/user-profile.tsx +103 -0
  236. package/src/components/settings/security/active-session.tsx +123 -0
  237. package/src/components/settings/security/active-sessions.tsx +77 -0
  238. package/src/components/settings/security/change-password.tsx +335 -0
  239. package/src/components/settings/security/danger-zone.tsx +32 -0
  240. package/src/components/settings/security/delete-user.tsx +187 -0
  241. package/src/components/settings/security/linked-account.tsx +136 -0
  242. package/src/components/settings/security/linked-accounts.tsx +98 -0
  243. package/src/components/settings/security/security-settings.tsx +43 -0
  244. package/src/components/settings/settings.tsx +96 -0
  245. package/src/components/user/switch-account-item.tsx +39 -0
  246. package/src/components/user/switch-account-menu.tsx +64 -0
  247. package/src/components/user/user-avatar.tsx +88 -0
  248. package/src/components/user/user-button.tsx +221 -0
  249. package/src/components/user/user-view.tsx +74 -0
  250. package/src/core.ts +1 -0
  251. package/src/index.tsx +35 -0
  252. package/src/react-exports.ts +1 -0
  253. package/src/styles.css +1 -0
@@ -0,0 +1,107 @@
1
+ import { useAuth, useRequestPasswordReset } from "@better-auth-ui/react"
2
+ import {
3
+ Button,
4
+ Card,
5
+ type CardProps,
6
+ cn,
7
+ Description,
8
+ FieldError,
9
+ Form,
10
+ Input,
11
+ Label,
12
+ Link,
13
+ Spinner,
14
+ TextField,
15
+ toast
16
+ } from "@heroui/react"
17
+ import type { SyntheticEvent } from "react"
18
+
19
+ export type ForgotPasswordProps = {
20
+ className?: string
21
+ variant?: CardProps["variant"]
22
+ }
23
+
24
+ /**
25
+ * Render a card-based "Forgot Password" form that sends a password-reset email.
26
+ *
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.
29
+ *
30
+ * @param className - Optional additional CSS class names applied to the card
31
+ * @returns The forgot-password form UI as a JSX element
32
+ */
33
+ export function ForgotPassword({
34
+ className,
35
+ variant,
36
+ ...props
37
+ }: ForgotPasswordProps & CardProps) {
38
+ const { basePaths, localization, viewPaths, navigate } = useAuth()
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}` })
45
+ }
46
+ })
47
+
48
+ function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
49
+ e.preventDefault()
50
+
51
+ const formData = new FormData(e.currentTarget)
52
+ requestPasswordReset({ email: formData.get("email") as string })
53
+ }
54
+
55
+ return (
56
+ <Card
57
+ className={cn("w-full max-w-sm p-4 md:p-6", className)}
58
+ variant={variant}
59
+ {...props}
60
+ >
61
+ <Card.Header>
62
+ <Card.Title className="text-xl font-semibold mb-1">
63
+ {localization.auth.forgotPassword}
64
+ </Card.Title>
65
+ </Card.Header>
66
+
67
+ <Card.Content>
68
+ <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
69
+ <TextField
70
+ name="email"
71
+ type="email"
72
+ autoComplete="email"
73
+ isDisabled={isPending}
74
+ >
75
+ <Label>{localization.auth.email}</Label>
76
+
77
+ <Input
78
+ placeholder={localization.auth.emailPlaceholder}
79
+ required
80
+ variant={variant === "transparent" ? "primary" : "secondary"}
81
+ />
82
+
83
+ <FieldError />
84
+ </TextField>
85
+
86
+ <Button type="submit" className="w-full" isPending={isPending}>
87
+ {isPending && <Spinner color="current" size="sm" />}
88
+
89
+ {localization.auth.sendResetLink}
90
+ </Button>
91
+ </Form>
92
+ </Card.Content>
93
+
94
+ <Card.Footer className="flex-col">
95
+ <Description className="text-sm">
96
+ {localization.auth.rememberYourPassword}{" "}
97
+ <Link
98
+ href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
99
+ className="text-accent decoration-accent no-underline hover:underline"
100
+ >
101
+ {localization.auth.signIn}
102
+ </Link>
103
+ </Description>
104
+ </Card.Footer>
105
+ </Card>
106
+ )
107
+ }
@@ -0,0 +1,43 @@
1
+ import type { AuthView } from "@better-auth-ui/core"
2
+ import { useAuth } 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
+
7
+ export type MagicLinkButtonProps = {
8
+ isPending: boolean
9
+ view?: AuthView
10
+ }
11
+
12
+ /**
13
+ * Renders a full-width tertiary link-style button that navigates to either the magic-link or sign-in route.
14
+ *
15
+ * @param isPending - If true, applies disabled styling and prevents interaction
16
+ * @param view - Current auth view; when `"magicLink"`, the button targets the sign-in/password variant
17
+ * @returns The rendered Link element with the appropriate href, icon, and label
18
+ */
19
+ export function MagicLinkButton({ isPending, view }: MagicLinkButtonProps) {
20
+ const { basePaths, viewPaths, localization } = useAuth()
21
+
22
+ const isMagicLinkView = view === "magicLink"
23
+
24
+ return (
25
+ <Link
26
+ href={`${basePaths.auth}/${isMagicLinkView ? viewPaths.auth.signIn : viewPaths.auth.magicLink}`}
27
+ className={cn(
28
+ buttonVariants({ variant: "tertiary" }),
29
+ "w-full gap-2",
30
+ isPending && "status-disabled pointer-events-none"
31
+ )}
32
+ >
33
+ {isMagicLinkView ? <Lock /> : <Envelope />}
34
+
35
+ {localization.auth.continueWith.replace(
36
+ "{{provider}}",
37
+ isMagicLinkView
38
+ ? localization.auth.password
39
+ : localization.auth.magicLink
40
+ )}
41
+ </Link>
42
+ )
43
+ }
@@ -0,0 +1,181 @@
1
+ import {
2
+ useAuth,
3
+ useSignInMagicLink,
4
+ useSignInSocial
5
+ } from "@better-auth-ui/react"
6
+ import {
7
+ Button,
8
+ Card,
9
+ type CardProps,
10
+ cn,
11
+ Description,
12
+ FieldError,
13
+ Form,
14
+ Input,
15
+ Label,
16
+ Link,
17
+ Spinner,
18
+ TextField,
19
+ toast
20
+ } from "@heroui/react"
21
+ import { type SyntheticEvent, useState } from "react"
22
+
23
+ import { FieldSeparator } from "./field-separator"
24
+ import { MagicLinkButton } from "./magic-link-button"
25
+ import { ProviderButtons, type SocialLayout } from "./provider-buttons"
26
+
27
+ export type MagicLinkProps = {
28
+ className?: string
29
+ socialLayout?: SocialLayout
30
+ socialPosition?: "top" | "bottom"
31
+ variant?: CardProps["variant"]
32
+ }
33
+
34
+ /**
35
+ * Render a card-based sign-in form that sends an email magic link and optionally shows social provider buttons.
36
+ *
37
+ * @param className - Additional CSS class names applied to the card container
38
+ * @param socialLayout - Layout style for social provider buttons
39
+ * @param socialPosition - Position of social provider buttons; `"top"` or `"bottom"`. Defaults to `"bottom"`.
40
+ * @returns The magic-link sign-in UI as a JSX element
41
+ */
42
+ export function MagicLink({
43
+ className,
44
+ socialLayout,
45
+ socialPosition = "bottom",
46
+ variant,
47
+ ...props
48
+ }: MagicLinkProps & CardProps) {
49
+ const {
50
+ basePaths,
51
+ baseURL,
52
+ localization,
53
+ redirectTo,
54
+ socialProviders,
55
+ viewPaths
56
+ } = useAuth()
57
+
58
+ const [email, setEmail] = useState("")
59
+
60
+ const { mutate: signInMagicLink, isPending: magicLinkPending } =
61
+ useSignInMagicLink({
62
+ onError: (error) => toast.danger(error.error?.message || error.message),
63
+ onSuccess: () => {
64
+ setEmail("")
65
+ toast.success(localization.auth.magicLinkSent)
66
+ }
67
+ })
68
+
69
+ const [socialRedirecting, setSocialRedirecting] = useState(false)
70
+
71
+ const { mutate: signInSocial, isPending: socialPending } = useSignInSocial({
72
+ onError: (error) => toast.danger(error.error?.message || error.message),
73
+ onSuccess: () => {
74
+ setSocialRedirecting(true)
75
+
76
+ setTimeout(() => {
77
+ setSocialRedirecting(false)
78
+ }, 5000)
79
+ }
80
+ })
81
+
82
+ const isPending = magicLinkPending || socialPending || socialRedirecting
83
+
84
+ const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
85
+ e.preventDefault()
86
+ signInMagicLink({ email, callbackURL: `${baseURL}${redirectTo}` })
87
+ }
88
+
89
+ const showSeparator = !!socialProviders?.length
90
+
91
+ return (
92
+ <Card
93
+ className={cn("w-full max-w-sm p-4 md:p-6", className)}
94
+ variant={variant}
95
+ {...props}
96
+ >
97
+ <Card.Header>
98
+ <Card.Title className="text-xl font-semibold mb-1">
99
+ {localization.auth.signIn}
100
+ </Card.Title>
101
+ </Card.Header>
102
+
103
+ <Card.Content className="gap-4">
104
+ {socialPosition === "top" && (
105
+ <>
106
+ {!!socialProviders?.length && (
107
+ <ProviderButtons
108
+ socialLayout={socialLayout}
109
+ signInSocial={signInSocial}
110
+ isPending={isPending}
111
+ />
112
+ )}
113
+
114
+ {showSeparator && (
115
+ <FieldSeparator>{localization.auth.or}</FieldSeparator>
116
+ )}
117
+ </>
118
+ )}
119
+
120
+ <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
121
+ <TextField
122
+ name="email"
123
+ type="email"
124
+ autoComplete="email"
125
+ isDisabled={isPending}
126
+ value={email}
127
+ onChange={setEmail}
128
+ >
129
+ <Label>{localization.auth.email}</Label>
130
+
131
+ <Input
132
+ placeholder={localization.auth.emailPlaceholder}
133
+ required
134
+ variant={variant === "transparent" ? "primary" : "secondary"}
135
+ />
136
+
137
+ <FieldError />
138
+ </TextField>
139
+
140
+ <div className="flex flex-col gap-3">
141
+ <Button type="submit" className="w-full" isPending={isPending}>
142
+ {isPending && <Spinner color="current" size="sm" />}
143
+
144
+ {localization.auth.sendMagicLink}
145
+ </Button>
146
+
147
+ <MagicLinkButton view="magicLink" isPending={isPending} />
148
+ </div>
149
+ </Form>
150
+
151
+ {socialPosition === "bottom" && (
152
+ <>
153
+ {showSeparator && (
154
+ <FieldSeparator>{localization.auth.or}</FieldSeparator>
155
+ )}
156
+
157
+ {!!socialProviders?.length && (
158
+ <ProviderButtons
159
+ socialLayout={socialLayout}
160
+ signInSocial={signInSocial}
161
+ isPending={isPending}
162
+ />
163
+ )}
164
+ </>
165
+ )}
166
+ </Card.Content>
167
+
168
+ <Card.Footer className="flex-col">
169
+ <Description className="text-sm">
170
+ {localization.auth.needToCreateAnAccount}{" "}
171
+ <Link
172
+ href={`${basePaths.auth}/${viewPaths.auth.signUp}`}
173
+ className="text-accent decoration-accent no-underline hover:underline"
174
+ >
175
+ {localization.auth.signUp}
176
+ </Link>
177
+ </Description>
178
+ </Card.Footer>
179
+ </Card>
180
+ )
181
+ }
@@ -0,0 +1,85 @@
1
+ import { getProviderName } from "@better-auth-ui/core"
2
+ import { providerIcons, useAuth } from "@better-auth-ui/react"
3
+ import { Button, cn } from "@heroui/react"
4
+ import type { SocialProvider } from "better-auth/social-providers"
5
+ import { useMemo } from "react"
6
+
7
+ export type ProviderButtonsProps = {
8
+ isPending: boolean
9
+ socialLayout?: SocialLayout
10
+ signInSocial: (params: {
11
+ provider: SocialProvider
12
+ callbackURL: string
13
+ }) => void
14
+ }
15
+
16
+ export type SocialLayout = "auto" | "horizontal" | "vertical" | "grid"
17
+
18
+ /**
19
+ * Render social provider sign-in buttons and handle initiation and pending state.
20
+ *
21
+ * @param isPending - Disables all provider buttons when true.
22
+ * @param socialLayout - Preferred layout for the buttons; `"auto"` picks `"horizontal"` when there are four or more providers, otherwise `"vertical"`.
23
+ * @param signInSocial - Callback invoked with the provider and callbackURL when a button is clicked.
24
+ * @returns The JSX element that renders the configured social provider buttons.
25
+ */
26
+ export function ProviderButtons({
27
+ isPending,
28
+ socialLayout = "auto",
29
+ signInSocial
30
+ }: ProviderButtonsProps) {
31
+ const { baseURL, localization, redirectTo, socialProviders } = useAuth()
32
+
33
+ const callbackURL = `${baseURL}${redirectTo}`
34
+
35
+ const resolvedSocialLayout = useMemo(() => {
36
+ if (socialLayout === "auto") {
37
+ if (socialProviders?.length && socialProviders.length >= 4) {
38
+ return "horizontal"
39
+ }
40
+
41
+ return "vertical"
42
+ }
43
+
44
+ return socialLayout
45
+ }, [socialLayout, socialProviders?.length])
46
+
47
+ return (
48
+ <div
49
+ className={cn(
50
+ "gap-3",
51
+ resolvedSocialLayout === "grid" && "grid grid-cols-2",
52
+ resolvedSocialLayout === "vertical" && "flex flex-col",
53
+ resolvedSocialLayout === "horizontal" && "flex flex-wrap"
54
+ )}
55
+ >
56
+ {socialProviders?.map((provider) => {
57
+ const ProviderIcon = providerIcons[provider]
58
+
59
+ return (
60
+ <Button
61
+ key={provider}
62
+ className={cn(
63
+ "w-full",
64
+ resolvedSocialLayout === "horizontal" && "flex-1"
65
+ )}
66
+ variant="tertiary"
67
+ isPending={isPending}
68
+ onPress={() => signInSocial({ provider, callbackURL })}
69
+ >
70
+ <ProviderIcon />
71
+
72
+ {resolvedSocialLayout === "vertical"
73
+ ? localization.auth.continueWith.replace(
74
+ "{{provider}}",
75
+ getProviderName(provider)
76
+ )
77
+ : resolvedSocialLayout === "grid"
78
+ ? getProviderName(provider)
79
+ : null}
80
+ </Button>
81
+ )
82
+ })}
83
+ </div>
84
+ )
85
+ }
@@ -0,0 +1,210 @@
1
+ import { useAuth, useResetPassword } from "@better-auth-ui/react"
2
+ import { Eye, EyeSlash } from "@gravity-ui/icons"
3
+ import {
4
+ Button,
5
+ Card,
6
+ type CardProps,
7
+ cn,
8
+ Description,
9
+ FieldError,
10
+ Form,
11
+ InputGroup,
12
+ Label,
13
+ Link,
14
+ Spinner,
15
+ TextField,
16
+ toast
17
+ } from "@heroui/react"
18
+ import { type SyntheticEvent, useEffect, useState } from "react"
19
+
20
+ export type ResetPasswordProps = {
21
+ className?: string
22
+ variant?: CardProps["variant"]
23
+ }
24
+
25
+ /**
26
+ * Renders a reset password form that verifies a "token" URL parameter on mount and redirects to sign-in if absent.
27
+ *
28
+ * Renders password (and optional confirm-password) inputs with visibility toggles, applies min/max length constraints from the auth configuration, shows field errors, and submits the new password to the auth client.
29
+ */
30
+ export function ResetPassword({
31
+ className,
32
+ variant,
33
+ ...props
34
+ }: ResetPasswordProps & CardProps) {
35
+ const { basePaths, emailAndPassword, localization, viewPaths, navigate } =
36
+ useAuth()
37
+
38
+ const { mutate: resetPassword, isPending } = useResetPassword({
39
+ onError: (error) => toast.danger(error.error?.message || error.message),
40
+ onSuccess: () => {
41
+ toast.success(localization.auth.passwordResetSuccess)
42
+ navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
43
+ }
44
+ })
45
+
46
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false)
47
+ const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
48
+ useState(false)
49
+
50
+ useEffect(() => {
51
+ const searchParams = new URLSearchParams(window.location.search)
52
+ const token = searchParams.get("token") as string
53
+
54
+ if (!token) {
55
+ toast.danger(localization.auth.invalidResetPasswordToken)
56
+ navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
57
+ }
58
+ }, [
59
+ basePaths.auth,
60
+ localization.auth.invalidResetPasswordToken,
61
+ viewPaths.auth.signIn,
62
+ navigate
63
+ ])
64
+
65
+ function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
66
+ e.preventDefault()
67
+
68
+ const searchParams = new URLSearchParams(window.location.search)
69
+ const token = searchParams.get("token") as string
70
+
71
+ if (!token) {
72
+ toast.danger(localization.auth.invalidResetPasswordToken)
73
+ navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
74
+ return
75
+ }
76
+
77
+ const formData = new FormData(e.currentTarget)
78
+ const password = formData.get("password") as string
79
+ const confirmPassword = formData.get("confirmPassword") as string
80
+
81
+ if (emailAndPassword?.confirmPassword && password !== confirmPassword) {
82
+ toast.danger(localization.auth.passwordsDoNotMatch)
83
+ return
84
+ }
85
+
86
+ resetPassword({ token, newPassword: password })
87
+ }
88
+
89
+ return (
90
+ <Card
91
+ className={cn("w-full max-w-sm p-4 md:p-6", className)}
92
+ variant={variant}
93
+ {...props}
94
+ >
95
+ <Card.Header>
96
+ <Card.Title className="text-xl font-semibold mb-1">
97
+ {localization.auth.resetPassword}
98
+ </Card.Title>
99
+ </Card.Header>
100
+
101
+ <Card.Content>
102
+ <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
103
+ <TextField
104
+ minLength={emailAndPassword?.minPasswordLength}
105
+ maxLength={emailAndPassword?.maxPasswordLength}
106
+ name="password"
107
+ autoComplete="new-password"
108
+ isDisabled={isPending}
109
+ >
110
+ <Label>{localization.auth.password}</Label>
111
+
112
+ <InputGroup
113
+ variant={variant === "transparent" ? "primary" : "secondary"}
114
+ >
115
+ <InputGroup.Input
116
+ name="password"
117
+ placeholder={localization.auth.newPasswordPlaceholder}
118
+ type={isPasswordVisible ? "text" : "password"}
119
+ required
120
+ />
121
+
122
+ <InputGroup.Suffix className="px-0">
123
+ <Button
124
+ isIconOnly
125
+ aria-label={
126
+ isPasswordVisible
127
+ ? localization.auth.hidePassword
128
+ : localization.auth.showPassword
129
+ }
130
+ size="sm"
131
+ variant="ghost"
132
+ onPress={() => setIsPasswordVisible(!isPasswordVisible)}
133
+ isDisabled={isPending}
134
+ >
135
+ {isPasswordVisible ? <EyeSlash /> : <Eye />}
136
+ </Button>
137
+ </InputGroup.Suffix>
138
+ </InputGroup>
139
+
140
+ <FieldError />
141
+ </TextField>
142
+
143
+ {emailAndPassword?.confirmPassword && (
144
+ <TextField
145
+ minLength={emailAndPassword?.minPasswordLength}
146
+ maxLength={emailAndPassword?.maxPasswordLength}
147
+ name="confirmPassword"
148
+ autoComplete="new-password"
149
+ isDisabled={isPending}
150
+ >
151
+ <Label>{localization.auth.confirmPassword}</Label>
152
+
153
+ <InputGroup
154
+ variant={variant === "transparent" ? "primary" : "secondary"}
155
+ >
156
+ <InputGroup.Input
157
+ placeholder={localization.auth.confirmPasswordPlaceholder}
158
+ type={isConfirmPasswordVisible ? "text" : "password"}
159
+ required
160
+ name="confirmPassword"
161
+ />
162
+
163
+ <InputGroup.Suffix className="px-0">
164
+ <Button
165
+ isIconOnly
166
+ aria-label={
167
+ isConfirmPasswordVisible
168
+ ? localization.auth.hidePassword
169
+ : localization.auth.showPassword
170
+ }
171
+ size="sm"
172
+ variant="ghost"
173
+ onPress={() =>
174
+ setIsConfirmPasswordVisible(!isConfirmPasswordVisible)
175
+ }
176
+ isDisabled={isPending}
177
+ >
178
+ {isConfirmPasswordVisible ? <EyeSlash /> : <Eye />}
179
+ </Button>
180
+ </InputGroup.Suffix>
181
+ </InputGroup>
182
+
183
+ <FieldError />
184
+ </TextField>
185
+ )}
186
+
187
+ <div className="flex flex-col gap-3">
188
+ <Button type="submit" className="w-full" isPending={isPending}>
189
+ {isPending && <Spinner color="current" size="sm" />}
190
+
191
+ {localization.auth.resetPassword}
192
+ </Button>
193
+ </div>
194
+ </Form>
195
+ </Card.Content>
196
+
197
+ <Card.Footer className="flex-col">
198
+ <Description className="text-sm">
199
+ {localization.auth.rememberYourPassword}{" "}
200
+ <Link
201
+ href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
202
+ className="text-accent decoration-accent no-underline hover:underline"
203
+ >
204
+ {localization.auth.signIn}
205
+ </Link>
206
+ </Description>
207
+ </Card.Footer>
208
+ </Card>
209
+ )
210
+ }