@better-auth-ui/heroui 1.6.1 → 1.6.3

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 (117) hide show
  1. package/dist/components/auth/auth-provider.d.ts +1 -1
  2. package/dist/components/auth/auth-provider.js +16 -11
  3. package/dist/components/auth/auth.d.ts +4 -4
  4. package/dist/components/auth/auth.js +36 -41
  5. package/dist/components/auth/error-toaster.d.ts +1 -0
  6. package/dist/components/auth/error-toaster.js +17 -0
  7. package/dist/components/auth/field-separator.d.ts +1 -1
  8. package/dist/components/auth/field-separator.js +17 -4
  9. package/dist/components/auth/forgot-password.d.ts +2 -2
  10. package/dist/components/auth/forgot-password.js +66 -27
  11. package/dist/components/auth/magic-link-button.d.ts +1 -1
  12. package/dist/components/auth/magic-link-button.js +15 -18
  13. package/dist/components/auth/magic-link.d.ts +3 -3
  14. package/dist/components/auth/magic-link.js +98 -43
  15. package/dist/components/auth/passkey-button.js +20 -12
  16. package/dist/components/auth/provider-button.d.ts +15 -0
  17. package/dist/components/auth/provider-button.js +29 -0
  18. package/dist/components/auth/provider-buttons.d.ts +4 -10
  19. package/dist/components/auth/provider-buttons.js +19 -33
  20. package/dist/components/auth/reset-password.d.ts +2 -2
  21. package/dist/components/auth/reset-password.js +143 -57
  22. package/dist/components/auth/sign-in.d.ts +3 -3
  23. package/dist/components/auth/sign-in.js +151 -64
  24. package/dist/components/auth/sign-out.js +26 -32
  25. package/dist/components/auth/sign-up.d.ts +3 -3
  26. package/dist/components/auth/sign-up.js +231 -70
  27. package/dist/components/email/email-changed-email.d.ts +2 -2
  28. package/dist/components/email/email-changed-email.js +29 -22
  29. package/dist/components/email/email-verification-email.d.ts +2 -2
  30. package/dist/components/email/email-verification-email.js +29 -22
  31. package/dist/components/email/magic-link-email.d.ts +2 -2
  32. package/dist/components/email/magic-link-email.js +29 -22
  33. package/dist/components/email/new-device-email.d.ts +2 -2
  34. package/dist/components/email/new-device-email.js +29 -22
  35. package/dist/components/email/otp-email.d.ts +2 -2
  36. package/dist/components/email/otp-email.js +28 -21
  37. package/dist/components/email/password-changed-email.d.ts +2 -2
  38. package/dist/components/email/password-changed-email.js +29 -22
  39. package/dist/components/email/reset-password-email.d.ts +2 -2
  40. package/dist/components/email/reset-password-email.js +29 -22
  41. package/dist/components/settings/account/account-settings.d.ts +2 -2
  42. package/dist/components/settings/account/account-settings.js +22 -19
  43. package/dist/components/settings/account/appearance.d.ts +2 -2
  44. package/dist/components/settings/account/appearance.js +70 -27
  45. package/dist/components/settings/account/change-avatar.js +88 -55
  46. package/dist/components/settings/account/change-email.d.ts +2 -2
  47. package/dist/components/settings/account/change-email.js +53 -28
  48. package/dist/components/settings/account/manage-account.d.ts +1 -1
  49. package/dist/components/settings/account/manage-account.js +47 -31
  50. package/dist/components/settings/account/manage-accounts.d.ts +2 -2
  51. package/dist/components/settings/account/manage-accounts.js +32 -36
  52. package/dist/components/settings/account/user-profile.d.ts +4 -4
  53. package/dist/components/settings/account/user-profile.js +102 -22
  54. package/dist/components/settings/security/active-session.d.ts +1 -1
  55. package/dist/components/settings/security/active-session.js +57 -51
  56. package/dist/components/settings/security/active-sessions.d.ts +2 -2
  57. package/dist/components/settings/security/active-sessions.js +32 -26
  58. package/dist/components/settings/security/change-password.d.ts +2 -2
  59. package/dist/components/settings/security/change-password.js +186 -76
  60. package/dist/components/settings/security/danger-zone.d.ts +2 -2
  61. package/dist/components/settings/security/danger-zone.js +17 -11
  62. package/dist/components/settings/security/delete-user.d.ts +2 -2
  63. package/dist/components/settings/security/delete-user.js +91 -46
  64. package/dist/components/settings/security/linked-account.d.ts +1 -1
  65. package/dist/components/settings/security/linked-account.js +55 -47
  66. package/dist/components/settings/security/linked-accounts.d.ts +2 -2
  67. package/dist/components/settings/security/linked-accounts.js +43 -40
  68. package/dist/components/settings/security/passkey.js +42 -13
  69. package/dist/components/settings/security/passkeys.d.ts +2 -2
  70. package/dist/components/settings/security/passkeys.js +51 -19
  71. package/dist/components/settings/security/security-settings.d.ts +2 -2
  72. package/dist/components/settings/security/security-settings.js +24 -21
  73. package/dist/components/settings/settings.d.ts +3 -3
  74. package/dist/components/settings/settings.js +44 -27
  75. package/dist/components/user/switch-account-item.d.ts +1 -1
  76. package/dist/components/user/switch-account-item.js +19 -15
  77. package/dist/components/user/switch-account-menu.js +27 -28
  78. package/dist/components/user/user-avatar.d.ts +3 -3
  79. package/dist/components/user/user-avatar.js +28 -30
  80. package/dist/components/user/user-button.d.ts +1 -1
  81. package/dist/components/user/user-button.js +90 -23
  82. package/dist/components/user/user-view.d.ts +3 -3
  83. package/dist/components/user/user-view.js +36 -23
  84. package/dist/core.d.ts +1 -1
  85. package/dist/index.d.ts +36 -35
  86. package/dist/index.js +37 -35
  87. package/dist/react-exports.d.ts +1 -1
  88. package/package.json +15 -11
  89. package/src/components/auth/auth-provider.tsx +4 -0
  90. package/src/components/auth/auth.tsx +2 -2
  91. package/src/components/auth/error-toaster.tsx +25 -0
  92. package/src/components/auth/forgot-password.tsx +1 -2
  93. package/src/components/auth/magic-link.tsx +3 -23
  94. package/src/components/auth/passkey-button.tsx +1 -2
  95. package/src/components/auth/provider-button.tsx +67 -0
  96. package/src/components/auth/provider-buttons.tsx +26 -43
  97. package/src/components/auth/reset-password.tsx +1 -2
  98. package/src/components/auth/sign-in.tsx +63 -48
  99. package/src/components/auth/sign-up.tsx +99 -20
  100. package/src/components/settings/account/appearance.tsx +1 -1
  101. package/src/components/settings/account/change-avatar.tsx +1 -3
  102. package/src/components/settings/account/change-email.tsx +2 -3
  103. package/src/components/settings/account/manage-account.tsx +1 -4
  104. package/src/components/settings/account/manage-accounts.tsx +3 -8
  105. package/src/components/settings/account/user-profile.tsx +122 -8
  106. package/src/components/settings/security/active-session.tsx +0 -1
  107. package/src/components/settings/security/active-sessions.tsx +3 -8
  108. package/src/components/settings/security/change-password.tsx +7 -4
  109. package/src/components/settings/security/delete-user.tsx +1 -4
  110. package/src/components/settings/security/linked-account.tsx +4 -14
  111. package/src/components/settings/security/linked-accounts.tsx +3 -8
  112. package/src/components/settings/security/passkey.tsx +2 -4
  113. package/src/components/settings/security/passkeys.tsx +4 -12
  114. package/src/components/settings/settings.tsx +1 -1
  115. package/src/components/user/switch-account-item.tsx +2 -4
  116. package/src/components/user/switch-account-menu.tsx +2 -7
  117. package/src/index.tsx +1 -0
