@better-auth-ui/heroui 1.6.3 → 1.6.5
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 +1 -1
- 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 +70 -48
- 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 +66 -85
- package/dist/components/auth/sign-out.js +11 -11
- package/dist/components/auth/sign-up.js +128 -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 +148 -0
- package/dist/components/auth/username/username-field.d.ts +8 -0
- package/dist/components/auth/username/username-field.js +53 -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 +1 -1
- 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 +42 -17
- 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 +50 -61
- package/src/components/auth/sign-out.tsx +2 -2
- package/src/components/auth/sign-up.tsx +119 -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 +299 -0
- package/src/components/auth/username/username-field.tsx +124 -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
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authMutationKeys } from "@better-auth-ui/core"
|
|
2
|
+
import {
|
|
3
|
+
type MagicLinkAuthClient,
|
|
4
|
+
useAuth,
|
|
5
|
+
useAuthPlugin,
|
|
6
|
+
useSignInMagicLink
|
|
7
|
+
} from "@better-auth-ui/react"
|
|
2
8
|
import {
|
|
3
9
|
Button,
|
|
4
10
|
Card,
|
|
@@ -14,12 +20,12 @@ import {
|
|
|
14
20
|
TextField,
|
|
15
21
|
toast
|
|
16
22
|
} from "@heroui/react"
|
|
23
|
+
import { useIsMutating } from "@tanstack/react-query"
|
|
17
24
|
import { type SyntheticEvent, useState } from "react"
|
|
18
25
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { ProviderButtons, type SocialLayout } from "./provider-buttons"
|
|
26
|
+
import { magicLinkPlugin } from "../../../lib/auth/magic-link-plugin"
|
|
27
|
+
import { FieldSeparator } from "../field-separator"
|
|
28
|
+
import { ProviderButtons, type SocialLayout } from "../provider-buttons"
|
|
23
29
|
|
|
24
30
|
export type MagicLinkProps = {
|
|
25
31
|
className?: string
|
|
@@ -29,12 +35,11 @@ export type MagicLinkProps = {
|
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
|
-
*
|
|
38
|
+
* Magic-link sign-in form.
|
|
33
39
|
*
|
|
34
|
-
* @param
|
|
35
|
-
* @param
|
|
36
|
-
* @param
|
|
37
|
-
* @returns The magic-link sign-in UI as a JSX element
|
|
40
|
+
* @param socialLayout - Provider button layout.
|
|
41
|
+
* @param socialPosition - `"top"` or `"bottom"`. Defaults to `"bottom"`.
|
|
42
|
+
* @param variant - Card variant.
|
|
38
43
|
*/
|
|
39
44
|
export function MagicLink({
|
|
40
45
|
className,
|
|
@@ -44,26 +49,35 @@ export function MagicLink({
|
|
|
44
49
|
...props
|
|
45
50
|
}: MagicLinkProps & Omit<CardProps, "children">) {
|
|
46
51
|
const {
|
|
52
|
+
authClient,
|
|
47
53
|
basePaths,
|
|
48
54
|
baseURL,
|
|
55
|
+
emailAndPassword,
|
|
49
56
|
localization,
|
|
50
|
-
|
|
57
|
+
plugins,
|
|
51
58
|
redirectTo,
|
|
52
59
|
socialProviders,
|
|
53
60
|
viewPaths
|
|
54
61
|
} = useAuth()
|
|
62
|
+
const { localization: magicLinkLocalization } = useAuthPlugin(magicLinkPlugin)
|
|
55
63
|
|
|
56
64
|
const [email, setEmail] = useState("")
|
|
57
65
|
|
|
58
|
-
const { mutate: signInMagicLink, isPending:
|
|
59
|
-
useSignInMagicLink({
|
|
66
|
+
const { mutate: signInMagicLink, isPending: signInMagicLinkPending } =
|
|
67
|
+
useSignInMagicLink(authClient as MagicLinkAuthClient, {
|
|
60
68
|
onSuccess: () => {
|
|
61
69
|
setEmail("")
|
|
62
|
-
toast.success(
|
|
70
|
+
toast.success(magicLinkLocalization.magicLinkSent)
|
|
63
71
|
}
|
|
64
72
|
})
|
|
65
73
|
|
|
66
|
-
const
|
|
74
|
+
const signInMutating = useIsMutating({
|
|
75
|
+
mutationKey: authMutationKeys.signIn.all
|
|
76
|
+
})
|
|
77
|
+
const signUpMutating = useIsMutating({
|
|
78
|
+
mutationKey: authMutationKeys.signUp.all
|
|
79
|
+
})
|
|
80
|
+
const isPending = signInMutating + signUpMutating > 0
|
|
67
81
|
|
|
68
82
|
const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
|
|
69
83
|
e.preventDefault()
|
|
@@ -74,7 +88,7 @@ export function MagicLink({
|
|
|
74
88
|
|
|
75
89
|
return (
|
|
76
90
|
<Card
|
|
77
|
-
className={cn("w-full max-w-sm
|
|
91
|
+
className={cn("w-full max-w-sm gap-4 md:p-6", className)}
|
|
78
92
|
variant={variant}
|
|
79
93
|
{...props}
|
|
80
94
|
>
|
|
@@ -88,10 +102,7 @@ export function MagicLink({
|
|
|
88
102
|
{socialPosition === "top" && (
|
|
89
103
|
<>
|
|
90
104
|
{!!socialProviders?.length && (
|
|
91
|
-
<ProviderButtons
|
|
92
|
-
socialLayout={socialLayout}
|
|
93
|
-
isPending={isPending}
|
|
94
|
-
/>
|
|
105
|
+
<ProviderButtons socialLayout={socialLayout} />
|
|
95
106
|
)}
|
|
96
107
|
|
|
97
108
|
{showSeparator && (
|
|
@@ -122,14 +133,19 @@ export function MagicLink({
|
|
|
122
133
|
|
|
123
134
|
<div className="flex flex-col gap-3">
|
|
124
135
|
<Button type="submit" className="w-full" isPending={isPending}>
|
|
125
|
-
{
|
|
136
|
+
{signInMagicLinkPending && <Spinner color="current" size="sm" />}
|
|
126
137
|
|
|
127
|
-
{
|
|
138
|
+
{magicLinkLocalization.sendMagicLink}
|
|
128
139
|
</Button>
|
|
129
140
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
{plugins.flatMap((plugin) =>
|
|
142
|
+
(plugin.authButtons ?? []).map((AuthButton, index) => (
|
|
143
|
+
<AuthButton
|
|
144
|
+
key={`${plugin.id}-${index.toString()}`}
|
|
145
|
+
view="magicLink"
|
|
146
|
+
/>
|
|
147
|
+
))
|
|
148
|
+
)}
|
|
133
149
|
</div>
|
|
134
150
|
</Form>
|
|
135
151
|
|
|
@@ -140,26 +156,25 @@ export function MagicLink({
|
|
|
140
156
|
)}
|
|
141
157
|
|
|
142
158
|
{!!socialProviders?.length && (
|
|
143
|
-
<ProviderButtons
|
|
144
|
-
socialLayout={socialLayout}
|
|
145
|
-
isPending={isPending}
|
|
146
|
-
/>
|
|
159
|
+
<ProviderButtons socialLayout={socialLayout} />
|
|
147
160
|
)}
|
|
148
161
|
</>
|
|
149
162
|
)}
|
|
150
163
|
</Card.Content>
|
|
151
164
|
|
|
152
|
-
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
{emailAndPassword?.enabled && (
|
|
166
|
+
<Card.Footer className="flex-col gap-3">
|
|
167
|
+
<Description className="text-sm">
|
|
168
|
+
{localization.auth.needToCreateAnAccount}{" "}
|
|
169
|
+
<Link
|
|
170
|
+
href={`${basePaths.auth}/${viewPaths.auth.signUp}`}
|
|
171
|
+
className="text-accent no-underline hover:underline decoration-accent-hover"
|
|
172
|
+
>
|
|
173
|
+
{localization.auth.signUp}
|
|
174
|
+
</Link>
|
|
175
|
+
</Description>
|
|
176
|
+
</Card.Footer>
|
|
177
|
+
)}
|
|
163
178
|
</Card>
|
|
164
179
|
)
|
|
165
180
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ListDeviceSession,
|
|
3
|
+
type MultiSessionAuthClient,
|
|
2
4
|
useAuth,
|
|
5
|
+
useAuthPlugin,
|
|
3
6
|
useRevokeMultiSession,
|
|
4
7
|
useSetActiveSession,
|
|
5
8
|
useUser
|
|
@@ -10,17 +13,13 @@ import {
|
|
|
10
13
|
Ellipsis
|
|
11
14
|
} from "@gravity-ui/icons"
|
|
12
15
|
import { Button, Dropdown, Spinner, toast } from "@heroui/react"
|
|
13
|
-
import type { Session, User } from "better-auth"
|
|
14
16
|
|
|
15
|
-
import {
|
|
17
|
+
import { multiSessionPlugin } from "../../../lib/auth/multi-session-plugin"
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
session: Session
|
|
19
|
-
user: User
|
|
20
|
-
}
|
|
19
|
+
import { UserView } from "../user/user-view"
|
|
21
20
|
|
|
22
21
|
export type ManageAccountProps = {
|
|
23
|
-
deviceSession?:
|
|
22
|
+
deviceSession?: ListDeviceSession | null
|
|
24
23
|
isPending?: boolean
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -37,14 +36,18 @@ export function ManageAccount({
|
|
|
37
36
|
deviceSession,
|
|
38
37
|
isPending
|
|
39
38
|
}: ManageAccountProps) {
|
|
40
|
-
const { localization } = useAuth()
|
|
41
|
-
const {
|
|
39
|
+
const { authClient, localization } = useAuth()
|
|
40
|
+
const { localization: multiSessionLocalization } =
|
|
41
|
+
useAuthPlugin(multiSessionPlugin)
|
|
42
|
+
const { data: user } = useUser(authClient)
|
|
42
43
|
|
|
43
44
|
const { mutate: setActiveSession, isPending: isSwitching } =
|
|
44
|
-
useSetActiveSession(
|
|
45
|
+
useSetActiveSession(authClient as MultiSessionAuthClient, {
|
|
46
|
+
onSuccess: () => window.scrollTo({ top: 0 })
|
|
47
|
+
})
|
|
45
48
|
|
|
46
49
|
const { mutate: revokeSession, isPending: isRevoking } =
|
|
47
|
-
useRevokeMultiSession({
|
|
50
|
+
useRevokeMultiSession(authClient as MultiSessionAuthClient, {
|
|
48
51
|
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
49
52
|
})
|
|
50
53
|
|
|
@@ -89,7 +92,7 @@ export function ManageAccount({
|
|
|
89
92
|
<Dropdown.Popover>
|
|
90
93
|
<Dropdown.Menu>
|
|
91
94
|
<Dropdown.Item
|
|
92
|
-
textValue={
|
|
95
|
+
textValue={multiSessionLocalization.switchAccount}
|
|
93
96
|
onAction={() =>
|
|
94
97
|
setActiveSession({
|
|
95
98
|
sessionToken: deviceSession.session.token
|
|
@@ -97,7 +100,7 @@ export function ManageAccount({
|
|
|
97
100
|
}
|
|
98
101
|
>
|
|
99
102
|
<ArrowRightArrowLeft className="text-muted" />
|
|
100
|
-
{
|
|
103
|
+
{multiSessionLocalization.switchAccount}
|
|
101
104
|
</Dropdown.Item>
|
|
102
105
|
|
|
103
106
|
<Dropdown.Item
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type MultiSessionAuthClient,
|
|
2
3
|
useAuth,
|
|
4
|
+
useAuthPlugin,
|
|
3
5
|
useListDeviceSessions,
|
|
4
6
|
useSession
|
|
5
7
|
} from "@better-auth-ui/react"
|
|
6
8
|
import { Card, type CardProps, cn } from "@heroui/react"
|
|
9
|
+
|
|
10
|
+
import { multiSessionPlugin } from "../../../lib/auth/multi-session-plugin"
|
|
11
|
+
|
|
7
12
|
import { ManageAccount } from "./manage-account"
|
|
8
13
|
|
|
9
14
|
export type ManageAccountsProps = {
|
|
@@ -24,10 +29,14 @@ export function ManageAccounts({
|
|
|
24
29
|
variant,
|
|
25
30
|
...props
|
|
26
31
|
}: ManageAccountsProps & Omit<CardProps, "children">) {
|
|
27
|
-
const {
|
|
28
|
-
const {
|
|
32
|
+
const { authClient } = useAuth()
|
|
33
|
+
const { localization: multiSessionLocalization } =
|
|
34
|
+
useAuthPlugin(multiSessionPlugin)
|
|
35
|
+
const { data: session } = useSession(authClient)
|
|
29
36
|
|
|
30
|
-
const { data: deviceSessions, isPending } = useListDeviceSessions(
|
|
37
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions(
|
|
38
|
+
authClient as MultiSessionAuthClient
|
|
39
|
+
)
|
|
31
40
|
|
|
32
41
|
const otherSessions = deviceSessions?.filter(
|
|
33
42
|
(deviceSession) => deviceSession.session.id !== session?.session.id
|
|
@@ -35,7 +44,7 @@ export function ManageAccounts({
|
|
|
35
44
|
|
|
36
45
|
const allRows = [
|
|
37
46
|
{
|
|
38
|
-
key: "current",
|
|
47
|
+
key: session?.session.id ?? "current",
|
|
39
48
|
deviceSession: !isPending ? session : null,
|
|
40
49
|
isPending
|
|
41
50
|
},
|
|
@@ -49,7 +58,7 @@ export function ManageAccounts({
|
|
|
49
58
|
return (
|
|
50
59
|
<div>
|
|
51
60
|
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
52
|
-
{
|
|
61
|
+
{multiSessionLocalization.manageAccounts}
|
|
53
62
|
</h2>
|
|
54
63
|
|
|
55
64
|
<Card className={cn(className)} variant={variant} {...props}>
|
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type MultiSessionAuthClient,
|
|
2
3
|
useAuth,
|
|
4
|
+
useAuthPlugin,
|
|
3
5
|
useListDeviceSessions,
|
|
4
6
|
useSession
|
|
5
7
|
} from "@better-auth-ui/react"
|
|
6
8
|
import { Check, CirclePlus } from "@gravity-ui/icons"
|
|
7
9
|
import { Dropdown, Label, Separator } from "@heroui/react"
|
|
8
10
|
|
|
9
|
-
import {
|
|
10
|
-
import { UserView } from "
|
|
11
|
+
import { multiSessionPlugin } from "../../../lib/auth/multi-session-plugin"
|
|
12
|
+
import { UserView } from "../user/user-view"
|
|
13
|
+
import { SwitchAccountSubmenuItem } from "./switch-account-submenu-item"
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
|
-
* Render the
|
|
16
|
+
* Render the submenu content for switching between multiple authenticated sessions.
|
|
14
17
|
*
|
|
15
18
|
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
16
19
|
* and provides an option to add a new account. This component should be rendered inside a
|
|
17
20
|
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
18
21
|
*
|
|
19
|
-
* @returns The switch account
|
|
22
|
+
* @returns The switch account submenu content as a JSX element
|
|
20
23
|
*/
|
|
21
|
-
export function
|
|
22
|
-
const { basePaths, viewPaths
|
|
23
|
-
const {
|
|
24
|
-
|
|
24
|
+
export function SwitchAccountSubmenuContent() {
|
|
25
|
+
const { authClient, basePaths, viewPaths } = useAuth()
|
|
26
|
+
const { localization: multiSessionLocalization } =
|
|
27
|
+
useAuthPlugin(multiSessionPlugin)
|
|
28
|
+
const { data: session } = useSession(authClient)
|
|
29
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions(
|
|
30
|
+
authClient as MultiSessionAuthClient
|
|
31
|
+
)
|
|
25
32
|
|
|
26
33
|
return (
|
|
27
34
|
<Dropdown.Popover className="min-w-40 md:min-w-56 max-w-[48svw]">
|
|
@@ -37,7 +44,7 @@ export function SwitchAccountMenu() {
|
|
|
37
44
|
(deviceSession) => deviceSession.session.id !== session?.session?.id
|
|
38
45
|
)
|
|
39
46
|
.map((deviceSession) => (
|
|
40
|
-
<
|
|
47
|
+
<SwitchAccountSubmenuItem
|
|
41
48
|
key={deviceSession.session.id}
|
|
42
49
|
deviceSession={deviceSession}
|
|
43
50
|
/>
|
|
@@ -46,12 +53,12 @@ export function SwitchAccountMenu() {
|
|
|
46
53
|
<Separator />
|
|
47
54
|
|
|
48
55
|
<Dropdown.Item
|
|
49
|
-
textValue={
|
|
56
|
+
textValue={multiSessionLocalization.addAccount}
|
|
50
57
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
51
58
|
>
|
|
52
59
|
<CirclePlus className="text-muted" />
|
|
53
60
|
|
|
54
|
-
<Label>{
|
|
61
|
+
<Label>{multiSessionLocalization.addAccount}</Label>
|
|
55
62
|
</Dropdown.Item>
|
|
56
63
|
</Dropdown.Menu>
|
|
57
64
|
</Dropdown.Popover>
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type MultiSessionAuthClient,
|
|
3
|
+
useAuth,
|
|
4
|
+
useSetActiveSession
|
|
5
|
+
} from "@better-auth-ui/react"
|
|
2
6
|
import { Dropdown, Spinner } from "@heroui/react"
|
|
3
7
|
import type { Session, User } from "better-auth"
|
|
4
|
-
import { UserView } from "
|
|
8
|
+
import { UserView } from "../user/user-view"
|
|
5
9
|
|
|
6
10
|
type DeviceSession = {
|
|
7
11
|
session: Session
|
|
8
12
|
user: User
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
export type
|
|
15
|
+
export type SwitchAccountSubmenuItemProps = {
|
|
12
16
|
deviceSession: DeviceSession
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -18,8 +22,16 @@ export type SwitchAccountItemProps = {
|
|
|
18
22
|
* @param deviceSession - The device session to display and switch to when pressed
|
|
19
23
|
* @returns The switch account dropdown item as a JSX element
|
|
20
24
|
*/
|
|
21
|
-
export function
|
|
22
|
-
|
|
25
|
+
export function SwitchAccountSubmenuItem({
|
|
26
|
+
deviceSession
|
|
27
|
+
}: SwitchAccountSubmenuItemProps) {
|
|
28
|
+
const { authClient } = useAuth()
|
|
29
|
+
const { mutate: setActiveSession, isPending } = useSetActiveSession(
|
|
30
|
+
authClient as MultiSessionAuthClient,
|
|
31
|
+
{
|
|
32
|
+
onSuccess: () => window.scrollTo({ top: 0 })
|
|
33
|
+
}
|
|
34
|
+
)
|
|
23
35
|
|
|
24
36
|
return (
|
|
25
37
|
<Dropdown.Item
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useAuth, useAuthPlugin, useSession } from "@better-auth-ui/react"
|
|
2
|
+
import { Persons } from "@gravity-ui/icons"
|
|
3
|
+
import { Dropdown, Label } from "@heroui/react"
|
|
4
|
+
|
|
5
|
+
import { multiSessionPlugin } from "../../../lib/auth/multi-session-plugin"
|
|
6
|
+
|
|
7
|
+
import { SwitchAccountSubmenuContent } from "./switch-account-submenu-content"
|
|
8
|
+
|
|
9
|
+
export type SwitchAccountSubmenuProps = {
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render a dropdown submenu for switching between multiple authenticated sessions.
|
|
15
|
+
*
|
|
16
|
+
* This component renders as a submenu trigger that opens the switch account menu.
|
|
17
|
+
* It should be rendered inside a Dropdown.Menu.
|
|
18
|
+
*
|
|
19
|
+
* @param className - Optional additional CSS class names
|
|
20
|
+
* @returns The switch account submenu as a JSX element
|
|
21
|
+
*/
|
|
22
|
+
export function SwitchAccountSubmenu({ className }: SwitchAccountSubmenuProps) {
|
|
23
|
+
const { authClient } = useAuth()
|
|
24
|
+
const { data: session } = useSession(authClient)
|
|
25
|
+
const { localization: multiSessionLocalization } =
|
|
26
|
+
useAuthPlugin(multiSessionPlugin)
|
|
27
|
+
|
|
28
|
+
if (!session) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Dropdown.SubmenuTrigger>
|
|
34
|
+
<Dropdown.Item
|
|
35
|
+
className={className}
|
|
36
|
+
textValue={multiSessionLocalization.switchAccount}
|
|
37
|
+
>
|
|
38
|
+
<Persons className="text-muted" />
|
|
39
|
+
|
|
40
|
+
<Label>{multiSessionLocalization.switchAccount}</Label>
|
|
41
|
+
|
|
42
|
+
<Dropdown.SubmenuIndicator />
|
|
43
|
+
</Dropdown.Item>
|
|
44
|
+
|
|
45
|
+
<Dropdown.Popover className="min-w-40 md:min-w-56 max-w-[48svw]">
|
|
46
|
+
<SwitchAccountSubmenuContent />
|
|
47
|
+
</Dropdown.Popover>
|
|
48
|
+
</Dropdown.SubmenuTrigger>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type AuthView, authMutationKeys } from "@better-auth-ui/core"
|
|
2
|
+
import {
|
|
3
|
+
type PasskeyAuthClient,
|
|
4
|
+
useAuth,
|
|
5
|
+
useAuthPlugin,
|
|
6
|
+
useSignInPasskey
|
|
7
|
+
} from "@better-auth-ui/react"
|
|
8
|
+
import { Fingerprint } from "@gravity-ui/icons"
|
|
9
|
+
import { Button, Spinner } from "@heroui/react"
|
|
10
|
+
import { useIsMutating } from "@tanstack/react-query"
|
|
11
|
+
|
|
12
|
+
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
13
|
+
|
|
14
|
+
export type PasskeyButtonProps = {
|
|
15
|
+
/** @remarks `AuthView` */
|
|
16
|
+
view?: AuthView
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function PasskeyButton({ view }: PasskeyButtonProps) {
|
|
20
|
+
const { authClient, localization, redirectTo, navigate } = useAuth()
|
|
21
|
+
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
22
|
+
|
|
23
|
+
const { mutate: signInPasskey, isPending: passkeyPending } = useSignInPasskey(
|
|
24
|
+
authClient as PasskeyAuthClient,
|
|
25
|
+
{
|
|
26
|
+
onSuccess: () => navigate({ to: redirectTo })
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const signInMutating = useIsMutating({
|
|
31
|
+
mutationKey: authMutationKeys.signIn.all
|
|
32
|
+
})
|
|
33
|
+
const signUpMutating = useIsMutating({
|
|
34
|
+
mutationKey: authMutationKeys.signUp.all
|
|
35
|
+
})
|
|
36
|
+
const isPending = signInMutating + signUpMutating > 0
|
|
37
|
+
|
|
38
|
+
// Passkey sign-in isn't relevant on the sign-up flow.
|
|
39
|
+
if (view === "signUp") return null
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Button
|
|
43
|
+
className="w-full"
|
|
44
|
+
variant="tertiary"
|
|
45
|
+
isDisabled={isPending}
|
|
46
|
+
isPending={passkeyPending}
|
|
47
|
+
onPress={() => signInPasskey()}
|
|
48
|
+
>
|
|
49
|
+
{passkeyPending ? <Spinner color="current" size="sm" /> : <Fingerprint />}
|
|
50
|
+
{localization.auth.continueWith.replace(
|
|
51
|
+
"{{provider}}",
|
|
52
|
+
passkeyLocalization.passkey
|
|
53
|
+
)}
|
|
54
|
+
</Button>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type PasskeyAuthClient,
|
|
3
|
+
useAuth,
|
|
4
|
+
useAuthPlugin,
|
|
5
|
+
useDeletePasskey
|
|
6
|
+
} from "@better-auth-ui/react"
|
|
2
7
|
import { Fingerprint, Xmark } from "@gravity-ui/icons"
|
|
3
8
|
import { Button, Spinner } from "@heroui/react"
|
|
4
9
|
|
|
10
|
+
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
11
|
+
|
|
5
12
|
export type PasskeyProps = {
|
|
6
13
|
passkey: {
|
|
7
14
|
id: string
|
|
@@ -11,9 +18,14 @@ export type PasskeyProps = {
|
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
export function Passkey({ passkey }: PasskeyProps) {
|
|
14
|
-
const { localization } = useAuth()
|
|
21
|
+
const { authClient, localization } = useAuth()
|
|
22
|
+
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
23
|
+
|
|
24
|
+
const { mutate: deletePasskey, isPending } = useDeletePasskey(
|
|
25
|
+
authClient as PasskeyAuthClient
|
|
26
|
+
)
|
|
15
27
|
|
|
16
|
-
const
|
|
28
|
+
const passkeyName = passkey.name || passkeyLocalization.passkey
|
|
17
29
|
|
|
18
30
|
return (
|
|
19
31
|
<div className="flex items-center gap-3">
|
|
@@ -22,9 +34,7 @@ export function Passkey({ passkey }: PasskeyProps) {
|
|
|
22
34
|
</div>
|
|
23
35
|
|
|
24
36
|
<div className="flex flex-col min-w-0">
|
|
25
|
-
<span className="text-sm font-medium leading-tight">
|
|
26
|
-
{passkey.name || localization.auth.passkey}
|
|
27
|
-
</span>
|
|
37
|
+
<span className="text-sm font-medium leading-tight">{passkeyName}</span>
|
|
28
38
|
|
|
29
39
|
<span className="text-xs text-muted">
|
|
30
40
|
{new Date(passkey.createdAt).toLocaleString(undefined, {
|
|
@@ -40,6 +50,10 @@ export function Passkey({ passkey }: PasskeyProps) {
|
|
|
40
50
|
size="sm"
|
|
41
51
|
isPending={isPending}
|
|
42
52
|
onPress={() => deletePasskey({ id: passkey.id })}
|
|
53
|
+
aria-label={passkeyLocalization.deletePasskey.replace(
|
|
54
|
+
"{{name}}",
|
|
55
|
+
passkeyName
|
|
56
|
+
)}
|
|
43
57
|
>
|
|
44
58
|
{isPending ? <Spinner color="current" size="sm" /> : <Xmark />}
|
|
45
59
|
{localization.settings.delete}
|