@better-auth-ui/heroui 1.6.3 → 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.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/auth/delete-user/delete-user.js +95 -0
- package/dist/components/{email → auth/email}/email-changed-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/email-changed-email.js +7 -7
- package/dist/components/{email → auth/email}/email-verification-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/email-verification-email.js +7 -7
- package/dist/components/auth/email/magic-link-email.d.ts +3 -0
- package/dist/components/{email → auth/email}/magic-link-email.js +7 -7
- package/dist/components/auth/email/new-device-email.d.ts +3 -0
- package/dist/components/{email → auth/email}/new-device-email.js +7 -7
- package/dist/components/auth/email/otp-email.d.ts +3 -0
- package/dist/components/{email → auth/email}/otp-email.js +6 -6
- package/dist/components/{email → auth/email}/password-changed-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/password-changed-email.js +7 -7
- package/dist/components/{email → auth/email}/reset-password-email.d.ts +2 -2
- package/dist/components/{email → auth/email}/reset-password-email.js +7 -7
- package/dist/components/auth/forgot-password.d.ts +1 -1
- package/dist/components/auth/forgot-password.js +41 -38
- package/dist/components/auth/magic-link/magic-link-button.d.ts +11 -0
- package/dist/components/auth/magic-link/magic-link-button.js +20 -0
- package/dist/components/auth/magic-link/magic-link.d.ts +16 -0
- package/dist/components/auth/magic-link/magic-link.js +87 -0
- package/dist/components/{settings/account → auth/multi-session}/manage-account.d.ts +2 -6
- package/dist/components/auth/multi-session/manage-account.js +49 -0
- package/dist/components/auth/multi-session/manage-accounts.js +34 -0
- package/dist/components/{user/switch-account-menu.d.ts → auth/multi-session/switch-account-submenu-content.d.ts} +3 -3
- package/dist/components/auth/multi-session/switch-account-submenu-content.js +29 -0
- package/dist/components/{user/switch-account-item.d.ts → auth/multi-session/switch-account-submenu-item.d.ts} +2 -2
- package/dist/components/auth/multi-session/switch-account-submenu-item.js +20 -0
- package/dist/components/auth/multi-session/switch-account-submenu.d.ts +13 -0
- package/dist/components/auth/multi-session/switch-account-submenu.js +24 -0
- package/dist/components/auth/passkey/passkey-button.d.ts +6 -0
- package/dist/components/auth/passkey/passkey-button.js +24 -0
- package/dist/components/auth/passkey/passkey.js +45 -0
- package/dist/components/auth/passkey/passkeys.js +106 -0
- package/dist/components/auth/provider-button.d.ts +6 -8
- package/dist/components/auth/provider-button.js +16 -20
- package/dist/components/auth/provider-buttons.d.ts +3 -4
- package/dist/components/auth/provider-buttons.js +9 -10
- package/dist/components/auth/reset-password.js +110 -107
- package/dist/components/{settings → auth/settings}/account/account-settings.d.ts +5 -2
- package/dist/components/auth/settings/account/account-settings.js +20 -0
- package/dist/components/auth/settings/account/change-avatar.js +89 -0
- package/dist/components/{settings → auth/settings}/account/change-email.js +17 -17
- package/dist/components/{settings → auth/settings}/account/user-profile.d.ts +2 -3
- package/dist/components/auth/settings/account/user-profile.js +86 -0
- package/dist/components/auth/settings/security/active-session.js +59 -0
- package/dist/components/{settings → auth/settings}/security/active-sessions.js +4 -4
- package/dist/components/auth/settings/security/change-password.js +189 -0
- package/dist/components/auth/settings/security/linked-account.js +56 -0
- package/dist/components/{settings → auth/settings}/security/linked-accounts.js +5 -5
- package/dist/components/{settings → auth/settings}/security/security-settings.d.ts +1 -1
- package/dist/components/auth/settings/security/security-settings.js +22 -0
- package/dist/components/auth/settings/settings.js +45 -0
- package/dist/components/auth/sign-in.js +75 -99
- package/dist/components/auth/sign-out.js +11 -11
- package/dist/components/auth/sign-up.js +123 -148
- package/dist/components/{settings/account → auth/theme}/appearance.d.ts +3 -6
- package/dist/components/auth/theme/appearance.js +72 -0
- package/dist/components/auth/theme/theme-toggle-item.d.ts +5 -0
- package/dist/components/auth/theme/theme-toggle-item.js +58 -0
- package/dist/components/auth/user/user-avatar.js +29 -0
- package/dist/components/{user → auth/user}/user-button.d.ts +2 -6
- package/dist/components/auth/user/user-button.js +55 -0
- package/dist/components/auth/user/user-view.js +37 -0
- package/dist/components/auth/username/sign-in-username.d.ts +14 -0
- package/dist/components/auth/username/sign-in-username.js +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 +12 -27
- package/dist/index.js +20 -35
- package/dist/lib/auth/auth-plugin.d.ts +44 -0
- package/dist/lib/auth/delete-user-plugin.d.ts +17 -0
- package/dist/lib/auth/delete-user-plugin.js +10 -0
- package/dist/lib/auth/magic-link-plugin.d.ts +31 -0
- package/dist/lib/auth/magic-link-plugin.js +13 -0
- package/dist/lib/auth/multi-session-plugin.d.ts +18 -0
- package/dist/lib/auth/multi-session-plugin.js +12 -0
- package/dist/lib/auth/passkey-plugin.d.ts +20 -0
- package/dist/lib/auth/passkey-plugin.js +12 -0
- package/dist/lib/auth/theme-plugin.d.ts +68 -0
- package/dist/lib/auth/theme-plugin.js +23 -0
- package/dist/lib/auth/username-plugin.d.ts +63 -0
- package/dist/lib/auth/username-plugin.js +18 -0
- package/dist/plugins.d.ts +20 -0
- package/dist/plugins.js +21 -0
- package/package.json +19 -21
- package/src/components/auth/additional-field.tsx +554 -0
- package/src/components/auth/auth-provider.tsx +5 -6
- package/src/components/auth/auth.tsx +99 -35
- package/src/components/{settings/security → auth/delete-user}/danger-zone.tsx +3 -10
- package/src/components/{settings/security → auth/delete-user}/delete-user.tsx +30 -20
- package/src/components/{email → auth/email}/email-changed-email.tsx +2 -2
- package/src/components/{email → auth/email}/email-verification-email.tsx +2 -2
- package/src/components/{email → auth/email}/magic-link-email.tsx +2 -2
- package/src/components/{email → auth/email}/new-device-email.tsx +2 -2
- package/src/components/{email → auth/email}/otp-email.tsx +2 -2
- package/src/components/{email → auth/email}/password-changed-email.tsx +2 -2
- package/src/components/{email → auth/email}/reset-password-email.tsx +2 -2
- package/src/components/auth/forgot-password.tsx +14 -11
- package/src/components/auth/magic-link/magic-link-button.tsx +61 -0
- package/src/components/auth/{magic-link.tsx → magic-link/magic-link.tsx} +55 -40
- package/src/components/{settings/account → auth/multi-session}/manage-account.tsx +16 -13
- package/src/components/{settings/account → auth/multi-session}/manage-accounts.tsx +14 -5
- package/src/components/{user/switch-account-menu.tsx → auth/multi-session/switch-account-submenu-content.tsx} +18 -11
- package/src/components/{user/switch-account-item.tsx → auth/multi-session/switch-account-submenu-item.tsx} +17 -5
- package/src/components/auth/multi-session/switch-account-submenu.tsx +50 -0
- package/src/components/auth/passkey/passkey-button.tsx +56 -0
- package/src/components/{settings/security → auth/passkey}/passkey.tsx +20 -6
- package/src/components/auth/passkey/passkeys.tsx +196 -0
- package/src/components/auth/provider-button.tsx +27 -29
- package/src/components/auth/provider-buttons.tsx +6 -11
- package/src/components/auth/reset-password.tsx +13 -7
- package/src/components/{settings → auth/settings}/account/account-settings.tsx +15 -13
- package/src/components/{settings → auth/settings}/account/change-avatar.tsx +4 -3
- package/src/components/{settings → auth/settings}/account/change-email.tsx +3 -3
- package/src/components/auth/settings/account/user-profile.tsx +171 -0
- package/src/components/{settings → auth/settings}/security/active-session.tsx +8 -5
- package/src/components/{settings → auth/settings}/security/active-sessions.tsx +3 -3
- package/src/components/{settings → auth/settings}/security/change-password.tsx +14 -9
- package/src/components/{settings → auth/settings}/security/linked-account.tsx +12 -8
- package/src/components/{settings → auth/settings}/security/linked-accounts.tsx +2 -2
- package/src/components/{settings → auth/settings}/security/security-settings.tsx +7 -6
- package/src/components/{settings → auth/settings}/settings.tsx +2 -2
- package/src/components/auth/sign-in.tsx +38 -61
- package/src/components/auth/sign-out.tsx +2 -2
- package/src/components/auth/sign-up.tsx +109 -131
- package/src/components/{settings/account → auth/theme}/appearance.tsx +17 -24
- package/src/components/auth/theme/theme-toggle-item.tsx +110 -0
- package/src/components/{user → auth/user}/user-avatar.tsx +12 -4
- package/src/components/{user → auth/user}/user-button.tsx +15 -88
- package/src/components/{user → auth/user}/user-view.tsx +12 -4
- package/src/components/auth/username/sign-in-username.tsx +283 -0
- package/src/components/auth/username/username-field.tsx +122 -0
- package/src/email.ts +9 -0
- package/src/index.tsx +12 -27
- package/src/lib/auth/auth-plugin.ts +67 -0
- package/src/lib/auth/delete-user-plugin.ts +15 -0
- package/src/lib/auth/magic-link-plugin.ts +26 -0
- package/src/lib/auth/multi-session-plugin.ts +17 -0
- package/src/lib/auth/passkey-plugin.ts +17 -0
- package/src/lib/auth/theme-plugin.ts +86 -0
- package/src/lib/auth/username-plugin.tsx +30 -0
- package/src/plugins.ts +23 -0
- package/dist/components/auth/magic-link-button.d.ts +0 -13
- package/dist/components/auth/magic-link-button.js +0 -16
- package/dist/components/auth/magic-link.d.ts +0 -17
- package/dist/components/auth/magic-link.js +0 -99
- package/dist/components/auth/passkey-button.d.ts +0 -4
- package/dist/components/auth/passkey-button.js +0 -21
- package/dist/components/email/magic-link-email.d.ts +0 -3
- package/dist/components/email/new-device-email.d.ts +0 -3
- package/dist/components/email/otp-email.d.ts +0 -3
- package/dist/components/settings/account/account-settings.js +0 -23
- package/dist/components/settings/account/appearance.js +0 -71
- package/dist/components/settings/account/change-avatar.js +0 -89
- package/dist/components/settings/account/manage-account.js +0 -48
- package/dist/components/settings/account/manage-accounts.js +0 -33
- package/dist/components/settings/account/user-profile.js +0 -103
- package/dist/components/settings/security/active-session.js +0 -59
- package/dist/components/settings/security/change-password.js +0 -189
- package/dist/components/settings/security/delete-user.js +0 -92
- package/dist/components/settings/security/linked-account.js +0 -56
- package/dist/components/settings/security/passkey.js +0 -43
- package/dist/components/settings/security/passkeys.js +0 -53
- package/dist/components/settings/security/security-settings.js +0 -25
- package/dist/components/settings/settings.js +0 -45
- package/dist/components/user/switch-account-item.js +0 -20
- package/dist/components/user/switch-account-menu.js +0 -28
- package/dist/components/user/user-avatar.js +0 -29
- package/dist/components/user/user-button.js +0 -91
- package/dist/components/user/user-view.js +0 -37
- package/dist/core.d.ts +0 -1
- package/dist/core.js +0 -1
- package/dist/react-exports.d.ts +0 -1
- package/dist/react-exports.js +0 -1
- package/src/components/auth/magic-link-button.tsx +0 -43
- package/src/components/auth/passkey-button.tsx +0 -35
- package/src/components/settings/account/user-profile.tsx +0 -217
- package/src/components/settings/security/passkeys.tsx +0 -96
- package/src/core.ts +0 -1
- package/src/react-exports.ts +0 -1
- /package/dist/components/{settings/security → auth/delete-user}/delete-user.d.ts +0 -0
- /package/dist/components/{settings/account → auth/multi-session}/manage-accounts.d.ts +0 -0
- /package/dist/components/{settings/security → auth/passkey}/passkey.d.ts +0 -0
- /package/dist/components/{settings/security → auth/passkey}/passkeys.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/account/change-avatar.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/account/change-email.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/active-session.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/active-sessions.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/change-password.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/linked-account.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/security/linked-accounts.d.ts +0 -0
- /package/dist/components/{settings → auth/settings}/settings.d.ts +0 -0
- /package/dist/components/{user → auth/user}/user-avatar.d.ts +0 -0
- /package/dist/components/{user → auth/user}/user-view.d.ts +0 -0
|
@@ -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
|
+
}
|
|
@@ -1,65 +1,63 @@
|
|
|
1
|
-
import { getProviderName } from "@better-auth-ui/core"
|
|
1
|
+
import { authMutationKeys, getProviderName } from "@better-auth-ui/core"
|
|
2
2
|
import { providerIcons, useAuth, useSignInSocial } from "@better-auth-ui/react"
|
|
3
3
|
import { Button, type ButtonProps, Spinner } from "@heroui/react"
|
|
4
|
+
import { useIsMutating } from "@tanstack/react-query"
|
|
4
5
|
import type { SocialProvider } from "better-auth/social-providers"
|
|
5
|
-
import { useState } from "react"
|
|
6
6
|
|
|
7
7
|
export type ProviderButtonProps = {
|
|
8
8
|
provider: SocialProvider
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} & Omit<ButtonProps, "children" | "onPress" | "isPending">
|
|
9
|
+
display?: "full" | "name" | "icon"
|
|
10
|
+
} & Omit<ButtonProps, "children" | "onPress" | "isPending" | "isDisabled">
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* Social provider sign-in button.
|
|
15
14
|
*
|
|
16
|
-
* @param provider -
|
|
17
|
-
* @param
|
|
18
|
-
* @param isDisabled - External disabled state (e.g. a sibling provider is mid-redirect).
|
|
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).
|
|
19
17
|
*/
|
|
20
18
|
export function ProviderButton({
|
|
21
19
|
provider,
|
|
22
|
-
|
|
23
|
-
isDisabled,
|
|
20
|
+
display = "full",
|
|
24
21
|
variant = "tertiary",
|
|
25
22
|
...props
|
|
26
23
|
}: ProviderButtonProps) {
|
|
27
|
-
const { baseURL, localization, redirectTo } = useAuth()
|
|
24
|
+
const { authClient, baseURL, localization, redirectTo } = useAuth()
|
|
28
25
|
|
|
29
26
|
const callbackURL = `${baseURL}${redirectTo}`
|
|
30
27
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const { mutate: signInSocial, isPending } = useSignInSocial({
|
|
34
|
-
onSuccess: () => {
|
|
35
|
-
setRedirecting(true)
|
|
36
|
-
|
|
37
|
-
setTimeout(() => {
|
|
38
|
-
setRedirecting(false)
|
|
39
|
-
}, 5000)
|
|
40
|
-
}
|
|
41
|
-
})
|
|
28
|
+
const { mutate: signInSocial, isPending: signInSocialPending } =
|
|
29
|
+
useSignInSocial(authClient)
|
|
42
30
|
|
|
43
31
|
const ProviderIcon = providerIcons[provider]
|
|
44
32
|
|
|
45
|
-
const
|
|
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
|
|
46
40
|
|
|
47
41
|
return (
|
|
48
42
|
<Button
|
|
49
43
|
variant={variant}
|
|
50
|
-
|
|
51
|
-
isPending={pending}
|
|
44
|
+
isPending={isPending}
|
|
52
45
|
onPress={() => signInSocial({ provider, callbackURL })}
|
|
53
46
|
{...props}
|
|
47
|
+
aria-label={getProviderName(provider)}
|
|
54
48
|
>
|
|
55
|
-
{
|
|
49
|
+
{signInSocialPending ? (
|
|
50
|
+
<Spinner color="current" size="sm" />
|
|
51
|
+
) : (
|
|
52
|
+
<ProviderIcon />
|
|
53
|
+
)}
|
|
56
54
|
|
|
57
|
-
{
|
|
55
|
+
{display === "full"
|
|
58
56
|
? localization.auth.continueWith.replace(
|
|
59
57
|
"{{provider}}",
|
|
60
58
|
getProviderName(provider)
|
|
61
59
|
)
|
|
62
|
-
:
|
|
60
|
+
: display === "name"
|
|
63
61
|
? getProviderName(provider)
|
|
64
62
|
: null}
|
|
65
63
|
</Button>
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import { useAuth } from "@better-auth-ui/react"
|
|
2
2
|
import { cn } from "@heroui/react"
|
|
3
3
|
import { useMemo } from "react"
|
|
4
|
-
|
|
5
4
|
import { ProviderButton } from "./provider-button"
|
|
6
|
-
|
|
7
5
|
export type ProviderButtonsProps = {
|
|
8
|
-
isPending?: boolean
|
|
9
6
|
socialLayout?: SocialLayout
|
|
10
7
|
}
|
|
11
8
|
|
|
12
9
|
export type SocialLayout = "auto" | "horizontal" | "vertical" | "grid"
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
|
-
* Render social provider sign-in buttons. Each button owns its own sign-in
|
|
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.
|
|
16
14
|
*
|
|
17
|
-
* @param isPending - External pending state (e.g. parent form submitting) that disables all buttons.
|
|
18
15
|
* @param socialLayout - Preferred layout for the buttons; `"auto"` picks `"horizontal"` when there are four or more providers, otherwise `"vertical"`.
|
|
19
16
|
* @returns The JSX element that renders the configured social provider buttons.
|
|
20
17
|
*/
|
|
21
18
|
export function ProviderButtons({
|
|
22
|
-
isPending,
|
|
23
19
|
socialLayout = "auto"
|
|
24
20
|
}: ProviderButtonsProps) {
|
|
25
21
|
const { socialProviders } = useAuth()
|
|
@@ -49,13 +45,12 @@ export function ProviderButtons({
|
|
|
49
45
|
<ProviderButton
|
|
50
46
|
key={provider}
|
|
51
47
|
provider={provider}
|
|
52
|
-
|
|
53
|
-
label={
|
|
48
|
+
display={
|
|
54
49
|
resolvedSocialLayout === "vertical"
|
|
55
|
-
? "
|
|
50
|
+
? "full"
|
|
56
51
|
: resolvedSocialLayout === "grid"
|
|
57
|
-
? "
|
|
58
|
-
: "
|
|
52
|
+
? "name"
|
|
53
|
+
: "icon"
|
|
59
54
|
}
|
|
60
55
|
className={cn(
|
|
61
56
|
"w-full",
|
|
@@ -32,10 +32,16 @@ export function ResetPassword({
|
|
|
32
32
|
variant,
|
|
33
33
|
...props
|
|
34
34
|
}: ResetPasswordProps & Omit<CardProps, "children">) {
|
|
35
|
-
const {
|
|
36
|
-
|
|
35
|
+
const {
|
|
36
|
+
authClient,
|
|
37
|
+
basePaths,
|
|
38
|
+
emailAndPassword,
|
|
39
|
+
localization,
|
|
40
|
+
viewPaths,
|
|
41
|
+
navigate
|
|
42
|
+
} = useAuth()
|
|
37
43
|
|
|
38
|
-
const { mutate: resetPassword, isPending } = useResetPassword({
|
|
44
|
+
const { mutate: resetPassword, isPending } = useResetPassword(authClient, {
|
|
39
45
|
onSuccess: () => {
|
|
40
46
|
toast.success(localization.auth.passwordResetSuccess)
|
|
41
47
|
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` })
|
|
@@ -87,7 +93,7 @@ export function ResetPassword({
|
|
|
87
93
|
|
|
88
94
|
return (
|
|
89
95
|
<Card
|
|
90
|
-
className={cn("w-full max-w-sm
|
|
96
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
91
97
|
variant={variant}
|
|
92
98
|
{...props}
|
|
93
99
|
>
|
|
@@ -97,7 +103,7 @@ export function ResetPassword({
|
|
|
97
103
|
</Card.Title>
|
|
98
104
|
</Card.Header>
|
|
99
105
|
|
|
100
|
-
<Card.Content>
|
|
106
|
+
<Card.Content className="gap-4">
|
|
101
107
|
<Form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
|
102
108
|
<TextField
|
|
103
109
|
minLength={emailAndPassword?.minPasswordLength}
|
|
@@ -193,12 +199,12 @@ export function ResetPassword({
|
|
|
193
199
|
</Form>
|
|
194
200
|
</Card.Content>
|
|
195
201
|
|
|
196
|
-
<Card.Footer className="flex-col">
|
|
202
|
+
<Card.Footer className="flex-col gap-3">
|
|
197
203
|
<Description className="text-sm">
|
|
198
204
|
{localization.auth.rememberYourPassword}{" "}
|
|
199
205
|
<Link
|
|
200
206
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
201
|
-
className="text-accent
|
|
207
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
202
208
|
>
|
|
203
209
|
{localization.auth.signIn}
|
|
204
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,10 +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 } =
|
|
17
|
+
const { mutate: updateUser, isPending: updatePending } =
|
|
18
|
+
useUpdateUser(authClient)
|
|
18
19
|
|
|
19
20
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
20
21
|
const [isUploading, setIsUploading] = useState(false)
|
|
@@ -35,10 +35,10 @@ export function ChangeEmail({
|
|
|
35
35
|
variant,
|
|
36
36
|
...props
|
|
37
37
|
}: ChangeEmailProps & Omit<CardProps, "children">) {
|
|
38
|
-
const { localization, baseURL, viewPaths } = useAuth()
|
|
39
|
-
const { data: session } = useSession()
|
|
38
|
+
const { authClient, localization, baseURL, viewPaths } = useAuth()
|
|
39
|
+
const { data: session } = useSession(authClient)
|
|
40
40
|
|
|
41
|
-
const { mutate: changeEmail, isPending } = useChangeEmail({
|
|
41
|
+
const { mutate: changeEmail, isPending } = useChangeEmail(authClient, {
|
|
42
42
|
onSuccess: () => toast.success(localization.settings.changeEmailSuccess)
|
|
43
43
|
})
|
|
44
44
|
|
|
@@ -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
|
+
}
|
|
@@ -46,12 +46,15 @@ export type ActiveSessionProps = {
|
|
|
46
46
|
* @returns A JSX element containing the active session row
|
|
47
47
|
*/
|
|
48
48
|
export function ActiveSession({ activeSession }: ActiveSessionProps) {
|
|
49
|
-
const { basePaths, localization, viewPaths, navigate } = useAuth()
|
|
50
|
-
const { data: session } = useSession({ refetchOnMount: false })
|
|
49
|
+
const { authClient, basePaths, localization, viewPaths, navigate } = useAuth()
|
|
50
|
+
const { data: session } = useSession(authClient, { refetchOnMount: false })
|
|
51
51
|
|
|
52
|
-
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession(
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession(
|
|
53
|
+
authClient,
|
|
54
|
+
{
|
|
55
|
+
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
56
|
+
}
|
|
57
|
+
)
|
|
55
58
|
|
|
56
59
|
const isCurrentSession = activeSession.token === session?.session.token
|
|
57
60
|
const ua = Bowser.parse(activeSession.userAgent || "")
|
|
@@ -20,10 +20,10 @@ export function ActiveSessions({
|
|
|
20
20
|
variant,
|
|
21
21
|
...props
|
|
22
22
|
}: ActiveSessionsProps & Omit<CardProps, "children">) {
|
|
23
|
-
const { localization } = useAuth()
|
|
24
|
-
const { data: session } = useSession()
|
|
23
|
+
const { authClient, localization } = useAuth()
|
|
24
|
+
const { data: session } = useSession(authClient)
|
|
25
25
|
|
|
26
|
-
const { data: sessions, isPending } = useListSessions()
|
|
26
|
+
const { data: sessions, isPending } = useListSessions(authClient)
|
|
27
27
|
|
|
28
28
|
const activeSessions = sessions?.toSorted((activeSession) =>
|
|
29
29
|
activeSession.id === session?.session.id ? -1 : 1
|