@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,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAuth,
|
|
3
|
+
useRevokeMultiSession,
|
|
4
|
+
useSetActiveSession,
|
|
5
|
+
useUser
|
|
6
|
+
} from "@better-auth-ui/react"
|
|
7
|
+
import {
|
|
8
|
+
ArrowRightArrowLeft,
|
|
9
|
+
ArrowRightFromSquare,
|
|
10
|
+
Ellipsis
|
|
11
|
+
} from "@gravity-ui/icons"
|
|
12
|
+
import { Button, Dropdown, Spinner, toast } from "@heroui/react"
|
|
13
|
+
import type { Session, User } from "better-auth"
|
|
14
|
+
|
|
15
|
+
import { UserView } from "../../user/user-view"
|
|
16
|
+
|
|
17
|
+
export type DeviceSession = {
|
|
18
|
+
session: Session
|
|
19
|
+
user: User
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ManageAccountProps = {
|
|
23
|
+
deviceSession?: DeviceSession | null
|
|
24
|
+
isPending?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Render a single account row with user info and a dropdown for switch/sign-out actions.
|
|
29
|
+
*
|
|
30
|
+
* Shows the user's avatar and info. A three-dot menu provides options to
|
|
31
|
+
* switch account (for non-active sessions) and sign out.
|
|
32
|
+
*
|
|
33
|
+
* @param deviceSession - The device session object containing session and user data
|
|
34
|
+
* @returns A JSX element containing the account row
|
|
35
|
+
*/
|
|
36
|
+
export function ManageAccount({
|
|
37
|
+
deviceSession,
|
|
38
|
+
isPending
|
|
39
|
+
}: ManageAccountProps) {
|
|
40
|
+
const { localization } = useAuth()
|
|
41
|
+
const { data: user } = useUser()
|
|
42
|
+
|
|
43
|
+
const { mutate: setActiveSession, isPending: isSwitching } =
|
|
44
|
+
useSetActiveSession({
|
|
45
|
+
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const { mutate: revokeSession, isPending: isRevoking } =
|
|
49
|
+
useRevokeMultiSession({
|
|
50
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
51
|
+
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const isActive = deviceSession?.session.userId === user?.id
|
|
55
|
+
const isBusy = isSwitching || isRevoking
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex items-center justify-between gap-3">
|
|
59
|
+
<UserView user={deviceSession?.user} isPending={isPending} size="md" />
|
|
60
|
+
|
|
61
|
+
{deviceSession && isActive && (
|
|
62
|
+
<Button
|
|
63
|
+
className="shrink-0"
|
|
64
|
+
variant="outline"
|
|
65
|
+
size="sm"
|
|
66
|
+
onPress={() =>
|
|
67
|
+
revokeSession({ sessionToken: deviceSession.session.token })
|
|
68
|
+
}
|
|
69
|
+
isDisabled={isBusy}
|
|
70
|
+
>
|
|
71
|
+
{isRevoking ? (
|
|
72
|
+
<Spinner color="current" size="sm" />
|
|
73
|
+
) : (
|
|
74
|
+
<ArrowRightFromSquare />
|
|
75
|
+
)}
|
|
76
|
+
{localization.auth.signOut}
|
|
77
|
+
</Button>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{deviceSession && !isActive && (
|
|
81
|
+
<Dropdown>
|
|
82
|
+
<Button
|
|
83
|
+
isIconOnly
|
|
84
|
+
variant="ghost"
|
|
85
|
+
className="shrink-0"
|
|
86
|
+
size="sm"
|
|
87
|
+
isDisabled={isBusy}
|
|
88
|
+
>
|
|
89
|
+
<Ellipsis />
|
|
90
|
+
</Button>
|
|
91
|
+
|
|
92
|
+
<Dropdown.Popover>
|
|
93
|
+
<Dropdown.Menu>
|
|
94
|
+
<Dropdown.Item
|
|
95
|
+
textValue={localization.auth.switchAccount}
|
|
96
|
+
onAction={() =>
|
|
97
|
+
setActiveSession({
|
|
98
|
+
sessionToken: deviceSession.session.token
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<ArrowRightArrowLeft className="text-muted" />
|
|
103
|
+
{localization.auth.switchAccount}
|
|
104
|
+
</Dropdown.Item>
|
|
105
|
+
|
|
106
|
+
<Dropdown.Item
|
|
107
|
+
textValue={localization.auth.signOut}
|
|
108
|
+
onAction={() =>
|
|
109
|
+
revokeSession({
|
|
110
|
+
sessionToken: deviceSession.session.token
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
<ArrowRightFromSquare className="text-muted" />
|
|
115
|
+
{localization.auth.signOut}
|
|
116
|
+
</Dropdown.Item>
|
|
117
|
+
</Dropdown.Menu>
|
|
118
|
+
</Dropdown.Popover>
|
|
119
|
+
</Dropdown>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAuth,
|
|
3
|
+
useListDeviceSessions,
|
|
4
|
+
useSession
|
|
5
|
+
} from "@better-auth-ui/react"
|
|
6
|
+
import { Card, type CardProps, cn, toast } from "@heroui/react"
|
|
7
|
+
import { ManageAccount } from "./manage-account"
|
|
8
|
+
|
|
9
|
+
export type ManageAccountsProps = {
|
|
10
|
+
className?: string
|
|
11
|
+
variant?: CardProps["variant"]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Render a card that lists and manages all device sessions for the current user.
|
|
16
|
+
*
|
|
17
|
+
* Shows each session with user information and actions to switch to or revoke a session.
|
|
18
|
+
* When device session data is loading, a pending placeholder row is displayed.
|
|
19
|
+
*
|
|
20
|
+
* @returns A JSX element containing the accounts management card
|
|
21
|
+
*/
|
|
22
|
+
export function ManageAccounts({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
...props
|
|
26
|
+
}: ManageAccountsProps & CardProps) {
|
|
27
|
+
const { localization } = useAuth()
|
|
28
|
+
const { data: session } = useSession()
|
|
29
|
+
|
|
30
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions({
|
|
31
|
+
throwOnError: (error) => {
|
|
32
|
+
if (error.error) toast.danger(error.error.message)
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const otherSessions = deviceSessions?.filter(
|
|
38
|
+
(deviceSession) => deviceSession.session.id !== session?.session.id
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const allRows = [
|
|
42
|
+
{
|
|
43
|
+
key: "current",
|
|
44
|
+
deviceSession: !isPending ? session : null,
|
|
45
|
+
isPending
|
|
46
|
+
},
|
|
47
|
+
...(otherSessions?.map((deviceSession) => ({
|
|
48
|
+
key: deviceSession.session.id,
|
|
49
|
+
deviceSession,
|
|
50
|
+
isPending: false
|
|
51
|
+
})) ?? [])
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div>
|
|
56
|
+
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
57
|
+
{localization.settings.manageAccounts}
|
|
58
|
+
</h2>
|
|
59
|
+
|
|
60
|
+
<Card className={cn(className)} variant={variant} {...props}>
|
|
61
|
+
<Card.Content className="gap-0">
|
|
62
|
+
{allRows.map((row, index) => (
|
|
63
|
+
<div key={row.key}>
|
|
64
|
+
{index > 0 && (
|
|
65
|
+
<div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
<ManageAccount
|
|
69
|
+
deviceSession={row.deviceSession}
|
|
70
|
+
isPending={row.isPending}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
))}
|
|
74
|
+
</Card.Content>
|
|
75
|
+
</Card>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react"
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
type CardProps,
|
|
6
|
+
cn,
|
|
7
|
+
FieldError,
|
|
8
|
+
Fieldset,
|
|
9
|
+
Form,
|
|
10
|
+
Input,
|
|
11
|
+
Label,
|
|
12
|
+
Skeleton,
|
|
13
|
+
Spinner,
|
|
14
|
+
TextField,
|
|
15
|
+
toast
|
|
16
|
+
} from "@heroui/react"
|
|
17
|
+
import type { SyntheticEvent } from "react"
|
|
18
|
+
import { ChangeAvatar } from "./change-avatar"
|
|
19
|
+
|
|
20
|
+
export type UserProfileProps = {
|
|
21
|
+
className?: string
|
|
22
|
+
variant?: CardProps["variant"]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Render a profile card that lets the authenticated user view and update their display name and avatar.
|
|
27
|
+
*
|
|
28
|
+
* @returns A JSX element containing the user profile card with avatar upload and an editable name form
|
|
29
|
+
*/
|
|
30
|
+
export function UserProfile({
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
...props
|
|
34
|
+
}: UserProfileProps & CardProps) {
|
|
35
|
+
const { localization } = useAuth()
|
|
36
|
+
const { data: session } = useSession()
|
|
37
|
+
|
|
38
|
+
const { mutate: updateUser, isPending } = useUpdateUser({
|
|
39
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
40
|
+
onSuccess: () => toast.success(localization.settings.profileUpdatedSuccess)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {
|
|
44
|
+
e.preventDefault()
|
|
45
|
+
const formData = new FormData(e.currentTarget)
|
|
46
|
+
updateUser({ name: formData.get("name") as string })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
52
|
+
{localization.settings.profile}
|
|
53
|
+
</h2>
|
|
54
|
+
|
|
55
|
+
<Card className={cn("p-4 gap-4", className)} variant={variant} {...props}>
|
|
56
|
+
<Card.Content>
|
|
57
|
+
<Form onSubmit={handleSubmit}>
|
|
58
|
+
<Fieldset className="w-full gap-4">
|
|
59
|
+
<ChangeAvatar />
|
|
60
|
+
|
|
61
|
+
<Fieldset.Group>
|
|
62
|
+
<TextField
|
|
63
|
+
key={session?.user?.name}
|
|
64
|
+
name="name"
|
|
65
|
+
defaultValue={session?.user.name}
|
|
66
|
+
isDisabled={isPending || !session}
|
|
67
|
+
>
|
|
68
|
+
<Label>{localization.auth.name}</Label>
|
|
69
|
+
|
|
70
|
+
{session ? (
|
|
71
|
+
<Input
|
|
72
|
+
autoComplete="name"
|
|
73
|
+
placeholder={localization.auth.name}
|
|
74
|
+
variant={
|
|
75
|
+
variant === "transparent" ? "primary" : "secondary"
|
|
76
|
+
}
|
|
77
|
+
/>
|
|
78
|
+
) : (
|
|
79
|
+
<Skeleton className="h-10 md:h-9 w-full rounded-xl" />
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<FieldError />
|
|
83
|
+
</TextField>
|
|
84
|
+
</Fieldset.Group>
|
|
85
|
+
|
|
86
|
+
<Fieldset.Actions>
|
|
87
|
+
<Button
|
|
88
|
+
type="submit"
|
|
89
|
+
isPending={isPending}
|
|
90
|
+
isDisabled={!session}
|
|
91
|
+
size="sm"
|
|
92
|
+
>
|
|
93
|
+
{isPending && <Spinner color="current" size="sm" />}
|
|
94
|
+
{localization.settings.saveChanges}
|
|
95
|
+
</Button>
|
|
96
|
+
</Fieldset.Actions>
|
|
97
|
+
</Fieldset>
|
|
98
|
+
</Form>
|
|
99
|
+
</Card.Content>
|
|
100
|
+
</Card>
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useAuth, useRevokeSession, useSession } from "@better-auth-ui/react"
|
|
2
|
+
import {
|
|
3
|
+
ArrowRightFromSquare,
|
|
4
|
+
Display,
|
|
5
|
+
Smartphone,
|
|
6
|
+
Xmark
|
|
7
|
+
} from "@gravity-ui/icons"
|
|
8
|
+
import { Button, Chip, Spinner, toast } from "@heroui/react"
|
|
9
|
+
import type { Session } from "better-auth"
|
|
10
|
+
import Bowser from "bowser"
|
|
11
|
+
|
|
12
|
+
function timeAgo(date: Date) {
|
|
13
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
|
|
14
|
+
const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" })
|
|
15
|
+
|
|
16
|
+
const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
|
|
17
|
+
["year", 31536000],
|
|
18
|
+
["month", 2592000],
|
|
19
|
+
["week", 604800],
|
|
20
|
+
["day", 86400],
|
|
21
|
+
["hour", 3600],
|
|
22
|
+
["minute", 60],
|
|
23
|
+
["second", 1]
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
for (const [unit, threshold] of UNITS) {
|
|
27
|
+
if (seconds >= threshold) {
|
|
28
|
+
return rtf.format(-Math.floor(seconds / threshold), unit)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return rtf.format(0, "second")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ActiveSessionProps = {
|
|
36
|
+
activeSession: Session
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Render a single active session row with device info and revoke control.
|
|
41
|
+
*
|
|
42
|
+
* Shows the session's browser, OS, IP address, and creation time. The current session is marked
|
|
43
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
44
|
+
*
|
|
45
|
+
* @param session - The session object containing id, token, userAgent, ipAddress, and createdAt
|
|
46
|
+
* @returns A JSX element containing the active session row
|
|
47
|
+
*/
|
|
48
|
+
export function ActiveSession({ activeSession }: ActiveSessionProps) {
|
|
49
|
+
const { basePaths, localization, viewPaths, navigate } = useAuth()
|
|
50
|
+
const { data: session } = useSession({ refetchOnMount: false })
|
|
51
|
+
|
|
52
|
+
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession({
|
|
53
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
54
|
+
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const isCurrentSession = activeSession.token === session?.session.token
|
|
58
|
+
const ua = Bowser.parse(activeSession.userAgent || "")
|
|
59
|
+
const isMobile =
|
|
60
|
+
ua.platform.type === "mobile" || ua.platform.type === "tablet"
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="flex items-center gap-3">
|
|
64
|
+
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary">
|
|
65
|
+
{isMobile ? (
|
|
66
|
+
<Smartphone className="size-4.5" />
|
|
67
|
+
) : (
|
|
68
|
+
<Display className="size-4.5" />
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="flex flex-col min-w-0">
|
|
73
|
+
<span className="text-sm font-medium truncate">
|
|
74
|
+
{ua.browser.name || "Unknown Browser"}
|
|
75
|
+
{ua.os.name ? `, ${ua.os.name}` : ""}
|
|
76
|
+
</span>
|
|
77
|
+
|
|
78
|
+
{isCurrentSession ? (
|
|
79
|
+
<Chip color="accent" size="sm">
|
|
80
|
+
{localization.settings.currentSession}
|
|
81
|
+
</Chip>
|
|
82
|
+
) : (
|
|
83
|
+
activeSession.createdAt && (
|
|
84
|
+
<span className="text-xs text-muted capitalize">
|
|
85
|
+
{timeAgo(activeSession.createdAt)}
|
|
86
|
+
</span>
|
|
87
|
+
)
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<Button
|
|
92
|
+
className="ml-auto shrink-0"
|
|
93
|
+
variant="outline"
|
|
94
|
+
size="sm"
|
|
95
|
+
onPress={() =>
|
|
96
|
+
isCurrentSession
|
|
97
|
+
? navigate({
|
|
98
|
+
to: `${basePaths.auth}/${viewPaths.auth.signOut}`
|
|
99
|
+
})
|
|
100
|
+
: revokeSession(activeSession)
|
|
101
|
+
}
|
|
102
|
+
isPending={isRevoking}
|
|
103
|
+
aria-label={
|
|
104
|
+
isCurrentSession
|
|
105
|
+
? localization.auth.signOut
|
|
106
|
+
: localization.settings.revokeSession
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
{isRevoking ? (
|
|
110
|
+
<Spinner color="current" size="sm" />
|
|
111
|
+
) : isCurrentSession ? (
|
|
112
|
+
<ArrowRightFromSquare />
|
|
113
|
+
) : (
|
|
114
|
+
<Xmark />
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{isCurrentSession
|
|
118
|
+
? localization.auth.signOut
|
|
119
|
+
: localization.settings.revoke}
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useAuth, useListSessions, useSession } from "@better-auth-ui/react"
|
|
2
|
+
import { Card, type CardProps, cn, Skeleton, toast } from "@heroui/react"
|
|
3
|
+
import { ActiveSession } from "./active-session"
|
|
4
|
+
|
|
5
|
+
export type ActiveSessionsProps = {
|
|
6
|
+
className?: string
|
|
7
|
+
variant?: CardProps["variant"]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Render a card listing all active sessions for the current user with revoke controls.
|
|
12
|
+
*
|
|
13
|
+
* Shows each session's browser, OS, IP address, and creation time. The current session is marked
|
|
14
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
15
|
+
*
|
|
16
|
+
* @returns A JSX element containing the sessions card
|
|
17
|
+
*/
|
|
18
|
+
export function ActiveSessions({
|
|
19
|
+
className,
|
|
20
|
+
variant,
|
|
21
|
+
...props
|
|
22
|
+
}: ActiveSessionsProps & CardProps) {
|
|
23
|
+
const { localization } = useAuth()
|
|
24
|
+
const { data: session } = useSession()
|
|
25
|
+
|
|
26
|
+
const { data: sessions, isPending } = useListSessions({
|
|
27
|
+
throwOnError: (error) => {
|
|
28
|
+
if (error.error) toast.danger(error.error.message)
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const activeSessions = sessions?.toSorted((activeSession) =>
|
|
34
|
+
activeSession.id === session?.session.id ? -1 : 1
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
<h2 className={cn("text-sm font-semibold mb-3")}>
|
|
40
|
+
{localization.settings.activeSessions}
|
|
41
|
+
</h2>
|
|
42
|
+
|
|
43
|
+
<Card className={cn(className)} variant={variant} {...props}>
|
|
44
|
+
<Card.Content className="gap-0">
|
|
45
|
+
{isPending ? (
|
|
46
|
+
<SessionRowSkeleton />
|
|
47
|
+
) : (
|
|
48
|
+
activeSessions?.map((activeSession, index) => (
|
|
49
|
+
<div key={activeSession.id}>
|
|
50
|
+
{index > 0 && (
|
|
51
|
+
<div className="border-b border-dashed -mx-4 md:-mx-6 my-4" />
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
<ActiveSession activeSession={activeSession} />
|
|
55
|
+
</div>
|
|
56
|
+
))
|
|
57
|
+
)}
|
|
58
|
+
</Card.Content>
|
|
59
|
+
</Card>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function SessionRowSkeleton() {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex items-center justify-between">
|
|
67
|
+
<div className="flex items-center gap-3">
|
|
68
|
+
<Skeleton className="size-10 rounded-xl" />
|
|
69
|
+
|
|
70
|
+
<div className="flex flex-col gap-1">
|
|
71
|
+
<Skeleton className="h-4 w-32 rounded-lg" />
|
|
72
|
+
<Skeleton className="h-3 w-24 rounded-lg" />
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|