@better-auth-ui/heroui 1.6.4 → 1.6.6
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.
- package/dist/components/auth/delete-user/danger-zone.d.ts +3 -2
- package/dist/components/auth/delete-user/danger-zone.js +1 -1
- package/dist/components/auth/delete-user/delete-user.js +53 -53
- package/dist/components/auth/forgot-password.js +67 -48
- package/dist/components/auth/sign-in.js +72 -67
- package/dist/components/auth/sign-up.js +82 -77
- package/dist/components/auth/theme/theme-toggle-item.js +50 -46
- package/dist/components/auth/user/user-button.d.ts +27 -2
- package/dist/components/auth/user/user-button.js +48 -29
- package/dist/components/auth/username/sign-in-username.js +80 -74
- package/dist/components/auth/username/username-field.js +4 -2
- package/package.json +12 -12
- package/src/components/auth/delete-user/danger-zone.tsx +10 -3
- package/src/components/auth/delete-user/delete-user.tsx +2 -7
- package/src/components/auth/forgot-password.tsx +29 -7
- package/src/components/auth/sign-in.tsx +13 -1
- package/src/components/auth/sign-up.tsx +12 -2
- package/src/components/auth/theme/theme-toggle-item.tsx +2 -1
- package/src/components/auth/user/user-button.tsx +80 -13
- package/src/components/auth/username/sign-in-username.tsx +40 -24
- package/src/components/auth/username/username-field.tsx +4 -2
|
@@ -11,14 +11,38 @@ import {
|
|
|
11
11
|
type ButtonProps,
|
|
12
12
|
cn,
|
|
13
13
|
Dropdown,
|
|
14
|
+
type DropdownItemProps,
|
|
14
15
|
type DropdownPopoverProps,
|
|
15
|
-
Label
|
|
16
|
-
Separator
|
|
16
|
+
Label
|
|
17
17
|
} from "@heroui/react"
|
|
18
|
+
import { isValidElement, type ReactElement, type ReactNode } from "react"
|
|
18
19
|
|
|
19
20
|
import { UserAvatar } from "./user-avatar"
|
|
20
21
|
import { UserView } from "./user-view"
|
|
21
22
|
|
|
23
|
+
/** Auth states a `UserButton` link can be visible in. */
|
|
24
|
+
export type UserButtonLinkVisibility =
|
|
25
|
+
| "authenticated"
|
|
26
|
+
| "unauthenticated"
|
|
27
|
+
| "always"
|
|
28
|
+
|
|
29
|
+
/** A simple link entry rendered as a `Dropdown.Item` in the `UserButton` menu. */
|
|
30
|
+
export type UserButtonLink = {
|
|
31
|
+
/** Visible label. */
|
|
32
|
+
label: ReactNode
|
|
33
|
+
/** Destination URL. */
|
|
34
|
+
href: string
|
|
35
|
+
/** Optional leading icon. Sized/coloured to match built-in items. */
|
|
36
|
+
icon?: ReactNode
|
|
37
|
+
/** Forwarded to the underlying `Dropdown.Item`. */
|
|
38
|
+
variant?: DropdownItemProps["variant"]
|
|
39
|
+
/**
|
|
40
|
+
* When this link is visible based on auth state.
|
|
41
|
+
* @default "always"
|
|
42
|
+
*/
|
|
43
|
+
visibility?: UserButtonLinkVisibility
|
|
44
|
+
}
|
|
45
|
+
|
|
22
46
|
export type UserButtonProps = {
|
|
23
47
|
className?: string
|
|
24
48
|
size?: "default" | "icon"
|
|
@@ -28,6 +52,30 @@ export type UserButtonProps = {
|
|
|
28
52
|
*/
|
|
29
53
|
placement?: DropdownPopoverProps["placement"]
|
|
30
54
|
variant?: ButtonProps["variant"]
|
|
55
|
+
/** Additional menu entries rendered above the built-in items. */
|
|
56
|
+
links?: (UserButtonLink | ReactElement)[]
|
|
57
|
+
/** Hide the built-in "Settings" link. Useful when replacing it via `links`. */
|
|
58
|
+
hideSettings?: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderUserLink(
|
|
62
|
+
link: UserButtonLink | ReactElement,
|
|
63
|
+
fallbackKey: string
|
|
64
|
+
): ReactNode {
|
|
65
|
+
if (isValidElement(link)) return link
|
|
66
|
+
|
|
67
|
+
const { label, href, icon, variant } = link
|
|
68
|
+
return (
|
|
69
|
+
<Dropdown.Item
|
|
70
|
+
key={fallbackKey}
|
|
71
|
+
href={href}
|
|
72
|
+
variant={variant}
|
|
73
|
+
textValue={typeof label === "string" ? label : undefined}
|
|
74
|
+
>
|
|
75
|
+
{icon}
|
|
76
|
+
<Label>{label}</Label>
|
|
77
|
+
</Dropdown.Item>
|
|
78
|
+
)
|
|
31
79
|
}
|
|
32
80
|
|
|
33
81
|
/**
|
|
@@ -37,13 +85,17 @@ export type UserButtonProps = {
|
|
|
37
85
|
* @param placement - Dropdown popover placement (e.g., "bottom", "top-start", "bottom-end")
|
|
38
86
|
* @param size - "icon" renders an avatar-only trigger; "default" renders a button with label and chevron
|
|
39
87
|
* @param variant - Button visual variant passed to the underlying Button component
|
|
88
|
+
* @param links - Additional menu entries rendered above the built-in items
|
|
89
|
+
* @param hideSettings - Hide the built-in "Settings" link
|
|
40
90
|
* @returns The user button and its dropdown menu as a JSX element
|
|
41
91
|
*/
|
|
42
92
|
export function UserButton({
|
|
43
93
|
className,
|
|
44
94
|
placement = "bottom",
|
|
45
95
|
size = "default",
|
|
46
|
-
variant = "ghost"
|
|
96
|
+
variant = "ghost",
|
|
97
|
+
links,
|
|
98
|
+
hideSettings = false
|
|
47
99
|
}: UserButtonProps) {
|
|
48
100
|
const { authClient, basePaths, viewPaths, localization, plugins } = useAuth()
|
|
49
101
|
|
|
@@ -55,6 +107,16 @@ export function UserButton({
|
|
|
55
107
|
<Item key={`${plugin.id}-${index.toString()}`} />
|
|
56
108
|
)) ?? []
|
|
57
109
|
)
|
|
110
|
+
|
|
111
|
+
const userLinks = links?.flatMap((link, index) => {
|
|
112
|
+
if (!isValidElement(link)) {
|
|
113
|
+
const visibility = link.visibility ?? "always"
|
|
114
|
+
if (visibility === "authenticated" && !session) return []
|
|
115
|
+
if (visibility === "unauthenticated" && session) return []
|
|
116
|
+
}
|
|
117
|
+
return [renderUserLink(link, `user-button-link-${index.toString()}`)]
|
|
118
|
+
})
|
|
119
|
+
|
|
58
120
|
return (
|
|
59
121
|
<Dropdown>
|
|
60
122
|
{size === "icon" ? (
|
|
@@ -96,30 +158,35 @@ export function UserButton({
|
|
|
96
158
|
<Dropdown.Menu>
|
|
97
159
|
{session ? (
|
|
98
160
|
<>
|
|
99
|
-
|
|
100
|
-
textValue={localization.settings.settings}
|
|
101
|
-
href={`${basePaths.settings}/${viewPaths.settings.account}`}
|
|
102
|
-
>
|
|
103
|
-
<Gear className="text-muted" />
|
|
161
|
+
{userLinks}
|
|
104
162
|
|
|
105
|
-
|
|
106
|
-
|
|
163
|
+
{!hideSettings && (
|
|
164
|
+
<Dropdown.Item
|
|
165
|
+
textValue={localization.settings.settings}
|
|
166
|
+
href={`${basePaths.settings}/${viewPaths.settings.account}`}
|
|
167
|
+
>
|
|
168
|
+
<Gear className="text-muted" />
|
|
107
169
|
|
|
108
|
-
|
|
170
|
+
<Label>{localization.settings.settings}</Label>
|
|
171
|
+
</Dropdown.Item>
|
|
172
|
+
)}
|
|
109
173
|
|
|
110
|
-
|
|
174
|
+
{userMenuItems}
|
|
111
175
|
|
|
112
176
|
<Dropdown.Item
|
|
113
177
|
textValue={localization.auth.signOut}
|
|
114
178
|
href={`${basePaths.auth}/${viewPaths.auth.signOut}`}
|
|
179
|
+
variant="danger"
|
|
115
180
|
>
|
|
116
|
-
<ArrowRightFromSquare className="text-
|
|
181
|
+
<ArrowRightFromSquare className="text-danger" />
|
|
117
182
|
|
|
118
183
|
<Label>{localization.auth.signOut}</Label>
|
|
119
184
|
</Dropdown.Item>
|
|
120
185
|
</>
|
|
121
186
|
) : (
|
|
122
187
|
<>
|
|
188
|
+
{userLinks}
|
|
189
|
+
|
|
123
190
|
<Dropdown.Item
|
|
124
191
|
textValue={localization.auth.signIn}
|
|
125
192
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type UsernameAuthClient,
|
|
4
4
|
useAuth,
|
|
5
5
|
useAuthPlugin,
|
|
6
|
+
useFetchOptions,
|
|
6
7
|
useSendVerificationEmail,
|
|
7
8
|
useSignInEmail,
|
|
8
9
|
useSignInUsername
|
|
@@ -61,6 +62,8 @@ export function SignInUsername({
|
|
|
61
62
|
navigate
|
|
62
63
|
} = useAuth()
|
|
63
64
|
|
|
65
|
+
const { fetchOptions, resetFetchOptions } = useFetchOptions()
|
|
66
|
+
|
|
64
67
|
const { localization: usernameLocalization } = useAuthPlugin(usernamePlugin)
|
|
65
68
|
|
|
66
69
|
const [password, setPassword] = useState("")
|
|
@@ -76,33 +79,37 @@ export function SignInUsername({
|
|
|
76
79
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
const { mutate: signInEmail } =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
82
|
+
const { mutate: signInEmail, isPending: isSignInEmailPending } =
|
|
83
|
+
useSignInEmail(authClient, {
|
|
84
|
+
onError: (error, { email }) => {
|
|
85
|
+
setPassword("")
|
|
86
|
+
|
|
87
|
+
if (error.error?.code === "EMAIL_NOT_VERIFIED") {
|
|
88
|
+
toast.danger(error.error?.message || error.message, {
|
|
89
|
+
actionProps: {
|
|
90
|
+
children: localization.auth.resend,
|
|
91
|
+
onClick: () =>
|
|
92
|
+
sendVerificationEmail({
|
|
93
|
+
email,
|
|
94
|
+
callbackURL: `${baseURL}${redirectTo}`
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
} else {
|
|
99
|
+
toast.danger(error.error?.message || error.message)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
resetFetchOptions()
|
|
103
|
+
},
|
|
104
|
+
onSuccess: () => navigate({ to: redirectTo })
|
|
105
|
+
})
|
|
100
106
|
|
|
101
|
-
const { mutate: signInUsername, isPending:
|
|
107
|
+
const { mutate: signInUsername, isPending: isSignInUsernamePending } =
|
|
102
108
|
useSignInUsername(authClient as UsernameAuthClient, {
|
|
103
109
|
onError: (error) => {
|
|
104
110
|
setPassword("")
|
|
105
111
|
toast.danger(error.error?.message || error.message)
|
|
112
|
+
resetFetchOptions()
|
|
106
113
|
},
|
|
107
114
|
onSuccess: () => navigate({ to: redirectTo })
|
|
108
115
|
})
|
|
@@ -118,13 +125,15 @@ export function SignInUsername({
|
|
|
118
125
|
signInEmail({
|
|
119
126
|
email,
|
|
120
127
|
password,
|
|
121
|
-
...(emailAndPassword?.rememberMe ? { rememberMe } : {})
|
|
128
|
+
...(emailAndPassword?.rememberMe ? { rememberMe } : {}),
|
|
129
|
+
fetchOptions
|
|
122
130
|
})
|
|
123
131
|
} else {
|
|
124
132
|
signInUsername({
|
|
125
133
|
username: email,
|
|
126
134
|
password,
|
|
127
|
-
...(emailAndPassword?.rememberMe ? { rememberMe } : {})
|
|
135
|
+
...(emailAndPassword?.rememberMe ? { rememberMe } : {}),
|
|
136
|
+
fetchOptions
|
|
128
137
|
})
|
|
129
138
|
}
|
|
130
139
|
}
|
|
@@ -136,6 +145,11 @@ export function SignInUsername({
|
|
|
136
145
|
mutationKey: authMutationKeys.signUp.all
|
|
137
146
|
})
|
|
138
147
|
const isPending = signInMutating + signUpMutating > 0
|
|
148
|
+
const isSignInPending = isSignInEmailPending || isSignInUsernamePending
|
|
149
|
+
|
|
150
|
+
const Captcha = plugins.find(
|
|
151
|
+
(plugin) => plugin.captchaComponent
|
|
152
|
+
)?.captchaComponent
|
|
139
153
|
|
|
140
154
|
const showSeparator = emailAndPassword?.enabled && !!socialProviders?.length
|
|
141
155
|
|
|
@@ -220,6 +234,8 @@ export function SignInUsername({
|
|
|
220
234
|
</Checkbox>
|
|
221
235
|
)}
|
|
222
236
|
|
|
237
|
+
{Captcha && <div className="flex justify-center">{Captcha}</div>}
|
|
238
|
+
|
|
223
239
|
<div className="flex flex-col gap-3">
|
|
224
240
|
<Button
|
|
225
241
|
type="submit"
|
|
@@ -84,7 +84,6 @@ export function UsernameField({
|
|
|
84
84
|
minLength={minUsernameLength}
|
|
85
85
|
maxLength={maxUsernameLength}
|
|
86
86
|
isDisabled={isPending}
|
|
87
|
-
isRequired={field.required}
|
|
88
87
|
isReadOnly={field.readOnly}
|
|
89
88
|
value={value}
|
|
90
89
|
onChange={handleChange}
|
|
@@ -92,7 +91,10 @@ export function UsernameField({
|
|
|
92
91
|
<Label>{field.label}</Label>
|
|
93
92
|
|
|
94
93
|
<InputGroup variant={variant === "transparent" ? "primary" : "secondary"}>
|
|
95
|
-
<InputGroup.Input
|
|
94
|
+
<InputGroup.Input
|
|
95
|
+
placeholder={field.placeholder}
|
|
96
|
+
required={field.required}
|
|
97
|
+
/>
|
|
96
98
|
|
|
97
99
|
{isCheckingAvailability && (
|
|
98
100
|
<InputGroup.Suffix
|