@better-auth-ui/heroui 1.6.1 → 1.6.2

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 (89) hide show
  1. package/dist/components/auth/auth-provider.d.ts +1 -1
  2. package/dist/components/auth/auth-provider.js +15 -11
  3. package/dist/components/auth/auth.d.ts +3 -3
  4. package/dist/components/auth/auth.js +36 -41
  5. package/dist/components/auth/field-separator.d.ts +1 -1
  6. package/dist/components/auth/field-separator.js +17 -4
  7. package/dist/components/auth/forgot-password.d.ts +1 -1
  8. package/dist/components/auth/forgot-password.js +69 -27
  9. package/dist/components/auth/magic-link-button.d.ts +1 -1
  10. package/dist/components/auth/magic-link-button.js +15 -18
  11. package/dist/components/auth/magic-link.d.ts +2 -2
  12. package/dist/components/auth/magic-link.js +110 -43
  13. package/dist/components/auth/passkey-button.js +23 -12
  14. package/dist/components/auth/provider-buttons.d.ts +1 -1
  15. package/dist/components/auth/provider-buttons.js +26 -33
  16. package/dist/components/auth/reset-password.d.ts +1 -1
  17. package/dist/components/auth/reset-password.js +146 -57
  18. package/dist/components/auth/sign-in.d.ts +2 -2
  19. package/dist/components/auth/sign-in.js +163 -64
  20. package/dist/components/auth/sign-out.js +26 -32
  21. package/dist/components/auth/sign-up.d.ts +2 -2
  22. package/dist/components/auth/sign-up.js +240 -70
  23. package/dist/components/email/email-changed-email.d.ts +2 -2
  24. package/dist/components/email/email-changed-email.js +29 -22
  25. package/dist/components/email/email-verification-email.d.ts +2 -2
  26. package/dist/components/email/email-verification-email.js +29 -22
  27. package/dist/components/email/magic-link-email.d.ts +2 -2
  28. package/dist/components/email/magic-link-email.js +29 -22
  29. package/dist/components/email/new-device-email.d.ts +2 -2
  30. package/dist/components/email/new-device-email.js +29 -22
  31. package/dist/components/email/otp-email.d.ts +2 -2
  32. package/dist/components/email/otp-email.js +28 -21
  33. package/dist/components/email/password-changed-email.d.ts +2 -2
  34. package/dist/components/email/password-changed-email.js +29 -22
  35. package/dist/components/email/reset-password-email.d.ts +2 -2
  36. package/dist/components/email/reset-password-email.js +29 -22
  37. package/dist/components/settings/account/account-settings.d.ts +2 -2
  38. package/dist/components/settings/account/account-settings.js +22 -19
  39. package/dist/components/settings/account/appearance.d.ts +1 -1
  40. package/dist/components/settings/account/appearance.js +70 -27
  41. package/dist/components/settings/account/change-avatar.js +88 -55
  42. package/dist/components/settings/account/change-email.d.ts +1 -1
  43. package/dist/components/settings/account/change-email.js +56 -28
  44. package/dist/components/settings/account/manage-account.d.ts +1 -1
  45. package/dist/components/settings/account/manage-account.js +50 -31
  46. package/dist/components/settings/account/manage-accounts.d.ts +1 -1
  47. package/dist/components/settings/account/manage-accounts.js +32 -36
  48. package/dist/components/settings/account/user-profile.d.ts +3 -3
  49. package/dist/components/settings/account/user-profile.js +105 -22
  50. package/dist/components/settings/security/active-session.d.ts +1 -1
  51. package/dist/components/settings/security/active-session.js +60 -51
  52. package/dist/components/settings/security/active-sessions.d.ts +1 -1
  53. package/dist/components/settings/security/active-sessions.js +32 -26
  54. package/dist/components/settings/security/change-password.d.ts +1 -1
  55. package/dist/components/settings/security/change-password.js +189 -76
  56. package/dist/components/settings/security/danger-zone.d.ts +2 -2
  57. package/dist/components/settings/security/danger-zone.js +17 -11
  58. package/dist/components/settings/security/delete-user.d.ts +1 -1
  59. package/dist/components/settings/security/delete-user.js +96 -46
  60. package/dist/components/settings/security/linked-account.d.ts +1 -1
  61. package/dist/components/settings/security/linked-account.js +61 -47
  62. package/dist/components/settings/security/linked-accounts.d.ts +1 -1
  63. package/dist/components/settings/security/linked-accounts.js +43 -40
  64. package/dist/components/settings/security/passkey.js +42 -13
  65. package/dist/components/settings/security/passkeys.d.ts +1 -1
  66. package/dist/components/settings/security/passkeys.js +51 -19
  67. package/dist/components/settings/security/security-settings.d.ts +2 -2
  68. package/dist/components/settings/security/security-settings.js +24 -21
  69. package/dist/components/settings/settings.d.ts +3 -3
  70. package/dist/components/settings/settings.js +44 -27
  71. package/dist/components/user/switch-account-item.d.ts +1 -1
  72. package/dist/components/user/switch-account-item.js +19 -15
  73. package/dist/components/user/switch-account-menu.js +27 -28
  74. package/dist/components/user/user-avatar.d.ts +3 -3
  75. package/dist/components/user/user-avatar.js +28 -30
  76. package/dist/components/user/user-button.d.ts +1 -1
  77. package/dist/components/user/user-button.js +90 -23
  78. package/dist/components/user/user-view.d.ts +3 -3
  79. package/dist/components/user/user-view.js +36 -23
  80. package/dist/core.d.ts +1 -1
  81. package/dist/index.d.ts +35 -35
  82. package/dist/index.js +36 -35
  83. package/dist/react-exports.d.ts +1 -1
  84. package/package.json +12 -10
  85. package/src/components/auth/auth.tsx +1 -1
  86. package/src/components/auth/sign-in.tsx +63 -30
  87. package/src/components/auth/sign-up.tsx +97 -3
  88. package/src/components/settings/account/user-profile.tsx +121 -6
  89. package/src/components/settings/settings.tsx +1 -1