@@ -0,0 +1,67 @@
1
+ import { getProviderName } from "@better-auth-ui/core"
2
+ import { providerIcons, useAuth, useSignInSocial } from "@better-auth-ui/react"
3
+ import { Button, type ButtonProps, Spinner } from "@heroui/react"
4
+ import type { SocialProvider } from "better-auth/social-providers"
5
+ import { useState } from "react"
6
+
7
+ export type ProviderButtonProps = {
8
+ provider: SocialProvider
9
+ label?: "continueWith" | "providerName" | "none"
10
+ isDisabled?: boolean
11
+ } & Omit<ButtonProps, "children" | "onPress" | "isPending">
12
+
13
+ /**
14
+ * Render a single social provider sign-in button with its own mutation and pending state.
15
+ *
16
+ * @param provider - The social provider this button signs in with.
17
+ * @param label - Label style: `"continueWith"` (e.g. "Continue with Google"), `"providerName"` (e.g. "Google"), or `"none"` (icon only).
18
+ * @param isDisabled - External disabled state (e.g. a sibling provider is mid-redirect).
19
+ */
20
+ export function ProviderButton({
21
+ provider,
22
+ label = "continueWith",
23
+ isDisabled,
24
+ variant = "tertiary",
25
+ ...props
26
+ }: ProviderButtonProps) {
27
+ const { baseURL, localization, redirectTo } = useAuth()
28
+
29
+ const callbackURL = `${baseURL}${redirectTo}`
30
+
31
+ const [redirecting, setRedirecting] = useState(false)
32
+
33
+ const { mutate: signInSocial, isPending } = useSignInSocial({
34
+ onSuccess: () => {
35
+ setRedirecting(true)
36
+
37
+ setTimeout(() => {
38
+ setRedirecting(false)
39
+ }, 5000)
40
+ }
41
+ })
42
+
43
+ const ProviderIcon = providerIcons[provider]
44
+
45
+ const pending = isPending || redirecting
46
+
47
+ return (
48
+ <Button
49
+ variant={variant}
50
+ isDisabled={isDisabled}
51
+ isPending={pending}
52
+ onPress={() => signInSocial({ provider, callbackURL })}
53
+ {...props}
54
+ >
55
+ {pending ? <Spinner color="current" size="sm" /> : <ProviderIcon />}
56
+
57
+ {label === "continueWith"
58
+ ? localization.auth.continueWith.replace(
59
+ "{{provider}}",
60
+ getProviderName(provider)
61
+ )
62
+ : label === "providerName"
63
+ ? getProviderName(provider)
64
+ : null}
65
+ </Button>
66
+ )
67
+ }
@@ -1,36 +1,28 @@
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"
1
+ import { useAuth } from "@better-auth-ui/react"
2
+ import { cn } from "@heroui/react"
5
3
  import { useMemo } from "react"
