@better-auth-ui/heroui 1.6.2 → 1.6.4
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-provider.js +12 -11
- 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 +3 -4
- package/dist/components/{settings/security → auth/delete-user}/danger-zone.js +1 -1
- package/dist/components/{settings/security → auth/delete-user}/delete-user.d.ts +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/error-toaster.d.ts +1 -0
- package/dist/components/auth/error-toaster.js +17 -0
- package/dist/components/auth/forgot-password.d.ts +2 -2
- package/dist/components/auth/forgot-password.js +42 -42
- 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/{settings/account → auth/multi-session}/manage-accounts.d.ts +1 -1
- 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/{settings/security → auth/passkey}/passkeys.d.ts +1 -1
- package/dist/components/auth/passkey/passkeys.js +106 -0
- package/dist/components/auth/provider-button.d.ts +13 -0
- package/dist/components/auth/provider-button.js +25 -0
- package/dist/components/auth/provider-buttons.d.ts +3 -10
- package/dist/components/auth/provider-buttons.js +14 -22
- package/dist/components/auth/reset-password.d.ts +1 -1
- package/dist/components/auth/reset-password.js +110 -110
- 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.d.ts +1 -1
- package/dist/components/{settings → auth/settings}/account/change-email.js +17 -20
- package/dist/components/{settings → auth/settings}/account/user-profile.d.ts +3 -4
- 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.d.ts +1 -1
- package/dist/components/auth/settings/security/active-sessions.js +34 -0
- package/dist/components/{settings → auth/settings}/security/change-password.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/components/auth/settings/security/linked-accounts.js +45 -0
- 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.d.ts +1 -1
- package/dist/components/auth/sign-in.js +64 -100
- package/dist/components/auth/sign-out.js +11 -11
- package/dist/components/auth/sign-up.d.ts +1 -1
- package/dist/components/auth/sign-up.js +123 -157
- 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 +142 -0
- package/dist/components/auth/username/username-field.d.ts +8 -0
- package/dist/components/auth/username/username-field.js +51 -0
- package/dist/email.d.ts +7 -0
- package/dist/email.js +9 -0
- package/dist/index.d.ts +13 -27
- package/dist/index.js +22 -36
- 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 +22 -22
- package/src/components/auth/additional-field.tsx +554 -0
- package/src/components/auth/auth-provider.tsx +8 -5
- package/src/components/auth/auth.tsx +100 -36
- package/src/components/{settings/security → auth/delete-user}/danger-zone.tsx +3 -10
- package/src/components/{settings/security → auth/delete-user}/delete-user.tsx +31 -24
- 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/error-toaster.tsx +25 -0
- package/src/components/auth/forgot-password.tsx +15 -13
- package/src/components/auth/magic-link/magic-link-button.tsx +61 -0
- package/src/components/auth/{magic-link.tsx → magic-link/magic-link.tsx} +52 -57
- package/src/components/{settings/account → auth/multi-session}/manage-account.tsx +15 -15
- package/src/components/{settings/account → auth/multi-session}/manage-accounts.tsx +16 -12
- package/src/components/{user/switch-account-menu.tsx → auth/multi-session/switch-account-submenu-content.tsx} +19 -17
- package/src/components/{user/switch-account-item.tsx → auth/multi-session/switch-account-submenu-item.tsx} +18 -8
- 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 +21 -9
- package/src/components/auth/passkey/passkeys.tsx +196 -0
- package/src/components/auth/provider-button.tsx +65 -0
- package/src/components/auth/provider-buttons.tsx +23 -45
- package/src/components/auth/reset-password.tsx +14 -9
- package/src/components/{settings → auth/settings}/account/account-settings.tsx +15 -13
- package/src/components/{settings → auth/settings}/account/change-avatar.tsx +4 -5
- package/src/components/{settings → auth/settings}/account/change-email.tsx +5 -6
- package/src/components/auth/settings/account/user-profile.tsx +171 -0
- package/src/components/{settings → auth/settings}/security/active-session.tsx +8 -6
- package/src/components/{settings → auth/settings}/security/active-sessions.tsx +5 -10
- package/src/components/{settings → auth/settings}/security/change-password.tsx +22 -14
- package/src/components/{settings → auth/settings}/security/linked-account.tsx +10 -16
- package/src/components/{settings → auth/settings}/security/linked-accounts.tsx +4 -9
- 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 +39 -80
- package/src/components/auth/sign-out.tsx +2 -2
- package/src/components/auth/sign-up.tsx +110 -147
- package/src/components/{settings/account → auth/theme}/appearance.tsx +18 -25
- 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 +283 -0
- package/src/components/auth/username/username-field.tsx +122 -0
- package/src/email.ts +9 -0
- package/src/index.tsx +13 -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 -111
- package/dist/components/auth/passkey-button.d.ts +0 -4
- package/dist/components/auth/passkey-button.js +0 -24
- 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 -51
- package/dist/components/settings/account/manage-accounts.js +0 -33
- package/dist/components/settings/account/user-profile.js +0 -106
- package/dist/components/settings/security/active-session.js +0 -62
- package/dist/components/settings/security/active-sessions.js +0 -34
- package/dist/components/settings/security/change-password.js +0 -192
- package/dist/components/settings/security/delete-user.js +0 -97
- package/dist/components/settings/security/linked-account.js +0 -62
- package/dist/components/settings/security/linked-accounts.js +0 -45
- 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 -36
- package/src/components/settings/account/user-profile.tsx +0 -218
- package/src/components/settings/security/passkeys.tsx +0 -104
- package/src/core.ts +0 -1
- package/src/react-exports.ts +0 -1
- /package/dist/components/{settings/security → auth/passkey}/passkey.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/account/change-avatar.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/active-session.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/linked-account.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,10 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "@
|
|
7
|
-
import { Check, Eye, EyeSlash, Xmark } from "@gravity-ui/icons"
|
|
2
|
+
authMutationKeys,
|
|
3
|
+
parseAdditionalFieldValue
|
|
4
|
+
} from "@better-auth-ui/core"
|
|
5
|
+
import { useAuth, useSignUpEmail } from "@better-auth-ui/react"
|
|
6
|
+
import { Eye, EyeSlash } from "@gravity-ui/icons"
|
|
8
7
|
import {
|
|
9
8
|
Button,
|
|
10
9
|
Card,
|
|
@@ -21,11 +20,10 @@ import {
|
|
|
21
20
|
TextField,
|
|
22
21
|
toast
|
|
23
22
|
} from "@heroui/react"
|
|
24
|
-
import {
|
|
23
|
+
import { useIsMutating } from "@tanstack/react-query"
|
|
25
24
|
import { type SyntheticEvent, useState } from "react"
|
|
26
|
-
|
|
25
|
+
import { AdditionalField } from "./additional-field"
|
|
27
26
|
import { FieldSeparator } from "./field-separator"
|
|
28
|
-
import { MagicLinkButton } from "./magic-link-button"
|
|
29
27
|
import { ProviderButtons, type SocialLayout } from "./provider-buttons"
|
|
30
28
|
|
|
31
29
|
export type SignUpProps = {
|
|
@@ -51,91 +49,60 @@ export function SignUp({
|
|
|
51
49
|
socialPosition = "bottom",
|
|
52
50
|
variant,
|
|
53
51
|
...props
|
|
54
|
-
}: SignUpProps & CardProps) {
|
|
52
|
+
}: SignUpProps & Omit<CardProps, "children">) {
|
|
55
53
|
const {
|
|
54
|
+
additionalFields,
|
|
55
|
+
authClient,
|
|
56
56
|
basePaths,
|
|
57
57
|
emailAndPassword,
|
|
58
58
|
localization,
|
|
59
|
-
|
|
59
|
+
plugins,
|
|
60
60
|
redirectTo,
|
|
61
61
|
socialProviders,
|
|
62
|
-
username: usernameConfig,
|
|
63
62
|
viewPaths,
|
|
64
63
|
navigate
|
|
65
64
|
} = useAuth()
|
|
66
65
|
|
|
67
66
|
const [password, setPassword] = useState("")
|
|
68
67
|
const [confirmPassword, setConfirmPassword] = useState("")
|
|
69
|
-
const [username, setUsername] = useState("")
|
|
70
68
|
|
|
71
|
-
const {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
const { mutate: signUpEmail, isPending: signUpEmailPending } = useSignUpEmail(
|
|
70
|
+
authClient,
|
|
71
|
+
{
|
|
72
|
+
onError: (error) => {
|
|
73
|
+
setPassword("")
|
|
74
|
+
setConfirmPassword("")
|
|
75
|
+
toast.danger(error.error?.message || error.message)
|
|
76
|
+
},
|
|
77
|
+
onSuccess: () => {
|
|
78
|
+
if (emailAndPassword?.requireEmailVerification) {
|
|
79
|
+
toast.success(localization.auth.verifyYourEmail)
|
|
80
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
81
|
+
} else {
|
|
82
|
+
navigate({ to: redirectTo })
|
|
83
|
+
}
|
|
83
84
|
}
|
|
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
85
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const { mutate: signUpEmail, isPending: signUpPending } = useSignUpEmail({
|
|
100
|
-
onError: (error) => {
|
|
101
|
-
setPassword("")
|
|
102
|
-
setConfirmPassword("")
|
|
103
|
-
toast.danger(error.error?.message || error.message)
|
|
104
|
-
},
|
|
105
|
-
onSuccess: () => {
|
|
106
|
-
if (emailAndPassword?.requireEmailVerification) {
|
|
107
|
-
toast.success(localization.auth.verifyYourEmail)
|
|
108
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
109
|
-
} else {
|
|
110
|
-
navigate({ to: redirectTo })
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const [socialRedirecting, setSocialRedirecting] = useState(false)
|
|
116
|
-
|
|
117
|
-
const { mutate: signInSocial, isPending: socialPending } = useSignInSocial({
|
|
118
|
-
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
119
|
-
onSuccess: () => {
|
|
120
|
-
setSocialRedirecting(true)
|
|
121
|
-
|
|
122
|
-
setTimeout(() => {
|
|
123
|
-
setSocialRedirecting(false)
|
|
124
|
-
}, 5000)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
86
|
+
)
|
|
127
87
|
|
|
128
88
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false)
|
|
129
89
|
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
|
|
130
90
|
useState(false)
|
|
131
91
|
|
|
132
|
-
const
|
|
92
|
+
const signInMutating = useIsMutating({
|
|
93
|
+
mutationKey: authMutationKeys.signIn.all
|
|
94
|
+
})
|
|
95
|
+
const signUpMutating = useIsMutating({
|
|
96
|
+
mutationKey: authMutationKeys.signUp.all
|
|
97
|
+
})
|
|
98
|
+
const isPending = signInMutating + signUpMutating > 0
|
|
133
99
|
|
|
134
|
-
const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
|
|
100
|
+
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
|
135
101
|
e.preventDefault()
|
|
136
102
|
|
|
137
103
|
const formData = new FormData(e.currentTarget)
|
|
138
|
-
|
|
104
|
+
// `emailAndPassword.name === false` hides the name field and submits "".
|
|
105
|
+
const name = (formData.get("name") as string | null) ?? ""
|
|
139
106
|
const email = formData.get("email") as string
|
|
140
107
|
|
|
141
108
|
if (emailAndPassword?.confirmPassword && password !== confirmPassword) {
|
|
@@ -145,18 +112,34 @@ export function SignUp({
|
|
|
145
112
|
return
|
|
146
113
|
}
|
|
147
114
|
|
|
115
|
+
const additionalFieldValues: Record<string, unknown> = {}
|
|
116
|
+
|
|
117
|
+
for (const field of additionalFields ?? []) {
|
|
118
|
+
if (!field.signUp || field.readOnly) continue
|
|
119
|
+
const value = parseAdditionalFieldValue(
|
|
120
|
+
field,
|
|
121
|
+
formData.get(field.name) as string | null
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if (field.validate) {
|
|
125
|
+
try {
|
|
126
|
+
await field.validate(value)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
toast.danger(error instanceof Error ? error.message : String(error))
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (value !== undefined) {
|
|
134
|
+
additionalFieldValues[field.name] = value
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
148
138
|
signUpEmail({
|
|
149
139
|
name,
|
|
150
140
|
email,
|
|
151
141
|
password,
|
|
152
|
-
...
|
|
153
|
-
? {
|
|
154
|
-
username: username.trim(),
|
|
155
|
-
...(usernameConfig.displayUsername
|
|
156
|
-
? { displayUsername: username.trim() }
|
|
157
|
-
: {})
|
|
158
|
-
}
|
|
159
|
-
: {})
|
|
142
|
+
...additionalFieldValues
|
|
160
143
|
})
|
|
161
144
|
}
|
|
162
145
|
|
|
@@ -164,7 +147,7 @@ export function SignUp({
|
|
|
164
147
|
|
|
165
148
|
return (
|
|
166
149
|
<Card
|
|
167
|
-
className={cn("w-full max-w-sm
|
|
150
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
168
151
|
variant={variant}
|
|
169
152
|
{...props}
|
|
170
153
|
>
|
|
@@ -178,11 +161,7 @@ export function SignUp({
|
|
|
178
161
|
{socialPosition === "top" && (
|
|
179
162
|
<>
|
|
180
163
|
{!!socialProviders?.length && (
|
|
181
|
-
<ProviderButtons
|
|
182
|
-
isPending={isPending}
|
|
183
|
-
socialLayout={socialLayout}
|
|
184
|
-
signInSocial={signInSocial}
|
|
185
|
-
/>
|
|
164
|
+
<ProviderButtons socialLayout={socialLayout} />
|
|
186
165
|
)}
|
|
187
166
|
|
|
188
167
|
{showSeparator && (
|
|
@@ -193,66 +172,22 @@ export function SignUp({
|
|
|
193
172
|
|
|
194
173
|
{emailAndPassword?.enabled && (
|
|
195
174
|
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
196
|
-
|
|
197
|
-
name="name"
|
|
198
|
-
type="text"
|
|
199
|
-
autoComplete="name"
|
|
200
|
-
isDisabled={isPending}
|
|
201
|
-
>
|
|
202
|
-
<Label>{localization.auth.name}</Label>
|
|
203
|
-
|
|
204
|
-
<Input
|
|
205
|
-
placeholder={localization.auth.namePlaceholder}
|
|
206
|
-
required
|
|
207
|
-
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
208
|
-
/>
|
|
209
|
-
|
|
210
|
-
<FieldError />
|
|
211
|
-
</TextField>
|
|
212
|
-
|
|
213
|
-
{usernameConfig?.enabled && (
|
|
175
|
+
{emailAndPassword.name !== false && (
|
|
214
176
|
<TextField
|
|
215
|
-
name="
|
|
177
|
+
name="name"
|
|
216
178
|
type="text"
|
|
217
|
-
autoComplete="
|
|
218
|
-
minLength={usernameConfig.minUsernameLength}
|
|
219
|
-
maxLength={usernameConfig.maxUsernameLength}
|
|
179
|
+
autoComplete="name"
|
|
220
180
|
isDisabled={isPending}
|
|
221
|
-
value={username}
|
|
222
|
-
onChange={handleUsernameChange}
|
|
223
|
-
isInvalid={
|
|
224
|
-
!!usernameError || (usernameData && !usernameData.available)
|
|
225
|
-
}
|
|
226
181
|
>
|
|
227
|
-
<Label>{localization.auth.
|
|
182
|
+
<Label>{localization.auth.name}</Label>
|
|
228
183
|
|
|
229
|
-
<
|
|
184
|
+
<Input
|
|
185
|
+
placeholder={localization.auth.namePlaceholder}
|
|
186
|
+
required
|
|
230
187
|
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>
|
|
188
|
+
/>
|
|
249
189
|
|
|
250
|
-
<FieldError
|
|
251
|
-
{usernameError?.error?.message ||
|
|
252
|
-
usernameError?.message ||
|
|
253
|
-
(usernameData?.available === false &&
|
|
254
|
-
localization.auth.usernameTaken)}
|
|
255
|
-
</FieldError>
|
|
190
|
+
<FieldError />
|
|
256
191
|
</TextField>
|
|
257
192
|
)}
|
|
258
193
|
|
|
@@ -273,6 +208,19 @@ export function SignUp({
|
|
|
273
208
|
<FieldError />
|
|
274
209
|
</TextField>
|
|
275
210
|
|
|
211
|
+
{additionalFields?.map(
|
|
212
|
+
(field) =>
|
|
213
|
+
field.signUp === "above" && (
|
|
214
|
+
<AdditionalField
|
|
215
|
+
key={field.name}
|
|
216
|
+
name={field.name}
|
|
217
|
+
field={field}
|
|
218
|
+
isPending={isPending}
|
|
219
|
+
variant={variant}
|
|
220
|
+
/>
|
|
221
|
+
)
|
|
222
|
+
)}
|
|
223
|
+
|
|
276
224
|
<TextField
|
|
277
225
|
minLength={emailAndPassword?.minPasswordLength}
|
|
278
226
|
maxLength={emailAndPassword?.maxPasswordLength}
|
|
@@ -361,15 +309,34 @@ export function SignUp({
|
|
|
361
309
|
</TextField>
|
|
362
310
|
)}
|
|
363
311
|
|
|
312
|
+
{additionalFields?.map(
|
|
313
|
+
(field) =>
|
|
314
|
+
field.signUp &&
|
|
315
|
+
field.signUp !== "above" && (
|
|
316
|
+
<AdditionalField
|
|
317
|
+
key={field.name}
|
|
318
|
+
name={field.name}
|
|
319
|
+
field={field}
|
|
320
|
+
isPending={isPending}
|
|
321
|
+
variant={variant}
|
|
322
|
+
/>
|
|
323
|
+
)
|
|
324
|
+
)}
|
|
325
|
+
|
|
364
326
|
<div className="flex flex-col gap-3">
|
|
365
327
|
<Button type="submit" className="w-full" isPending={isPending}>
|
|
366
|
-
{
|
|
328
|
+
{signUpEmailPending && <Spinner color="current" size="sm" />}
|
|
367
329
|
|
|
368
330
|
{localization.auth.signUp}
|
|
369
331
|
</Button>
|
|
370
332
|
|
|
371
|
-
{
|
|
372
|
-
|
|
333
|
+
{plugins.flatMap((plugin) =>
|
|
334
|
+
plugin.authButtons?.map((AuthButton, index) => (
|
|
335
|
+
<AuthButton
|
|
336
|
+
key={`${plugin.id}-${index.toString()}`}
|
|
337
|
+
view="signUp"
|
|
338
|
+
/>
|
|
339
|
+
))
|
|
373
340
|
)}
|
|
374
341
|
</div>
|
|
375
342
|
</Form>
|
|
@@ -382,22 +349,18 @@ export function SignUp({
|
|
|
382
349
|
)}
|
|
383
350
|
|
|
384
351
|
{!!socialProviders?.length && (
|
|
385
|
-
<ProviderButtons
|
|
386
|
-
socialLayout={socialLayout}
|
|
387
|
-
signInSocial={signInSocial}
|
|
388
|
-
isPending={isPending}
|
|
389
|
-
/>
|
|
352
|
+
<ProviderButtons socialLayout={socialLayout} />
|
|
390
353
|
)}
|
|
391
354
|
</>
|
|
392
355
|
)}
|
|
393
356
|
</Card.Content>
|
|
394
357
|
|
|
395
|
-
<Card.Footer className="flex-col">
|
|
358
|
+
<Card.Footer className="flex-col gap-3">
|
|
396
359
|
<Description className="text-sm">
|
|
397
360
|
{localization.auth.alreadyHaveAnAccount}{" "}
|
|
398
361
|
<Link
|
|
399
362
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
400
|
-
className="text-accent
|
|
363
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
401
364
|
>
|
|
402
365
|
{localization.auth.signIn}
|
|
403
366
|
</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
|
-
}: AppearanceProps & CardProps) {
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} = useAuth()
|
|
42
|
-
const { data: session } = useSession()
|
|
43
|
-
|
|
44
|
-
if (!setTheme || !themes?.length) {
|
|
45
|
-
return null
|
|
46
|
-
}
|
|
36
|
+
}: AppearanceProps & Omit<CardProps, "children">) {
|
|
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 (
|