@@ -1,5 +1,10 @@
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
+ useSignInSocial,
5
+ useSignUpEmail
6
+ } from "@better-auth-ui/react"
7
+ import { Check, Eye, EyeSlash, Xmark } from "@gravity-ui/icons"
3
8
  import {
4
9
  Button,
5
10
  Card,
@@ -16,6 +21,7 @@ import {
16
21
  TextField,
17
22
  toast
18
23
  } from "@heroui/react"
24
+ import { useDebouncer } from "@tanstack/react-pacer"
19
25
  import { type SyntheticEvent, useState } from "react"
20
26
 
21
27
  import { FieldSeparator } from "./field-separator"
@@ -53,12 +59,42 @@ export function SignUp({
53
59
  magicLink,
54
60
  redirectTo,
55
61
  socialProviders,
62
+ username: usernameConfig,
56
63
  viewPaths,
57
64
  navigate
58
65
  } = useAuth()
59
66
 
60
67
  const [password, setPassword] = useState("")
61
68
  const [confirmPassword, setConfirmPassword] = useState("")
69
+ const [username, setUsername] = useState("")
70
+
71
+ const {
72
+ mutate: isUsernameAvailable,
73
+ data: usernameData,
74
+ error: usernameError,
75
+ reset: resetUsername
76
+ } = useIsUsernameAvailable()
77
+
78
+ const usernameDebouncer = useDebouncer(
79
+ (value: string) => {
80
+ if (!value.trim()) {
81
+ resetUsername()
82
+ return
83
+ }
84
+
85
+ isUsernameAvailable({ username: value.trim() })
86
+ },
87
+ { wait: 500 }
88
+ )
89
+
90
+ function handleUsernameChange(value: string) {
91
+ setUsername(value)
92
+ resetUsername()
93
+
94
+ if (usernameConfig?.isUsernameAvailable) {
95
+ usernameDebouncer.maybeExecute(value)
96
+ }
97
+ }
62
98
 
63
99
  const { mutate: signUpEmail, isPending: signUpPending } = useSignUpEmail({
64
100
  onError: (error) => {
@@ -109,7 +145,19 @@ export function SignUp({
109
145
  return
110
146
  }
111
147
 
112
- signUpEmail({ name, email, password })
148
+ signUpEmail({
149
+ name,
150
+ email,
151
+ password,
152
+ ...(usernameConfig?.enabled
153
+ ? {
154
+ username: username.trim(),
155
+ ...(usernameConfig.displayUsername
156
+ ? { displayUsername: username.trim() }
157
+ : {})
158
+ }
159
+ : {})
160
+ })
113
161
  }
114
162
 
115
163
  const showSeparator = emailAndPassword?.enabled && !!socialProviders?.length
@@ -162,6 +210,52 @@ export function SignUp({
162
210
  <FieldError />
163
211
  </TextField>
164
212
 
213
+ {usernameConfig?.enabled && (
214
+ <TextField
215
+ name="username"
216
+ type="text"
217
+ autoComplete="username"
218
+ minLength={usernameConfig.minUsernameLength}
219
+ maxLength={usernameConfig.maxUsernameLength}
220
+ isDisabled={isPending}
221
+ value={username}
222
+ onChange={handleUsernameChange}
223
+ isInvalid={
224
+ !!usernameError || (usernameData && !usernameData.available)
225
+ }
226
+ >
227
+ <Label>{localization.auth.username}</Label>
228
+
229
+ <InputGroup
230
+ variant={variant === "transparent" ? "primary" : "secondary"}
231
+ >
232
+ <InputGroup.Input
233
+ placeholder={localization.auth.usernamePlaceholder}
234
+ required
235
+ />
236
+
237
+ {usernameConfig.isUsernameAvailable && username.trim() && (
238
+ <InputGroup.Suffix className="px-2">
239
+ {usernameData?.available ? (
240
+ <Check className="text-success" />
241
+ ) : usernameError || usernameData?.available === false ? (
242
+ <Xmark className="text-danger" />
243
+ ) : (
244
+ <Spinner size="sm" color="current" />
245
+ )}
246
+ </InputGroup.Suffix>
247
+ )}
248
+ </InputGroup>
249
+
250
+ <FieldError>
251
+ {usernameError?.error?.message ||
252
+ usernameError?.message ||
253
+ (usernameData?.available === false &&
254
+ localization.auth.usernameTaken)}
255
+ </FieldError>
256
+ </TextField>
257
+ )}
258
+
165
259
  <TextField
166
260
  name="email"
167
261
  type="email"
@@ -1,4 +1,10 @@
1
- import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react"
1
+ import {
2
+ useAuth,
3
+ useIsUsernameAvailable,
4
+ useSession,
5
+ useUpdateUser
6
+ } from "@better-auth-ui/react"
7
+ import { Check, Xmark } from "@gravity-ui/icons"
2
8
  import {
3
9
  Button,
4
10
  Card,
@@ -8,13 +14,16 @@ import {
8
14
  Fieldset,
9
15
  Form,
10
16
  Input,
17
+ InputGroup,
11
18
  Label,
12
19
  Skeleton,
13
20
  Spinner,
14
21
  TextField,
15
22
  toast
16
23
  } from "@heroui/react"
17
- import type { SyntheticEvent } from "react"
24
+ import { useDebouncer } from "@tanstack/react-pacer"
25
+ import { type SyntheticEvent, useEffect, useState } from "react"
26
+
18
27
  import { ChangeAvatar } from "./change-avatar"
19
28
 
20
29
  export type UserProfileProps = {
@@ -23,18 +32,57 @@ export type UserProfileProps = {
23
32
  }
24
33
 
25
34
  /**
26
- * Render a profile card that lets the authenticated user view and update their display name and avatar.
35
+ * Render a profile card that lets the authenticated user view and update their display name, username, and avatar.
27
36
  *
28
- * @returns A JSX element containing the user profile card with avatar upload and an editable name form
37
+ * @returns A JSX element containing the user profile card with avatar upload and editable form fields
29
38
  */
30
39
  export function UserProfile({
31
40
  className,
32
41
  variant,
33
42
  ...props
34
43
  }: UserProfileProps & CardProps) {
35
- const { localization } = useAuth()
44
+ const { localization, username: usernameConfig } = useAuth()
36
45
  const { data: session } = useSession()
37
46
 
47
+ const currentUsername =
48
+ (usernameConfig?.displayUsername
49
+ ? session?.user?.displayUsername
50
+ : session?.user?.username) || ""
51
+
52
+ const [username, setUsername] = useState(currentUsername)
53
+
54
+ useEffect(() => {
55
+ setUsername(currentUsername)
56
+ }, [currentUsername])
57
+
58
+ const {
59
+ mutate: isUsernameAvailable,
60
+ data: usernameData,
61
+ error: usernameError,
62
+ reset: resetUsername
63
+ } = useIsUsernameAvailable()
64
+
65
+ const usernameDebouncer = useDebouncer(
66
+ (value: string) => {
67
+ if (!value.trim() || value.trim() === currentUsername) {
68
+ resetUsername()
69
+ return
70
+ }
71
+
72
+ isUsernameAvailable({ username: value.trim() })
73
+ },
74
+ { wait: 500 }
75
+ )
76
+
77
+ function handleUsernameChange(value: string) {
78
+ setUsername(value)
79
+ resetUsername()
80
+
81
+ if (usernameConfig?.isUsernameAvailable) {
82
+ usernameDebouncer.maybeExecute(value)
83
+ }
84
+ }
85
+
38
86
  const { mutate: updateUser, isPending } = useUpdateUser({
39
87
  onError: (error) => toast.danger(error.error?.message || error.message),
40
88
  onSuccess: () => toast.success(localization.settings.profileUpdatedSuccess)
@@ -43,7 +91,19 @@ export function UserProfile({
43
91
  function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
44
92
  e.preventDefault()
45
93
  const formData = new FormData(e.currentTarget)
46
- updateUser({ name: formData.get("name") as string })
94
+ const name = formData.get("name") as string
95
+
96
+ updateUser({
97
+ name,
98
+ ...(usernameConfig?.enabled
99
+ ? {
100
+ username: username.trim(),
101
+ ...(usernameConfig.displayUsername
102
+ ? { displayUsername: username.trim() }
103
+ : {})
104
+ }
105
+ : {})
106
+ })
47
107
  }
48
108
 
49
109
  return (
@@ -59,6 +119,61 @@ export function UserProfile({
59
119
  <ChangeAvatar />
60
120
 
61
121
  <Fieldset.Group>
122
+ {usernameConfig?.enabled && (
123
+ <TextField
124
+ type="text"
125
+ autoComplete="username"
126
+ minLength={usernameConfig.minUsernameLength}
127
+ maxLength={usernameConfig.maxUsernameLength}
128
+ isDisabled={isPending || !session}
129
+ value={username}
130
+ onChange={handleUsernameChange}
131
+ isInvalid={
132
+ !!usernameError ||
133
+ (usernameData && !usernameData.available)
134
+ }
135
+ >
136
+ <Label>{localization.auth.username}</Label>
137
+
138
+ {session ? (
139
+ <InputGroup
140
+ variant={
141
+ variant === "transparent" ? "primary" : "secondary"
142
+ }
143
+ >
144
+ <InputGroup.Input
145
+ placeholder={localization.auth.usernamePlaceholder}
146
+ name="username"
147
+ />
148
+
149
+ {usernameConfig.isUsernameAvailable &&
150
+ username.trim() &&
151
+ username.trim() !== currentUsername && (
152
+ <InputGroup.Suffix className="px-2">
153
+ {usernameData?.available ? (
154
+ <Check className="text-success" />
155
+ ) : usernameError ||
156
+ usernameData?.available === false ? (
157
+ <Xmark className="text-danger" />
158
+ ) : (
159
+ <Spinner size="sm" color="current" />
160
+ )}
161
+ </InputGroup.Suffix>
162
+ )}
163
+ </InputGroup>
164
+ ) : (
165
+ <Skeleton className="h-10 md:h-9 w-full rounded-xl" />
166
+ )}
167
+
168
+ <FieldError>
169
+ {usernameError?.error?.message ||
170
+ usernameError?.message ||
171
+ (usernameData?.available === false &&
172
+ localization.auth.usernameTaken)}
173
+ </FieldError>
174
+ </TextField>
175
+ )}
176
+
62
177
  <TextField
63
178
  key={session?.user?.name}
64
179
  name="name"
@@ -1,5 +1,5 @@
1
+ import type { SettingsView } from "@better-auth-ui/core"
1
2
  import { useAuth, useAuthenticate } from "@better-auth-ui/react"
2
- import type { SettingsView } from "@better-auth-ui/react/core"
3
3
  import { type CardProps, cn, Tabs } from "@heroui/react"
4
4
  import { type ComponentProps, useMemo } from "react"
5
5