@better-auth-ui/heroui 1.6.0
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/account/account.d.ts +18 -0
- package/dist/components/account/account.js +24 -0
- package/dist/components/account/settings/account-settings.d.ts +5 -0
- package/dist/components/account/settings/account-settings.js +6 -0
- package/dist/components/account/settings/settings.d.ts +5 -0
- package/dist/components/account/settings/settings.js +5 -0
- package/dist/components/account/settings/user-profile.d.ts +5 -0
- package/dist/components/account/settings/user-profile.js +20 -0
- package/dist/components/auth/auth-provider.d.ts +9 -0
- package/dist/components/auth/auth-provider.js +12 -0
- package/dist/components/auth/auth.d.ts +23 -0
- package/dist/components/auth/auth.js +42 -0
- package/dist/components/auth/field-separator.d.ts +4 -0
- package/dist/components/auth/field-separator.js +5 -0
- package/dist/components/auth/forgot-password.d.ts +15 -0
- package/dist/components/auth/forgot-password.js +28 -0
- package/dist/components/auth/magic-link-button.d.ts +13 -0
- package/dist/components/auth/magic-link-button.js +19 -0
- package/dist/components/auth/magic-link.d.ts +17 -0
- package/dist/components/auth/magic-link.js +43 -0
- package/dist/components/auth/provider-buttons.d.ts +19 -0
- package/dist/components/auth/provider-buttons.js +34 -0
- package/dist/components/auth/reset-password.d.ts +11 -0
- package/dist/components/auth/reset-password.js +58 -0
- package/dist/components/auth/sign-in.d.ts +14 -0
- package/dist/components/auth/sign-in.js +64 -0
- package/dist/components/auth/sign-out.d.ts +9 -0
- package/dist/components/auth/sign-out.js +33 -0
- package/dist/components/auth/sign-up.d.ts +19 -0
- package/dist/components/auth/sign-up.js +71 -0
- package/dist/components/email/email-changed-email.d.ts +3 -0
- package/dist/components/email/email-changed-email.js +23 -0
- package/dist/components/email/email-verification-email.d.ts +3 -0
- package/dist/components/email/email-verification-email.js +23 -0
- package/dist/components/email/magic-link-email.d.ts +3 -0
- package/dist/components/email/magic-link-email.js +23 -0
- package/dist/components/email/new-device-email.d.ts +3 -0
- package/dist/components/email/new-device-email.js +23 -0
- package/dist/components/email/otp-email.d.ts +3 -0
- package/dist/components/email/otp-email.js +22 -0
- package/dist/components/email/password-changed-email.d.ts +3 -0
- package/dist/components/email/password-changed-email.js +23 -0
- package/dist/components/email/reset-password-email.d.ts +3 -0
- package/dist/components/email/reset-password-email.js +23 -0
- package/dist/components/settings/account/account-settings.d.ts +16 -0
- package/dist/components/settings/account/account-settings.js +20 -0
- package/dist/components/settings/account/accounts.d.ts +14 -0
- package/dist/components/settings/account/accounts.js +26 -0
- package/dist/components/settings/account/appearance.d.ts +16 -0
- package/dist/components/settings/account/appearance.js +28 -0
- package/dist/components/settings/account/change-avatar.d.ts +4 -0
- package/dist/components/settings/account/change-avatar.js +56 -0
- package/dist/components/settings/account/change-email.d.ts +15 -0
- package/dist/components/settings/account/change-email.js +29 -0
- package/dist/components/settings/account/manage-account.d.ts +19 -0
- package/dist/components/settings/account/manage-account.js +32 -0
- package/dist/components/settings/account/manage-accounts.d.ts +14 -0
- package/dist/components/settings/account/manage-accounts.js +37 -0
- package/dist/components/settings/account/theme-selector.d.ts +16 -0
- package/dist/components/settings/account/theme-selector.js +33 -0
- package/dist/components/settings/account/theme-settings.d.ts +16 -0
- package/dist/components/settings/account/theme-settings.js +31 -0
- package/dist/components/settings/account/user-profile.d.ts +11 -0
- package/dist/components/settings/account/user-profile.js +23 -0
- package/dist/components/settings/account-settings.d.ts +5 -0
- package/dist/components/settings/account-settings.js +6 -0
- package/dist/components/settings/account.d.ts +18 -0
- package/dist/components/settings/account.js +24 -0
- package/dist/components/settings/security/active-session.d.ts +14 -0
- package/dist/components/settings/security/active-session.js +53 -0
- package/dist/components/settings/security/active-sessions.d.ts +14 -0
- package/dist/components/settings/security/active-sessions.js +28 -0
- package/dist/components/settings/security/change-password.d.ts +15 -0
- package/dist/components/settings/security/change-password.js +79 -0
- package/dist/components/settings/security/connected-account.d.ts +16 -0
- package/dist/components/settings/security/connected-account.js +46 -0
- package/dist/components/settings/security/connected-accounts.d.ts +15 -0
- package/dist/components/settings/security/connected-accounts.js +31 -0
- package/dist/components/settings/security/danger-zone.d.ts +11 -0
- package/dist/components/settings/security/danger-zone.js +12 -0
- package/dist/components/settings/security/delete-user.d.ts +9 -0
- package/dist/components/settings/security/delete-user.js +47 -0
- package/dist/components/settings/security/linked-account.d.ts +16 -0
- package/dist/components/settings/security/linked-account.js +48 -0
- package/dist/components/settings/security/linked-accounts.d.ts +15 -0
- package/dist/components/settings/security/linked-accounts.js +42 -0
- package/dist/components/settings/security/security-settings.d.ts +17 -0
- package/dist/components/settings/security/security-settings.js +21 -0
- package/dist/components/settings/security/sessions.d.ts +14 -0
- package/dist/components/settings/security/sessions.js +40 -0
- package/dist/components/settings/settings/account-settings.d.ts +5 -0
- package/dist/components/settings/settings/account-settings.js +6 -0
- package/dist/components/settings/settings/user-profile.d.ts +5 -0
- package/dist/components/settings/settings/user-profile.js +20 -0
- package/dist/components/settings/settings.d.ts +24 -0
- package/dist/components/settings/settings.js +28 -0
- package/dist/components/settings/user-profile.d.ts +5 -0
- package/dist/components/settings/user-profile.js +20 -0
- package/dist/components/user/switch-account-item.d.ts +16 -0
- package/dist/components/user/switch-account-item.js +16 -0
- package/dist/components/user/switch-account-menu.d.ts +10 -0
- package/dist/components/user/switch-account-menu.js +29 -0
- package/dist/components/user/switch-account-popover.d.ts +10 -0
- package/dist/components/user/switch-account-popover.js +25 -0
- package/dist/components/user/switch-account-submenu.d.ts +10 -0
- package/dist/components/user/switch-account-submenu.js +25 -0
- package/dist/components/user/user-avatar.d.ts +25 -0
- package/dist/components/user/user-avatar.js +31 -0
- package/dist/components/user/user-button.d.ts +25 -0
- package/dist/components/user/user-button.js +24 -0
- package/dist/components/user/user-view.d.ts +22 -0
- package/dist/components/user/user-view.js +24 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +1 -0
- package/dist/hooks/use-auth.d.ts +2 -0
- package/dist/hooks/use-auth.js +8 -0
- package/dist/hooks/use-redirect-to.d.ts +8 -0
- package/dist/hooks/use-redirect-to.js +19 -0
- package/dist/hooks/use-sign-in.d.ts +8 -0
- package/dist/hooks/use-sign-in.js +53 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +34 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/react-exports.d.ts +1 -0
- package/dist/react-exports.js +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/dist/src/components/auth/auth-provider.d.ts +8 -0
- package/dist/src/components/auth/auth-provider.js +13 -0
- package/dist/src/components/auth/auth.d.ts +22 -0
- package/dist/src/components/auth/auth.js +42 -0
- package/dist/src/components/auth/field-separator.d.ts +4 -0
- package/dist/src/components/auth/field-separator.js +5 -0
- package/dist/src/components/auth/forgot-password.d.ts +15 -0
- package/dist/src/components/auth/forgot-password.js +29 -0
- package/dist/src/components/auth/magic-link-button.d.ts +13 -0
- package/dist/src/components/auth/magic-link-button.js +20 -0
- package/dist/src/components/auth/magic-link.d.ts +17 -0
- package/dist/src/components/auth/magic-link.js +37 -0
- package/dist/src/components/auth/provider-buttons.d.ts +19 -0
- package/dist/src/components/auth/provider-buttons.js +35 -0
- package/dist/src/components/auth/reset-password.d.ts +11 -0
- package/dist/src/components/auth/reset-password.js +59 -0
- package/dist/src/components/auth/sign-in.d.ts +14 -0
- package/dist/src/components/auth/sign-in.js +58 -0
- package/dist/src/components/auth/sign-out.d.ts +11 -0
- package/dist/src/components/auth/sign-out.js +34 -0
- package/dist/src/components/auth/sign-up.d.ts +19 -0
- package/dist/src/components/auth/sign-up.js +65 -0
- package/dist/src/components/email/email-changed-email.d.ts +3 -0
- package/dist/src/components/email/email-changed-email.js +23 -0
- package/dist/src/components/email/email-verification-email.d.ts +3 -0
- package/dist/src/components/email/email-verification-email.js +23 -0
- package/dist/src/components/email/magic-link-email.d.ts +3 -0
- package/dist/src/components/email/magic-link-email.js +23 -0
- package/dist/src/components/email/new-device-email.d.ts +3 -0
- package/dist/src/components/email/new-device-email.js +23 -0
- package/dist/src/components/email/otp-email.d.ts +3 -0
- package/dist/src/components/email/otp-email.js +22 -0
- package/dist/src/components/email/password-changed-email.d.ts +3 -0
- package/dist/src/components/email/password-changed-email.js +23 -0
- package/dist/src/components/email/reset-password-email.d.ts +3 -0
- package/dist/src/components/email/reset-password-email.js +23 -0
- package/dist/src/components/settings/account/account-settings.d.ts +13 -0
- package/dist/src/components/settings/account/account-settings.js +19 -0
- package/dist/src/components/settings/account/appearance.d.ts +16 -0
- package/dist/src/components/settings/account/appearance.js +28 -0
- package/dist/src/components/settings/account/change-email.d.ts +15 -0
- package/dist/src/components/settings/account/change-email.js +35 -0
- package/dist/src/components/settings/account/manage-account.d.ts +19 -0
- package/dist/src/components/settings/account/manage-account.js +27 -0
- package/dist/src/components/settings/account/manage-accounts.d.ts +14 -0
- package/dist/src/components/settings/account/manage-accounts.js +27 -0
- package/dist/src/components/settings/account/user-profile.d.ts +13 -0
- package/dist/src/components/settings/account/user-profile.js +29 -0
- package/dist/src/components/settings/security/active-session.d.ts +14 -0
- package/dist/src/components/settings/security/active-session.js +35 -0
- package/dist/src/components/settings/security/active-sessions.d.ts +14 -0
- package/dist/src/components/settings/security/active-sessions.js +29 -0
- package/dist/src/components/settings/security/change-password.d.ts +15 -0
- package/dist/src/components/settings/security/change-password.js +57 -0
- package/dist/src/components/settings/security/linked-account.d.ts +16 -0
- package/dist/src/components/settings/security/linked-account.js +46 -0
- package/dist/src/components/settings/security/linked-accounts.d.ts +15 -0
- package/dist/src/components/settings/security/linked-accounts.js +31 -0
- package/dist/src/components/settings/security/security-settings.d.ts +15 -0
- package/dist/src/components/settings/security/security-settings.js +18 -0
- package/dist/src/components/settings/settings.d.ts +29 -0
- package/dist/src/components/settings/settings.js +39 -0
- package/dist/src/components/user/switch-account-item.d.ts +16 -0
- package/dist/src/components/user/switch-account-item.js +14 -0
- package/dist/src/components/user/switch-account-menu.d.ts +10 -0
- package/dist/src/components/user/switch-account-menu.js +23 -0
- package/dist/src/components/user/user-avatar.d.ts +24 -0
- package/dist/src/components/user/user-avatar.js +31 -0
- package/dist/src/components/user/user-button.d.ts +21 -0
- package/dist/src/components/user/user-button.js +24 -0
- package/dist/src/components/user/user-view.d.ts +21 -0
- package/dist/src/components/user/user-view.js +24 -0
- package/dist/src/core.d.ts +1 -0
- package/dist/src/core.js +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +32 -0
- package/dist/src/lib/utils.d.ts +2 -0
- package/dist/src/lib/utils.js +5 -0
- package/dist/src/react-exports.d.ts +1 -0
- package/dist/src/react-exports.js +1 -0
- package/dist/styles.css +1 -0
- package/package.json +63 -0
- package/src/components/auth/auth-provider.tsx +27 -0
- package/src/components/auth/auth.tsx +87 -0
- package/src/components/auth/field-separator.tsx +14 -0
- package/src/components/auth/forgot-password.tsx +107 -0
- package/src/components/auth/magic-link-button.tsx +43 -0
- package/src/components/auth/magic-link.tsx +181 -0
- package/src/components/auth/provider-buttons.tsx +85 -0
- package/src/components/auth/reset-password.tsx +210 -0
- package/src/components/auth/sign-in.tsx +260 -0
- package/src/components/auth/sign-out.tsx +44 -0
- package/src/components/auth/sign-up.tsx +314 -0
- package/src/components/email/email-changed-email.tsx +38 -0
- package/src/components/email/email-verification-email.tsx +38 -0
- package/src/components/email/magic-link-email.tsx +38 -0
- package/src/components/email/new-device-email.tsx +38 -0
- package/src/components/email/otp-email.tsx +33 -0
- package/src/components/email/password-changed-email.tsx +38 -0
- package/src/components/email/reset-password-email.tsx +38 -0
- package/src/components/settings/account/account-settings.tsx +48 -0
- package/src/components/settings/account/appearance.tsx +157 -0
- package/src/components/settings/account/change-avatar.tsx +142 -0
- package/src/components/settings/account/change-email.tsx +111 -0
- package/src/components/settings/account/manage-account.tsx +123 -0
- package/src/components/settings/account/manage-accounts.tsx +78 -0
- package/src/components/settings/account/user-profile.tsx +103 -0
- package/src/components/settings/security/active-session.tsx +123 -0
- package/src/components/settings/security/active-sessions.tsx +77 -0
- package/src/components/settings/security/change-password.tsx +335 -0
- package/src/components/settings/security/danger-zone.tsx +32 -0
- package/src/components/settings/security/delete-user.tsx +187 -0
- package/src/components/settings/security/linked-account.tsx +136 -0
- package/src/components/settings/security/linked-accounts.tsx +98 -0
- package/src/components/settings/security/security-settings.tsx +43 -0
- package/src/components/settings/settings.tsx +96 -0
- package/src/components/user/switch-account-item.tsx +39 -0
- package/src/components/user/switch-account-menu.tsx +64 -0
- package/src/components/user/user-avatar.tsx +88 -0
- package/src/components/user/user-button.tsx +221 -0
- package/src/components/user/user-view.tsx +74 -0
- package/src/core.ts +1 -0
- package/src/index.tsx +35 -0
- package/src/react-exports.ts +1 -0
- package/src/styles.css +1 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useAuth, useRequestPasswordReset } from "@better-auth-ui/react"
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
type CardProps,
|
|
6
|
+
cn,
|
|
7
|
+
Description,
|
|
8
|
+
FieldError,
|
|
9
|
+
Form,
|
|
10
|
+
Input,
|
|
11
|
+
Label,
|
|
12
|
+
Link,
|
|
13
|
+
Spinner,
|
|
14
|
+
TextField,
|
|
15
|
+
toast
|
|
16
|
+
} from "@heroui/react"
|
|
17
|
+
import type { SyntheticEvent } from "react"
|
|
18
|
+
|
|
19
|
+
export type ForgotPasswordProps = {
|
|
20
|
+
className?: string
|
|
21
|
+
variant?: CardProps["variant"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Render a card-based "Forgot Password" form that sends a password-reset email.
|
|
26
|
+
*
|
|
27
|
+
* The form displays an email input, submit button, and a link back to sign-in.
|
|
28
|
+
* Toasts are displayed on success or error via the `useForgotPassword` hook.
|
|
29
|
+
*
|
|
30
|
+
* @param className - Optional additional CSS class names applied to the card
|
|
31
|
+
* @returns The forgot-password form UI as a JSX element
|
|
32
|
+
*/
|
|
33
|
+
export function ForgotPassword({
|
|
34
|
+
className,
|
|
35
|
+
variant,
|
|
36
|
+
...props
|
|
37
|
+
}: ForgotPasswordProps & CardProps) {
|
|
38
|
+
const { basePaths, localization, viewPaths, navigate } = useAuth()
|
|
39
|
+
|
|
40
|
+
const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset({
|
|
41
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
42
|
+
onSuccess: () => {
|
|
43
|
+
toast.success(localization.auth.passwordResetEmailSent)
|
|
44
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
|
|
51
|
+
const formData = new FormData(e.currentTarget)
|
|
52
|
+
requestPasswordReset({ email: formData.get("email") as string })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Card
|
|
57
|
+
className={cn("w-full max-w-sm p-4 md:p-6", className)}
|
|
58
|
+
variant={variant}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<Card.Header>
|
|
62
|
+
<Card.Title className="text-xl font-semibold mb-1">
|
|
63
|
+
{localization.auth.forgotPassword}
|
|
64
|
+
</Card.Title>
|
|
65
|
+
</Card.Header>
|
|
66
|
+
|
|
67
|
+
<Card.Content>
|
|
68
|
+
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
69
|
+
<TextField
|
|
70
|
+
name="email"
|
|
71
|
+
type="email"
|
|
72
|
+
autoComplete="email"
|
|
73
|
+
isDisabled={isPending}
|
|
74
|
+
>
|
|
75
|
+
<Label>{localization.auth.email}</Label>
|
|
76
|
+
|
|
77
|
+
<Input
|
|
78
|
+
placeholder={localization.auth.emailPlaceholder}
|
|
79
|
+
required
|
|
80
|
+
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<FieldError />
|
|
84
|
+
</TextField>
|
|
85
|
+
|
|
86
|
+
<Button type="submit" className="w-full" isPending={isPending}>
|
|
87
|
+
{isPending && <Spinner color="current" size="sm" />}
|
|
88
|
+
|
|
89
|
+
{localization.auth.sendResetLink}
|
|
90
|
+
</Button>
|
|
91
|
+
</Form>
|
|
92
|
+
</Card.Content>
|
|
93
|
+
|
|
94
|
+
<Card.Footer className="flex-col">
|
|
95
|
+
<Description className="text-sm">
|
|
96
|
+
{localization.auth.rememberYourPassword}{" "}
|
|
97
|
+
<Link
|
|
98
|
+
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
99
|
+
className="text-accent decoration-accent no-underline hover:underline"
|
|
100
|
+
>
|
|
101
|
+
{localization.auth.signIn}
|
|
102
|
+
</Link>
|
|
103
|
+
</Description>
|
|
104
|
+
</Card.Footer>
|
|
105
|
+
</Card>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AuthView } from "@better-auth-ui/core"
|
|
2
|
+
import { useAuth } from "@better-auth-ui/react"
|
|
3
|
+
import { Envelope, Lock } from "@gravity-ui/icons"
|
|
4
|
+
import { cn, Link } from "@heroui/react"
|
|
5
|
+
import { buttonVariants } from "@heroui/styles"
|
|
6
|
+
|
|
7
|
+
export type MagicLinkButtonProps = {
|
|
8
|
+
isPending: boolean
|
|
9
|
+
view?: AuthView
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders a full-width tertiary link-style button that navigates to either the magic-link or sign-in route.
|
|
14
|
+
*
|
|
15
|
+
* @param isPending - If true, applies disabled styling and prevents interaction
|
|
16
|
+
* @param view - Current auth view; when `"magicLink"`, the button targets the sign-in/password variant
|
|
17
|
+
* @returns The rendered Link element with the appropriate href, icon, and label
|
|
18
|
+
*/
|
|
19
|
+
export function MagicLinkButton({ isPending, view }: MagicLinkButtonProps) {
|
|
20
|
+
const { basePaths, viewPaths, localization } = useAuth()
|
|
21
|
+
|
|
22
|
+
const isMagicLinkView = view === "magicLink"
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Link
|
|
26
|
+
href={`${basePaths.auth}/${isMagicLinkView ? viewPaths.auth.signIn : viewPaths.auth.magicLink}`}
|
|
27
|
+
className={cn(
|
|
28
|
+
buttonVariants({ variant: "tertiary" }),
|
|
29
|
+
"w-full gap-2",
|
|
30
|
+
isPending && "status-disabled pointer-events-none"
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{isMagicLinkView ? <Lock /> : <Envelope />}
|
|
34
|
+
|
|
35
|
+
{localization.auth.continueWith.replace(
|
|
36
|
+
"{{provider}}",
|
|
37
|
+
isMagicLinkView
|
|
38
|
+
? localization.auth.password
|
|
39
|
+
: localization.auth.magicLink
|
|
40
|
+
)}
|
|
41
|
+
</Link>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAuth,
|
|
3
|
+
useSignInMagicLink,
|
|
4
|
+
useSignInSocial
|
|
5
|
+
} from "@better-auth-ui/react"
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
Card,
|
|
9
|
+
type CardProps,
|
|
10
|
+
cn,
|
|
11
|
+
Description,
|
|
12
|
+
FieldError,
|
|
13
|
+
Form,
|
|
14
|
+
Input,
|
|
15
|
+
Label,
|
|
16
|
+
Link,
|
|
17
|
+
Spinner,
|
|
18
|
+
TextField,
|
|
19
|
+
toast
|
|
20
|
+
} from "@heroui/react"
|
|
21
|
+
import { type SyntheticEvent, useState } from "react"
|
|
22
|
+
|
|
23
|
+
import { FieldSeparator } from "./field-separator"
|
|
24
|
+
import { MagicLinkButton } from "./magic-link-button"
|
|
25
|
+
import { ProviderButtons, type SocialLayout } from "./provider-buttons"
|
|
26
|
+
|
|
27
|
+
export type MagicLinkProps = {
|
|
28
|
+
className?: string
|
|
29
|
+
socialLayout?: SocialLayout
|
|
30
|
+
socialPosition?: "top" | "bottom"
|
|
31
|
+
variant?: CardProps["variant"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render a card-based sign-in form that sends an email magic link and optionally shows social provider buttons.
|
|
36
|
+
*
|
|
37
|
+
* @param className - Additional CSS class names applied to the card container
|
|
38
|
+
* @param socialLayout - Layout style for social provider buttons
|
|
39
|
+
* @param socialPosition - Position of social provider buttons; `"top"` or `"bottom"`. Defaults to `"bottom"`.
|
|
40
|
+
* @returns The magic-link sign-in UI as a JSX element
|
|
41
|
+
*/
|
|
42
|
+
export function MagicLink({
|
|
43
|
+
className,
|
|
44
|
+
socialLayout,
|
|
45
|
+
socialPosition = "bottom",
|
|
46
|
+
variant,
|
|
47
|
+
...props
|
|
48
|
+
}: MagicLinkProps & CardProps) {
|
|
49
|
+
const {
|
|
50
|
+
basePaths,
|
|
51
|
+
baseURL,
|
|
52
|
+
localization,
|
|
53
|
+
redirectTo,
|
|
54
|
+
socialProviders,
|
|
55
|
+
viewPaths
|
|
56
|
+
} = useAuth()
|
|
57
|
+
|
|
58
|
+
const [email, setEmail] = useState("")
|
|
59
|
+
|
|
60
|
+
const { mutate: signInMagicLink, isPending: magicLinkPending } =
|
|
61
|
+
useSignInMagicLink({
|
|
62
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
63
|
+
onSuccess: () => {
|
|
64
|
+
setEmail("")
|
|
65
|
+
toast.success(localization.auth.magicLinkSent)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const [socialRedirecting, setSocialRedirecting] = useState(false)
|
|
70
|
+
|
|
71
|
+
const { mutate: signInSocial, isPending: socialPending } = useSignInSocial({
|
|
72
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
73
|
+
onSuccess: () => {
|
|
74
|
+
setSocialRedirecting(true)
|
|
75
|
+
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
setSocialRedirecting(false)
|
|
78
|
+
}, 5000)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const isPending = magicLinkPending || socialPending || socialRedirecting
|
|
83
|
+
|
|
84
|
+
const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
signInMagicLink({ email, callbackURL: `${baseURL}${redirectTo}` })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const showSeparator = !!socialProviders?.length
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<Card
|
|
93
|
+
className={cn("w-full max-w-sm p-4 md:p-6", className)}
|
|
94
|
+
variant={variant}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
<Card.Header>
|
|
98
|
+
<Card.Title className="text-xl font-semibold mb-1">
|
|
99
|
+
{localization.auth.signIn}
|
|
100
|
+
</Card.Title>
|
|
101
|
+
</Card.Header>
|
|
102
|
+
|
|
103
|
+
<Card.Content className="gap-4">
|
|
104
|
+
{socialPosition === "top" && (
|
|
105
|
+
<>
|
|
106
|
+
{!!socialProviders?.length && (
|
|
107
|
+
<ProviderButtons
|
|
108
|
+
socialLayout={socialLayout}
|
|
109
|
+
signInSocial={signInSocial}
|
|
110
|
+
isPending={isPending}
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{showSeparator && (
|
|
115
|
+
<FieldSeparator>{localization.auth.or}</FieldSeparator>
|
|
116
|
+
)}
|
|
117
|
+
</>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
121
|
+
<TextField
|
|
122
|
+
name="email"
|
|
123
|
+
type="email"
|
|
124
|
+
autoComplete="email"
|
|
125
|
+
isDisabled={isPending}
|
|
126
|
+
value={email}
|
|
127
|
+
onChange={setEmail}
|
|
128
|
+
>
|
|
129
|
+
<Label>{localization.auth.email}</Label>
|
|
130
|
+
|
|
131
|
+
<Input
|
|
132
|
+
placeholder={localization.auth.emailPlaceholder}
|
|
133
|
+
required
|
|
134
|
+
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<FieldError />
|
|
138
|
+
</TextField>
|
|
139
|
+
|
|
140
|
+
<div className="flex flex-col gap-3">
|
|
141
|
+
<Button type="submit" className="w-full" isPending={isPending}>
|
|
142
|
+
{isPending && <Spinner color="current" size="sm" />}
|
|
143
|
+
|
|
144
|
+
{localization.auth.sendMagicLink}
|
|
145
|
+
</Button>
|
|
146
|
+
|
|
147
|
+
<MagicLinkButton view="magicLink" isPending={isPending} />
|
|
148
|
+
</div>
|
|
149
|
+
</Form>
|
|
150
|
+
|
|
151
|
+
{socialPosition === "bottom" && (
|
|
152
|
+
<>
|
|
153
|
+
{showSeparator && (
|
|
154
|
+
<FieldSeparator>{localization.auth.or}</FieldSeparator>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
{!!socialProviders?.length && (
|
|
158
|
+
<ProviderButtons
|
|
159
|
+
socialLayout={socialLayout}
|
|
160
|
+
signInSocial={signInSocial}
|
|
161
|
+
isPending={isPending}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
</Card.Content>
|
|
167
|
+
|
|
168
|
+
<Card.Footer className="flex-col">
|
|
169
|
+
<Description className="text-sm">
|
|
170
|
+
{localization.auth.needToCreateAnAccount}{" "}
|
|
171
|
+
<Link
|
|
172
|
+
href={`${basePaths.auth}/${viewPaths.auth.signUp}`}
|
|
173
|
+
className="text-accent decoration-accent no-underline hover:underline"
|
|
174
|
+
>
|
|
175
|
+
{localization.auth.signUp}
|
|
176
|
+
</Link>
|
|
177
|
+
</Description>
|
|
178
|
+
</Card.Footer>
|
|
179
|
+
</Card>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getProviderName } from "@better-auth-ui/core"
|
|
2
|
+
import { providerIcons, useAuth } from "@better-auth-ui/react"
|
|
3
|
+
import { Button, cn } from "@heroui/react"
|
|
4
|
+
import type { SocialProvider } from "better-auth/social-providers"
|
|
5
|
+
import { useMemo } from "react"
|
|
6
|
+
|
|
7
|
+
export type ProviderButtonsProps = {
|
|
8
|
+
isPending: boolean
|
|
9
|
+
socialLayout?: SocialLayout
|
|
10
|
+
signInSocial: (params: {
|
|
11
|
+
provider: SocialProvider
|
|
12
|
+
callbackURL: string
|
|
13
|
+
}) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SocialLayout = "auto" | "horizontal" | "vertical" | "grid"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Render social provider sign-in buttons and handle initiation and pending state.
|
|
20
|
+
*
|
|
21
|
+
* @param isPending - Disables all provider buttons when true.
|
|
22
|
+
* @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
|
+
* @returns The JSX element that renders the configured social provider buttons.
|
|
25
|
+
*/
|
|
26
|
+
export function ProviderButtons({
|
|
27
|
+
isPending,
|
|
28
|
+
socialLayout = "auto",
|
|
29
|
+
signInSocial
|
|
30
|
+
}: ProviderButtonsProps) {
|
|
31
|
+
const { baseURL, localization, redirectTo, socialProviders } = useAuth()
|
|
32
|
+
|
|
33
|
+
const callbackURL = `${baseURL}${redirectTo}`
|
|
34
|
+
|
|
35
|
+
const resolvedSocialLayout = useMemo(() => {
|
|
36
|
+
if (socialLayout === "auto") {
|
|
37
|
+
if (socialProviders?.length && socialProviders.length >= 4) {
|
|
38
|
+
return "horizontal"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return "vertical"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return socialLayout
|
|
45
|
+
}, [socialLayout, socialProviders?.length])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
className={cn(
|
|
50
|
+
"gap-3",
|
|
51
|
+
resolvedSocialLayout === "grid" && "grid grid-cols-2",
|
|
52
|
+
resolvedSocialLayout === "vertical" && "flex flex-col",
|
|
53
|
+
resolvedSocialLayout === "horizontal" && "flex flex-wrap"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{socialProviders?.map((provider) => {
|
|
57
|
+
const ProviderIcon = providerIcons[provider]
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Button
|
|
61
|
+
key={provider}
|
|
62
|
+
className={cn(
|
|
63
|
+
"w-full",
|
|
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
|
+
)
|
|
77
|
+
: resolvedSocialLayout === "grid"
|
|
78
|
+
? getProviderName(provider)
|
|
79
|
+
: null}
|
|
80
|
+
</Button>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { useAuth, useResetPassword } from "@better-auth-ui/react"
|
|
2
|
+
import { Eye, EyeSlash } from "@gravity-ui/icons"
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
type CardProps,
|
|
7
|
+
cn,
|
|
8
|
+
Description,
|
|
9
|
+
FieldError,
|
|
10
|
+
Form,
|
|
11
|
+
InputGroup,
|
|
12
|
+
Label,
|
|
13
|
+
Link,
|
|
14
|
+
Spinner,
|
|
15
|
+
TextField,
|
|
16
|
+
toast
|
|
17
|
+
} from "@heroui/react"
|
|
18
|
+
import { type SyntheticEvent, useEffect, useState } from "react"
|
|
19
|
+
|
|
20
|
+
export type ResetPasswordProps = {
|
|
21
|
+
className?: string
|
|
22
|
+
variant?: CardProps["variant"]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Renders a reset password form that verifies a "token" URL parameter on mount and redirects to sign-in if absent.
|
|
27
|
+
*
|
|
28
|
+
* Renders password (and optional confirm-password) inputs with visibility toggles, applies min/max length constraints from the auth configuration, shows field errors, and submits the new password to the auth client.
|
|
29
|
+
*/
|
|
30
|
+
export function ResetPassword({
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
...props
|
|
34
|
+
}: ResetPasswordProps & CardProps) {
|
|
35
|
+
const { basePaths, emailAndPassword, localization, viewPaths, navigate } =
|
|
36
|
+
useAuth()
|
|
37
|
+
|
|
38
|
+
const { mutate: resetPassword, isPending } = useResetPassword({
|
|
39
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
40
|
+
onSuccess: () => {
|
|
41
|
+
toast.success(localization.auth.passwordResetSuccess)
|
|
42
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const [isPasswordVisible, setIsPasswordVisible] = useState(false)
|
|
47
|
+
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
|
|
48
|
+
useState(false)
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
52
|
+
const token = searchParams.get("token") as string
|
|
53
|
+
|
|
54
|
+
if (!token) {
|
|
55
|
+
toast.danger(localization.auth.invalidResetPasswordToken)
|
|
56
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
57
|
+
}
|
|
58
|
+
}, [
|
|
59
|
+
basePaths.auth,
|
|
60
|
+
localization.auth.invalidResetPasswordToken,
|
|
61
|
+
viewPaths.auth.signIn,
|
|
62
|
+
navigate
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
|
|
68
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
69
|
+
const token = searchParams.get("token") as string
|
|
70
|
+
|
|
71
|
+
if (!token) {
|
|
72
|
+
toast.danger(localization.auth.invalidResetPasswordToken)
|
|
73
|
+
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const formData = new FormData(e.currentTarget)
|
|
78
|
+
const password = formData.get("password") as string
|
|
79
|
+
const confirmPassword = formData.get("confirmPassword") as string
|
|
80
|
+
|
|
81
|
+
if (emailAndPassword?.confirmPassword && password !== confirmPassword) {
|
|
82
|
+
toast.danger(localization.auth.passwordsDoNotMatch)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
resetPassword({ token, newPassword: password })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Card
|
|
91
|
+
className={cn("w-full max-w-sm p-4 md:p-6", className)}
|
|
92
|
+
variant={variant}
|
|
93
|
+
{...props}
|
|
94
|
+
>
|
|
95
|
+
<Card.Header>
|
|
96
|
+
<Card.Title className="text-xl font-semibold mb-1">
|
|
97
|
+
{localization.auth.resetPassword}
|
|
98
|
+
</Card.Title>
|
|
99
|
+
</Card.Header>
|
|
100
|
+
|
|
101
|
+
<Card.Content>
|
|
102
|
+
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
103
|
+
<TextField
|
|
104
|
+
minLength={emailAndPassword?.minPasswordLength}
|
|
105
|
+
maxLength={emailAndPassword?.maxPasswordLength}
|
|
106
|
+
name="password"
|
|
107
|
+
autoComplete="new-password"
|
|
108
|
+
isDisabled={isPending}
|
|
109
|
+
>
|
|
110
|
+
<Label>{localization.auth.password}</Label>
|
|
111
|
+
|
|
112
|
+
<InputGroup
|
|
113
|
+
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
114
|
+
>
|
|
115
|
+
<InputGroup.Input
|
|
116
|
+
name="password"
|
|
117
|
+
placeholder={localization.auth.newPasswordPlaceholder}
|
|
118
|
+
type={isPasswordVisible ? "text" : "password"}
|
|
119
|
+
required
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<InputGroup.Suffix className="px-0">
|
|
123
|
+
<Button
|
|
124
|
+
isIconOnly
|
|
125
|
+
aria-label={
|
|
126
|
+
isPasswordVisible
|
|
127
|
+
? localization.auth.hidePassword
|
|
128
|
+
: localization.auth.showPassword
|
|
129
|
+
}
|
|
130
|
+
size="sm"
|
|
131
|
+
variant="ghost"
|
|
132
|
+
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
|
|
133
|
+
isDisabled={isPending}
|
|
134
|
+
>
|
|
135
|
+
{isPasswordVisible ? <EyeSlash /> : <Eye />}
|
|
136
|
+
</Button>
|
|
137
|
+
</InputGroup.Suffix>
|
|
138
|
+
</InputGroup>
|
|
139
|
+
|
|
140
|
+
<FieldError />
|
|
141
|
+
</TextField>
|
|
142
|
+
|
|
143
|
+
{emailAndPassword?.confirmPassword && (
|
|
144
|
+
<TextField
|
|
145
|
+
minLength={emailAndPassword?.minPasswordLength}
|
|
146
|
+
maxLength={emailAndPassword?.maxPasswordLength}
|
|
147
|
+
name="confirmPassword"
|
|
148
|
+
autoComplete="new-password"
|
|
149
|
+
isDisabled={isPending}
|
|
150
|
+
>
|
|
151
|
+
<Label>{localization.auth.confirmPassword}</Label>
|
|
152
|
+
|
|
153
|
+
<InputGroup
|
|
154
|
+
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
155
|
+
>
|
|
156
|
+
<InputGroup.Input
|
|
157
|
+
placeholder={localization.auth.confirmPasswordPlaceholder}
|
|
158
|
+
type={isConfirmPasswordVisible ? "text" : "password"}
|
|
159
|
+
required
|
|
160
|
+
name="confirmPassword"
|
|
161
|
+
/>
|
|
162
|
+
|
|
163
|
+
<InputGroup.Suffix className="px-0">
|
|
164
|
+
<Button
|
|
165
|
+
isIconOnly
|
|
166
|
+
aria-label={
|
|
167
|
+
isConfirmPasswordVisible
|
|
168
|
+
? localization.auth.hidePassword
|
|
169
|
+
: localization.auth.showPassword
|
|
170
|
+
}
|
|
171
|
+
size="sm"
|
|
172
|
+
variant="ghost"
|
|
173
|
+
onPress={() =>
|
|
174
|
+
setIsConfirmPasswordVisible(!isConfirmPasswordVisible)
|
|
175
|
+
}
|
|
176
|
+
isDisabled={isPending}
|
|
177
|
+
>
|
|
178
|
+
{isConfirmPasswordVisible ? <EyeSlash /> : <Eye />}
|
|
179
|
+
</Button>
|
|
180
|
+
</InputGroup.Suffix>
|
|
181
|
+
</InputGroup>
|
|
182
|
+
|
|
183
|
+
<FieldError />
|
|
184
|
+
</TextField>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
<div className="flex flex-col gap-3">
|
|
188
|
+
<Button type="submit" className="w-full" isPending={isPending}>
|
|
189
|
+
{isPending && <Spinner color="current" size="sm" />}
|
|
190
|
+
|
|
191
|
+
{localization.auth.resetPassword}
|
|
192
|
+
</Button>
|
|
193
|
+
</div>
|
|
194
|
+
</Form>
|
|
195
|
+
</Card.Content>
|
|
196
|
+
|
|
197
|
+
<Card.Footer className="flex-col">
|
|
198
|
+
<Description className="text-sm">
|
|
199
|
+
{localization.auth.rememberYourPassword}{" "}
|
|
200
|
+
<Link
|
|
201
|
+
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
202
|
+
className="text-accent decoration-accent no-underline hover:underline"
|
|
203
|
+
>
|
|
204
|
+
{localization.auth.signIn}
|
|
205
|
+
</Link>
|
|
206
|
+
</Description>
|
|
207
|
+
</Card.Footer>
|
|
208
|
+
</Card>
|
|
209
|
+
)
|
|
210
|
+
}
|