@better-auth-ui/heroui 1.6.3 → 1.6.5
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/additional-field.d.ts +4 -0
- package/dist/components/auth/additional-field.js +270 -0
- package/dist/components/auth/auth-provider.d.ts +4 -6
- package/dist/components/auth/auth.d.ts +1 -1
- package/dist/components/auth/auth.js +54 -28
- package/dist/components/{settings/security → auth/delete-user}/danger-zone.d.ts +1 -1
- package/dist/components/{settings/security → auth/delete-user}/danger-zone.js +1 -1
- package/dist/components/auth/delete-user/delete-user.js +95 -0
- package/dist/components/{email → auth/email}/email-changed-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/email-changed-email.js +7 -7
- package/dist/components/{email → auth/email}/email-verification-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/email-verification-email.js +7 -7
- package/dist/components/auth/email/magic-link-email.d.ts +3 -0
- package/dist/components/{email → auth/email}/magic-link-email.js +7 -7
- package/dist/components/auth/email/new-device-email.d.ts +3 -0
- package/dist/components/{email → auth/email}/new-device-email.js +7 -7
- package/dist/components/auth/email/otp-email.d.ts +3 -0
- package/dist/components/{email → auth/email}/otp-email.js +6 -6
- package/dist/components/{email → auth/email}/password-changed-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/password-changed-email.js +7 -7
- package/dist/components/{email → auth/email}/reset-password-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/reset-password-email.js +7 -7
- package/dist/components/auth/forgot-password.d.ts +1 -1
- package/dist/components/auth/forgot-password.js +70 -48
- package/dist/components/auth/magic-link/magic-link-button.d.ts +11 -0
- package/dist/components/auth/magic-link/magic-link-button.js +20 -0
- package/dist/components/auth/magic-link/magic-link.d.ts +16 -0
- package/dist/components/auth/magic-link/magic-link.js +87 -0
- package/dist/components/{settings/account → auth/multi-session}/manage-account.d.ts +2 -6
- package/dist/components/auth/multi-session/manage-account.js +49 -0
- package/dist/components/auth/multi-session/manage-accounts.js +34 -0
- package/dist/components/{user/switch-account-menu.d.ts → auth/multi-session/switch-account-submenu-content.d.ts} +3 -3
- package/dist/components/auth/multi-session/switch-account-submenu-content.js +29 -0
- package/dist/components/{user/switch-account-item.d.ts → auth/multi-session/switch-account-submenu-item.d.ts} +2 -2
- package/dist/components/auth/multi-session/switch-account-submenu-item.js +20 -0
- package/dist/components/auth/multi-session/switch-account-submenu.d.ts +13 -0
- package/dist/components/auth/multi-session/switch-account-submenu.js +24 -0
- package/dist/components/auth/passkey/passkey-button.d.ts +6 -0
- package/dist/components/auth/passkey/passkey-button.js +24 -0
- package/dist/components/auth/passkey/passkey.js +45 -0
- package/dist/components/auth/passkey/passkeys.js +106 -0
- package/dist/components/auth/provider-button.d.ts +6 -8
- package/dist/components/auth/provider-button.js +16 -20
- package/dist/components/auth/provider-buttons.d.ts +3 -4
- package/dist/components/auth/provider-buttons.js +9 -10
- package/dist/components/auth/reset-password.js +110 -107
- package/dist/components/{settings → auth/settings}/account/account-settings.d.ts +5 -2
- package/dist/components/auth/settings/account/account-settings.js +20 -0
- package/dist/components/auth/settings/account/change-avatar.js +89 -0
- package/dist/components/{settings → auth/settings}/account/change-email.js +17 -17
- package/dist/components/{settings → auth/settings}/account/user-profile.d.ts +2 -3
- package/dist/components/auth/settings/account/user-profile.js +86 -0
- package/dist/components/auth/settings/security/active-session.js +59 -0
- package/dist/components/{settings → auth/settings}/security/active-sessions.js +4 -4
- package/dist/components/auth/settings/security/change-password.js +189 -0
- package/dist/components/auth/settings/security/linked-account.js +56 -0
- package/dist/components/{settings → auth/settings}/security/linked-accounts.js +5 -5
- package/dist/components/{settings → auth/settings}/security/security-settings.d.ts +1 -1
- package/dist/components/auth/settings/security/security-settings.js +22 -0
- package/dist/components/auth/settings/settings.js +45 -0
- package/dist/components/auth/sign-in.js +66 -85
- package/dist/components/auth/sign-out.js +11 -11
- package/dist/components/auth/sign-up.js +128 -148
- package/dist/components/{settings/account → auth/theme}/appearance.d.ts +3 -6
- package/dist/components/auth/theme/appearance.js +72 -0
- package/dist/components/auth/theme/theme-toggle-item.d.ts +5 -0
- package/dist/components/auth/theme/theme-toggle-item.js +58 -0
- package/dist/components/auth/user/user-avatar.js +29 -0
- package/dist/components/{user → auth/user}/user-button.d.ts +2 -6
- package/dist/components/auth/user/user-button.js +55 -0
- package/dist/components/auth/user/user-view.js +37 -0
- package/dist/components/auth/username/sign-in-username.d.ts +14 -0
- package/dist/components/auth/username/sign-in-username.js +148 -0
- package/dist/components/auth/username/username-field.d.ts +8 -0
- package/dist/components/auth/username/username-field.js +53 -0
- package/dist/email.d.ts +7 -0
- package/dist/email.js +9 -0
- package/dist/index.d.ts +12 -27
- package/dist/index.js +20 -35
- package/dist/lib/auth/auth-plugin.d.ts +44 -0
- package/dist/lib/auth/delete-user-plugin.d.ts +17 -0
- package/dist/lib/auth/delete-user-plugin.js +10 -0
- package/dist/lib/auth/magic-link-plugin.d.ts +31 -0
- package/dist/lib/auth/magic-link-plugin.js +13 -0
- package/dist/lib/auth/multi-session-plugin.d.ts +18 -0
- package/dist/lib/auth/multi-session-plugin.js +12 -0
- package/dist/lib/auth/passkey-plugin.d.ts +20 -0
- package/dist/lib/auth/passkey-plugin.js +12 -0
- package/dist/lib/auth/theme-plugin.d.ts +68 -0
- package/dist/lib/auth/theme-plugin.js +23 -0
- package/dist/lib/auth/username-plugin.d.ts +63 -0
- package/dist/lib/auth/username-plugin.js +18 -0
- package/dist/plugins.d.ts +20 -0
- package/dist/plugins.js +21 -0
- package/package.json +19 -21
- package/src/components/auth/additional-field.tsx +554 -0
- package/src/components/auth/auth-provider.tsx +5 -6
- package/src/components/auth/auth.tsx +99 -35
- package/src/components/{settings/security → auth/delete-user}/danger-zone.tsx +1 -1
- package/src/components/{settings/security → auth/delete-user}/delete-user.tsx +30 -20
- package/src/components/{email → auth/email}/email-changed-email.tsx +2 -2
- package/src/components/{email → auth/email}/email-verification-email.tsx +2 -2
- package/src/components/{email → auth/email}/magic-link-email.tsx +2 -2
- package/src/components/{email → auth/email}/new-device-email.tsx +2 -2
- package/src/components/{email → auth/email}/otp-email.tsx +2 -2
- package/src/components/{email → auth/email}/password-changed-email.tsx +2 -2
- package/src/components/{email → auth/email}/reset-password-email.tsx +2 -2
- package/src/components/auth/forgot-password.tsx +42 -17
- package/src/components/auth/magic-link/magic-link-button.tsx +61 -0
- package/src/components/auth/{magic-link.tsx → magic-link/magic-link.tsx} +55 -40
- package/src/components/{settings/account → auth/multi-session}/manage-account.tsx +16 -13
- package/src/components/{settings/account → auth/multi-session}/manage-accounts.tsx +14 -5
- package/src/components/{user/switch-account-menu.tsx → auth/multi-session/switch-account-submenu-content.tsx} +18 -11
- package/src/components/{user/switch-account-item.tsx → auth/multi-session/switch-account-submenu-item.tsx} +17 -5
- package/src/components/auth/multi-session/switch-account-submenu.tsx +50 -0
- package/src/components/auth/passkey/passkey-button.tsx +56 -0
- package/src/components/{settings/security → auth/passkey}/passkey.tsx +20 -6
- package/src/components/auth/passkey/passkeys.tsx +196 -0
- package/src/components/auth/provider-button.tsx +27 -29
- package/src/components/auth/provider-buttons.tsx +6 -11
- package/src/components/auth/reset-password.tsx +13 -7
- package/src/components/{settings → auth/settings}/account/account-settings.tsx +15 -13
- package/src/components/{settings → auth/settings}/account/change-avatar.tsx +4 -3
- package/src/components/{settings → auth/settings}/account/change-email.tsx +3 -3
- package/src/components/auth/settings/account/user-profile.tsx +171 -0
- package/src/components/{settings → auth/settings}/security/active-session.tsx +8 -5
- package/src/components/{settings → auth/settings}/security/active-sessions.tsx +3 -3
- package/src/components/{settings → auth/settings}/security/change-password.tsx +14 -9
- package/src/components/{settings → auth/settings}/security/linked-account.tsx +12 -8
- package/src/components/{settings → auth/settings}/security/linked-accounts.tsx +2 -2
- package/src/components/{settings → auth/settings}/security/security-settings.tsx +7 -6
- package/src/components/{settings → auth/settings}/settings.tsx +2 -2
- package/src/components/auth/sign-in.tsx +50 -61
- package/src/components/auth/sign-out.tsx +2 -2
- package/src/components/auth/sign-up.tsx +119 -131
- package/src/components/{settings/account → auth/theme}/appearance.tsx +17 -24
- package/src/components/auth/theme/theme-toggle-item.tsx +110 -0
- package/src/components/{user → auth/user}/user-avatar.tsx +12 -4
- package/src/components/{user → auth/user}/user-button.tsx +15 -88
- package/src/components/{user → auth/user}/user-view.tsx +12 -4
- package/src/components/auth/username/sign-in-username.tsx +299 -0
- package/src/components/auth/username/username-field.tsx +124 -0
- package/src/email.ts +9 -0
- package/src/index.tsx +12 -27
- package/src/lib/auth/auth-plugin.ts +67 -0
- package/src/lib/auth/delete-user-plugin.ts +15 -0
- package/src/lib/auth/magic-link-plugin.ts +26 -0
- package/src/lib/auth/multi-session-plugin.ts +17 -0
- package/src/lib/auth/passkey-plugin.ts +17 -0
- package/src/lib/auth/theme-plugin.ts +86 -0
- package/src/lib/auth/username-plugin.tsx +30 -0
- package/src/plugins.ts +23 -0
- package/dist/components/auth/magic-link-button.d.ts +0 -13
- package/dist/components/auth/magic-link-button.js +0 -16
- package/dist/components/auth/magic-link.d.ts +0 -17
- package/dist/components/auth/magic-link.js +0 -99
- package/dist/components/auth/passkey-button.d.ts +0 -4
- package/dist/components/auth/passkey-button.js +0 -21
- package/dist/components/email/magic-link-email.d.ts +0 -3
- package/dist/components/email/new-device-email.d.ts +0 -3
- package/dist/components/email/otp-email.d.ts +0 -3
- package/dist/components/settings/account/account-settings.js +0 -23
- package/dist/components/settings/account/appearance.js +0 -71
- package/dist/components/settings/account/change-avatar.js +0 -89
- package/dist/components/settings/account/manage-account.js +0 -48
- package/dist/components/settings/account/manage-accounts.js +0 -33
- package/dist/components/settings/account/user-profile.js +0 -103
- package/dist/components/settings/security/active-session.js +0 -59
- package/dist/components/settings/security/change-password.js +0 -189
- package/dist/components/settings/security/delete-user.js +0 -92
- package/dist/components/settings/security/linked-account.js +0 -56
- package/dist/components/settings/security/passkey.js +0 -43
- package/dist/components/settings/security/passkeys.js +0 -53
- package/dist/components/settings/security/security-settings.js +0 -25
- package/dist/components/settings/settings.js +0 -45
- package/dist/components/user/switch-account-item.js +0 -20
- package/dist/components/user/switch-account-menu.js +0 -28
- package/dist/components/user/user-avatar.js +0 -29
- package/dist/components/user/user-button.js +0 -91
- package/dist/components/user/user-view.js +0 -37
- package/dist/core.d.ts +0 -1
- package/dist/core.js +0 -1
- package/dist/react-exports.d.ts +0 -1
- package/dist/react-exports.js +0 -1
- package/src/components/auth/magic-link-button.tsx +0 -43
- package/src/components/auth/passkey-button.tsx +0 -35
- package/src/components/settings/account/user-profile.tsx +0 -217
- package/src/components/settings/security/passkeys.tsx +0 -96
- package/src/core.ts +0 -1
- package/src/react-exports.ts +0 -1
- /package/dist/components/{settings/security → auth/delete-user}/delete-user.d.ts +0 -0
- /package/dist/components/{settings/account → auth/multi-session}/manage-accounts.d.ts +0 -0
- /package/dist/components/{settings/security → auth/passkey}/passkey.d.ts +0 -0
- /package/dist/components/{settings/security → auth/passkey}/passkeys.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/account/change-avatar.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/account/change-email.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/active-session.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/active-sessions.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/change-password.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/linked-account.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/linked-accounts.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/settings.d.ts +0 -0
- /package/dist/components/{user → auth/user}/user-avatar.d.ts +0 -0
- /package/dist/components/{user → auth/user}/user-view.d.ts +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@better-auth-ui/react"
|
|
6
|
-
import {
|
|
2
|
+
authMutationKeys,
|
|
3
|
+
parseAdditionalFieldValue
|
|
4
|
+
} from "@better-auth-ui/core"
|
|
5
|
+
import { useAuth, useFetchOptions, useSignUpEmail } from "@better-auth-ui/react"
|
|
6
|
+
import { Eye, EyeSlash } from "@gravity-ui/icons"
|
|
7
7
|
import {
|
|
8
8
|
Button,
|
|
9
9
|
Card,
|
|
@@ -20,11 +20,10 @@ import {
|
|
|
20
20
|
TextField,
|
|
21
21
|
toast
|
|
22
22
|
} from "@heroui/react"
|
|
23
|
-
import {
|
|
23
|
+
import { useIsMutating } from "@tanstack/react-query"
|
|
24
24
|
import { type SyntheticEvent, useState } from "react"
|
|
25
|
-
|
|
25
|
+
import { AdditionalField } from "./additional-field"
|
|
26
26
|
import { FieldSeparator } from "./field-separator"
|
|
27
|
-
import { MagicLinkButton } from "./magic-link-button"
|
|
28
27
|
import { ProviderButtons, type SocialLayout } from "./provider-buttons"
|
|
29
28
|
|
|
30
29
|
export type SignUpProps = {
|
|
@@ -52,76 +51,65 @@ export function SignUp({
|
|
|
52
51
|
...props
|
|
53
52
|
}: SignUpProps & Omit<CardProps, "children">) {
|
|
54
53
|
const {
|
|
54
|
+
additionalFields,
|
|
55
|
+
authClient,
|
|
55
56
|
basePaths,
|
|
56
57
|
emailAndPassword,
|
|
57
58
|
localization,
|
|
58
|
-
|
|
59
|
+
plugins,
|
|
59
60
|
redirectTo,
|
|
60
61
|
socialProviders,
|
|
61
|
-
username: usernameConfig,
|
|
62
62
|
viewPaths,
|
|
63
63
|
navigate
|
|
64
64
|
} = useAuth()
|
|
65
65
|
|
|
66
|
+
const { fetchOptions, resetFetchOptions } = useFetchOptions()
|
|
67
|
+
|
|
66
68
|
const [password, setPassword] = useState("")
|
|
67
69
|
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
70
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (emailAndPassword?.requireEmailVerification) {
|
|
106
|
-
toast.success(localization.auth.verifyYourEmail)
|
|
107
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
108
|
-
} else {
|
|
109
|
-
navigate({ to: redirectTo })
|
|
71
|
+
const { mutate: signUpEmail, isPending: signUpEmailPending } = useSignUpEmail(
|
|
72
|
+
authClient,
|
|
73
|
+
{
|
|
74
|
+
onError: (error) => {
|
|
75
|
+
setPassword("")
|
|
76
|
+
setConfirmPassword("")
|
|
77
|
+
toast.danger(error.error?.message || error.message)
|
|
78
|
+
resetFetchOptions()
|
|
79
|
+
},
|
|
80
|
+
onSuccess: () => {
|
|
81
|
+
if (emailAndPassword?.requireEmailVerification) {
|
|
82
|
+
toast.success(localization.auth.verifyYourEmail)
|
|
83
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
84
|
+
} else {
|
|
85
|
+
navigate({ to: redirectTo })
|
|
86
|
+
}
|
|
110
87
|
}
|
|
111
88
|
}
|
|
112
|
-
|
|
89
|
+
)
|
|
113
90
|
|
|
114
91
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false)
|
|
115
92
|
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
|
|
116
93
|
useState(false)
|
|
117
94
|
|
|
118
|
-
const
|
|
95
|
+
const signInMutating = useIsMutating({
|
|
96
|
+
mutationKey: authMutationKeys.signIn.all
|
|
97
|
+
})
|
|
98
|
+
const signUpMutating = useIsMutating({
|
|
99
|
+
mutationKey: authMutationKeys.signUp.all
|
|
100
|
+
})
|
|
101
|
+
const isPending = signInMutating + signUpMutating > 0
|
|
102
|
+
|
|
103
|
+
const Captcha = plugins.find(
|
|
104
|
+
(plugin) => plugin.captchaComponent
|
|
105
|
+
)?.captchaComponent
|
|
119
106
|
|
|
120
|
-
const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
|
|
107
|
+
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
|
121
108
|
e.preventDefault()
|
|
122
109
|
|
|
123
110
|
const formData = new FormData(e.currentTarget)
|
|
124
|
-
|
|
111
|
+
// `emailAndPassword.name === false` hides the name field and submits "".
|
|
112
|
+
const name = (formData.get("name") as string | null) ?? ""
|
|
125
113
|
const email = formData.get("email") as string
|
|
126
114
|
|
|
127
115
|
if (emailAndPassword?.confirmPassword && password !== confirmPassword) {
|
|
@@ -131,18 +119,35 @@ export function SignUp({
|
|
|
131
119
|
return
|
|
132
120
|
}
|
|
133
121
|
|
|
122
|
+
const additionalFieldValues: Record<string, unknown> = {}
|
|
123
|
+
|
|
124
|
+
for (const field of additionalFields ?? []) {
|
|
125
|
+
if (!field.signUp || field.readOnly) continue
|
|
126
|
+
const value = parseAdditionalFieldValue(
|
|
127
|
+
field,
|
|
128
|
+
formData.get(field.name) as string | null
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if (field.validate) {
|
|
132
|
+
try {
|
|
133
|
+
await field.validate(value)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
toast.danger(error instanceof Error ? error.message : String(error))
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (value !== undefined) {
|
|
141
|
+
additionalFieldValues[field.name] = value
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
134
145
|
signUpEmail({
|
|
135
146
|
name,
|
|
136
147
|
email,
|
|
137
148
|
password,
|
|
138
|
-
...
|
|
139
|
-
|
|
140
|
-
username: username.trim(),
|
|
141
|
-
...(usernameConfig.displayUsername
|
|
142
|
-
? { displayUsername: username.trim() }
|
|
143
|
-
: {})
|
|
144
|
-
}
|
|
145
|
-
: {})
|
|
149
|
+
...additionalFieldValues,
|
|
150
|
+
fetchOptions
|
|
146
151
|
})
|
|
147
152
|
}
|
|
148
153
|
|
|
@@ -150,7 +155,7 @@ export function SignUp({
|
|
|
150
155
|
|
|
151
156
|
return (
|
|
152
157
|
<Card
|
|
153
|
-
className={cn("w-full max-w-sm
|
|
158
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
154
159
|
variant={variant}
|
|
155
160
|
{...props}
|
|
156
161
|
>
|
|
@@ -164,10 +169,7 @@ export function SignUp({
|
|
|
164
169
|
{socialPosition === "top" && (
|
|
165
170
|
<>
|
|
166
171
|
{!!socialProviders?.length && (
|
|
167
|
-
<ProviderButtons
|
|
168
|
-
isPending={isPending}
|
|
169
|
-
socialLayout={socialLayout}
|
|
170
|
-
/>
|
|
172
|
+
<ProviderButtons socialLayout={socialLayout} />
|
|
171
173
|
)}
|
|
172
174
|
|
|
173
175
|
{showSeparator && (
|
|
@@ -178,67 +180,22 @@ export function SignUp({
|
|
|
178
180
|
|
|
179
181
|
{emailAndPassword?.enabled && (
|
|
180
182
|
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
181
|
-
|
|
182
|
-
name="name"
|
|
183
|
-
type="text"
|
|
184
|
-
autoComplete="name"
|
|
185
|
-
isDisabled={isPending}
|
|
186
|
-
>
|
|
187
|
-
<Label>{localization.auth.name}</Label>
|
|
188
|
-
|
|
189
|
-
<Input
|
|
190
|
-
placeholder={localization.auth.namePlaceholder}
|
|
191
|
-
required
|
|
192
|
-
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
193
|
-
/>
|
|
194
|
-
|
|
195
|
-
<FieldError />
|
|
196
|
-
</TextField>
|
|
197
|
-
|
|
198
|
-
{usernameConfig?.enabled && (
|
|
183
|
+
{emailAndPassword.name !== false && (
|
|
199
184
|
<TextField
|
|
200
|
-
name="
|
|
185
|
+
name="name"
|
|
201
186
|
type="text"
|
|
202
|
-
autoComplete="
|
|
203
|
-
minLength={usernameConfig.minUsernameLength}
|
|
204
|
-
maxLength={usernameConfig.maxUsernameLength}
|
|
187
|
+
autoComplete="name"
|
|
205
188
|
isDisabled={isPending}
|
|
206
|
-
value={username}
|
|
207
|
-
onChange={handleUsernameChange}
|
|
208
|
-
isInvalid={
|
|
209
|
-
!!usernameError || (usernameData && !usernameData.available)
|
|
210
|
-
}
|
|
211
189
|
>
|
|
212
|
-
<Label>{localization.auth.
|
|
190
|
+
<Label>{localization.auth.name}</Label>
|
|
213
191
|
|
|
214
|
-
<
|
|
192
|
+
<Input
|
|
193
|
+
placeholder={localization.auth.namePlaceholder}
|
|
194
|
+
required
|
|
215
195
|
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>
|
|
196
|
+
/>
|
|
234
197
|
|
|
235
|
-
<FieldError
|
|
236
|
-
{usernameError?.error?.message ||
|
|
237
|
-
usernameError?.message ||
|
|
238
|
-
(usernameData?.available === false
|
|
239
|
-
? localization.auth.usernameTaken
|
|
240
|
-
: null)}
|
|
241
|
-
</FieldError>
|
|
198
|
+
<FieldError />
|
|
242
199
|
</TextField>
|
|
243
200
|
)}
|
|
244
201
|
|
|
@@ -259,6 +216,19 @@ export function SignUp({
|
|
|
259
216
|
<FieldError />
|
|
260
217
|
</TextField>
|
|
261
218
|
|
|
219
|
+
{additionalFields?.map(
|
|
220
|
+
(field) =>
|
|
221
|
+
field.signUp === "above" && (
|
|
222
|
+
<AdditionalField
|
|
223
|
+
key={field.name}
|
|
224
|
+
name={field.name}
|
|
225
|
+
field={field}
|
|
226
|
+
isPending={isPending}
|
|
227
|
+
variant={variant}
|
|
228
|
+
/>
|
|
229
|
+
)
|
|
230
|
+
)}
|
|
231
|
+
|
|
262
232
|
<TextField
|
|
263
233
|
minLength={emailAndPassword?.minPasswordLength}
|
|
264
234
|
maxLength={emailAndPassword?.maxPasswordLength}
|
|
@@ -347,15 +317,36 @@ export function SignUp({
|
|
|
347
317
|
</TextField>
|
|
348
318
|
)}
|
|
349
319
|
|
|
320
|
+
{additionalFields?.map(
|
|
321
|
+
(field) =>
|
|
322
|
+
field.signUp &&
|
|
323
|
+
field.signUp !== "above" && (
|
|
324
|
+
<AdditionalField
|
|
325
|
+
key={field.name}
|
|
326
|
+
name={field.name}
|
|
327
|
+
field={field}
|
|
328
|
+
isPending={isPending}
|
|
329
|
+
variant={variant}
|
|
330
|
+
/>
|
|
331
|
+
)
|
|
332
|
+
)}
|
|
333
|
+
|
|
334
|
+
{Captcha && <div className="flex justify-center">{Captcha}</div>}
|
|
335
|
+
|
|
350
336
|
<div className="flex flex-col gap-3">
|
|
351
337
|
<Button type="submit" className="w-full" isPending={isPending}>
|
|
352
|
-
{
|
|
338
|
+
{signUpEmailPending && <Spinner color="current" size="sm" />}
|
|
353
339
|
|
|
354
340
|
{localization.auth.signUp}
|
|
355
341
|
</Button>
|
|
356
342
|
|
|
357
|
-
{
|
|
358
|
-
|
|
343
|
+
{plugins.flatMap((plugin) =>
|
|
344
|
+
plugin.authButtons?.map((AuthButton, index) => (
|
|
345
|
+
<AuthButton
|
|
346
|
+
key={`${plugin.id}-${index.toString()}`}
|
|
347
|
+
view="signUp"
|
|
348
|
+
/>
|
|
349
|
+
))
|
|
359
350
|
)}
|
|
360
351
|
</div>
|
|
361
352
|
</Form>
|
|
@@ -368,21 +359,18 @@ export function SignUp({
|
|
|
368
359
|
)}
|
|
369
360
|
|
|
370
361
|
{!!socialProviders?.length && (
|
|
371
|
-
<ProviderButtons
|
|
372
|
-
socialLayout={socialLayout}
|
|
373
|
-
isPending={isPending}
|
|
374
|
-
/>
|
|
362
|
+
<ProviderButtons socialLayout={socialLayout} />
|
|
375
363
|
)}
|
|
376
364
|
</>
|
|
377
365
|
)}
|
|
378
366
|
</Card.Content>
|
|
379
367
|
|
|
380
|
-
<Card.Footer className="flex-col">
|
|
368
|
+
<Card.Footer className="flex-col gap-3">
|
|
381
369
|
<Description className="text-sm">
|
|
382
370
|
{localization.auth.alreadyHaveAnAccount}{" "}
|
|
383
371
|
<Link
|
|
384
372
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
385
|
-
className="text-accent
|
|
373
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
386
374
|
>
|
|
387
375
|
{localization.auth.signIn}
|
|
388
376
|
</Link>
|
|
@@ -2,8 +2,7 @@ import {
|
|
|
2
2
|
ThemePreviewDark,
|
|
3
3
|
ThemePreviewLight,
|
|
4
4
|
ThemePreviewSystem,
|
|
5
|
-
|
|
6
|
-
useSession
|
|
5
|
+
useAuthPlugin
|
|
7
6
|
} from "@better-auth-ui/react"
|
|
8
7
|
import { Display, Moon, Sun } from "@gravity-ui/icons"
|
|
9
8
|
import {
|
|
@@ -12,9 +11,12 @@ import {
|
|
|
12
11
|
cn,
|
|
13
12
|
Label,
|
|
14
13
|
Radio,
|
|
15
|
-
RadioGroup
|
|
14
|
+
RadioGroup,
|
|
15
|
+
useIsHydrated
|
|
16
16
|
} from "@heroui/react"
|
|
17
17
|
|
|
18
|
+
import { themePlugin } from "../../../lib/auth/theme-plugin"
|
|
19
|
+
|
|
18
20
|
export type AppearanceProps = {
|
|
19
21
|
className?: string
|
|
20
22
|
variant?: CardProps["variant"]
|
|
@@ -24,42 +26,33 @@ export type AppearanceProps = {
|
|
|
24
26
|
* Renders a theme selector card with visual theme previews.
|
|
25
27
|
*
|
|
26
28
|
* Displays a card containing radio buttons for selecting between system, light,
|
|
27
|
-
* and dark themes. Each option shows a visual preview of the theme.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @param className - Optional additional CSS class names for the card container.
|
|
31
|
-
* @returns A JSX element containing the theme selector card, or null if theme settings are not configured.
|
|
29
|
+
* and dark themes. Each option shows a visual preview of the theme. Theme options
|
|
30
|
+
* are conditionally shown based on the `themes` array provided via the theme plugin.
|
|
32
31
|
*/
|
|
33
32
|
export function Appearance({
|
|
34
33
|
className,
|
|
35
34
|
variant,
|
|
36
35
|
...props
|
|
37
36
|
}: AppearanceProps & Omit<CardProps, "children">) {
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} = useAuth()
|
|
42
|
-
const { data: session } = useSession()
|
|
43
|
-
|
|
44
|
-
if (!setTheme || !themes?.length) {
|
|
45
|
-
return null
|
|
46
|
-
}
|
|
37
|
+
const { useTheme, localization } = useAuthPlugin(themePlugin)
|
|
38
|
+
const { theme, setTheme, themes = [] } = useTheme()
|
|
39
|
+
const hydrated = useIsHydrated()
|
|
47
40
|
|
|
48
41
|
return (
|
|
49
42
|
<div>
|
|
50
43
|
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
51
|
-
{localization.
|
|
44
|
+
{localization.appearance}
|
|
52
45
|
</h2>
|
|
53
46
|
|
|
54
47
|
<Card className={cn("p-4 gap-4", className)} variant={variant} {...props}>
|
|
55
48
|
<Card.Content>
|
|
56
|
-
<Label>{localization.
|
|
49
|
+
<Label>{localization.theme}</Label>
|
|
57
50
|
|
|
58
51
|
<RadioGroup
|
|
59
52
|
variant={variant === "transparent" ? "secondary" : "primary"}
|
|
60
|
-
value={
|
|
53
|
+
value={hydrated ? theme : ""}
|
|
61
54
|
onChange={setTheme}
|
|
62
|
-
isDisabled={!
|
|
55
|
+
isDisabled={!hydrated || !theme}
|
|
63
56
|
>
|
|
64
57
|
<div className="grid gap-3 grid-cols-2 sm:grid-cols-3">
|
|
65
58
|
{themes.includes("system") && (
|
|
@@ -78,7 +71,7 @@ export function Appearance({
|
|
|
78
71
|
<div className="flex gap-2 justify-between">
|
|
79
72
|
<Label className="flex gap-2 items-center">
|
|
80
73
|
<Display className="text-muted" />
|
|
81
|
-
{localization.
|
|
74
|
+
{localization.system}
|
|
82
75
|
</Label>
|
|
83
76
|
|
|
84
77
|
<Radio.Control>
|
|
@@ -107,7 +100,7 @@ export function Appearance({
|
|
|
107
100
|
<div className="flex gap-2 justify-between">
|
|
108
101
|
<Label className="flex gap-2 items-center">
|
|
109
102
|
<Sun className="text-muted" />
|
|
110
|
-
{localization.
|
|
103
|
+
{localization.light}
|
|
111
104
|
</Label>
|
|
112
105
|
|
|
113
106
|
<Radio.Control>
|
|
@@ -136,7 +129,7 @@ export function Appearance({
|
|
|
136
129
|
<div className="flex gap-2 justify-between">
|
|
137
130
|
<Label className="flex gap-2 items-center">
|
|
138
131
|
<Moon className="text-muted" />
|
|
139
|
-
{localization.
|
|
132
|
+
{localization.dark}
|
|
140
133
|
</Label>
|
|
141
134
|
|
|
142
135
|
<Radio.Control>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useAuthPlugin } from "@better-auth-ui/react"
|
|
2
|
+
import { Display, Moon, Sun } from "@gravity-ui/icons"
|
|
3
|
+
import { Dropdown, Label, Tabs } from "@heroui/react"
|
|
4
|
+
|
|
5
|
+
import { themePlugin } from "../../../lib/auth/theme-plugin"
|
|
6
|
+
|
|
7
|
+
const ACTIVE_TAB_SELECTOR = '[role="tab"][aria-selected="true"]'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Theme toggle dropdown item used inside `UserButton`. Callers are responsible
|
|
11
|
+
* for ensuring theming is configured before rendering this component.
|
|
12
|
+
*/
|
|
13
|
+
export function ThemeToggleItem() {
|
|
14
|
+
const { useTheme, localization } = useAuthPlugin(themePlugin)
|
|
15
|
+
const { theme, setTheme, themes = [] } = useTheme()
|
|
16
|
+
|
|
17
|
+
// The Tabs aren't part of the menu's keyboard-navigation list, so arrow-key
|
|
18
|
+
// nav can't reach them on its own. When the wrapper menu item receives focus
|
|
19
|
+
// we delegate focus to the active Tab so the user can switch themes with the
|
|
20
|
+
// Left/Right arrows. React's synthetic `onFocus` bubbles (it's wired to
|
|
21
|
+
// `focusin`), so we must guard against child-originated events: without the
|
|
22
|
+
// `target === currentTarget` check, an arrow-key focus move between tabs
|
|
23
|
+
// bubbles up here and the still-stale `aria-selected="true"` lookup yanks
|
|
24
|
+
// focus back to the previously selected tab.
|
|
25
|
+
const focusActiveTab = (event: React.FocusEvent<Element>) => {
|
|
26
|
+
if (event.target !== event.currentTarget) return
|
|
27
|
+
const activeTab =
|
|
28
|
+
event.currentTarget.querySelector<HTMLElement>(ACTIVE_TAB_SELECTOR)
|
|
29
|
+
activeTab?.focus({ preventScroll: true })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Up/Down on a Tab escapes back to the previous/next sibling menu item so
|
|
33
|
+
// users can keep navigating the menu with the arrow keys after they've
|
|
34
|
+
// entered the Tabs.
|
|
35
|
+
const handleTabsKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
36
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return
|
|
37
|
+
|
|
38
|
+
const target = event.target as HTMLElement
|
|
39
|
+
if (target.getAttribute("role") !== "tab") return
|
|
40
|
+
|
|
41
|
+
const wrapper = target.closest<HTMLElement>('[role="menuitem"]')
|
|
42
|
+
const menu = wrapper?.closest<HTMLElement>('[role="menu"]')
|
|
43
|
+
if (!wrapper || !menu) return
|
|
44
|
+
|
|
45
|
+
const items = Array.from(
|
|
46
|
+
menu.querySelectorAll<HTMLElement>(
|
|
47
|
+
'[role="menuitem"]:not([data-disabled])'
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
const currentIndex = items.indexOf(wrapper)
|
|
51
|
+
if (currentIndex === -1) return
|
|
52
|
+
const nextIndex =
|
|
53
|
+
event.key === "ArrowDown" ? currentIndex + 1 : currentIndex - 1
|
|
54
|
+
const next = items[nextIndex]
|
|
55
|
+
if (!next) return
|
|
56
|
+
|
|
57
|
+
event.preventDefault()
|
|
58
|
+
next.focus()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Dropdown.Item
|
|
63
|
+
className="py-1 pe-2"
|
|
64
|
+
onFocus={focusActiveTab}
|
|
65
|
+
// Without `shouldCloseOnSelect={false}` the menu auto-closes after the
|
|
66
|
+
// user changes themes, because clicking on a Tab bubbles up as a
|
|
67
|
+
// menu-item activation.
|
|
68
|
+
shouldCloseOnSelect={false}
|
|
69
|
+
>
|
|
70
|
+
<Label>{localization.theme}</Label>
|
|
71
|
+
|
|
72
|
+
<Tabs
|
|
73
|
+
className="ml-auto"
|
|
74
|
+
selectedKey={theme}
|
|
75
|
+
onSelectionChange={(key) => setTheme(key as string)}
|
|
76
|
+
>
|
|
77
|
+
<Tabs.ListContainer onKeyDown={handleTabsKeyDown}>
|
|
78
|
+
<Tabs.List
|
|
79
|
+
aria-label={localization.theme}
|
|
80
|
+
className="*:h-5 *:w-5 *:p-0"
|
|
81
|
+
>
|
|
82
|
+
{themes.includes("system") && (
|
|
83
|
+
<Tabs.Tab id="system" aria-label={localization.system}>
|
|
84
|
+
<Display className="size-3" />
|
|
85
|
+
|
|
86
|
+
<Tabs.Indicator />
|
|
87
|
+
</Tabs.Tab>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{themes.includes("light") && (
|
|
91
|
+
<Tabs.Tab id="light" aria-label={localization.light}>
|
|
92
|
+
<Sun className="size-3" />
|
|
93
|
+
|
|
94
|
+
<Tabs.Indicator />
|
|
95
|
+
</Tabs.Tab>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{themes.includes("dark") && (
|
|
99
|
+
<Tabs.Tab id="dark" aria-label={localization.dark}>
|
|
100
|
+
<Moon className="size-3" />
|
|
101
|
+
|
|
102
|
+
<Tabs.Indicator />
|
|
103
|
+
</Tabs.Tab>
|
|
104
|
+
)}
|
|
105
|
+
</Tabs.List>
|
|
106
|
+
</Tabs.ListContainer>
|
|
107
|
+
</Tabs>
|
|
108
|
+
</Dropdown.Item>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type UsernameAuthClient,
|
|
3
|
+
useAuth,
|
|
4
|
+
useSession
|
|
5
|
+
} from "@better-auth-ui/react"
|
|
2
6
|
import { Person } from "@gravity-ui/icons"
|
|
3
7
|
import { Avatar, type AvatarProps, cn, Skeleton } from "@heroui/react"
|
|
4
8
|
import type { User } from "better-auth"
|
|
@@ -32,9 +36,13 @@ export function UserAvatar({
|
|
|
32
36
|
style,
|
|
33
37
|
...props
|
|
34
38
|
}: UserAvatarProps & AvatarProps) {
|
|
35
|
-
const {
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const { authClient } = useAuth()
|
|
40
|
+
const { data: session, isPending: sessionPending } = useSession(
|
|
41
|
+
authClient as UsernameAuthClient,
|
|
42
|
+
{
|
|
43
|
+
enabled: !user && !isPending
|
|
44
|
+
}
|
|
45
|
+
)
|
|
38
46
|
|
|
39
47
|
if ((isPending || sessionPending) && !user) {
|
|
40
48
|
return (
|