6
4
 
5
+ import { ProviderButton } from "./provider-button"
6
+
7
7
  export type ProviderButtonsProps = {
8
- isPending: boolean
8
+ isPending?: boolean
9
9
  socialLayout?: SocialLayout
10
- signInSocial: (params: {
11
- provider: SocialProvider
12
- callbackURL: string
13
- }) => void
14
10
  }
15
11
 
16
12
  export type SocialLayout = "auto" | "horizontal" | "vertical" | "grid"
17
13
 
18
14
  /**
19
- * Render social provider sign-in buttons and handle initiation and pending state.
15
+ * Render social provider sign-in buttons. Each button owns its own sign-in mutation.
20
16
  *
21
- * @param isPending - Disables all provider buttons when true.
17
+ * @param isPending - External pending state (e.g. parent form submitting) that disables all buttons.
22
18
  * @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
19
  * @returns The JSX element that renders the configured social provider buttons.
25
20
  */
26
21
  export function ProviderButtons({
27
22
  isPending,
28
- socialLayout = "auto",
29
- signInSocial
23
+ socialLayout = "auto"
30
24
  }: ProviderButtonsProps) {
31
- const { baseURL, localization, redirectTo, socialProviders } = useAuth()
32
-
33
- const callbackURL = `${baseURL}${redirectTo}`
25
+ const { socialProviders } = useAuth()
34
26
 
35
27
  const resolvedSocialLayout = useMemo(() => {
36
28
  if (socialLayout === "auto") {
@@ -53,33 +45,24 @@ export function ProviderButtons({
53
45
  resolvedSocialLayout === "horizontal" && "flex flex-wrap"
54
46
  )}
55
47
  >
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
- )
48
+ {socialProviders?.map((provider) => (
49
+ <ProviderButton
50
+ key={provider}
51
+ provider={provider}
52
+ isDisabled={isPending}
53
+ label={
54
+ resolvedSocialLayout === "vertical"
55
+ ? "continueWith"
77
56
  : resolvedSocialLayout === "grid"
78
- ? getProviderName(provider)
79
- : null}
80
- </Button>
81
- )
82
- })}
57
+ ? "providerName"
58
+ : "none"
59
+ }
60
+ className={cn(
61
+ "w-full",
62
+ resolvedSocialLayout === "horizontal" && "flex-1"
63
+ )}
64
+ />
65
+ ))}
83
66
  </div>
