@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,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth } from "@better-auth-ui/react";
|
|
3
|
+
import { cn } from "../../../lib/utils";
|
|
4
|
+
import { Appearance } from "./appearance";
|
|
5
|
+
import { ChangeEmail } from "./change-email";
|
|
6
|
+
import { ManageAccounts } from "./manage-accounts";
|
|
7
|
+
import { UserProfile } from "./user-profile";
|
|
8
|
+
/**
|
|
9
|
+
* Renders the account settings layout including user profile, change email, appearance, and accounts management.
|
|
10
|
+
*
|
|
11
|
+
* UserProfile, ChangeEmail, and Appearance are always rendered; Accounts is rendered when `multiSession` is enabled.
|
|
12
|
+
*
|
|
13
|
+
* @param className - Optional additional CSS class names for the outer container.
|
|
14
|
+
* @returns The account settings container as a JSX element.
|
|
15
|
+
*/
|
|
16
|
+
export function AccountSettings({ className, ...props }) {
|
|
17
|
+
const { multiSession } = useAuth();
|
|
18
|
+
return (_jsxs("div", { className: cn("flex w-full flex-col gap-4 md:gap-6", className), ...props, children: [_jsx(UserProfile, {}), _jsx(ChangeEmail, {}), _jsx(Appearance, {}), multiSession && _jsx(ManageAccounts, {})] }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type AppearanceProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Renders a theme selector card with visual theme previews.
|
|
8
|
+
*
|
|
9
|
+
* Displays a card containing radio buttons for selecting between system, light,
|
|
10
|
+
* and dark themes. Each option shows a visual preview of the theme. Only renders
|
|
11
|
+
* if theme settings are configured (theme, setTheme, and themes are provided).
|
|
12
|
+
*
|
|
13
|
+
* @param className - Optional additional CSS class names for the card container.
|
|
14
|
+
* @returns A JSX element containing the theme selector card, or null if theme settings are not configured.
|
|
15
|
+
*/
|
|
16
|
+
export declare function Appearance({ className, variant, ...props }: AppearanceProps & CardProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ThemePreviewDark, ThemePreviewLight, ThemePreviewSystem, useAuth } from "@better-auth-ui/react";
|
|
3
|
+
import { Display, Moon, Sun } from "@gravity-ui/icons";
|
|
4
|
+
import { Card, cn, Label, Radio, RadioGroup, useIsHydrated } from "@heroui/react";
|
|
5
|
+
/**
|
|
6
|
+
* Renders a theme selector card with visual theme previews.
|
|
7
|
+
*
|
|
8
|
+
* Displays a card containing radio buttons for selecting between system, light,
|
|
9
|
+
* and dark themes. Each option shows a visual preview of the theme. Only renders
|
|
10
|
+
* if theme settings are configured (theme, setTheme, and themes are provided).
|
|
11
|
+
*
|
|
12
|
+
* @param className - Optional additional CSS class names for the card container.
|
|
13
|
+
* @returns A JSX element containing the theme selector card, or null if theme settings are not configured.
|
|
14
|
+
*/
|
|
15
|
+
export function Appearance({ className, variant, ...props }) {
|
|
16
|
+
const { localization, settings: { theme, setTheme, themes } } = useAuth();
|
|
17
|
+
const hydrated = useIsHydrated();
|
|
18
|
+
if (!setTheme || !themes?.length) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), variant: variant, ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.appearance }) }), _jsx(Card.Content, { children: _jsxs(RadioGroup, { variant: variant === "transparent" ? "secondary" : "primary", value: hydrated ? theme : undefined, onChange: setTheme, isDisabled: !hydrated || !theme, children: [_jsx(Label, { className: "mb-2", children: localization.settings.theme }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: [themes.includes("system") && (_jsxs(Radio, { value: "system", className: cn(variant === "transparent"
|
|
22
|
+
? "bg-surface"
|
|
23
|
+
: "bg-surface-secondary", "mt-0 group relative flex-col gap-4 rounded-xl border border-transparent px-5 py-4 transition-all data-[selected=true]:border-accent data-[selected=true]:bg-accent/10", "data-[focus-visible=true]:border-accent data-[focus-visible=true]:bg-accent/10"), children: [_jsx(Radio.Control, { className: "absolute top-3 right-3 size-5", children: _jsx(Radio.Indicator, {}) }), _jsxs(Radio.Content, { className: "flex flex-col gap-3", children: [_jsxs(Label, { className: "cursor-pointer font-medium flex gap-2 items-center", children: [_jsx(Display, { className: "text-muted" }), localization.settings.system] }), _jsx(ThemePreviewSystem, { className: "w-full" })] })] })), themes.includes("light") && (_jsxs(Radio, { value: "light", className: cn(variant === "transparent"
|
|
24
|
+
? "bg-surface"
|
|
25
|
+
: "bg-surface-secondary", "mt-0 group relative flex-col gap-4 rounded-xl border border-transparent px-5 py-4 transition-all data-[selected=true]:border-accent data-[selected=true]:bg-accent/10", "data-[focus-visible=true]:border-accent data-[focus-visible=true]:bg-accent/10"), children: [_jsx(Radio.Control, { className: "absolute top-3 right-3 size-5", children: _jsx(Radio.Indicator, {}) }), _jsxs(Radio.Content, { className: "flex flex-col gap-3", children: [_jsxs(Label, { className: "cursor-pointer font-medium flex gap-2 items-center", children: [_jsx(Sun, { className: "text-muted" }), localization.settings.light] }), _jsx(ThemePreviewLight, { className: "w-full" })] })] })), themes.includes("dark") && (_jsxs(Radio, { value: "dark", className: cn(variant === "transparent"
|
|
26
|
+
? "bg-surface"
|
|
27
|
+
: "bg-surface-secondary", "mt-0 group relative flex-col gap-4 rounded-xl border border-transparent px-5 py-4 transition-all data-[selected=true]:border-accent data-[selected=true]:bg-accent/10", "data-[focus-visible=true]:border-accent data-[focus-visible=true]:bg-accent/10"), children: [_jsx(Radio.Control, { className: "absolute top-3 right-3 size-5", children: _jsx(Radio.Indicator, {}) }), _jsxs(Radio.Content, { className: "flex flex-col gap-3", children: [_jsxs(Label, { className: "cursor-pointer font-medium flex gap-2 items-center", children: [_jsx(Moon, { className: "text-muted" }), localization.settings.dark] }), _jsx(ThemePreviewDark, { className: "w-full" })] })] }))] })] }) })] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type ChangeEmailProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a card containing a form to view and update the authenticated user's email.
|
|
8
|
+
*
|
|
9
|
+
* Shows a loading skeleton until session data is available, displays the current
|
|
10
|
+
* email as the form's default value, and sends a verification email to the
|
|
11
|
+
* new address upon successful submission.
|
|
12
|
+
*
|
|
13
|
+
* @returns A JSX element rendering the change-email card and form
|
|
14
|
+
*/
|
|
15
|
+
export declare function ChangeEmail({ className, variant, ...props }: ChangeEmailProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useChangeEmail, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Check, Envelope } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Card, cn, FieldError, Fieldset, Form, InputGroup, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
+
/**
|
|
6
|
+
* Render a card containing a form to view and update the authenticated user's email.
|
|
7
|
+
*
|
|
8
|
+
* Shows a loading skeleton until session data is available, displays the current
|
|
9
|
+
* email as the form's default value, and sends a verification email to the
|
|
10
|
+
* new address upon successful submission.
|
|
11
|
+
*
|
|
12
|
+
* @returns A JSX element rendering the change-email card and form
|
|
13
|
+
*/
|
|
14
|
+
export function ChangeEmail({ className, variant, ...props }) {
|
|
15
|
+
const { localization, baseURL, viewPaths } = useAuth();
|
|
16
|
+
const { data: sessionData } = useSession({
|
|
17
|
+
throwOnError: (error) => {
|
|
18
|
+
toast.danger(error.error?.message || error.message);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const { mutate: changeEmail, isPending } = useChangeEmail({
|
|
23
|
+
onSuccess: () => toast.success(localization.settings.changeEmailSuccess),
|
|
24
|
+
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
25
|
+
});
|
|
26
|
+
function handleSubmit(e) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
const formData = new FormData(e.currentTarget);
|
|
29
|
+
changeEmail({
|
|
30
|
+
newEmail: formData.get("email"),
|
|
31
|
+
callbackURL: `${baseURL}/${viewPaths.settings.account}`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), variant: variant, ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.changeEmail }) }), _jsx(Card.Content, { children: _jsx(Form, { onSubmit: handleSubmit, children: _jsxs(Fieldset, { className: "w-full gap-4", children: [_jsx(Fieldset.Group, { children: _jsxs(TextField, { name: "email", type: "email", defaultValue: sessionData?.user.email, isDisabled: isPending || !sessionData, children: [_jsx(Label, { children: localization.auth.email }), sessionData ? (_jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Prefix, { children: _jsx(Envelope, {}) }), _jsx(InputGroup.Input, { required: true, autoComplete: "email", placeholder: localization.auth.emailPlaceholder })] })) : (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }, sessionData?.user.email) }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !sessionData, children: [isPending ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Check, {}), localization.settings.updateEmail] }) })] }) }) })] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Session, User } from "better-auth";
|
|
2
|
+
export type DeviceSession = {
|
|
3
|
+
session: Session;
|
|
4
|
+
user: User;
|
|
5
|
+
};
|
|
6
|
+
export type ManageAccountProps = {
|
|
7
|
+
deviceSession?: DeviceSession | null;
|
|
8
|
+
isPending?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Render a single account card with user info and switch/revoke controls.
|
|
12
|
+
*
|
|
13
|
+
* Shows the user's avatar and info. For non-active sessions, provides a switch button.
|
|
14
|
+
* All sessions have a revoke/sign-out button.
|
|
15
|
+
*
|
|
16
|
+
* @param deviceSession - The device session object containing session and user data
|
|
17
|
+
* @returns A JSX element containing the account card
|
|
18
|
+
*/
|
|
19
|
+
export declare function ManageAccount({ deviceSession, isPending }: ManageAccountProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useRevokeMultiSession, useSession, useSetActiveSession } from "@better-auth-ui/react";
|
|
3
|
+
import { ArrowRightArrowLeft, ArrowRightFromSquare } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Card, Spinner, toast } from "@heroui/react";
|
|
5
|
+
import { UserView } from "../../user/user-view";
|
|
6
|
+
/**
|
|
7
|
+
* Render a single account card with user info and switch/revoke controls.
|
|
8
|
+
*
|
|
9
|
+
* Shows the user's avatar and info. For non-active sessions, provides a switch button.
|
|
10
|
+
* All sessions have a revoke/sign-out button.
|
|
11
|
+
*
|
|
12
|
+
* @param deviceSession - The device session object containing session and user data
|
|
13
|
+
* @returns A JSX element containing the account card
|
|
14
|
+
*/
|
|
15
|
+
export function ManageAccount({ deviceSession, isPending }) {
|
|
16
|
+
const { localization } = useAuth();
|
|
17
|
+
const { data: sessionData } = useSession();
|
|
18
|
+
const { mutate: setActiveSession, isPending: isSwitching } = useSetActiveSession({
|
|
19
|
+
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
20
|
+
});
|
|
21
|
+
const { mutate: revokeSession, isPending: isRevoking } = useRevokeMultiSession({
|
|
22
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
23
|
+
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
24
|
+
});
|
|
25
|
+
const isActive = deviceSession?.session.userId === sessionData?.session.userId;
|
|
26
|
+
return (_jsxs(Card, { className: "flex-row items-center border p-3 shadow-none", children: [_jsx(UserView, { user: deviceSession?.user, isPending: isPending, size: "md" }), deviceSession && (_jsxs("div", { className: "flex items-center gap-1 shrink-0 ml-auto", children: [!isActive && (_jsx(Button, { isIconOnly: true, variant: "ghost", size: "sm", onPress: () => setActiveSession({ sessionToken: deviceSession.session.token }), isPending: isSwitching || isRevoking, "aria-label": localization.auth.switchAccount, children: isSwitching ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(ArrowRightArrowLeft, {})) })), _jsx(Button, { isIconOnly: true, variant: "ghost", size: "sm", onPress: () => revokeSession({ sessionToken: deviceSession.session.token }), isPending: isSwitching || isRevoking, "aria-label": localization.auth.signOut, children: isRevoking ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(ArrowRightFromSquare, {})) })] }))] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type ManageAccountsProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a card that lists and manages all device sessions for the current user.
|
|
8
|
+
*
|
|
9
|
+
* Shows each session with user information and actions to switch to or revoke a session.
|
|
10
|
+
* When device session data is loading, a pending placeholder row is displayed.
|
|
11
|
+
*
|
|
12
|
+
* @returns A JSX element containing the accounts management card
|
|
13
|
+
*/
|
|
14
|
+
export declare function ManageAccounts({ className, ...props }: ManageAccountsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useListDeviceSessions, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { CirclePlus } from "@gravity-ui/icons";
|
|
4
|
+
import { buttonVariants, Card, cn, Link, toast } from "@heroui/react";
|
|
5
|
+
import { ManageAccount } from "./manage-account";
|
|
6
|
+
/**
|
|
7
|
+
* Render a card that lists and manages all device sessions for the current user.
|
|
8
|
+
*
|
|
9
|
+
* Shows each session with user information and actions to switch to or revoke a session.
|
|
10
|
+
* When device session data is loading, a pending placeholder row is displayed.
|
|
11
|
+
*
|
|
12
|
+
* @returns A JSX element containing the accounts management card
|
|
13
|
+
*/
|
|
14
|
+
export function ManageAccounts({ className, ...props }) {
|
|
15
|
+
const { basePaths, localization, viewPaths } = useAuth();
|
|
16
|
+
const { data: sessionData } = useSession();
|
|
17
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions({
|
|
18
|
+
throwOnError: (error) => {
|
|
19
|
+
if (error.error)
|
|
20
|
+
toast.danger(error.error.message);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.manageAccounts }) }), _jsxs(Card.Content, { className: "gap-3", children: [_jsx(ManageAccount, { isPending: isPending, deviceSession: !isPending ? sessionData : null }), deviceSessions
|
|
25
|
+
?.filter((deviceSession) => deviceSession.session.id !== sessionData?.session.id)
|
|
26
|
+
.map((deviceSession) => (_jsx(ManageAccount, { deviceSession: deviceSession }, deviceSession.session.id)))] }), _jsx(Card.Footer, { children: _jsxs(Link, { href: `${basePaths.auth}/${viewPaths.auth.signIn}`, className: cn("gap-2", buttonVariants({ variant: "secondary" })), children: [_jsx(CirclePlus, {}), localization.auth.addAccount] }) })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type UserProfileProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a profile card that lets the authenticated user view and update their display name.
|
|
8
|
+
*
|
|
9
|
+
* The component uses auth localization for labels, reflects session loading state with skeletons, shows the best-available user identifier (display username, name, or email), and wires the form submission to the user update action from auth hooks.
|
|
10
|
+
*
|
|
11
|
+
* @returns A JSX element containing the user profile card and an editable name form
|
|
12
|
+
*/
|
|
13
|
+
export declare function UserProfile({ className, variant, ...props }: UserProfileProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react";
|
|
3
|
+
import { FloppyDisk, Pencil } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Card, cn, FieldError, Fieldset, Form, Input, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
+
import { UserAvatar } from "../../user/user-avatar";
|
|
6
|
+
/**
|
|
7
|
+
* Render a profile card that lets the authenticated user view and update their display name.
|
|
8
|
+
*
|
|
9
|
+
* The component uses auth localization for labels, reflects session loading state with skeletons, shows the best-available user identifier (display username, name, or email), and wires the form submission to the user update action from auth hooks.
|
|
10
|
+
*
|
|
11
|
+
* @returns A JSX element containing the user profile card and an editable name form
|
|
12
|
+
*/
|
|
13
|
+
export function UserProfile({ className, variant, ...props }) {
|
|
14
|
+
const { localization } = useAuth();
|
|
15
|
+
const { data: sessionData } = useSession();
|
|
16
|
+
const { mutate: updateUser, isPending } = useUpdateUser({
|
|
17
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
18
|
+
onSuccess: () => toast.success(localization.settings.profileUpdatedSuccess)
|
|
19
|
+
});
|
|
20
|
+
function handleSubmit(e) {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
const formData = new FormData(e.currentTarget);
|
|
23
|
+
updateUser({ name: formData.get("name") });
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), variant: variant, ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.profile }) }), _jsx(Card.Content, { children: _jsx(Form, { onSubmit: handleSubmit, children: _jsxs(Fieldset, { className: "w-full gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { type: "button", isIconOnly: true, variant: "ghost", className: "p-0 h-auto w-auto rounded-full", isDisabled: !sessionData, children: [_jsx(UserAvatar, { size: "lg" }), _jsx("span", { className: "absolute right-0 bottom-0 size-4 rounded-full bg-background ring ring-2 ring-border flex items-center justify-center", children: _jsx(Pencil, { className: "size-2.5 text-muted" }) })] }), sessionData ? (_jsxs("div", { className: "flex flex-col", children: [_jsx("p", { className: "text-sm font-medium leading-tight", children: sessionData?.user?.displayUsername ||
|
|
26
|
+
sessionData?.user?.name ||
|
|
27
|
+
sessionData?.user?.email }), (sessionData?.user?.displayUsername ||
|
|
28
|
+
sessionData?.user?.name) && (_jsx("p", { className: "text-muted text-xs", children: sessionData?.user?.email }))] })) : (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-3.5 w-24 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-32 rounded-lg" })] }))] }), _jsx(Fieldset.Group, { children: _jsxs(TextField, { name: "name", defaultValue: sessionData?.user?.name, isDisabled: isPending || !sessionData, children: [_jsx(Label, { children: localization.auth.name }), sessionData ? (_jsx(Input, { autoComplete: "name", placeholder: localization.auth.name, variant: variant === "transparent" ? "primary" : "secondary" })) : (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }, sessionData?.user?.name) }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !sessionData, children: [isPending ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(FloppyDisk, {})), localization.settings.saveChanges] }) })] }) }) })] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Session } from "better-auth";
|
|
2
|
+
export type ActiveSessionProps = {
|
|
3
|
+
session: Session;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Render a single active session card with device info and revoke control.
|
|
7
|
+
*
|
|
8
|
+
* Shows the session's browser, OS, IP address, and creation time. The current session is marked
|
|
9
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
10
|
+
*
|
|
11
|
+
* @param session - The session object containing id, token, userAgent, ipAddress, and createdAt
|
|
12
|
+
* @returns A JSX element containing the active session card
|
|
13
|
+
*/
|
|
14
|
+
export declare function ActiveSession({ session }: ActiveSessionProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useRevokeSession, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { ArrowRightFromSquare, Display, Smartphone } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Card, Chip, Spinner, toast } from "@heroui/react";
|
|
5
|
+
import { UAParser } from "ua-parser-js";
|
|
6
|
+
/**
|
|
7
|
+
* Render a single active session card with device info and revoke control.
|
|
8
|
+
*
|
|
9
|
+
* Shows the session's browser, OS, IP address, and creation time. The current session is marked
|
|
10
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
11
|
+
*
|
|
12
|
+
* @param session - The session object containing id, token, userAgent, ipAddress, and createdAt
|
|
13
|
+
* @returns A JSX element containing the active session card
|
|
14
|
+
*/
|
|
15
|
+
export function ActiveSession({ session }) {
|
|
16
|
+
const { basePaths, localization, viewPaths, navigate } = useAuth();
|
|
17
|
+
const { data: sessionData } = useSession();
|
|
18
|
+
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession({
|
|
19
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
20
|
+
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
21
|
+
});
|
|
22
|
+
const isCurrentSession = session.token === sessionData?.session.token;
|
|
23
|
+
const ua = new UAParser(session.userAgent || "").getResult();
|
|
24
|
+
const isMobile = ua.device.type === "mobile" ||
|
|
25
|
+
ua.device.type === "tablet" ||
|
|
26
|
+
ua.device.type === "wearable";
|
|
27
|
+
return (_jsxs(Card, { className: "flex-row border items-center p-3 shadow-none", children: [_jsx("div", { className: "flex size-10 items-center justify-center rounded-xl bg-surface-secondary", children: isMobile ? (_jsx(Smartphone, { className: "size-5" })) : (_jsx(Display, { className: "size-5" })) }), _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("span", { className: "text-sm font-medium truncate", children: [ua.browser.name || "Unknown Browser", ua.os.name ? `, ${ua.os.name}` : ""] }), isCurrentSession && (_jsx(Chip, { color: "accent", size: "sm", className: "px-1.5", children: localization.settings.currentSession }))] }), _jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted", children: [session.ipAddress && _jsx("span", { children: session.ipAddress }), session.createdAt && (_jsxs(_Fragment, { children: [session.ipAddress && _jsx("span", { children: "\u2022" }), _jsx("span", { children: session.createdAt.toLocaleString([], {
|
|
28
|
+
dateStyle: "short",
|
|
29
|
+
timeStyle: "short"
|
|
30
|
+
}) })] }))] })] }), _jsx(Button, { isIconOnly: true, className: "ml-auto", variant: "ghost", size: "sm", onPress: () => isCurrentSession
|
|
31
|
+
? navigate({
|
|
32
|
+
to: `${basePaths.auth}/${viewPaths.auth.signOut}`
|
|
33
|
+
})
|
|
34
|
+
: revokeSession({ token: session.token }), isPending: isRevoking, "aria-label": localization.settings.revokeSession, children: isRevoking ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(ArrowRightFromSquare, {})) })] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type ActiveSessionsProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a card listing all active sessions for the current user with revoke controls.
|
|
8
|
+
*
|
|
9
|
+
* Shows each session's browser, OS, IP address, and creation time. The current session is marked
|
|
10
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
11
|
+
*
|
|
12
|
+
* @returns A JSX element containing the sessions card
|
|
13
|
+
*/
|
|
14
|
+
export declare function ActiveSessions({ className, ...props }: ActiveSessionsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useListSessions, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Card, cn, Skeleton, toast } from "@heroui/react";
|
|
4
|
+
import { ActiveSession } from "./active-session";
|
|
5
|
+
/**
|
|
6
|
+
* Render a card listing all active sessions for the current user with revoke controls.
|
|
7
|
+
*
|
|
8
|
+
* Shows each session's browser, OS, IP address, and creation time. The current session is marked
|
|
9
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
10
|
+
*
|
|
11
|
+
* @returns A JSX element containing the sessions card
|
|
12
|
+
*/
|
|
13
|
+
export function ActiveSessions({ className, ...props }) {
|
|
14
|
+
const { localization } = useAuth();
|
|
15
|
+
const { data: sessionData } = useSession();
|
|
16
|
+
const { data: sessions, isPending } = useListSessions({
|
|
17
|
+
throwOnError: (error) => {
|
|
18
|
+
if (error.error)
|
|
19
|
+
toast.danger(error.error.message);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.activeSessions }) }), _jsx(Card.Content, { className: "gap-3", children: isPending ? (_jsx(SessionRowSkeleton, {})) : (sessions
|
|
24
|
+
?.toSorted((session) => session.id === sessionData?.session.id ? -1 : 1)
|
|
25
|
+
.map((session) => (_jsx(ActiveSession, { session: session }, session.id)))) })] }));
|
|
26
|
+
}
|
|
27
|
+
function SessionRowSkeleton() {
|
|
28
|
+
return (_jsx("div", { className: "flex items-center rounded-3xl border p-3 justify-between", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-10 rounded-xl" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-4 w-32 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-24 rounded-lg" })] })] }) }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type ChangePasswordProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a card form for changing the authenticated user's password.
|
|
8
|
+
*
|
|
9
|
+
* Displays a card with fields for current password, new password, and optionally
|
|
10
|
+
* confirm password (based on `emailAndPassword.confirmPassword`). All other sessions
|
|
11
|
+
* are revoked upon successful password change.
|
|
12
|
+
*
|
|
13
|
+
* @returns A JSX element containing the change-password card and form
|
|
14
|
+
*/
|
|
15
|
+
export declare function ChangePassword({ className, variant, ...props }: ChangePasswordProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useChangePassword, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Check, Eye, EyeSlash } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Card, cn, FieldError, Fieldset, Form, Input, InputGroup, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
/**
|
|
7
|
+
* Render a card form for changing the authenticated user's password.
|
|
8
|
+
*
|
|
9
|
+
* Displays a card with fields for current password, new password, and optionally
|
|
10
|
+
* confirm password (based on `emailAndPassword.confirmPassword`). All other sessions
|
|
11
|
+
* are revoked upon successful password change.
|
|
12
|
+
*
|
|
13
|
+
* @returns A JSX element containing the change-password card and form
|
|
14
|
+
*/
|
|
15
|
+
export function ChangePassword({ className, variant, ...props }) {
|
|
16
|
+
const { emailAndPassword, localization } = useAuth();
|
|
17
|
+
const { data: sessionData } = useSession();
|
|
18
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
19
|
+
const [newPassword, setNewPassword] = useState("");
|
|
20
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
21
|
+
const { mutate: changePassword, isPending } = useChangePassword({
|
|
22
|
+
onError: (error) => {
|
|
23
|
+
setCurrentPassword("");
|
|
24
|
+
setNewPassword("");
|
|
25
|
+
setConfirmPassword("");
|
|
26
|
+
toast.danger(error.error?.message || error.message);
|
|
27
|
+
},
|
|
28
|
+
onSuccess: () => {
|
|
29
|
+
setCurrentPassword("");
|
|
30
|
+
setNewPassword("");
|
|
31
|
+
setConfirmPassword("");
|
|
32
|
+
toast.success(localization.settings.changePasswordSuccess);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const [isNewPasswordVisible, setIsNewPasswordVisible] = useState(false);
|
|
36
|
+
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] = useState(false);
|
|
37
|
+
const handleSubmit = (e) => {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
if (emailAndPassword?.confirmPassword && newPassword !== confirmPassword) {
|
|
40
|
+
setCurrentPassword("");
|
|
41
|
+
setNewPassword("");
|
|
42
|
+
setConfirmPassword("");
|
|
43
|
+
toast.danger(localization.auth.passwordsDoNotMatch);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
changePassword({
|
|
47
|
+
currentPassword,
|
|
48
|
+
newPassword,
|
|
49
|
+
revokeOtherSessions: true
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), variant: variant, ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.changePassword }) }), _jsx(Card.Content, { children: _jsx(Form, { onSubmit: handleSubmit, children: _jsxs(Fieldset, { className: "w-full gap-4", children: [_jsxs(Fieldset.Group, { children: [_jsxs(TextField, { name: "currentPassword", type: "password", isDisabled: isPending || !sessionData, value: currentPassword, onChange: setCurrentPassword, children: [_jsx(Label, { children: localization.settings.currentPassword }), sessionData ? (_jsx(Input, { autoComplete: "current-password", placeholder: localization.settings.currentPasswordPlaceholder, required: true, variant: variant === "transparent" ? "primary" : "secondary" })) : (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }), _jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, isDisabled: isPending || !sessionData, value: newPassword, onChange: setNewPassword, children: [_jsx(Label, { children: localization.auth.newPassword }), sessionData ? (_jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Input, { name: "newPassword", type: isNewPasswordVisible ? "text" : "password", autoComplete: "new-password", placeholder: localization.auth.newPasswordPlaceholder, required: true }), _jsx(InputGroup.Suffix, { className: "px-0", children: _jsx(Button, { isIconOnly: true, "aria-label": isNewPasswordVisible
|
|
53
|
+
? localization.auth.hidePassword
|
|
54
|
+
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsNewPasswordVisible(!isNewPasswordVisible), isDisabled: isPending, children: isNewPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] })) : (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }), emailAndPassword?.confirmPassword && (_jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, isDisabled: isPending || !sessionData, isRequired: true, value: confirmPassword, onChange: setConfirmPassword, children: [_jsx(Label, { children: localization.auth.confirmPassword }), sessionData ? (_jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Input, { name: "confirmPassword", type: isConfirmPasswordVisible ? "text" : "password", autoComplete: "new-password", placeholder: localization.auth.confirmPasswordPlaceholder, required: true }), _jsx(InputGroup.Suffix, { className: "px-0", children: _jsx(Button, { isIconOnly: true, "aria-label": isConfirmPasswordVisible
|
|
55
|
+
? localization.auth.hidePassword
|
|
56
|
+
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsConfirmPasswordVisible(!isConfirmPasswordVisible), isDisabled: isPending, children: isConfirmPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] })) : (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }))] }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !sessionData, children: [isPending ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Check, {}), localization.settings.updatePassword] }) })] }) }) })] }));
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Account, SocialProvider } from "better-auth";
|
|
2
|
+
export type LinkedAccountProps = {
|
|
3
|
+
account?: Account;
|
|
4
|
+
provider: SocialProvider;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a single linked social account card with provider info and unlink control.
|
|
8
|
+
*
|
|
9
|
+
* Fetches additional account information from the provider using the accountInfo API
|
|
10
|
+
* and displays the provider name, account details, and an unlink button.
|
|
11
|
+
*
|
|
12
|
+
* @param account - The account object containing id, accountId, and providerId
|
|
13
|
+
* @param provider - The provider id
|
|
14
|
+
* @returns A JSX element containing the linked account card
|
|
15
|
+
*/
|
|
16
|
+
export declare function LinkedAccount({ account, provider }: LinkedAccountProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { getProviderName } from "@better-auth-ui/core";
|
|
3
|
+
import { providerIcons, useAccountInfo, useAuth, useLinkSocial, useUnlinkAccount } from "@better-auth-ui/react";
|
|
4
|
+
import { Link, PlugConnection, Xmark } from "@gravity-ui/icons";
|
|
5
|
+
import { Button, Card, cn, Skeleton, Spinner, toast } from "@heroui/react";
|
|
6
|
+
/**
|
|
7
|
+
* Render a single linked social account card with provider info and unlink control.
|
|
8
|
+
*
|
|
9
|
+
* Fetches additional account information from the provider using the accountInfo API
|
|
10
|
+
* and displays the provider name, account details, and an unlink button.
|
|
11
|
+
*
|
|
12
|
+
* @param account - The account object containing id, accountId, and providerId
|
|
13
|
+
* @param provider - The provider id
|
|
14
|
+
* @returns A JSX element containing the linked account card
|
|
15
|
+
*/
|
|
16
|
+
export function LinkedAccount({ account, provider }) {
|
|
17
|
+
const { baseURL, localization } = useAuth();
|
|
18
|
+
const { data: accountInfo, isPending: isLoadingInfo } = useAccountInfo(account?.accountId, {
|
|
19
|
+
enabled: !!account,
|
|
20
|
+
throwOnError: (error) => {
|
|
21
|
+
if (error.error)
|
|
22
|
+
toast.danger(error.error.message);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const { mutate: linkSocial, isPending: isLinking } = useLinkSocial({
|
|
27
|
+
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
28
|
+
});
|
|
29
|
+
const { mutate: unlinkAccount, isPending: isUnlinking } = useUnlinkAccount({
|
|
30
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
31
|
+
onSuccess: () => toast.success(localization.settings.accountUnlinked)
|
|
32
|
+
});
|
|
33
|
+
const ProviderIcon = providerIcons[provider];
|
|
34
|
+
const providerName = getProviderName(provider);
|
|
35
|
+
const displayName = accountInfo?.data?.login ||
|
|
36
|
+
accountInfo?.data?.username ||
|
|
37
|
+
accountInfo?.user?.email ||
|
|
38
|
+
accountInfo?.user?.name ||
|
|
39
|
+
account?.accountId;
|
|
40
|
+
return (_jsxs(Card, { className: cn("flex-row items-center border p-3 shadow-none", !account && "border-dashed"), children: [_jsx("div", { className: "flex size-10 items-center justify-center rounded-xl bg-surface-secondary", children: ProviderIcon ? (_jsx(ProviderIcon, { className: cn("size-5", !account && "opacity-50") })) : (_jsx(PlugConnection, { className: cn("size-5", !account && "opacity-50") })) }), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "text-sm font-medium leading-tight", children: providerName }), account && isLoadingInfo ? (_jsx(Skeleton, { className: "h-3 w-24 my-0.5 rounded-lg" })) : (_jsx("span", { className: "text-xs text-muted", children: account
|
|
41
|
+
? displayName
|
|
42
|
+
: localization.settings.linkProvider.replace("{{provider}}", providerName) }))] }), account ? (_jsx(Button, { className: "ml-auto", isIconOnly: true, variant: "ghost", size: "sm", onPress: () => unlinkAccount({ providerId: account.providerId }), isPending: isUnlinking, "aria-label": localization.settings.unlinkProvider.replace("{{provider}}", providerName), children: isUnlinking ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Xmark, {}) })) : (_jsxs(Button, { className: "ml-auto", variant: "secondary", size: "sm", onPress: () => linkSocial({
|
|
43
|
+
provider,
|
|
44
|
+
callbackURL: `${baseURL}${window.location.pathname}`
|
|
45
|
+
}), isPending: isLinking, "aria-label": localization.settings.linkProvider.replace("{{provider}}", providerName), children: [isLinking ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Link, {}), localization.settings.link] }))] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type LinkedAccountsProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a card showing linked social accounts and available social providers to link.
|
|
8
|
+
*
|
|
9
|
+
* Linked accounts (excluding the "credential" provider) are shown with an unlink control;
|
|
10
|
+
* available providers are shown with a link control. Button states and labels reflect
|
|
11
|
+
* ongoing link/unlink activity and use localization for provider-specific text.
|
|
12
|
+
*
|
|
13
|
+
* @returns A JSX element containing the linked accounts card
|
|
14
|
+
*/
|
|
15
|
+
export declare function LinkedAccounts({ className, ...props }: LinkedAccountsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useListAccounts } from "@better-auth-ui/react";
|
|
3
|
+
import { Card, cn, Skeleton, toast } from "@heroui/react";
|
|
4
|
+
import { LinkedAccount } from "./linked-account";
|
|
5
|
+
/**
|
|
6
|
+
* Render a card showing linked social accounts and available social providers to link.
|
|
7
|
+
*
|
|
8
|
+
* Linked accounts (excluding the "credential" provider) are shown with an unlink control;
|
|
9
|
+
* available providers are shown with a link control. Button states and labels reflect
|
|
10
|
+
* ongoing link/unlink activity and use localization for provider-specific text.
|
|
11
|
+
*
|
|
12
|
+
* @returns A JSX element containing the linked accounts card
|
|
13
|
+
*/
|
|
14
|
+
export function LinkedAccounts({ className, ...props }) {
|
|
15
|
+
const { localization, socialProviders } = useAuth();
|
|
16
|
+
const { data: accounts, isPending } = useListAccounts({
|
|
17
|
+
throwOnError: (error) => {
|
|
18
|
+
if (error.error)
|
|
19
|
+
toast.danger(error.error.message);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4", className), ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.linkedAccounts }) }), _jsx(Card.Content, { className: "gap-3", children: isPending ? (_jsx(AccountRowSkeleton, {})) : (_jsxs(_Fragment, { children: [accounts
|
|
24
|
+
?.filter((account) => account.providerId !== "credential")
|
|
25
|
+
.map((account) => (_jsx(LinkedAccount, { account: account, provider: account.providerId }, account.id))), socialProviders?.map((provider) => {
|
|
26
|
+
return _jsx(LinkedAccount, { provider: provider }, provider);
|
|
27
|
+
})] })) })] }));
|
|
28
|
+
}
|
|
29
|
+
function AccountRowSkeleton() {
|
|
30
|
+
return (_jsx("div", { className: "flex items-center rounded-3xl border p-3 justify-between", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-10 rounded-xl" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-4 w-20 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-32 rounded-lg" })] })] }) }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CardProps } from "@heroui/react";
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
export type SecuritySettingsProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
variant?: CardProps["variant"];
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Renders the security settings layout including password management, linked accounts, and active sessions.
|
|
9
|
+
*
|
|
10
|
+
* ChangePassword is rendered when password authentication is enabled; LinkedAccounts is rendered when social providers are present.
|
|
11
|
+
*
|
|
12
|
+
* @param className - Optional additional CSS class names for the outer container.
|
|
13
|
+
* @returns The security settings container as a JSX element.
|
|
14
|
+
*/
|
|
15
|
+
export declare function SecuritySettings({ className, ...props }: SecuritySettingsProps & ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth } from "@better-auth-ui/react";
|
|
3
|
+
import { cn } from "../../../lib/utils";
|
|
4
|
+
import { ActiveSessions } from "./active-sessions";
|
|
5
|
+
import { ChangePassword } from "./change-password";
|
|
6
|
+
import { LinkedAccounts } from "./linked-accounts";
|
|
7
|
+
/**
|
|
8
|
+
* Renders the security settings layout including password management, linked accounts, and active sessions.
|
|
9
|
+
*
|
|
10
|
+
* ChangePassword is rendered when password authentication is enabled; LinkedAccounts is rendered when social providers are present.
|
|
11
|
+
*
|
|
12
|
+
* @param className - Optional additional CSS class names for the outer container.
|
|
13
|
+
* @returns The security settings container as a JSX element.
|
|
14
|
+
*/
|
|
15
|
+
export function SecuritySettings({ className, ...props }) {
|
|
16
|
+
const { emailAndPassword, socialProviders } = useAuth();
|
|
17
|
+
return (_jsxs("div", { className: cn("flex w-full flex-col gap-4 md:gap-6", className), ...props, children: [emailAndPassword?.enabled && _jsx(ChangePassword, {}), socialProviders?.length && _jsx(LinkedAccounts, {}), _jsx(ActiveSessions, {})] }));
|
|
18
|
+
}
|