@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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PasskeyAuthClient,
|
|
3
|
+
useAddPasskey,
|
|
4
|
+
useAuth,
|
|
5
|
+
useAuthPlugin,
|
|
6
|
+
useListPasskeys
|
|
7
|
+
} from "@better-auth-ui/react"
|
|
8
|
+
import { Fingerprint } from "@gravity-ui/icons"
|
|
9
|
+
import {
|
|
10
|
+
AlertDialog,
|
|
11
|
+
Button,
|
|
12
|
+
Card,
|
|
13
|
+
type CardProps,
|
|
14
|
+
cn,
|
|
15
|
+
FieldError,
|
|
16
|
+
Form,
|
|
17
|
+
Input,
|
|
18
|
+
Label,
|
|
19
|
+
Skeleton,
|
|
20
|
+
Spinner,
|
|
21
|
+
TextField
|
|
22
|
+
} from "@heroui/react"
|
|
23
|
+
import { type SyntheticEvent, useState } from "react"
|
|
24
|
+
|
|
25
|
+
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
26
|
+
import { Passkey } from "./passkey"
|
|
27
|
+
|
|
28
|
+
export type PasskeysProps = {
|
|
29
|
+
className?: string
|
|
30
|
+
variant?: CardProps["variant"]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function Passkeys({
|
|
34
|
+
className,
|
|
35
|
+
variant,
|
|
36
|
+
...props
|
|
37
|
+
}: PasskeysProps & Omit<CardProps, "children">) {
|
|
38
|
+
const { authClient, localization } = useAuth()
|
|
39
|
+
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
40
|
+
|
|
41
|
+
const { data: passkeys, isPending } = useListPasskeys(
|
|
42
|
+
authClient as PasskeyAuthClient
|
|
43
|
+
)
|
|
44
|
+
const { mutate: addPasskey, isPending: isAdding } = useAddPasskey(
|
|
45
|
+
authClient as PasskeyAuthClient
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const [nameOpen, setNameOpen] = useState(false)
|
|
49
|
+
const [name, setName] = useState("")
|
|
50
|
+
|
|
51
|
+
const handleDialogOpenChange = (open: boolean) => {
|
|
52
|
+
setNameOpen(open)
|
|
53
|
+
setName("")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
|
57
|
+
e.preventDefault()
|
|
58
|
+
|
|
59
|
+
addPasskey(
|
|
60
|
+
{ name: name.trim() || undefined },
|
|
61
|
+
{
|
|
62
|
+
onSuccess: () => {
|
|
63
|
+
setNameOpen(false)
|
|
64
|
+
setName("")
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div>
|
|
72
|
+
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
73
|
+
{passkeyLocalization.passkeys}
|
|
74
|
+
</h2>
|
|
75
|
+
|
|
76
|
+
<Card className={cn(className)} variant={variant} {...props}>
|
|
77
|
+
<Card.Content className="gap-0">
|
|
78
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
79
|
+
<div>
|
|
80
|
+
<p className="text-sm font-medium leading-tight">
|
|
81
|
+
{passkeyLocalization.passkeysDescription}
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
<p className="text-muted text-xs mt-0.5">
|
|
85
|
+
{passkeyLocalization.passkeysInstructions}
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<AlertDialog>
|
|
90
|
+
<Button
|
|
91
|
+
className="shrink-0"
|
|
92
|
+
size="sm"
|
|
93
|
+
isDisabled={isPending}
|
|
94
|
+
onPress={() => setNameOpen(true)}
|
|
95
|
+
>
|
|
96
|
+
{passkeyLocalization.addPasskey}
|
|
97
|
+
</Button>
|
|
98
|
+
|
|
99
|
+
<AlertDialog.Backdrop
|
|
100
|
+
isOpen={nameOpen}
|
|
101
|
+
onOpenChange={handleDialogOpenChange}
|
|
102
|
+
>
|
|
103
|
+
<AlertDialog.Container>
|
|
104
|
+
<AlertDialog.Dialog>
|
|
105
|
+
<Form onSubmit={handleSubmit}>
|
|
106
|
+
<AlertDialog.CloseTrigger />
|
|
107
|
+
|
|
108
|
+
<AlertDialog.Header>
|
|
109
|
+
<AlertDialog.Icon status="default">
|
|
110
|
+
<Fingerprint />
|
|
111
|
+
</AlertDialog.Icon>
|
|
112
|
+
|
|
113
|
+
<AlertDialog.Heading>
|
|
114
|
+
{passkeyLocalization.addPasskey}
|
|
115
|
+
</AlertDialog.Heading>
|
|
116
|
+
</AlertDialog.Header>
|
|
117
|
+
|
|
118
|
+
<AlertDialog.Body className="overflow-visible">
|
|
119
|
+
<p className="text-muted text-sm">
|
|
120
|
+
{passkeyLocalization.passkeysInstructions}
|
|
121
|
+
</p>
|
|
122
|
+
|
|
123
|
+
<TextField
|
|
124
|
+
className="mt-4"
|
|
125
|
+
name="passkey-name"
|
|
126
|
+
isDisabled={isAdding}
|
|
127
|
+
value={name}
|
|
128
|
+
onChange={setName}
|
|
129
|
+
>
|
|
130
|
+
<Label>{passkeyLocalization.passkey}</Label>
|
|
131
|
+
|
|
132
|
+
<Input
|
|
133
|
+
autoFocus
|
|
134
|
+
placeholder={localization.settings.optional}
|
|
135
|
+
variant="secondary"
|
|
136
|
+
/>
|
|
137
|
+
|
|
138
|
+
<FieldError />
|
|
139
|
+
</TextField>
|
|
140
|
+
</AlertDialog.Body>
|
|
141
|
+
|
|
142
|
+
<AlertDialog.Footer>
|
|
143
|
+
<Button
|
|
144
|
+
slot="close"
|
|
145
|
+
variant="tertiary"
|
|
146
|
+
isDisabled={isAdding}
|
|
147
|
+
>
|
|
148
|
+
{localization.settings.cancel}
|
|
149
|
+
</Button>
|
|
150
|
+
|
|
151
|
+
<Button type="submit" isPending={isAdding}>
|
|
152
|
+
{isAdding && <Spinner color="current" size="sm" />}
|
|
153
|
+
|
|
154
|
+
{passkeyLocalization.addPasskey}
|
|
155
|
+
</Button>
|
|
156
|
+
</AlertDialog.Footer>
|
|
157
|
+
</Form>
|
|
158
|
+
</AlertDialog.Dialog>
|
|
159
|
+
</AlertDialog.Container>
|
|
160
|
+
</AlertDialog.Backdrop>
|
|
161
|
+
</AlertDialog>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{isPending ? (
|
|
165
|
+
<>
|
|
166
|
+
<div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
|
|
167
|
+
<PasskeySkeleton />
|
|
168
|
+
</>
|
|
169
|
+
) : (
|
|
170
|
+
passkeys?.map((passkey) => (
|
|
171
|
+
<div key={passkey.id}>
|
|
172
|
+
<div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
|
|
173
|
+
<Passkey passkey={passkey} />
|
|
174
|
+
</div>
|
|
175
|
+
))
|
|
176
|
+
)}
|
|
177
|
+
</Card.Content>
|
|
178
|
+
</Card>
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function PasskeySkeleton() {
|
|
184
|
+
return (
|
|
185
|
+
<div className="flex items-center justify-between">
|
|
186
|
+
<div className="flex items-center gap-3">
|
|
187
|
+
<Skeleton className="size-10 rounded-xl" />
|
|
188
|
+
|
|
189
|
+
<div className="flex flex-col gap-1">
|
|
190
|
+
<Skeleton className="h-4 w-24 rounded-lg" />
|
|
191
|
+
<Skeleton className="h-3 w-20 rounded-lg" />
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { authMutationKeys, 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 { useIsMutating } from "@tanstack/react-query"
|
|
5
|
+
import type { SocialProvider } from "better-auth/social-providers"
|
|
6
|
+
|
|
7
|
+
export type ProviderButtonProps = {
|
|
8
|
+
provider: SocialProvider
|
|
9
|
+
display?: "full" | "name" | "icon"
|
|
10
|
+
} & Omit<ButtonProps, "children" | "onPress" | "isPending" | "isDisabled">
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Social provider sign-in button.
|
|
14
|
+
*
|
|
15
|
+
* @param provider - Provider to sign in with.
|
|
16
|
+
* @param display - `"full"` (e.g. "Continue with Google"), `"name"` (just the provider name), or `"icon"` (icon only).
|
|
17
|
+
*/
|
|
18
|
+
export function ProviderButton({
|
|
19
|
+
provider,
|
|
20
|
+
display = "full",
|
|
21
|
+
variant = "tertiary",
|
|
22
|
+
...props
|
|
23
|
+
}: ProviderButtonProps) {
|
|
24
|
+
const { authClient, baseURL, localization, redirectTo } = useAuth()
|
|
25
|
+
|
|
26
|
+
const callbackURL = `${baseURL}${redirectTo}`
|
|
27
|
+
|
|
28
|
+
const { mutate: signInSocial, isPending: signInSocialPending } =
|
|
29
|
+
useSignInSocial(authClient)
|
|
30
|
+
|
|
31
|
+
const ProviderIcon = providerIcons[provider]
|
|
32
|
+
|
|
33
|
+
const signInMutating = useIsMutating({
|
|
34
|
+
mutationKey: authMutationKeys.signIn.all
|
|
35
|
+
})
|
|
36
|
+
const signUpMutating = useIsMutating({
|
|
37
|
+
mutationKey: authMutationKeys.signUp.all
|
|
38
|
+
})
|
|
39
|
+
const isPending = signInMutating + signUpMutating > 0
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Button
|
|
43
|
+
variant={variant}
|
|
44
|
+
isPending={isPending}
|
|
45
|
+
onPress={() => signInSocial({ provider, callbackURL })}
|
|
46
|
+
{...props}
|
|
47
|
+
aria-label={getProviderName(provider)}
|
|
48
|
+
>
|
|
49
|
+
{signInSocialPending ? (
|
|
50
|
+
<Spinner color="current" size="sm" />
|
|
51
|
+
) : (
|
|
52
|
+
<ProviderIcon />
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{display === "full"
|
|
56
|
+
? localization.auth.continueWith.replace(
|
|
57
|
+
"{{provider}}",
|
|
58
|
+
getProviderName(provider)
|
|
59
|
+
)
|
|
60
|
+
: display === "name"
|
|
61
|
+
? getProviderName(provider)
|
|
62
|
+
: null}
|
|
63
|
+
</Button>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -1,36 +1,24 @@
|
|
|
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
|
+
import { ProviderButton } from "./provider-button"
|
|
7
5
|
export type ProviderButtonsProps = {
|
|
8
|
-
isPending: boolean
|
|
9
6
|
socialLayout?: SocialLayout
|
|
10
|
-
signInSocial: (params: {
|
|
11
|
-
provider: SocialProvider
|
|
12
|
-
callbackURL: string
|
|
13
|
-
}) => void
|
|
14
7
|
}
|
|
15
8
|
|
|
16
9
|
export type SocialLayout = "auto" | "horizontal" | "vertical" | "grid"
|
|
17
10
|
|
|
18
11
|
/**
|
|
19
|
-
* Render social provider sign-in buttons
|
|
12
|
+
* Render social provider sign-in buttons. Each button owns its own sign-in
|
|
13
|
+
* mutation and reads shared auth pending state from React Query.
|
|
20
14
|
*
|
|
21
|
-
* @param isPending - Disables all provider buttons when true.
|
|
22
15
|
* @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
16
|
* @returns The JSX element that renders the configured social provider buttons.
|
|
25
17
|
*/
|
|
26
18
|
export function ProviderButtons({
|
|
27
|
-
|
|
28
|
-
socialLayout = "auto",
|
|
29
|
-
signInSocial
|
|
19
|
+
socialLayout = "auto"
|
|
30
20
|
}: ProviderButtonsProps) {
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
const callbackURL = `${baseURL}${redirectTo}`
|
|
21
|
+
const { socialProviders } = useAuth()
|
|
34
22
|
|
|
35
23
|
const resolvedSocialLayout = useMemo(() => {
|
|
36
24
|
if (socialLayout === "auto") {
|
|
@@ -53,33 +41,23 @@ export function ProviderButtons({
|
|
|
53
41
|
resolvedSocialLayout === "horizontal" && "flex flex-wrap"
|
|
54
42
|
)}
|
|
55
43
|
>
|
|
56
|
-
{socialProviders?.map((provider) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
)
|
|
44
|
+
{socialProviders?.map((provider) => (
|
|
45
|
+
<ProviderButton
|
|
46
|
+
key={provider}
|
|
47
|
+
provider={provider}
|
|
48
|
+
display={
|
|
49
|
+
resolvedSocialLayout === "vertical"
|
|
50
|
+
? "full"
|
|
77
51
|
: resolvedSocialLayout === "grid"
|
|
78
|
-
?
|
|
79
|
-
:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
52
|
+
? "name"
|
|
53
|
+
: "icon"
|
|
54
|
+
}
|
|
55
|
+
className={cn(
|
|
56
|
+
"w-full",
|
|
57
|
+
resolvedSocialLayout === "horizontal" && "flex-1"
|
|
58
|
+
)}
|
|
59
|
+
/>
|
|
60
|
+
))}
|
|
83
61
|
</div>
|
|
84
62
|
)
|
|
85
63
|
}
|
|
@@ -31,12 +31,17 @@ export function ResetPassword({
|
|
|
31
31
|
className,
|
|
32
32
|
variant,
|
|
33
33
|
...props
|
|
34
|
-
}: ResetPasswordProps & CardProps) {
|
|
35
|
-
const {
|
|
36
|
-
|
|
34
|
+
}: ResetPasswordProps & Omit<CardProps, "children">) {
|
|
35
|
+
const {
|
|
36
|
+
authClient,
|
|
37
|
+
basePaths,
|
|
38
|
+
emailAndPassword,
|
|
39
|
+
localization,
|
|
40
|
+
viewPaths,
|
|
41
|
+
navigate
|
|
42
|
+
} = useAuth()
|
|
37
43
|
|
|
38
|
-
const { mutate: resetPassword, isPending } = useResetPassword({
|
|
39
|
-
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
44
|
+
const { mutate: resetPassword, isPending } = useResetPassword(authClient, {
|
|
40
45
|
onSuccess: () => {
|
|
41
46
|
toast.success(localization.auth.passwordResetSuccess)
|
|
42
47
|
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
@@ -88,7 +93,7 @@ export function ResetPassword({
|
|
|
88
93
|
|
|
89
94
|
return (
|
|
90
95
|
<Card
|
|
91
|
-
className={cn("w-full max-w-sm
|
|
96
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
92
97
|
variant={variant}
|
|
93
98
|
{...props}
|
|
94
99
|
>
|
|
@@ -98,7 +103,7 @@ export function ResetPassword({
|
|
|
98
103
|
</Card.Title>
|
|
99
104
|
</Card.Header>
|
|
100
105
|
|
|
101
|
-
<Card.Content>
|
|
106
|
+
<Card.Content className="gap-4">
|
|
102
107
|
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
103
108
|
<TextField
|
|
104
109
|
minLength={emailAndPassword?.minPasswordLength}
|
|
@@ -194,12 +199,12 @@ export function ResetPassword({
|
|
|
194
199
|
</Form>
|
|
195
200
|
</Card.Content>
|
|
196
201
|
|
|
197
|
-
<Card.Footer className="flex-col">
|
|
202
|
+
<Card.Footer className="flex-col gap-3">
|
|
198
203
|
<Description className="text-sm">
|
|
199
204
|
{localization.auth.rememberYourPassword}{" "}
|
|
200
205
|
<Link
|
|
201
206
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
202
|
-
className="text-accent
|
|
207
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
203
208
|
>
|
|
204
209
|
{localization.auth.signIn}
|
|
205
210
|
</Link>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useAuth } from "@better-auth-ui/react"
|
|
2
2
|
import { type CardProps, cn } from "@heroui/react"
|
|
3
3
|
import type { ComponentProps } from "react"
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import { ChangeEmail } from "./change-email"
|
|
6
|
-
import { ManageAccounts } from "./manage-accounts"
|
|
7
6
|
import { UserProfile } from "./user-profile"
|
|
8
7
|
|
|
9
8
|
export type AccountSettingsProps = {
|
|
@@ -12,9 +11,12 @@ export type AccountSettingsProps = {
|
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
* Renders the account settings layout
|
|
14
|
+
* Renders the account settings layout.
|
|
16
15
|
*
|
|
17
|
-
* UserProfile
|
|
16
|
+
* `UserProfile` always renders. `ChangeEmail` renders when password auth is
|
|
17
|
+
* enabled or the `magicLink` plugin is registered. Plugin-contributed account
|
|
18
|
+
* cards (e.g. `Appearance` from the theme plugin, multi-session accounts) are
|
|
19
|
+
* rendered via the plugins array.
|
|
18
20
|
*
|
|
19
21
|
* @param className - Optional additional CSS class names for the outer container.
|
|
20
22
|
* @param variant - Card variant forwarded to each account settings card.
|
|
@@ -25,12 +27,9 @@ export function AccountSettings({
|
|
|
25
27
|
variant,
|
|
26
28
|
...props
|
|
27
29
|
}: AccountSettingsProps & ComponentProps<"div">) {
|
|
28
|
-
const {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
magicLink,
|
|
32
|
-
appearance: { setTheme }
|
|
33
|
-
} = useAuth()
|
|
30
|
+
const { emailAndPassword, plugins } = useAuth()
|
|
31
|
+
|
|
32
|
+
const hasMagicLink = plugins.some((plugin) => plugin.id === "magicLink")
|
|
34
33
|
|
|
35
34
|
return (
|
|
36
35
|
<div
|
|
@@ -38,11 +37,14 @@ export function AccountSettings({
|
|
|
38
37
|
{...props}
|
|
39
38
|
>
|
|
40
39
|
<UserProfile variant={variant} />
|
|
41
|
-
{(emailAndPassword?.enabled ||
|
|
40
|
+
{(emailAndPassword?.enabled || hasMagicLink) && (
|
|
42
41
|
<ChangeEmail variant={variant} />
|
|
43
42
|
)}
|
|
44
|
-
{
|
|
45
|
-
|
|
43
|
+
{plugins.flatMap((plugin) =>
|
|
44
|
+
plugin.accountCards?.map((Card, index) => (
|
|
45
|
+
<Card key={`${plugin.id}-${index.toString()}`} variant={variant} />
|
|
46
|
+
))
|
|
47
|
+
)}
|
|
46
48
|
</div>
|
|
47
49
|
)
|
|
48
50
|
}
|
|
@@ -11,12 +11,11 @@ export type ChangeAvatarProps = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function ChangeAvatar({ className }: ChangeAvatarProps) {
|
|
14
|
-
const { localization, avatar } = useAuth()
|
|
15
|
-
const { data: session } = useSession()
|
|
14
|
+
const { authClient, localization, avatar } = useAuth()
|
|
15
|
+
const { data: session } = useSession(authClient)
|
|
16
16
|
|
|
17
|
-
const { mutate: updateUser, isPending: updatePending } =
|
|
18
|
-
|
|
19
|
-
})
|
|
17
|
+
const { mutate: updateUser, isPending: updatePending } =
|
|
18
|
+
useUpdateUser(authClient)
|
|
20
19
|
|
|
21
20
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
22
21
|
const [isUploading, setIsUploading] = useState(false)
|
|
@@ -34,13 +34,12 @@ export function ChangeEmail({
|
|
|
34
34
|
className,
|
|
35
35
|
variant,
|
|
36
36
|
...props
|
|
37
|
-
}: ChangeEmailProps & CardProps) {
|
|
38
|
-
const { localization, baseURL, viewPaths } = useAuth()
|
|
39
|
-
const { data: session } = useSession()
|
|
37
|
+
}: ChangeEmailProps & Omit<CardProps, "children">) {
|
|
38
|
+
const { authClient, localization, baseURL, viewPaths } = useAuth()
|
|
39
|
+
const { data: session } = useSession(authClient)
|
|
40
40
|
|
|
41
|
-
const { mutate: changeEmail, isPending } = useChangeEmail({
|
|
42
|
-
onSuccess: () => toast.success(localization.settings.changeEmailSuccess)
|
|
43
|
-
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
41
|
+
const { mutate: changeEmail, isPending } = useChangeEmail(authClient, {
|
|
42
|
+
onSuccess: () => toast.success(localization.settings.changeEmailSuccess)
|
|
44
43
|
})
|
|
45
44
|
|
|
46
45
|
function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AdditionalFieldValue,
|
|
3
|
+
parseAdditionalFieldValue
|
|
4
|
+
} from "@better-auth-ui/core"
|
|
5
|
+
import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react"
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
Card,
|
|
9
|
+
type CardProps,
|
|
10
|
+
cn,
|
|
11
|
+
FieldError,
|
|
12
|
+
Form,
|
|
13
|
+
Input,
|
|
14
|
+
Label,
|
|
15
|
+
Skeleton,
|
|
16
|
+
Spinner,
|
|
17
|
+
TextField,
|
|
18
|
+
toast
|
|
19
|
+
} from "@heroui/react"
|
|
20
|
+
import type { SyntheticEvent } from "react"
|
|
21
|
+
|
|
22
|
+
import { AdditionalField } from "../../additional-field"
|
|
23
|
+
import { ChangeAvatar } from "./change-avatar"
|
|
24
|
+
|
|
25
|
+
export type UserProfileProps = {
|
|
26
|
+
className?: string
|
|
27
|
+
variant?: CardProps["variant"]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Render a profile card that lets the authenticated user view and update their
|
|
32
|
+
* display name, avatar, and any plugin- or user-supplied additional fields.
|
|
33
|
+
*/
|
|
34
|
+
export function UserProfile({
|
|
35
|
+
className,
|
|
36
|
+
variant,
|
|
37
|
+
...props
|
|
38
|
+
}: UserProfileProps & Omit<CardProps, "children">) {
|
|
39
|
+
const { additionalFields, authClient, localization } = useAuth()
|
|
40
|
+
const { data: session } = useSession(authClient)
|
|
41
|
+
|
|
42
|
+
const { mutate: updateUser, isPending } = useUpdateUser(authClient, {
|
|
43
|
+
onSuccess: () => toast.success(localization.settings.profileUpdatedSuccess)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
async function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
const formData = new FormData(e.currentTarget)
|
|
49
|
+
const name = formData.get("name") as string
|
|
50
|
+
|
|
51
|
+
const additionalFieldValues: Record<string, unknown> = {}
|
|
52
|
+
|
|
53
|
+
for (const field of additionalFields ?? []) {
|
|
54
|
+
if (field.profile === false || field.readOnly) continue
|
|
55
|
+
const value = parseAdditionalFieldValue(
|
|
56
|
+
field,
|
|
57
|
+
formData.get(field.name) as string | null
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (field.validate) {
|
|
61
|
+
try {
|
|
62
|
+
await field.validate(value)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
toast.danger(error instanceof Error ? error.message : String(error))
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// `null` = explicit clear (forward to backend); `undefined` = omitted.
|
|
70
|
+
if (value !== undefined) {
|
|
71
|
+
additionalFieldValues[field.name] = value
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateUser({
|
|
76
|
+
name,
|
|
77
|
+
...additionalFieldValues
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
84
|
+
{localization.settings.profile}
|
|
85
|
+
</h2>
|
|
86
|
+
|
|
87
|
+
<Card className={cn("p-4 gap-4", className)} variant={variant} {...props}>
|
|
88
|
+
<Card.Content>
|
|
89
|
+
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
90
|
+
<ChangeAvatar />
|
|
91
|
+
|
|
92
|
+
<TextField
|
|
93
|
+
key={session?.user?.name}
|
|
94
|
+
name="name"
|
|
95
|
+
defaultValue={session?.user.name}
|
|
96
|
+
isDisabled={isPending || !session}
|
|
97
|
+
>
|
|
98
|
+
<Label>{localization.auth.name}</Label>
|
|
99
|
+
|
|
100
|
+
<Input
|
|
101
|
+
className={cn(!session && "hidden")}
|
|
102
|
+
autoComplete="name"
|
|
103
|
+
placeholder={localization.auth.name}
|
|
104
|
+
variant={variant === "transparent" ? "primary" : "secondary"}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
{!session && (
|
|
108
|
+
<Skeleton className="h-10 md:h-9 w-full rounded-xl" />
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<FieldError />
|
|
112
|
+
</TextField>
|
|
113
|
+
|
|
114
|
+
{additionalFields
|
|
115
|
+
?.filter((field) => field.profile !== false)
|
|
116
|
+
.map((field) => {
|
|
117
|
+
if (!session) {
|
|
118
|
+
if (field.inputType === "hidden") {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Skeleton
|
|
124
|
+
key={field.name}
|
|
125
|
+
className="h-10 md:h-9 w-full rounded-xl"
|
|
126
|
+
/>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const value = (session.user as Record<string, unknown>)[
|
|
131
|
+
field.name
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
// Re-mount when the session value loads so the field's
|
|
135
|
+
// uncontrolled `defaultValue` reflects the latest data.
|
|
136
|
+
const key = `${field.name}:${
|
|
137
|
+
value instanceof Date
|
|
138
|
+
? value.toISOString()
|
|
139
|
+
: String(value ?? "")
|
|
140
|
+
}`
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<AdditionalField
|
|
144
|
+
key={key}
|
|
145
|
+
name={field.name}
|
|
146
|
+
field={{
|
|
147
|
+
...field,
|
|
148
|
+
defaultValue: value as AdditionalFieldValue | null
|
|
149
|
+
}}
|
|
150
|
+
isPending={isPending}
|
|
151
|
+
variant={variant}
|
|
152
|
+
/>
|
|
153
|
+
)
|
|
154
|
+
})}
|
|
155
|
+
|
|
156
|
+
<Button
|
|
157
|
+
type="submit"
|
|
158
|
+
isPending={isPending}
|
|
159
|
+
isDisabled={!session}
|
|
160
|
+
size="sm"
|
|
161
|
+
className="self-start mt-1"
|
|
162
|
+
>
|
|
163
|
+
{isPending && <Spinner color="current" size="sm" />}
|
|
164
|
+
{localization.settings.saveChanges}
|
|
165
|
+
</Button>
|
|
166
|
+
</Form>
|
|
167
|
+
</Card.Content>
|
|
168
|
+
</Card>
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|