@better-auth-ui/heroui 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/account/account.d.ts +18 -0
- package/dist/components/account/account.js +24 -0
- package/dist/components/account/settings/account-settings.d.ts +5 -0
- package/dist/components/account/settings/account-settings.js +6 -0
- package/dist/components/account/settings/settings.d.ts +5 -0
- package/dist/components/account/settings/settings.js +5 -0
- package/dist/components/account/settings/user-profile.d.ts +5 -0
- package/dist/components/account/settings/user-profile.js +20 -0
- package/dist/components/auth/auth-provider.d.ts +9 -0
- package/dist/components/auth/auth-provider.js +12 -0
- package/dist/components/auth/auth.d.ts +23 -0
- package/dist/components/auth/auth.js +42 -0
- package/dist/components/auth/field-separator.d.ts +4 -0
- package/dist/components/auth/field-separator.js +5 -0
- package/dist/components/auth/forgot-password.d.ts +15 -0
- package/dist/components/auth/forgot-password.js +28 -0
- package/dist/components/auth/magic-link-button.d.ts +13 -0
- package/dist/components/auth/magic-link-button.js +19 -0
- package/dist/components/auth/magic-link.d.ts +17 -0
- package/dist/components/auth/magic-link.js +43 -0
- package/dist/components/auth/provider-buttons.d.ts +19 -0
- package/dist/components/auth/provider-buttons.js +34 -0
- package/dist/components/auth/reset-password.d.ts +11 -0
- package/dist/components/auth/reset-password.js +58 -0
- package/dist/components/auth/sign-in.d.ts +14 -0
- package/dist/components/auth/sign-in.js +64 -0
- package/dist/components/auth/sign-out.d.ts +9 -0
- package/dist/components/auth/sign-out.js +33 -0
- package/dist/components/auth/sign-up.d.ts +19 -0
- package/dist/components/auth/sign-up.js +71 -0
- package/dist/components/email/email-changed-email.d.ts +3 -0
- package/dist/components/email/email-changed-email.js +23 -0
- package/dist/components/email/email-verification-email.d.ts +3 -0
- package/dist/components/email/email-verification-email.js +23 -0
- package/dist/components/email/magic-link-email.d.ts +3 -0
- package/dist/components/email/magic-link-email.js +23 -0
- package/dist/components/email/new-device-email.d.ts +3 -0
- package/dist/components/email/new-device-email.js +23 -0
- package/dist/components/email/otp-email.d.ts +3 -0
- package/dist/components/email/otp-email.js +22 -0
- package/dist/components/email/password-changed-email.d.ts +3 -0
- package/dist/components/email/password-changed-email.js +23 -0
- package/dist/components/email/reset-password-email.d.ts +3 -0
- package/dist/components/email/reset-password-email.js +23 -0
- package/dist/components/settings/account/account-settings.d.ts +16 -0
- package/dist/components/settings/account/account-settings.js +20 -0
- package/dist/components/settings/account/accounts.d.ts +14 -0
- package/dist/components/settings/account/accounts.js +26 -0
- package/dist/components/settings/account/appearance.d.ts +16 -0
- package/dist/components/settings/account/appearance.js +28 -0
- package/dist/components/settings/account/change-avatar.d.ts +4 -0
- package/dist/components/settings/account/change-avatar.js +56 -0
- package/dist/components/settings/account/change-email.d.ts +15 -0
- package/dist/components/settings/account/change-email.js +29 -0
- package/dist/components/settings/account/manage-account.d.ts +19 -0
- package/dist/components/settings/account/manage-account.js +32 -0
- package/dist/components/settings/account/manage-accounts.d.ts +14 -0
- package/dist/components/settings/account/manage-accounts.js +37 -0
- package/dist/components/settings/account/theme-selector.d.ts +16 -0
- package/dist/components/settings/account/theme-selector.js +33 -0
- package/dist/components/settings/account/theme-settings.d.ts +16 -0
- package/dist/components/settings/account/theme-settings.js +31 -0
- package/dist/components/settings/account/user-profile.d.ts +11 -0
- package/dist/components/settings/account/user-profile.js +23 -0
- package/dist/components/settings/account-settings.d.ts +5 -0
- package/dist/components/settings/account-settings.js +6 -0
- package/dist/components/settings/account.d.ts +18 -0
- package/dist/components/settings/account.js +24 -0
- package/dist/components/settings/security/active-session.d.ts +14 -0
- package/dist/components/settings/security/active-session.js +53 -0
- package/dist/components/settings/security/active-sessions.d.ts +14 -0
- package/dist/components/settings/security/active-sessions.js +28 -0
- package/dist/components/settings/security/change-password.d.ts +15 -0
- package/dist/components/settings/security/change-password.js +79 -0
- package/dist/components/settings/security/connected-account.d.ts +16 -0
- package/dist/components/settings/security/connected-account.js +46 -0
- package/dist/components/settings/security/connected-accounts.d.ts +15 -0
- package/dist/components/settings/security/connected-accounts.js +31 -0
- package/dist/components/settings/security/danger-zone.d.ts +11 -0
- package/dist/components/settings/security/danger-zone.js +12 -0
- package/dist/components/settings/security/delete-user.d.ts +9 -0
- package/dist/components/settings/security/delete-user.js +47 -0
- package/dist/components/settings/security/linked-account.d.ts +16 -0
- package/dist/components/settings/security/linked-account.js +48 -0
- package/dist/components/settings/security/linked-accounts.d.ts +15 -0
- package/dist/components/settings/security/linked-accounts.js +42 -0
- package/dist/components/settings/security/security-settings.d.ts +17 -0
- package/dist/components/settings/security/security-settings.js +21 -0
- package/dist/components/settings/security/sessions.d.ts +14 -0
- package/dist/components/settings/security/sessions.js +40 -0
- package/dist/components/settings/settings/account-settings.d.ts +5 -0
- package/dist/components/settings/settings/account-settings.js +6 -0
- package/dist/components/settings/settings/user-profile.d.ts +5 -0
- package/dist/components/settings/settings/user-profile.js +20 -0
- package/dist/components/settings/settings.d.ts +24 -0
- package/dist/components/settings/settings.js +28 -0
- package/dist/components/settings/user-profile.d.ts +5 -0
- package/dist/components/settings/user-profile.js +20 -0
- package/dist/components/user/switch-account-item.d.ts +16 -0
- package/dist/components/user/switch-account-item.js +16 -0
- package/dist/components/user/switch-account-menu.d.ts +10 -0
- package/dist/components/user/switch-account-menu.js +29 -0
- package/dist/components/user/switch-account-popover.d.ts +10 -0
- package/dist/components/user/switch-account-popover.js +25 -0
- package/dist/components/user/switch-account-submenu.d.ts +10 -0
- package/dist/components/user/switch-account-submenu.js +25 -0
- package/dist/components/user/user-avatar.d.ts +25 -0
- package/dist/components/user/user-avatar.js +31 -0
- package/dist/components/user/user-button.d.ts +25 -0
- package/dist/components/user/user-button.js +24 -0
- package/dist/components/user/user-view.d.ts +22 -0
- package/dist/components/user/user-view.js +24 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +1 -0
- package/dist/hooks/use-auth.d.ts +2 -0
- package/dist/hooks/use-auth.js +8 -0
- package/dist/hooks/use-redirect-to.d.ts +8 -0
- package/dist/hooks/use-redirect-to.js +19 -0
- package/dist/hooks/use-sign-in.d.ts +8 -0
- package/dist/hooks/use-sign-in.js +53 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +34 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/react-exports.d.ts +1 -0
- package/dist/react-exports.js +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/dist/src/components/auth/auth-provider.d.ts +8 -0
- package/dist/src/components/auth/auth-provider.js +13 -0
- package/dist/src/components/auth/auth.d.ts +22 -0
- package/dist/src/components/auth/auth.js +42 -0
- package/dist/src/components/auth/field-separator.d.ts +4 -0
- package/dist/src/components/auth/field-separator.js +5 -0
- package/dist/src/components/auth/forgot-password.d.ts +15 -0
- package/dist/src/components/auth/forgot-password.js +29 -0
- package/dist/src/components/auth/magic-link-button.d.ts +13 -0
- package/dist/src/components/auth/magic-link-button.js +20 -0
- package/dist/src/components/auth/magic-link.d.ts +17 -0
- package/dist/src/components/auth/magic-link.js +37 -0
- package/dist/src/components/auth/provider-buttons.d.ts +19 -0
- package/dist/src/components/auth/provider-buttons.js +35 -0
- package/dist/src/components/auth/reset-password.d.ts +11 -0
- package/dist/src/components/auth/reset-password.js +59 -0
- package/dist/src/components/auth/sign-in.d.ts +14 -0
- package/dist/src/components/auth/sign-in.js +58 -0
- package/dist/src/components/auth/sign-out.d.ts +11 -0
- package/dist/src/components/auth/sign-out.js +34 -0
- package/dist/src/components/auth/sign-up.d.ts +19 -0
- package/dist/src/components/auth/sign-up.js +65 -0
- package/dist/src/components/email/email-changed-email.d.ts +3 -0
- package/dist/src/components/email/email-changed-email.js +23 -0
- package/dist/src/components/email/email-verification-email.d.ts +3 -0
- package/dist/src/components/email/email-verification-email.js +23 -0
- package/dist/src/components/email/magic-link-email.d.ts +3 -0
- package/dist/src/components/email/magic-link-email.js +23 -0
- package/dist/src/components/email/new-device-email.d.ts +3 -0
- package/dist/src/components/email/new-device-email.js +23 -0
- package/dist/src/components/email/otp-email.d.ts +3 -0
- package/dist/src/components/email/otp-email.js +22 -0
- package/dist/src/components/email/password-changed-email.d.ts +3 -0
- package/dist/src/components/email/password-changed-email.js +23 -0
- package/dist/src/components/email/reset-password-email.d.ts +3 -0
- package/dist/src/components/email/reset-password-email.js +23 -0
- package/dist/src/components/settings/account/account-settings.d.ts +13 -0
- package/dist/src/components/settings/account/account-settings.js +19 -0
- package/dist/src/components/settings/account/appearance.d.ts +16 -0
- package/dist/src/components/settings/account/appearance.js +28 -0
- package/dist/src/components/settings/account/change-email.d.ts +15 -0
- package/dist/src/components/settings/account/change-email.js +35 -0
- package/dist/src/components/settings/account/manage-account.d.ts +19 -0
- package/dist/src/components/settings/account/manage-account.js +27 -0
- package/dist/src/components/settings/account/manage-accounts.d.ts +14 -0
- package/dist/src/components/settings/account/manage-accounts.js +27 -0
- package/dist/src/components/settings/account/user-profile.d.ts +13 -0
- package/dist/src/components/settings/account/user-profile.js +29 -0
- package/dist/src/components/settings/security/active-session.d.ts +14 -0
- package/dist/src/components/settings/security/active-session.js +35 -0
- package/dist/src/components/settings/security/active-sessions.d.ts +14 -0
- package/dist/src/components/settings/security/active-sessions.js +29 -0
- package/dist/src/components/settings/security/change-password.d.ts +15 -0
- package/dist/src/components/settings/security/change-password.js +57 -0
- package/dist/src/components/settings/security/linked-account.d.ts +16 -0
- package/dist/src/components/settings/security/linked-account.js +46 -0
- package/dist/src/components/settings/security/linked-accounts.d.ts +15 -0
- package/dist/src/components/settings/security/linked-accounts.js +31 -0
- package/dist/src/components/settings/security/security-settings.d.ts +15 -0
- package/dist/src/components/settings/security/security-settings.js +18 -0
- package/dist/src/components/settings/settings.d.ts +29 -0
- package/dist/src/components/settings/settings.js +39 -0
- package/dist/src/components/user/switch-account-item.d.ts +16 -0
- package/dist/src/components/user/switch-account-item.js +14 -0
- package/dist/src/components/user/switch-account-menu.d.ts +10 -0
- package/dist/src/components/user/switch-account-menu.js +23 -0
- package/dist/src/components/user/user-avatar.d.ts +24 -0
- package/dist/src/components/user/user-avatar.js +31 -0
- package/dist/src/components/user/user-button.d.ts +21 -0
- package/dist/src/components/user/user-button.js +24 -0
- package/dist/src/components/user/user-view.d.ts +21 -0
- package/dist/src/components/user/user-view.js +24 -0
- package/dist/src/core.d.ts +1 -0
- package/dist/src/core.js +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +32 -0
- package/dist/src/lib/utils.d.ts +2 -0
- package/dist/src/lib/utils.js +5 -0
- package/dist/src/react-exports.d.ts +1 -0
- package/dist/src/react-exports.js +1 -0
- package/dist/styles.css +1 -0
- package/package.json +63 -0
- package/src/components/auth/auth-provider.tsx +27 -0
- package/src/components/auth/auth.tsx +87 -0
- package/src/components/auth/field-separator.tsx +14 -0
- package/src/components/auth/forgot-password.tsx +107 -0
- package/src/components/auth/magic-link-button.tsx +43 -0
- package/src/components/auth/magic-link.tsx +181 -0
- package/src/components/auth/provider-buttons.tsx +85 -0
- package/src/components/auth/reset-password.tsx +210 -0
- package/src/components/auth/sign-in.tsx +260 -0
- package/src/components/auth/sign-out.tsx +44 -0
- package/src/components/auth/sign-up.tsx +314 -0
- package/src/components/email/email-changed-email.tsx +38 -0
- package/src/components/email/email-verification-email.tsx +38 -0
- package/src/components/email/magic-link-email.tsx +38 -0
- package/src/components/email/new-device-email.tsx +38 -0
- package/src/components/email/otp-email.tsx +33 -0
- package/src/components/email/password-changed-email.tsx +38 -0
- package/src/components/email/reset-password-email.tsx +38 -0
- package/src/components/settings/account/account-settings.tsx +48 -0
- package/src/components/settings/account/appearance.tsx +157 -0
- package/src/components/settings/account/change-avatar.tsx +142 -0
- package/src/components/settings/account/change-email.tsx +111 -0
- package/src/components/settings/account/manage-account.tsx +123 -0
- package/src/components/settings/account/manage-accounts.tsx +78 -0
- package/src/components/settings/account/user-profile.tsx +103 -0
- package/src/components/settings/security/active-session.tsx +123 -0
- package/src/components/settings/security/active-sessions.tsx +77 -0
- package/src/components/settings/security/change-password.tsx +335 -0
- package/src/components/settings/security/danger-zone.tsx +32 -0
- package/src/components/settings/security/delete-user.tsx +187 -0
- package/src/components/settings/security/linked-account.tsx +136 -0
- package/src/components/settings/security/linked-accounts.tsx +98 -0
- package/src/components/settings/security/security-settings.tsx +43 -0
- package/src/components/settings/settings.tsx +96 -0
- package/src/components/user/switch-account-item.tsx +39 -0
- package/src/components/user/switch-account-menu.tsx +64 -0
- package/src/components/user/user-avatar.tsx +88 -0
- package/src/components/user/user-button.tsx +221 -0
- package/src/components/user/user-view.tsx +74 -0
- package/src/core.ts +1 -0
- package/src/index.tsx +35 -0
- package/src/react-exports.ts +1 -0
- package/src/styles.css +1 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useAuth, useListAccounts } from "@better-auth-ui/react"
|
|
2
|
+
import { Card, type CardProps, cn, Skeleton, toast } from "@heroui/react"
|
|
3
|
+
import { LinkedAccount } from "./linked-account"
|
|
4
|
+
|
|
5
|
+
export type LinkedAccountsProps = {
|
|
6
|
+
className?: string
|
|
7
|
+
variant?: CardProps["variant"]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Render a card showing linked social accounts and available social providers to link.
|
|
12
|
+
*
|
|
13
|
+
* Linked accounts (excluding the "credential" provider) are shown with an unlink control;
|
|
14
|
+
* available providers are shown with a link control. Button states and labels reflect
|
|
15
|
+
* ongoing link/unlink activity and use localization for provider-specific text.
|
|
16
|
+
*
|
|
17
|
+
* @returns A JSX element containing the linked accounts card
|
|
18
|
+
*/
|
|
19
|
+
export function LinkedAccounts({
|
|
20
|
+
className,
|
|
21
|
+
variant,
|
|
22
|
+
...props
|
|
23
|
+
}: LinkedAccountsProps & CardProps) {
|
|
24
|
+
const { localization, socialProviders } = useAuth()
|
|
25
|
+
|
|
26
|
+
const { data: accounts, isPending } = useListAccounts({
|
|
27
|
+
throwOnError: (error) => {
|
|
28
|
+
if (error.error) toast.danger(error.error.message)
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const linkedAccounts = accounts?.filter(
|
|
34
|
+
(account) => account.providerId !== "credential"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const allRows = [
|
|
38
|
+
...(linkedAccounts?.map((account) => ({
|
|
39
|
+
key: account.id,
|
|
40
|
+
account,
|
|
41
|
+
provider: account.providerId
|
|
42
|
+
})) ?? []),
|
|
43
|
+
...(socialProviders?.map((provider) => ({
|
|
44
|
+
key: provider,
|
|
45
|
+
account: undefined,
|
|
46
|
+
provider
|
|
47
|
+
})) ?? [])
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div>
|
|
52
|
+
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
53
|
+
{localization.settings.linkedAccounts}
|
|
54
|
+
</h2>
|
|
55
|
+
|
|
56
|
+
<Card className={cn(className)} variant={variant} {...props}>
|
|
57
|
+
<Card.Content className="gap-0">
|
|
58
|
+
{isPending
|
|
59
|
+
? socialProviders?.map((provider, index) => (
|
|
60
|
+
<div key={provider}>
|
|
61
|
+
{index > 0 && (
|
|
62
|
+
<div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
|
|
63
|
+
)}
|
|
64
|
+
<AccountRowSkeleton />
|
|
65
|
+
</div>
|
|
66
|
+
))
|
|
67
|
+
: allRows.map((row, index) => (
|
|
68
|
+
<div key={row.key}>
|
|
69
|
+
{index > 0 && (
|
|
70
|
+
<div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
<LinkedAccount
|
|
74
|
+
account={row.account}
|
|
75
|
+
provider={row.provider}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</Card.Content>
|
|
80
|
+
</Card>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function AccountRowSkeleton() {
|
|
86
|
+
return (
|
|
87
|
+
<div className="flex items-center justify-between">
|
|
88
|
+
<div className="flex items-center gap-3">
|
|
89
|
+
<Skeleton className="size-10 rounded-xl" />
|
|
90
|
+
|
|
91
|
+
<div className="flex flex-col gap-1">
|
|
92
|
+
<Skeleton className="h-4 w-20 rounded-lg" />
|
|
93
|
+
<Skeleton className="h-3 w-32 rounded-lg" />
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useAuth } from "@better-auth-ui/react"
|
|
2
|
+
import { type CardProps, cn } from "@heroui/react"
|
|
3
|
+
import type { ComponentProps } from "react"
|
|
4
|
+
|
|
5
|
+
import { ActiveSessions } from "./active-sessions"
|
|
6
|
+
import { ChangePassword } from "./change-password"
|
|
7
|
+
import { DangerZone } from "./danger-zone"
|
|
8
|
+
import { LinkedAccounts } from "./linked-accounts"
|
|
9
|
+
|
|
10
|
+
export type SecuritySettingsProps = {
|
|
11
|
+
className?: string
|
|
12
|
+
variant?: CardProps["variant"]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renders the security settings layout including password management, linked accounts, and active sessions.
|
|
17
|
+
*
|
|
18
|
+
* ChangePassword is rendered when password authentication is enabled; LinkedAccounts is rendered when social providers are present.
|
|
19
|
+
* DangerZone is rendered when `deleteUser.enabled` is true in auth config.
|
|
20
|
+
*
|
|
21
|
+
* @param className - Optional additional CSS class names for the outer container.
|
|
22
|
+
* @param variant - Card variant forwarded to each security settings card.
|
|
23
|
+
* @returns The security settings container as a JSX element.
|
|
24
|
+
*/
|
|
25
|
+
export function SecuritySettings({
|
|
26
|
+
className,
|
|
27
|
+
variant,
|
|
28
|
+
...props
|
|
29
|
+
}: SecuritySettingsProps & ComponentProps<"div">) {
|
|
30
|
+
const { deleteUser, emailAndPassword, socialProviders } = useAuth()
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cn("flex w-full flex-col gap-4 md:gap-6", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
{emailAndPassword?.enabled && <ChangePassword variant={variant} />}
|
|
38
|
+
{!!socialProviders?.length && <LinkedAccounts variant={variant} />}
|
|
39
|
+
<ActiveSessions variant={variant} />
|
|
40
|
+
{deleteUser?.enabled && <DangerZone variant={variant} />}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useAuth, useAuthenticate } from "@better-auth-ui/react"
|
|
2
|
+
import type { SettingsView } from "@better-auth-ui/react/core"
|
|
3
|
+
import { type CardProps, cn, Tabs } from "@heroui/react"
|
|
4
|
+
import { type ComponentProps, useMemo } from "react"
|
|
5
|
+
|
|
6
|
+
import { AccountSettings } from "./account/account-settings"
|
|
7
|
+
import { SecuritySettings } from "./security/security-settings"
|
|
8
|
+
|
|
9
|
+
export type SettingsProps = {
|
|
10
|
+
className?: string
|
|
11
|
+
hideNav?: boolean
|
|
12
|
+
path?: string
|
|
13
|
+
variant?: CardProps["variant"]
|
|
14
|
+
/** @remarks `SettingsView` */
|
|
15
|
+
view?: SettingsView
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Renders the settings UI and activates the appropriate settings view based on `view` or `path`.
|
|
20
|
+
*
|
|
21
|
+
* @param className - Additional CSS class names applied to the root container
|
|
22
|
+
* @param path - Route path used to resolve which settings view to activate when `view` is not provided
|
|
23
|
+
* @param view - Explicit settings view to activate, e.g. `"account"` or `"security"`
|
|
24
|
+
* @param hideNav - When `true`, hide the navigation tabs
|
|
25
|
+
* @param variant - Card variant forwarded to each settings section card
|
|
26
|
+
* @returns A DOM tree containing the responsive settings tabs and the currently selected settings panel
|
|
27
|
+
*
|
|
28
|
+
* @throws Error if neither `view` nor `path` is provided
|
|
29
|
+
*/
|
|
30
|
+
export function Settings({
|
|
31
|
+
className,
|
|
32
|
+
hideNav,
|
|
33
|
+
path,
|
|
34
|
+
variant,
|
|
35
|
+
view,
|
|
36
|
+
...props
|
|
37
|
+
}: SettingsProps & ComponentProps<"div">) {
|
|
38
|
+
const { basePaths, localization, viewPaths } = useAuth()
|
|
39
|
+
useAuthenticate()
|
|
40
|
+
|
|
41
|
+
if (!view && !path) {
|
|
42
|
+
throw new Error("[Better Auth UI] Either `view` or `path` must be provided")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const settingsPathViews = useMemo(
|
|
46
|
+
() =>
|
|
47
|
+
Object.fromEntries(
|
|
48
|
+
Object.entries(viewPaths.settings).map(([k, v]) => [v, k])
|
|
49
|
+
) as Record<string, SettingsView>,
|
|
50
|
+
[viewPaths.settings]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const currentView = view || (path ? settingsPathViews[path] : undefined)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Tabs
|
|
57
|
+
className={cn(className)}
|
|
58
|
+
orientation="horizontal"
|
|
59
|
+
selectedKey={currentView}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
<Tabs.ListContainer>
|
|
63
|
+
<Tabs.List
|
|
64
|
+
aria-label={localization.settings.settings}
|
|
65
|
+
className="overflow-auto w-fit"
|
|
66
|
+
>
|
|
67
|
+
<Tabs.Tab
|
|
68
|
+
id="account"
|
|
69
|
+
href={`${basePaths.settings}/${viewPaths.settings.account}`}
|
|
70
|
+
>
|
|
71
|
+
{localization.settings.account}
|
|
72
|
+
|
|
73
|
+
<Tabs.Indicator />
|
|
74
|
+
</Tabs.Tab>
|
|
75
|
+
|
|
76
|
+
<Tabs.Tab
|
|
77
|
+
id="security"
|
|
78
|
+
href={`${basePaths.settings}/${viewPaths.settings.security}`}
|
|
79
|
+
>
|
|
80
|
+
{localization.settings.security}
|
|
81
|
+
|
|
82
|
+
<Tabs.Indicator />
|
|
83
|
+
</Tabs.Tab>
|
|
84
|
+
</Tabs.List>
|
|
85
|
+
</Tabs.ListContainer>
|
|
86
|
+
|
|
87
|
+
<Tabs.Panel id="account" className="px-0">
|
|
88
|
+
<AccountSettings variant={variant} />
|
|
89
|
+
</Tabs.Panel>
|
|
90
|
+
|
|
91
|
+
<Tabs.Panel id="security" className="px-0">
|
|
92
|
+
<SecuritySettings variant={variant} />
|
|
93
|
+
</Tabs.Panel>
|
|
94
|
+
</Tabs>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useSetActiveSession } from "@better-auth-ui/react"
|
|
2
|
+
import { Dropdown, Spinner, toast } from "@heroui/react"
|
|
3
|
+
import type { Session, User } from "better-auth"
|
|
4
|
+
import { UserView } from "./user-view"
|
|
5
|
+
|
|
6
|
+
type DeviceSession = {
|
|
7
|
+
session: Session
|
|
8
|
+
user: User
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SwitchAccountItemProps = {
|
|
12
|
+
deviceSession: DeviceSession
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render a dropdown item for switching to a different authenticated session.
|
|
17
|
+
*
|
|
18
|
+
* @param deviceSession - The device session to display and switch to when pressed
|
|
19
|
+
* @returns The switch account dropdown item as a JSX element
|
|
20
|
+
*/
|
|
21
|
+
export function SwitchAccountItem({ deviceSession }: SwitchAccountItemProps) {
|
|
22
|
+
const { mutate: setActiveSession, isPending } = useSetActiveSession({
|
|
23
|
+
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Dropdown.Item
|
|
28
|
+
className="px-2"
|
|
29
|
+
isDisabled={isPending}
|
|
30
|
+
onPress={() =>
|
|
31
|
+
setActiveSession({ sessionToken: deviceSession.session.token })
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
<UserView user={deviceSession.user} />
|
|
35
|
+
|
|
36
|
+
{isPending && <Spinner color="current" size="sm" className="ml-auto" />}
|
|
37
|
+
</Dropdown.Item>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAuth,
|
|
3
|
+
useListDeviceSessions,
|
|
4
|
+
useSession
|
|
5
|
+
} from "@better-auth-ui/react"
|
|
6
|
+
import { Check, CirclePlus } from "@gravity-ui/icons"
|
|
7
|
+
import { Dropdown, Label, Separator, toast } from "@heroui/react"
|
|
8
|
+
|
|
9
|
+
import { SwitchAccountItem } from "./switch-account-item"
|
|
10
|
+
import { UserView } from "./user-view"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render the menu content for switching between multiple authenticated sessions.
|
|
14
|
+
*
|
|
15
|
+
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
16
|
+
* and provides an option to add a new account. This component should be rendered inside a
|
|
17
|
+
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
18
|
+
*
|
|
19
|
+
* @returns The switch account menu content as a JSX element
|
|
20
|
+
*/
|
|
21
|
+
export function SwitchAccountMenu() {
|
|
22
|
+
const { basePaths, viewPaths, localization } = useAuth()
|
|
23
|
+
const { data: session } = useSession()
|
|
24
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions({
|
|
25
|
+
throwOnError: (error) => {
|
|
26
|
+
if (error.error) toast.danger(error.error.message)
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Dropdown.Popover className="min-w-40 md:min-w-56 max-w-[48svw]">
|
|
33
|
+
<Dropdown.Menu>
|
|
34
|
+
<Dropdown.Item className="px-2">
|
|
35
|
+
<UserView isPending={isPending} />
|
|
36
|
+
|
|
37
|
+
{!isPending && <Check className="ml-auto" />}
|
|
38
|
+
</Dropdown.Item>
|
|
39
|
+
|
|
40
|
+
{deviceSessions
|
|
41
|
+
?.filter(
|
|
42
|
+
(deviceSession) => deviceSession.session.id !== session?.session?.id
|
|
43
|
+
)
|
|
44
|
+
.map((deviceSession) => (
|
|
45
|
+
<SwitchAccountItem
|
|
46
|
+
key={deviceSession.session.id}
|
|
47
|
+
deviceSession={deviceSession}
|
|
48
|
+
/>
|
|
49
|
+
))}
|
|
50
|
+
|
|
51
|
+
<Separator />
|
|
52
|
+
|
|
53
|
+
<Dropdown.Item
|
|
54
|
+
textValue={localization.auth.addAccount}
|
|
55
|
+
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
56
|
+
>
|
|
57
|
+
<CirclePlus className="text-muted" />
|
|
58
|
+
|
|
59
|
+
<Label>{localization.auth.addAccount}</Label>
|
|
60
|
+
</Dropdown.Item>
|
|
61
|
+
</Dropdown.Menu>
|
|
62
|
+
</Dropdown.Popover>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useSession } from "@better-auth-ui/react"
|
|
2
|
+
import { Person } from "@gravity-ui/icons"
|
|
3
|
+
import { Avatar, type AvatarProps, cn, Skeleton } from "@heroui/react"
|
|
4
|
+
import type { User } from "better-auth"
|
|
5
|
+
import type { ReactNode } from "react"
|
|
6
|
+
|
|
7
|
+
export type UserAvatarProps = {
|
|
8
|
+
className?: string
|
|
9
|
+
fallback?: ReactNode
|
|
10
|
+
isPending?: boolean
|
|
11
|
+
/** @remarks `User` */
|
|
12
|
+
user?: User & { username?: string | null; displayUsername?: string | null }
|
|
13
|
+
size?: AvatarProps["size"]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render a user avatar that shows the user's image, initials, or a custom fallback; displays a circular skeleton while the session (or explicit pending flag) is pending.
|
|
18
|
+
*
|
|
19
|
+
* @param className - Additional CSS classes applied to the outer avatar container
|
|
20
|
+
* @param fallback - Custom fallback content to render when no image or initials are available
|
|
21
|
+
* @param isPending - When true, force rendering of the loading skeleton (unless an explicit `user` prop is provided)
|
|
22
|
+
* @param user - Optional user object to display; when omitted the current session user is used if available
|
|
23
|
+
* @param size - Visual size of the avatar; one of `"sm"`, `"md"`, or `"lg"` (default: `"sm"`)
|
|
24
|
+
* @returns A React element that displays the user's avatar image, initials, or the provided fallback; renders a circular skeleton while loading
|
|
25
|
+
*/
|
|
26
|
+
export function UserAvatar({
|
|
27
|
+
className,
|
|
28
|
+
fallback,
|
|
29
|
+
isPending,
|
|
30
|
+
user,
|
|
31
|
+
size = "sm",
|
|
32
|
+
style,
|
|
33
|
+
...props
|
|
34
|
+
}: UserAvatarProps & AvatarProps) {
|
|
35
|
+
const { data: session, isPending: sessionPending } = useSession({
|
|
36
|
+
enabled: !user && !isPending
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if ((isPending || sessionPending) && !user) {
|
|
40
|
+
return (
|
|
41
|
+
<Skeleton
|
|
42
|
+
className={cn(
|
|
43
|
+
"rounded-full",
|
|
44
|
+
size === "sm" ? "size-8" : size === "md" ? "size-10" : "size-12",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
style={style}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const resolvedUser = user ?? session?.user
|
|
53
|
+
|
|
54
|
+
const initials = (
|
|
55
|
+
resolvedUser?.username ||
|
|
56
|
+
resolvedUser?.name ||
|
|
57
|
+
resolvedUser?.email
|
|
58
|
+
)
|
|
59
|
+
?.slice(0, 2)
|
|
60
|
+
.toUpperCase()
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Avatar
|
|
64
|
+
size={size}
|
|
65
|
+
className={cn("rounded-full", className)}
|
|
66
|
+
style={style}
|
|
67
|
+
{...props}
|
|
68
|
+
>
|
|
69
|
+
<Avatar.Image
|
|
70
|
+
alt={
|
|
71
|
+
resolvedUser?.displayUsername ||
|
|
72
|
+
resolvedUser?.name ||
|
|
73
|
+
resolvedUser?.email
|
|
74
|
+
}
|
|
75
|
+
src={resolvedUser?.image ?? undefined}
|
|
76
|
+
/>
|
|
77
|
+
|
|
78
|
+
<Avatar.Fallback
|
|
79
|
+
className={cn(
|
|
80
|
+
size === "lg" ? "text-xl" : size === "md" ? "text-base" : "text-sm"
|
|
81
|
+
)}
|
|
82
|
+
delayMs={resolvedUser?.image ? 600 : undefined}
|
|
83
|
+
>
|
|
84
|
+
{fallback || initials || <Person className="size-4" />}
|
|
85
|
+
</Avatar.Fallback>
|
|
86
|
+
</Avatar>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { useAuth, useSession } from "@better-auth-ui/react"
|
|
2
|
+
import {
|
|
3
|
+
ArrowRightFromSquare,
|
|
4
|
+
ArrowRightToSquare,
|
|
5
|
+
ChevronsExpandVertical,
|
|
6
|
+
Display,
|
|
7
|
+
Gear,
|
|
8
|
+
Moon,
|
|
9
|
+
PersonPlus,
|
|
10
|
+
Persons,
|
|
11
|
+
Sun
|
|
12
|
+
} from "@gravity-ui/icons"
|
|
13
|
+
import {
|
|
14
|
+
Button,
|
|
15
|
+
type ButtonProps,
|
|
16
|
+
cn,
|
|
17
|
+
Dropdown,
|
|
18
|
+
type DropdownPopoverProps,
|
|
19
|
+
Label,
|
|
20
|
+
Separator,
|
|
21
|
+
Tabs
|
|
22
|
+
} from "@heroui/react"
|
|
23
|
+
|
|
24
|
+
import { SwitchAccountMenu } from "./switch-account-menu"
|
|
25
|
+
import { UserAvatar } from "./user-avatar"
|
|
26
|
+
import { UserView } from "./user-view"
|
|
27
|
+
|
|
28
|
+
export type UserButtonProps = {
|
|
29
|
+
className?: string
|
|
30
|
+
size?: "default" | "icon"
|
|
31
|
+
/**
|
|
32
|
+
* The placement of the element with respect to its anchor element.
|
|
33
|
+
* @default "bottom"
|
|
34
|
+
*/
|
|
35
|
+
placement?: DropdownPopoverProps["placement"]
|
|
36
|
+
themeToggle?: boolean
|
|
37
|
+
variant?: ButtonProps["variant"]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Render a user account dropdown button that shows account actions, session switching, and theme controls.
|
|
42
|
+
*
|
|
43
|
+
* Renders either an icon-only trigger or a full button showing the current user state; its dropdown contains account settings, multi-session switching (when enabled), theme selection (when enabled), and sign-in/sign-out flows depending on authentication state.
|
|
44
|
+
*
|
|
45
|
+
* @param className - Additional CSS classes applied to the trigger element
|
|
46
|
+
* @param placement - Dropdown popover placement (e.g., "bottom", "top-start", "bottom-end")
|
|
47
|
+
* @param size - "icon" renders an avatar-only trigger; "default" renders a button with label and chevron
|
|
48
|
+
* @param variant - Button visual variant passed to the underlying Button component
|
|
49
|
+
* @param themeToggle - When true and theming is available, show theme selection controls in the menu
|
|
50
|
+
* @returns The user button and its dropdown menu as a JSX element
|
|
51
|
+
*/
|
|
52
|
+
export function UserButton({
|
|
53
|
+
className,
|
|
54
|
+
placement = "bottom",
|
|
55
|
+
size = "default",
|
|
56
|
+
variant = "ghost",
|
|
57
|
+
themeToggle = true
|
|
58
|
+
}: UserButtonProps) {
|
|
59
|
+
const {
|
|
60
|
+
basePaths,
|
|
61
|
+
viewPaths,
|
|
62
|
+
localization,
|
|
63
|
+
multiSession,
|
|
64
|
+
appearance: { theme, setTheme, themes }
|
|
65
|
+
} = useAuth()
|
|
66
|
+
|
|
67
|
+
const { data: session, isPending: sessionPending } = useSession()
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Dropdown>
|
|
71
|
+
{size === "icon" ? (
|
|
72
|
+
<Dropdown.Trigger className={cn("rounded-full", className)}>
|
|
73
|
+
<UserAvatar />
|
|
74
|
+
</Dropdown.Trigger>
|
|
75
|
+
) : (
|
|
76
|
+
<Button
|
|
77
|
+
variant={variant}
|
|
78
|
+
className={cn(
|
|
79
|
+
"h-auto font-normal justify-start px-3 py-2 text-left",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
{session || sessionPending ? (
|
|
84
|
+
<UserView isPending={sessionPending} />
|
|
85
|
+
) : (
|
|
86
|
+
<>
|
|
87
|
+
<UserAvatar />
|
|
88
|
+
|
|
89
|
+
<p className="text-sm font-medium">{localization.auth.account}</p>
|
|
90
|
+
</>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<ChevronsExpandVertical className="ml-auto size-3.5" />
|
|
94
|
+
</Button>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
<Dropdown.Popover
|
|
98
|
+
placement={placement}
|
|
99
|
+
className="min-w-40 md:min-w-56 max-w-[48svw]"
|
|
100
|
+
>
|
|
101
|
+
{session && (
|
|
102
|
+
<div className="px-3 pt-3 pb-1">
|
|
103
|
+
<UserView />
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
<Dropdown.Menu>
|
|
108
|
+
{session ? (
|
|
109
|
+
<>
|
|
110
|
+
<Dropdown.Item
|
|
111
|
+
textValue={localization.settings.settings}
|
|
112
|
+
href={`${basePaths.settings}/${viewPaths.settings.account}`}
|
|
113
|
+
>
|
|
114
|
+
<Gear className="text-muted" />
|
|
115
|
+
|
|
116
|
+
<Label>{localization.settings.settings}</Label>
|
|
117
|
+
</Dropdown.Item>
|
|
118
|
+
|
|
119
|
+
{multiSession && (
|
|
120
|
+
<Dropdown.SubmenuTrigger>
|
|
121
|
+
<Dropdown.Item textValue={localization.auth.switchAccount}>
|
|
122
|
+
<Persons className="text-muted" />
|
|
123
|
+
|
|
124
|
+
<Label>{localization.auth.switchAccount}</Label>
|
|
125
|
+
|
|
126
|
+
<Dropdown.SubmenuIndicator />
|
|
127
|
+
</Dropdown.Item>
|
|
128
|
+
|
|
129
|
+
<Dropdown.Popover className="min-w-40 md:min-w-56 max-w-[48svw]">
|
|
130
|
+
<SwitchAccountMenu />
|
|
131
|
+
</Dropdown.Popover>
|
|
132
|
+
</Dropdown.SubmenuTrigger>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{themeToggle && theme && setTheme && !!themes?.length && (
|
|
136
|
+
<Dropdown.Item className="py-1 pe-2">
|
|
137
|
+
<Label>{localization.settings.theme}</Label>
|
|
138
|
+
|
|
139
|
+
<Tabs
|
|
140
|
+
className="ml-auto"
|
|
141
|
+
selectedKey={theme}
|
|
142
|
+
onSelectionChange={(key) => setTheme(key as string)}
|
|
143
|
+
>
|
|
144
|
+
<Tabs.ListContainer>
|
|
145
|
+
<Tabs.List
|
|
146
|
+
aria-label={localization.settings.theme}
|
|
147
|
+
className="*:h-5 *:w-5 *:p-0"
|
|
148
|
+
>
|
|
149
|
+
{themes.includes("system") && (
|
|
150
|
+
<Tabs.Tab
|
|
151
|
+
id="system"
|
|
152
|
+
aria-label={localization.settings.system}
|
|
153
|
+
>
|
|
154
|
+
<Display className="size-3" />
|
|
155
|
+
|
|
156
|
+
<Tabs.Indicator />
|
|
157
|
+
</Tabs.Tab>
|
|
158
|
+
)}
|
|
159
|
+
{themes.includes("light") && (
|
|
160
|
+
<Tabs.Tab
|
|
161
|
+
id="light"
|
|
162
|
+
aria-label={localization.settings.light}
|
|
163
|
+
>
|
|
164
|
+
<Sun className="size-3" />
|
|
165
|
+
|
|
166
|
+
<Tabs.Indicator />
|
|
167
|
+
</Tabs.Tab>
|
|
168
|
+
)}
|
|
169
|
+
{themes.includes("dark") && (
|
|
170
|
+
<Tabs.Tab
|
|
171
|
+
id="dark"
|
|
172
|
+
aria-label={localization.settings.dark}
|
|
173
|
+
>
|
|
174
|
+
<Moon className="size-3" />
|
|
175
|
+
|
|
176
|
+
<Tabs.Indicator />
|
|
177
|
+
</Tabs.Tab>
|
|
178
|
+
)}
|
|
179
|
+
</Tabs.List>
|
|
180
|
+
</Tabs.ListContainer>
|
|
181
|
+
</Tabs>
|
|
182
|
+
</Dropdown.Item>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
<Separator />
|
|
186
|
+
|
|
187
|
+
<Dropdown.Item
|
|
188
|
+
textValue={localization.auth.signOut}
|
|
189
|
+
href={`${basePaths.auth}/${viewPaths.auth.signOut}`}
|
|
190
|
+
>
|
|
191
|
+
<ArrowRightFromSquare className="text-muted" />
|
|
192
|
+
|
|
193
|
+
<Label>{localization.auth.signOut}</Label>
|
|
194
|
+
</Dropdown.Item>
|
|
195
|
+
</>
|
|
196
|
+
) : (
|
|
197
|
+
<>
|
|
198
|
+
<Dropdown.Item
|
|
199
|
+
textValue={localization.auth.signIn}
|
|
200
|
+
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
201
|
+
>
|
|
202
|
+
<ArrowRightToSquare className="text-muted" />
|
|
203
|
+
|
|
204
|
+
<Label>{localization.auth.signIn}</Label>
|
|
205
|
+
</Dropdown.Item>
|
|
206
|
+
|
|
207
|
+
<Dropdown.Item
|
|
208
|
+
textValue={localization.auth.signUp}
|
|
209
|
+
href={`${basePaths.auth}/${viewPaths.auth.signUp}`}
|
|
210
|
+
>
|
|
211
|
+
<PersonPlus className="text-muted" />
|
|
212
|
+
|
|
213
|
+
<Label>{localization.auth.signUp}</Label>
|
|
214
|
+
</Dropdown.Item>
|
|
215
|
+
</>
|
|
216
|
+
)}
|
|
217
|
+
</Dropdown.Menu>
|
|
218
|
+
</Dropdown.Popover>
|
|
219
|
+
</Dropdown>
|
|
220
|
+
)
|
|
221
|
+
}
|