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