84
67
  )
85
68
  }
@@ -31,12 +31,11 @@ export function ResetPassword({
31
31
  className,
32
32
  variant,
33
33
  ...props
34
- }: ResetPasswordProps & CardProps) {
34
+ }: ResetPasswordProps & Omit<CardProps, "children">) {
35
35
  const { basePaths, emailAndPassword, localization, viewPaths, navigate } =
36
36
  useAuth()
37
37
 
38
38
  const { mutate: resetPassword, isPending } = useResetPassword({
39
- onError: (error) => toast.danger(error.error?.message || error.message),
40
39
  onSuccess: () => {
41
40
  toast.success(localization.auth.passwordResetSuccess)
42
41
  navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
@@ -2,7 +2,7 @@ import {
2
2
  useAuth,
3
3
  useSendVerificationEmail,
4
4
  useSignInEmail,
5
- useSignInSocial
5
+ useSignInUsername
6
6
  } from "@better-auth-ui/react"
7
7
  import {
8
8
  Button,
@@ -34,6 +34,10 @@ export interface SignInProps {
34
34
  variant?: CardProps["variant"]
35
35
  }
36
36
 
37
+ function isEmail(value: string): boolean {
38
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
39
+ }
40
+
37
41
  /**
38
42
  * Render the sign-in UI using auth context for configuration and localization.
39
43
  *
@@ -45,7 +49,7 @@ export function SignIn({
45
49
  socialPosition = "bottom",
46
50
  variant,
47
51
  ...props
48
- }: SignInProps & CardProps) {
52
+ }: SignInProps & Omit<CardProps, "children">) {
49
53
  const {
50
54
  basePaths,
51
55
  baseURL,
@@ -55,6 +59,7 @@ export function SignIn({
55
59
  passkey,
56
60
  redirectTo,
57
61
  socialProviders,
62
+ username: usernameConfig,
58
63
  viewPaths,
59
64
  navigate
60
65
  } = useAuth()
@@ -62,44 +67,41 @@ export function SignIn({
62
67
  const [password, setPassword] = useState("")
63
68
 
64
69
  const { mutate: sendVerificationEmail } = useSendVerificationEmail({
65
- onError: (error) => toast.danger(error.error?.message || error.message),
66
70
  onSuccess: () => toast.success(localization.auth.verificationEmailSent)
67
71
  })
68
72
 
69
- const { mutate: signInEmail, isPending: signInPending } = useSignInEmail({
70
- onError: (error, { email }) => {
71
- setPassword("")
72
-
73
- if (error.error?.code === "EMAIL_NOT_VERIFIED") {
74
- toast.danger(error.error?.message || error.message, {
75
- actionProps: {
76
- children: localization.auth.resend,
77
- onClick: () =>
78
- sendVerificationEmail({
79
- email,
80
- callbackURL: `${baseURL}${redirectTo}`
81
- })
82
- }
83
- })
84
- } else {
85
- toast.danger(error.error?.message || error.message)
86
- }
87
- },
88
- onSuccess: () => navigate({ to: redirectTo })
89
- })
90
-
91
- const [socialRedirecting, setSocialRedirecting] = useState(false)
92
-
93
- const { mutate: signInSocial, isPending: socialPending } = useSignInSocial({
94
- onError: (error) => toast.danger(error.error?.message || error.message),
95
- onSuccess: () => {
96
- setSocialRedirecting(true)
97
-
98
- setTimeout(() => {
99
- setSocialRedirecting(false)
100
- }, 5000)
73
+ const { mutate: signInEmail, isPending: signInEmailPending } = useSignInEmail(
74
+ {
75
+ onError: (error, { email }) => {
76
+ setPassword("")
77
+
78
+ if (error.error?.code === "EMAIL_NOT_VERIFIED") {
79
+ toast.danger(error.error?.message || error.message, {
80
+ actionProps: {
81
+ children: localization.auth.resend,
82
+ onClick: () =>
83
+ sendVerificationEmail({
84
+ email,
85
+ callbackURL: `${baseURL}${redirectTo}`
86
+ })
87
+ }
88
+ })
89
+ } else {
90
+ toast.danger(error.error?.message || error.message)
91
+ }
92
+ },
93
+ onSuccess: () => navigate({ to: redirectTo })
101
94
  }
102
- })
95
+ )
96
+
97
+ const { mutate: signInUsername, isPending: signInUsernamePending } =
98
+ useSignInUsername({
99
+ onError: (error) => {
100
+ setPassword("")
101
+ toast.danger(error.error?.message || error.message)
102
+ },
103
+ onSuccess: () => navigate({ to: redirectTo })
104
+ })
103
105
 
104
106
  const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
105
107
  e.preventDefault()
@@ -108,14 +110,21 @@ export function SignIn({
108
110
  const email = formData.get("email") as string
109
111
  const rememberMe = formData.get("rememberMe") === "on"
110
112
 
111
- signInEmail({
112
- email,
113
- password,
114
- ...(emailAndPassword?.rememberMe ? { rememberMe } : {})
115
- })
113
+ if (usernameConfig?.enabled && !isEmail(email)) {
114
+ signInUsername({
115
+ username: email,
116
+ password
117
+ })
118
+ } else {
119
+ signInEmail({
120
+ email,
121
+ password,
122
+ ...(emailAndPassword?.rememberMe ? { rememberMe } : {})
123
+ })
124
+ }
116
125
  }
117
126
 
118
- const isPending = signInPending || socialPending || socialRedirecting
127
+ const isPending = signInEmailPending || signInUsernamePending
119
128
 
120
129
  const showSeparator = emailAndPassword?.enabled && !!socialProviders?.length
121
130
 
@@ -137,7 +146,6 @@ export function SignIn({
137
146
  {!!socialProviders?.length && (
138
147
  <ProviderButtons
139
148
  socialLayout={socialLayout}
140
- signInSocial={signInSocial}
141
149
  isPending={isPending}
142
150
  />
143
151
  )}
@@ -152,14 +160,22 @@ export function SignIn({
152
160
  <Form onSubmit={handleSubmit} className="flex flex-col gap-4">
153
161
  <TextField
154
162
  name="email"
155
- type="email"
156
- autoComplete="email"
163
+ type={usernameConfig?.enabled ? "text" : "email"}
164
+ autoComplete={usernameConfig?.enabled ? "username" : "email"}
157
165
  isDisabled={isPending}
158
166
  >
159
- <Label>{localization.auth.email}</Label>
167
+ <Label>
168
+ {usernameConfig?.enabled
169
+ ? localization.auth.username
170
+ : localization.auth.email}
171
+ </Label>
160
172
 
161
173
  <Input
162
- placeholder={localization.auth.emailPlaceholder}
174
+ placeholder={
175
+ usernameConfig?.enabled
176
+ ? localization.auth.usernameOrEmailPlaceholder
177
+ : localization.auth.emailPlaceholder
178
+ }
163
179
  variant={variant === "transparent" ? "primary" : "secondary"}
164
180
  required
165
181
  />
@@ -229,7 +245,6 @@ export function SignIn({
229
245
  {!!socialProviders?.length && (
230
246
  <ProviderButtons
231
247
  socialLayout={socialLayout}
232
- signInSocial={signInSocial}
233
248
  isPending={isPending}
234
249
  />
235
250
  )}
@@ -1,5 +1,9 @@
1
- import { useAuth, useSignInSocial, useSignUpEmail } from "@better-auth-ui/react"
2
- import { Eye, EyeSlash } from "@gravity-ui/icons"
1
+ import {
2
+ useAuth,
3
+ useIsUsernameAvailable,
4
+ useSignUpEmail
5
+ } from "@better-auth-ui/react"
6
+ import { Check, Eye, EyeSlash, Xmark } from "@gravity-ui/icons"
3
7
  import {
4
8
  Button,
5
9
  Card,
@@ -16,6 +20,7 @@ import {
16
20
  TextField,
17
21
  toast
18
22
  } from "@heroui/react"
23
+ import { useDebouncer } from "@tanstack/react-pacer"
19
24
  import { type SyntheticEvent, useState } from "react"
20
25
 
21
26
  import { FieldSeparator } from "./field-separator"
@@ -45,7 +50,7 @@ export function SignUp({
45
50
  socialPosition = "bottom",
46
51
  variant,
47
52
  ...props
48
- }: SignUpProps & CardProps) {
53
+ }: SignUpProps & Omit<CardProps, "children">) {
49
54
  const {
50
55
  basePaths,
51
56
  emailAndPassword,
@@ -53,12 +58,42 @@ export function SignUp({
53
58
  magicLink,
54
59
  redirectTo,
55
60
  socialProviders,
61
+ username: usernameConfig,
56
62
  viewPaths,
57
63
  navigate
58
64
  } = useAuth()
59
65
 
60
66
  const [password, setPassword] = useState("")
61
67
  const [confirmPassword, setConfirmPassword] = useState("")
68
+ const [username, setUsername] = useState("")
69
+
70
+ const {
71
+ mutate: isUsernameAvailable,
72
+ data: usernameData,
73
+ error: usernameError,
74
+ reset: resetUsername
75
+ } = useIsUsernameAvailable()
76
+
77
+ const usernameDebouncer = useDebouncer(
78
+ (value: string) => {
79
+ if (!value.trim()) {
80
+ resetUsername()
81
+ return
82
+ }
83
+
84
+ isUsernameAvailable({ username: value.trim() })
85
+ },
86
+ { wait: 500 }
87
+ )
88
+
89
+ function handleUsernameChange(value: string) {
90
+ setUsername(value)
91
+ resetUsername()
92
+
93
+ if (usernameConfig?.isUsernameAvailable) {
94
+ usernameDebouncer.maybeExecute(value)
95
+ }
96
+ }
62
97
 
63
98
  const { mutate: signUpEmail, isPending: signUpPending } = useSignUpEmail({
64
99
  onError: (error) => {
@@ -76,24 +111,11 @@ export function SignUp({
76
111
  }
77
112
  })
78
113
 
79
- const [socialRedirecting, setSocialRedirecting] = useState(false)
80
-
81
- const { mutate: signInSocial, isPending: socialPending } = useSignInSocial({
82
- onError: (error) => toast.danger(error.error?.message || error.message),
83
- onSuccess: () => {
84
- setSocialRedirecting(true)
85
-
86
- setTimeout(() => {
87
- setSocialRedirecting(false)
88
- }, 5000)
89
- }
90
- })
91
-
92
114
  const [isPasswordVisible, setIsPasswordVisible] = useState(false)
93
115
  const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
94
116
  useState(false)
95
117
 
96
- const isPending = signUpPending || socialPending || socialRedirecting
118
+ const isPending = signUpPending
97
119
 
98
120
  const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
99
121
  e.preventDefault()
@@ -109,7 +131,19 @@ export function SignUp({
109
131
  return
110
132
  }
111
133
 
112
- signUpEmail({ name, email, password })
134
+ signUpEmail({
135
+ name,
136
+ email,
137
+ password,
138
+ ...(usernameConfig?.enabled
139
+ ? {
140
+ username: username.trim(),
141
+ ...(usernameConfig.displayUsername
142
+ ? { displayUsername: username.trim() }
143
+ : {})
144
+ }
145
+ : {})
146
+ })
113
147
  }
114
148
 
115
149
  const showSeparator = emailAndPassword?.enabled && !!socialProviders?.length
@@ -133,7 +167,6 @@ export function SignUp({
133
167
  <ProviderButtons
134
168
  isPending={isPending}
135
169
  socialLayout={socialLayout}
136
- signInSocial={signInSocial}
137
170
  />
138
171
  )}
139
172
 
@@ -162,6 +195,53 @@ export function SignUp({
162
195
  <FieldError />
163
196
  </TextField>
164
197
 
198
+ {usernameConfig?.enabled && (
199
+ <TextField
200
+ name="username"
201
+ type="text"
202
+ autoComplete="username"
203
+ minLength={usernameConfig.minUsernameLength}
204
+ maxLength={usernameConfig.maxUsernameLength}
205
+ isDisabled={isPending}
206
+ value={username}
207
+ onChange={handleUsernameChange}
208
+ isInvalid={
209
+ !!usernameError || (usernameData && !usernameData.available)
210
+ }
211
+ >
212
+ <Label>{localization.auth.username}</Label>
213
+
214
+ <InputGroup
215
+ variant={variant === "transparent" ? "primary" : "secondary"}
216
+ >
217
+ <InputGroup.Input
218
+ placeholder={localization.auth.usernamePlaceholder}
219
+ required
220
+ />
221
+
222
+ {usernameConfig.isUsernameAvailable && username.trim() && (
223
+ <InputGroup.Suffix className="px-2">
224
+ {usernameData?.available ? (
225
+ <Check className="text-success" />
226
+ ) : usernameError || usernameData?.available === false ? (
227
+ <Xmark className="text-danger" />
228
+ ) : (
229
+ <Spinner size="sm" color="current" />
230
+ )}
231
+ </InputGroup.Suffix>
232
+ )}
233
+ </InputGroup>
234
+
235
+ <FieldError>
236
+ {usernameError?.error?.message ||
237
+ usernameError?.message ||
238
+ (usernameData?.available === false
239
+ ? localization.auth.usernameTaken
240
+ : null)}
241
+ </FieldError>
242
+ </TextField>
243
+ )}
244
+
165
245
  <TextField
166
246
  name="email"
167
247
  type="email"
@@ -290,7 +370,6 @@ export function SignUp({
290
370
  {!!socialProviders?.length && (
291
371
  <ProviderButtons
292
372
  socialLayout={socialLayout}
293
- signInSocial={signInSocial}
294
373
  isPending={isPending}
295
374
  />
296
375
  )}
@@ -34,7 +34,7 @@ export function Appearance({
34
34
  className,
35
35
  variant,
36
36
  ...props
37
- }: AppearanceProps & CardProps) {
37
+ }: AppearanceProps & Omit<CardProps, "children">) {
38
38
  const {
39
39
  localization,
40
40
  appearance: { theme, setTheme, themes }
@@ -14,9 +14,7 @@ export function ChangeAvatar({ className }: ChangeAvatarProps) {
14
14
  const { localization, avatar } = useAuth()
15
15
  const { data: session } = useSession()
16
16
 
17
- const { mutate: updateUser, isPending: updatePending } = useUpdateUser({
18
- onError: (error) => toast.danger(error.error?.message || error.message)
19
- })
17
+ const { mutate: updateUser, isPending: updatePending } = useUpdateUser()
20
18
 
21
19
  const fileInputRef = useRef<HTMLInputElement>(null)
22
20
  const [isUploading, setIsUploading] = useState(false)
@@ -34,13 +34,12 @@ export function ChangeEmail({
34
34
  className,
35
35
  variant,
36
36
  ...props
37
- }: ChangeEmailProps & CardProps) {
37
+ }: ChangeEmailProps & Omit<CardProps, "children">) {
38
38
  const { localization, baseURL, viewPaths } = useAuth()
39
39
  const { data: session } = useSession()
40
40
 
41
41
  const { mutate: changeEmail, isPending } = useChangeEmail({
42
- onSuccess: () => toast.success(localization.settings.changeEmailSuccess),
43
- onError: (error) => toast.danger(error.error?.message || error.message)
42
+ onSuccess: () => toast.success(localization.settings.changeEmailSuccess)
44
43
  })
45
44
 
46
45
  function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
@@ -41,13 +41,10 @@ export function ManageAccount({
41
41
  const { data: user } = useUser()
42
42
 
43
43
  const { mutate: setActiveSession, isPending: isSwitching } =
44
- useSetActiveSession({
45
- onError: (error) => toast.danger(error.error?.message || error.message)
46
- })
44
+ useSetActiveSession()
47
45
 
48
46
  const { mutate: revokeSession, isPending: isRevoking } =
49
47
  useRevokeMultiSession({
50
- onError: (error) => toast.danger(error.error?.message || error.message),
51
48
  onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
52
49
  })
53
50
 
@@ -3,7 +3,7 @@ import {
3
3
  useListDeviceSessions,
4
4
  useSession
5
5
  } from "@better-auth-ui/react"
6
- import { Card, type CardProps, cn, toast } from "@heroui/react"
6
+ import { Card, type CardProps, cn } from "@heroui/react"
7
7
  import { ManageAccount } from "./manage-account"
8
8
 
9
9
  export type ManageAccountsProps = {
@@ -23,16 +23,11 @@ export function ManageAccounts({
23
23
  className,
24
24
  variant,
25
25
  ...props
26
- }: ManageAccountsProps & CardProps) {
26
+ }: ManageAccountsProps & Omit<CardProps, "children">) {
27
27
  const { localization } = useAuth()
28
28
  const { data: session } = useSession()
29
29
 
30
- const { data: deviceSessions, isPending } = useListDeviceSessions({
31
- throwOnError: (error) => {
32
- if (error.error) toast.danger(error.error.message)
33
- return false
34
- }
35
- })
30
+ const { data: deviceSessions, isPending } = useListDeviceSessions()
36
31
 
37
32
  const otherSessions = deviceSessions?.filter(
38
33
  (deviceSession) => deviceSession.session.id !== session?.session.id