@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,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useListDeviceSessions, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Card, cn, toast } from "@heroui/react";
|
|
4
|
+
import { ManageAccount } from "./manage-account";
|
|
5
|
+
/**
|
|
6
|
+
* Render a card that lists and manages all device sessions for the current user.
|
|
7
|
+
*
|
|
8
|
+
* Shows each session with user information and actions to switch to or revoke a session.
|
|
9
|
+
* When device session data is loading, a pending placeholder row is displayed.
|
|
10
|
+
*
|
|
11
|
+
* @returns A JSX element containing the accounts management card
|
|
12
|
+
*/
|
|
13
|
+
export function ManageAccounts({ className, variant, ...props }) {
|
|
14
|
+
const { localization } = useAuth();
|
|
15
|
+
const { data: session } = useSession();
|
|
16
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions({
|
|
17
|
+
throwOnError: (error) => {
|
|
18
|
+
if (error.error)
|
|
19
|
+
toast.danger(error.error.message);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const otherSessions = deviceSessions?.filter((deviceSession) => deviceSession.session.id !== session?.session.id);
|
|
24
|
+
const allRows = [
|
|
25
|
+
{
|
|
26
|
+
key: "current",
|
|
27
|
+
deviceSession: !isPending ? session : null,
|
|
28
|
+
isPending
|
|
29
|
+
},
|
|
30
|
+
...(otherSessions?.map((deviceSession) => ({
|
|
31
|
+
key: deviceSession.session.id,
|
|
32
|
+
deviceSession,
|
|
33
|
+
isPending: false
|
|
34
|
+
})) ?? [])
|
|
35
|
+
];
|
|
36
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.manageAccounts }), _jsx(Card, { className: cn(className), variant: variant, ...props, children: _jsx(Card.Content, { className: "gap-0", children: allRows.map((row, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 md:-mx-6 my-4" })), _jsx(ManageAccount, { deviceSession: row.deviceSession, isPending: row.isPending })] }, row.key))) }) })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnyAuthConfig } from "@better-auth-ui/react";
|
|
2
|
+
export type ThemeSelectorProps = AnyAuthConfig & {
|
|
3
|
+
className?: string;
|
|
4
|
+
};
|
|
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
|
+
* @param config - Auth-related props forwarded to access theme settings.
|
|
14
|
+
* @returns A JSX element containing the theme selector card, or null if theme settings are not configured.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ThemeSelector({ className, ...config }: ThemeSelectorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Display, Moon, Sun } from "@gravity-ui/icons";
|
|
3
|
+
import { Card, cn, Label, Radio, RadioGroup, useIsHydrated } from "@heroui/react";
|
|
4
|
+
import { useAuth } from "../../../hooks/use-auth";
|
|
5
|
+
function ThemePreviewSystem() {
|
|
6
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "w-full h-auto", fill: "none", viewBox: "0 0 240 117", xmlns: "http://www.w3.org/2000/svg", children: [_jsxs("defs", { children: [_jsx("clipPath", { id: "systemDiagonalLight", children: _jsx("polygon", { points: "0,0 240,0 0,117" }) }), _jsx("clipPath", { id: "systemDiagonalDark", children: _jsx("polygon", { points: "240,0 240,117 0,117" }) })] }), _jsxs("g", { clipPath: "url(#systemDiagonalLight)", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "white" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#E4E4E7" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#F4F4F5" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#E4E4E7", r: "5.5" })] }), _jsxs("g", { clipPath: "url(#systemDiagonalDark)", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "black" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#3F3F46" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#27272A" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#27272A" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#3F3F46", r: "5.5" })] })] }));
|
|
7
|
+
}
|
|
8
|
+
function ThemePreviewLight() {
|
|
9
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "w-full h-auto", fill: "none", viewBox: "0 0 240 117", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "white" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#E4E4E7" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#F4F4F5" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#E4E4E7", r: "5.5" })] }));
|
|
10
|
+
}
|
|
11
|
+
function ThemePreviewDark() {
|
|
12
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "w-full h-auto", fill: "none", viewBox: "0 0 240 117", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "black" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V105C239.5 111.351 234.351 116.5 228 116.5H12C5.64873 116.5 0.5 111.351 0.5 105V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#3F3F46" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#27272A" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#27272A" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#3F3F46", r: "5.5" })] }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Renders a theme selector card with visual theme previews.
|
|
16
|
+
*
|
|
17
|
+
* Displays a card containing radio buttons for selecting between system, light,
|
|
18
|
+
* and dark themes. Each option shows a visual preview of the theme. Only renders
|
|
19
|
+
* if theme settings are configured (theme, setTheme, and themes are provided).
|
|
20
|
+
*
|
|
21
|
+
* @param className - Optional additional CSS class names for the card container.
|
|
22
|
+
* @param config - Auth-related props forwarded to access theme settings.
|
|
23
|
+
* @returns A JSX element containing the theme selector card, or null if theme settings are not configured.
|
|
24
|
+
*/
|
|
25
|
+
export function ThemeSelector({ className, ...config }) {
|
|
26
|
+
const context = useAuth(config);
|
|
27
|
+
const { localization, settings: { theme, setTheme, themes } } = context;
|
|
28
|
+
const hydrated = useIsHydrated();
|
|
29
|
+
if (!setTheme || !themes?.length) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4 md:gap-6", className), children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.appearance }) }), _jsx(Card.Content, { children: _jsxs(RadioGroup, { isOnSurface: true, value: hydrated ? theme : undefined, onChange: setTheme, isDisabled: !hydrated || !theme, children: [_jsx(Label, { className: "mb-2", children: localization.settings.theme }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: [themes.includes("system") && (_jsxs(Radio, { value: "system", className: cn("mt-0 group relative flex-col gap-4 rounded-xl border border-default px-5 py-4 hover:bg-default/20 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, {})] })] })), themes.includes("light") && (_jsxs(Radio, { value: "light", className: cn("mt-0 group relative flex-col gap-4 rounded-xl border border-default px-5 py-4 hover:bg-default/20 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, {})] })] })), themes.includes("dark") && (_jsxs(Radio, { value: "dark", className: cn("mt-0 group relative flex-col gap-4 rounded-xl border border-default px-5 py-4 hover:bg-default/20 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, {})] })] }))] })] }) })] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnyAuthConfig } from "@better-auth-ui/react";
|
|
2
|
+
export type ThemeSettingsProps = AnyAuthConfig & {
|
|
3
|
+
className?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Renders the theme settings card with theme selection options.
|
|
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
|
+
* @param config - Auth-related props forwarded to access theme settings.
|
|
14
|
+
* @returns A JSX element containing the theme settings card, or null if theme settings are not configured.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ThemeSettings({ className, ...config }: ThemeSettingsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, cn, Label, Radio, RadioGroup } from "@heroui/react";
|
|
3
|
+
import { useAuth } from "../../../hooks/use-auth";
|
|
4
|
+
function ThemePreviewSystem() {
|
|
5
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "w-full h-full", fill: "none", viewBox: "0 0 240 117", xmlns: "http://www.w3.org/2000/svg", children: [_jsxs("defs", { children: [_jsx("clipPath", { id: "systemDiagonalLight", children: _jsx("polygon", { points: "0,0 240,0 0,117" }) }), _jsx("clipPath", { id: "systemDiagonalDark", children: _jsx("polygon", { points: "240,0 240,117 0,117" }) })] }), _jsxs("g", { clipPath: "url(#systemDiagonalLight)", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "white" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#E4E4E7" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#F4F4F5" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#E4E4E7", r: "5.5" })] }), _jsxs("g", { clipPath: "url(#systemDiagonalDark)", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "black" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#3F3F46" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#27272A" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#27272A" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#3F3F46", r: "5.5" })] })] }));
|
|
6
|
+
}
|
|
7
|
+
function ThemePreviewLight() {
|
|
8
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "w-full h-full", fill: "none", viewBox: "0 0 240 117", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "white" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#E4E4E7" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#F4F4F5" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#E4E4E7" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#F4F4F5" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#F4F4F5" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#E4E4E7", r: "5.5" })] }));
|
|
9
|
+
}
|
|
10
|
+
function ThemePreviewDark() {
|
|
11
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "w-full h-full", fill: "none", viewBox: "0 0 240 117", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", fill: "black" }), _jsx("path", { d: "M12 0.5H228C234.351 0.5 239.5 5.64873 239.5 12V116.5H0.5V12C0.5 5.64873 5.64873 0.5 12 0.5Z", stroke: "#3F3F46" }), _jsx("path", { d: "M32 48.5C32 45.4624 34.4624 43 37.5 43H67.5C70.5376 43 73 45.4624 73 48.5C73 51.5376 70.5376 54 67.5 54H37.5C34.4624 54 32 51.5376 32 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 105C17 101.686 19.6863 99 23 99H67C70.3137 99 73 101.686 73 105C73 108.314 70.3137 111 67 111H23C19.6863 111 17 108.314 17 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 25.5C88 22.4624 90.4624 20 93.5 20H207.5C210.538 20 213 22.4624 213 25.5C213 28.5376 210.538 31 207.5 31H93.5C90.4624 31 88 28.5376 88 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M88 105C88 101.686 90.6863 99 94 99H189C192.314 99 195 101.686 195 105C195 108.314 192.314 111 189 111H94C90.6863 111 88 108.314 88 105Z", fill: "#27272A" }), _jsx("path", { d: "M88 51C88 46.5817 91.5817 43 96 43H221C225.418 43 229 46.5817 229 51V85C229 89.4183 225.418 93 221 93H96C91.5817 93 88 89.4183 88 85V51Z", fill: "#27272A" }), _jsx("path", { d: "M17 48.5C17 45.4624 19.4624 43 22.5 43C25.5376 43 28 45.4624 28 48.5C28 51.5376 25.5376 54 22.5 54C19.4624 54 17 51.5376 17 48.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 66.5C17 63.4624 19.4624 61 22.5 61C25.5376 61 28 63.4624 28 66.5C28 69.5376 25.5376 72 22.5 72C19.4624 72 17 69.5376 17 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M17 86.5C17 83.4624 19.4624 81 22.5 81C25.5376 81 28 83.4624 28 86.5V87.5C28 90.5376 25.5376 93 22.5 93C19.4624 93 17 90.5376 17 87.5V86.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 25.5C32 22.4624 34.4624 20 37.5 20H67.5C70.5376 20 73 22.4624 73 25.5C73 28.5376 70.5376 31 67.5 31H37.5C34.4624 31 32 28.5376 32 25.5Z", fill: "#3F3F46" }), _jsx("path", { d: "M32 66.5C32 63.4624 34.4624 61 37.5 61H67.5C70.5376 61 73 63.4624 73 66.5C73 69.5376 70.5376 72 67.5 72H37.5C34.4624 72 32 69.5376 32 66.5Z", fill: "#27272A" }), _jsx("path", { d: "M32 87C32 83.6863 34.6863 81 38 81H67C70.3137 81 73 83.6863 73 87C73 90.3137 70.3137 93 67 93H38C34.6863 93 32 90.3137 32 87Z", fill: "#27272A" }), _jsx("circle", { cx: "22.5", cy: "25.5", fill: "#3F3F46", r: "5.5" })] }));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renders the theme settings card with theme selection options.
|
|
15
|
+
*
|
|
16
|
+
* Displays a card containing radio buttons for selecting between system, light,
|
|
17
|
+
* and dark themes. Each option shows a visual preview of the theme. Only renders
|
|
18
|
+
* if theme settings are configured (theme, setTheme, and themes are provided).
|
|
19
|
+
*
|
|
20
|
+
* @param className - Optional additional CSS class names for the card container.
|
|
21
|
+
* @param config - Auth-related props forwarded to access theme settings.
|
|
22
|
+
* @returns A JSX element containing the theme settings card, or null if theme settings are not configured.
|
|
23
|
+
*/
|
|
24
|
+
export function ThemeSettings({ className, ...config }) {
|
|
25
|
+
const context = useAuth(config);
|
|
26
|
+
const { localization, settings: { theme, setTheme, themes } } = context;
|
|
27
|
+
if (!theme || !setTheme || !themes?.length) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return (_jsxs(Card, { className: cn("p-4 md:p-6 gap-4 md:gap-6", className), children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl", children: localization.settings.theme }) }), _jsx(Card.Content, { children: _jsxs(RadioGroup, { orientation: "horizontal", value: theme, onChange: setTheme, className: "flex-wrap", children: [themes.includes("system") && (_jsx(Radio, { value: "system", className: "flex-1 min-w-[140px] max-w-[200px]", children: ({ isSelected }) => (_jsxs("div", { className: cn("flex flex-col gap-3 p-3 rounded-xl border cursor-pointer transition-colors", "hover:bg-surface-secondary", isSelected ? "border-foreground border-2" : "border-default"), children: [_jsx("div", { className: "rounded-lg overflow-hidden aspect-[240/117]", children: _jsx(ThemePreviewSystem, {}) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Radio.Control, { children: _jsx(Radio.Indicator, {}) }), _jsx(Label, { className: "cursor-pointer", children: localization.settings.system })] })] })) })), themes.includes("light") && (_jsx(Radio, { value: "light", className: "flex-1 min-w-[140px] max-w-[200px]", children: ({ isSelected }) => (_jsxs("div", { className: cn("flex flex-col gap-3 p-3 rounded-xl border cursor-pointer transition-colors", "hover:bg-surface-secondary", isSelected ? "border-foreground border-2" : "border-default"), children: [_jsx("div", { className: "rounded-lg overflow-hidden aspect-[240/117]", children: _jsx(ThemePreviewLight, {}) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Radio.Control, { children: _jsx(Radio.Indicator, {}) }), _jsx(Label, { className: "cursor-pointer", children: localization.settings.light })] })] })) })), themes.includes("dark") && (_jsx(Radio, { value: "dark", className: "flex-1 min-w-[140px] max-w-[200px]", children: ({ isSelected }) => (_jsxs("div", { className: cn("flex flex-col gap-3 p-3 rounded-xl border cursor-pointer transition-colors", "hover:bg-surface-secondary", isSelected ? "border-foreground border-2" : "border-default"), children: [_jsx("div", { className: "rounded-lg overflow-hidden aspect-[240/117]", children: _jsx(ThemePreviewDark, {}) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Radio.Control, { children: _jsx(Radio.Indicator, {}) }), _jsx(Label, { className: "cursor-pointer", children: localization.settings.dark })] })] })) }))] }) })] }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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 and avatar.
|
|
8
|
+
*
|
|
9
|
+
* @returns A JSX element containing the user profile card with avatar upload and an editable name form
|
|
10
|
+
*/
|
|
11
|
+
export declare function UserProfile({ className, variant, ...props }: UserProfileProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react";
|
|
3
|
+
import { Button, Card, cn, FieldError, Fieldset, Form, Input, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
|
|
4
|
+
import { ChangeAvatar } from "./change-avatar";
|
|
5
|
+
/**
|
|
6
|
+
* Render a profile card that lets the authenticated user view and update their display name and avatar.
|
|
7
|
+
*
|
|
8
|
+
* @returns A JSX element containing the user profile card with avatar upload and an editable name form
|
|
9
|
+
*/
|
|
10
|
+
export function UserProfile({ className, variant, ...props }) {
|
|
11
|
+
const { localization } = useAuth();
|
|
12
|
+
const { data: session } = useSession();
|
|
13
|
+
const { mutate: updateUser, isPending } = useUpdateUser({
|
|
14
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
15
|
+
onSuccess: () => toast.success(localization.settings.profileUpdatedSuccess)
|
|
16
|
+
});
|
|
17
|
+
function handleSubmit(e) {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
const formData = new FormData(e.currentTarget);
|
|
20
|
+
updateUser({ name: formData.get("name") });
|
|
21
|
+
}
|
|
22
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.profile }), _jsx(Card, { className: cn("p-4 gap-4", className), variant: variant, ...props, children: _jsx(Card.Content, { children: _jsx(Form, { onSubmit: handleSubmit, children: _jsxs(Fieldset, { className: "w-full gap-4", children: [_jsx(ChangeAvatar, {}), _jsx(Fieldset.Group, { children: _jsxs(TextField, { name: "name", defaultValue: session?.user.name, isDisabled: isPending || !session, children: [_jsx(Label, { children: localization.auth.name }), session ? (_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, {})] }, session?.user?.name) }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !session, size: "sm", children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.settings.saveChanges] }) })] }) }) }) })] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AnyAuthConfig } from "@better-auth-ui/react";
|
|
2
|
+
export type AccountSettingsProps = AnyAuthConfig & {
|
|
3
|
+
className?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function AccountSettings({ className, ...config }: AccountSettingsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/utils";
|
|
3
|
+
import { UserProfile } from "./user-profile";
|
|
4
|
+
export function AccountSettings({ className, ...config }) {
|
|
5
|
+
return (_jsx("div", { className: cn("w-full flex flex-col gap-4 md:gap-6", className), children: _jsx(UserProfile, { ...config }) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AnyAuthConfig } from "@better-auth-ui/react";
|
|
2
|
+
import type { AccountView } from "@better-auth-ui/react/core";
|
|
3
|
+
import { type TabsProps } from "@heroui/react";
|
|
4
|
+
export type AccountProps = AnyAuthConfig & {
|
|
5
|
+
className?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
view?: AccountView;
|
|
8
|
+
hideNav?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Selects and renders the appropriate account view component.
|
|
12
|
+
*
|
|
13
|
+
* @param path - Route path used to resolve an account view when `view` is not provided
|
|
14
|
+
* @param view - Explicit auth view to render (e.g., "userProfile")
|
|
15
|
+
* @param hideNav - Hide the navigation tabs
|
|
16
|
+
*/
|
|
17
|
+
export declare function Account({ className, view, path, hideNav, ...config }: AccountProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function ResponsiveTabs({ className, ...props }: TabsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ShieldCheckIcon, UserCircleIcon } from "@heroicons/react/24/outline";
|
|
3
|
+
import { cn, Tabs } from "@heroui/react";
|
|
4
|
+
import { useAuth } from "../../hooks/use-auth";
|
|
5
|
+
import { AccountSettings } from "./settings/account-settings";
|
|
6
|
+
/**
|
|
7
|
+
* Selects and renders the appropriate account view component.
|
|
8
|
+
*
|
|
9
|
+
* @param path - Route path used to resolve an account view when `view` is not provided
|
|
10
|
+
* @param view - Explicit auth view to render (e.g., "userProfile")
|
|
11
|
+
* @param hideNav - Hide the navigation tabs
|
|
12
|
+
*/
|
|
13
|
+
export function Account({ className, view, path, hideNav, ...config }) {
|
|
14
|
+
const { basePaths, viewPaths } = useAuth(config);
|
|
15
|
+
if (!view && !path) {
|
|
16
|
+
throw new Error("[Better Auth UI] Either `view` or `path` must be provided");
|
|
17
|
+
}
|
|
18
|
+
const accountPathViews = Object.fromEntries(Object.entries(viewPaths.account).map(([k, v]) => [v, k]));
|
|
19
|
+
const currentView = view || (path ? accountPathViews[path] : undefined);
|
|
20
|
+
return (_jsxs(ResponsiveTabs, { selectedKey: currentView, className: "w-full", children: [_jsx(Tabs.ListContainer, { children: _jsxs(Tabs.List, { "aria-label": "Account", className: cn("overflow-auto md:w-64 lg:w-72 xl:w-80", hideNav && "hidden"), children: [_jsxs(Tabs.Tab, { id: "settings", href: `${basePaths.account}/${viewPaths.account.settings}`, className: "gap-1.5", children: [_jsx(UserCircleIcon, { className: "size-4" }), "Account", _jsx(Tabs.Indicator, {})] }), _jsxs(Tabs.Tab, { id: "security", href: `${basePaths.account}/${viewPaths.account.security}`, className: "gap-1.5", children: [_jsx(ShieldCheckIcon, { className: "size-4" }), "Security", _jsx(Tabs.Indicator, {})] })] }) }), _jsx(Tabs.Panel, { id: "settings", className: "md:pt-0 px-0 md:px-2", children: _jsx(AccountSettings, {}) }), _jsx(Tabs.Panel, { id: "security", children: "Security" })] }));
|
|
21
|
+
}
|
|
22
|
+
export function ResponsiveTabs({ className, ...props }) {
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { className: cn("md:hidden", className), orientation: "horizontal", ...props }), _jsx(Tabs, { className: cn("hidden md:flex", className), orientation: "vertical", ...props })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Session } from "better-auth";
|
|
2
|
+
export type ActiveSessionProps = {
|
|
3
|
+
activeSession: Session;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Render a single active session row 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 row
|
|
13
|
+
*/
|
|
14
|
+
export declare function ActiveSession({ activeSession }: ActiveSessionProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useRevokeSession, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { ArrowRightFromSquare, Display, Smartphone, Xmark } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Chip, Spinner, toast } from "@heroui/react";
|
|
5
|
+
import Bowser from "bowser";
|
|
6
|
+
function timeAgo(date) {
|
|
7
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
8
|
+
const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" });
|
|
9
|
+
const UNITS = [
|
|
10
|
+
["year", 31536000],
|
|
11
|
+
["month", 2592000],
|
|
12
|
+
["week", 604800],
|
|
13
|
+
["day", 86400],
|
|
14
|
+
["hour", 3600],
|
|
15
|
+
["minute", 60],
|
|
16
|
+
["second", 1]
|
|
17
|
+
];
|
|
18
|
+
for (const [unit, threshold] of UNITS) {
|
|
19
|
+
if (seconds >= threshold) {
|
|
20
|
+
return rtf.format(-Math.floor(seconds / threshold), unit);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return rtf.format(0, "second");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Render a single active session row with device info and revoke control.
|
|
27
|
+
*
|
|
28
|
+
* Shows the session's browser, OS, IP address, and creation time. The current session is marked
|
|
29
|
+
* and navigates to sign-out on click, while other sessions can be revoked individually.
|
|
30
|
+
*
|
|
31
|
+
* @param session - The session object containing id, token, userAgent, ipAddress, and createdAt
|
|
32
|
+
* @returns A JSX element containing the active session row
|
|
33
|
+
*/
|
|
34
|
+
export function ActiveSession({ activeSession }) {
|
|
35
|
+
const { basePaths, localization, viewPaths, navigate } = useAuth();
|
|
36
|
+
const { data: session } = useSession({ refetchOnMount: false });
|
|
37
|
+
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession({
|
|
38
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
39
|
+
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
40
|
+
});
|
|
41
|
+
const isCurrentSession = activeSession.token === session?.session.token;
|
|
42
|
+
const ua = Bowser.parse(activeSession.userAgent || "");
|
|
43
|
+
const isMobile = ua.platform.type === "mobile" || ua.platform.type === "tablet";
|
|
44
|
+
return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary", children: isMobile ? (_jsx(Smartphone, { className: "size-4.5" })) : (_jsx(Display, { className: "size-4.5" })) }), _jsxs("div", { className: "flex flex-col min-w-0", 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", children: localization.settings.currentSession })) : (activeSession.createdAt && (_jsx("span", { className: "text-xs text-muted capitalize", children: timeAgo(activeSession.createdAt) })))] }), _jsxs(Button, { className: "ml-auto shrink-0", variant: "outline", size: "sm", onPress: () => isCurrentSession
|
|
45
|
+
? navigate({
|
|
46
|
+
to: `${basePaths.auth}/${viewPaths.auth.signOut}`
|
|
47
|
+
})
|
|
48
|
+
: revokeSession(activeSession), isPending: isRevoking, "aria-label": isCurrentSession
|
|
49
|
+
? localization.auth.signOut
|
|
50
|
+
: localization.settings.revokeSession, children: [isRevoking ? (_jsx(Spinner, { color: "current", size: "sm" })) : isCurrentSession ? (_jsx(ArrowRightFromSquare, {})) : (_jsx(Xmark, {})), isCurrentSession
|
|
51
|
+
? localization.auth.signOut
|
|
52
|
+
: localization.settings.revoke] })] }));
|
|
53
|
+
}
|
|
@@ -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, variant, ...props }: ActiveSessionsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
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, variant, ...props }) {
|
|
14
|
+
const { localization } = useAuth();
|
|
15
|
+
const { data: session } = 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
|
+
const activeSessions = sessions?.toSorted((activeSession) => activeSession.id === session?.session.id ? -1 : 1);
|
|
24
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.activeSessions }), _jsx(Card, { className: cn(className), variant: variant, ...props, children: _jsx(Card.Content, { className: "gap-0", children: isPending ? (_jsx(SessionRowSkeleton, {})) : (activeSessions?.map((activeSession, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 md:-mx-6 my-4" })), _jsx(ActiveSession, { activeSession: activeSession })] }, activeSession.id)))) }) })] }));
|
|
25
|
+
}
|
|
26
|
+
function SessionRowSkeleton() {
|
|
27
|
+
return (_jsx("div", { className: "flex items-center 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" })] })] }) }));
|
|
28
|
+
}
|
|
@@ -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
|
+
* When the user has a credential account, displays fields for current password,
|
|
10
|
+
* new password, and optionally confirm password. When the user only has social
|
|
11
|
+
* accounts, displays a prompt to set a password via the reset flow.
|
|
12
|
+
*
|
|
13
|
+
* @returns A JSX element containing the change-password or set-password card
|
|
14
|
+
*/
|
|
15
|
+
export declare function ChangePassword({ className, variant, ...props }: ChangePasswordProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useChangePassword, useListAccounts, useRequestPasswordReset, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { 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
|
+
* When the user has a credential account, displays fields for current password,
|
|
10
|
+
* new password, and optionally confirm password. When the user only has social
|
|
11
|
+
* accounts, displays a prompt to set a password via the reset flow.
|
|
12
|
+
*
|
|
13
|
+
* @returns A JSX element containing the change-password or set-password card
|
|
14
|
+
*/
|
|
15
|
+
export function ChangePassword({ className, variant, ...props }) {
|
|
16
|
+
const { emailAndPassword, localization } = useAuth();
|
|
17
|
+
const { data: session } = useSession();
|
|
18
|
+
const { data: accounts, isPending: isAccountsPending } = useListAccounts();
|
|
19
|
+
const hasCredentialAccount = accounts?.some((account) => account.providerId === "credential");
|
|
20
|
+
if (!isAccountsPending && !hasCredentialAccount) {
|
|
21
|
+
return _jsx(SetPassword, { className: className, variant: variant, ...props });
|
|
22
|
+
}
|
|
23
|
+
return (_jsx(ChangePasswordForm, { className: className, variant: variant, emailAndPassword: emailAndPassword, localization: localization, session: isAccountsPending ? undefined : session, ...props }));
|
|
24
|
+
}
|
|
25
|
+
function SetPassword({ className, variant, ...props }) {
|
|
26
|
+
const { localization } = useAuth();
|
|
27
|
+
const { data: session } = useSession();
|
|
28
|
+
const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset({
|
|
29
|
+
onError: (error) => toast.danger(error.error?.message || error.message),
|
|
30
|
+
onSuccess: () => toast.success(localization.auth.passwordResetEmailSent)
|
|
31
|
+
});
|
|
32
|
+
const handleSetPassword = () => {
|
|
33
|
+
if (!session?.user.email)
|
|
34
|
+
return;
|
|
35
|
+
requestPasswordReset({ email: session.user.email });
|
|
36
|
+
};
|
|
37
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.changePassword }), _jsx(Card, { className: cn(className), variant: variant, ...props, children: _jsxs(Card.Content, { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium leading-tight", children: localization.settings.setPassword }), _jsx("p", { className: "text-muted text-xs mt-0.5", children: localization.settings.setPasswordDescription })] }), _jsxs(Button, { size: "sm", isPending: isPending, isDisabled: !session, onPress: handleSetPassword, children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.auth.sendResetLink] })] }) })] }));
|
|
38
|
+
}
|
|
39
|
+
function ChangePasswordForm({ className, variant, emailAndPassword, localization, session, ...props }) {
|
|
40
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
41
|
+
const [newPassword, setNewPassword] = useState("");
|
|
42
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
43
|
+
const { mutate: changePassword, isPending } = useChangePassword({
|
|
44
|
+
onError: (error) => {
|
|
45
|
+
setCurrentPassword("");
|
|
46
|
+
setNewPassword("");
|
|
47
|
+
setConfirmPassword("");
|
|
48
|
+
toast.danger(error.error?.message || error.message);
|
|
49
|
+
},
|
|
50
|
+
onSuccess: () => {
|
|
51
|
+
setCurrentPassword("");
|
|
52
|
+
setNewPassword("");
|
|
53
|
+
setConfirmPassword("");
|
|
54
|
+
toast.success(localization.settings.changePasswordSuccess);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const [isNewPasswordVisible, setIsNewPasswordVisible] = useState(false);
|
|
58
|
+
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] = useState(false);
|
|
59
|
+
const handleSubmit = (e) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
if (emailAndPassword?.confirmPassword && newPassword !== confirmPassword) {
|
|
62
|
+
setCurrentPassword("");
|
|
63
|
+
setNewPassword("");
|
|
64
|
+
setConfirmPassword("");
|
|
65
|
+
toast.danger(localization.auth.passwordsDoNotMatch);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
changePassword({
|
|
69
|
+
currentPassword,
|
|
70
|
+
newPassword,
|
|
71
|
+
revokeOtherSessions: true
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.changePassword }), _jsx(Card, { className: cn("p-4 gap-4", className), variant: variant, ...props, children: _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 || !session, defaultValue: "", value: currentPassword, onChange: setCurrentPassword, children: [_jsx(Label, { children: localization.settings.currentPassword }), _jsx(Input, { className: cn(!session && "hidden"), autoComplete: "current-password", placeholder: localization.settings.currentPasswordPlaceholder, required: true, variant: variant === "transparent" ? "primary" : "secondary" }), !session && (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }), _jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, isDisabled: isPending || !session, value: newPassword, onChange: setNewPassword, children: [_jsx(Label, { children: localization.auth.newPassword }), _jsxs(InputGroup, { className: cn(!session && "hidden"), 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
|
|
75
|
+
? localization.auth.hidePassword
|
|
76
|
+
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsNewPasswordVisible(!isNewPasswordVisible), isDisabled: isPending, children: isNewPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] }), !session && (_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 || !session, isRequired: true, value: confirmPassword, onChange: setConfirmPassword, children: [_jsx(Label, { children: localization.auth.confirmPassword }), _jsxs(InputGroup, { className: cn(!session && "hidden"), 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
|
|
77
|
+
? localization.auth.hidePassword
|
|
78
|
+
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsConfirmPasswordVisible(!isConfirmPasswordVisible), isDisabled: isPending, children: isConfirmPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] }), !session && (_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: !session, size: "sm", children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.settings.updatePassword] }) })] }) }) }) })] }));
|
|
79
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Account, SocialProvider } from "better-auth";
|
|
2
|
+
export type ConnectedAccountProps = {
|
|
3
|
+
account?: Account;
|
|
4
|
+
provider: SocialProvider;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a single connected 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 connected account card
|
|
15
|
+
*/
|
|
16
|
+
export declare function ConnectedAccount({ account, provider }: ConnectedAccountProps): 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 connected 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 connected account card
|
|
15
|
+
*/
|
|
16
|
+
export function ConnectedAccount({ 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, { timeout: 3000 });
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const { mutate: linkSocial, isPending: isLinking } = useLinkSocial({
|
|
27
|
+
onError: (error) => toast.danger(error.error?.message || error.message, { timeout: 3000 })
|
|
28
|
+
});
|
|
29
|
+
const { mutate: unlinkAccount, isPending: isUnlinking } = useUnlinkAccount({
|
|
30
|
+
onError: (error) => toast.danger(error.error?.message || error.message, { timeout: 3000 }),
|
|
31
|
+
onSuccess: () => toast.success(localization.settings.accountUnlinked, { timeout: 3000 })
|
|
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", children: providerName }), account && isLoadingInfo ? (_jsx(Skeleton, { className: "h-3 my-0.5 w-24 rounded-lg" })) : (_jsx("span", { className: "text-xs text-muted", children: account
|
|
41
|
+
? displayName
|
|
42
|
+
: localization.settings.connectProvider.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.connectProvider.replace("{{provider}}", providerName), children: [isLinking ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Link, {}), localization.settings.connect] }))] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type ConnectedAccountsProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Render a card showing linked social accounts and available social providers to connect.
|
|
8
|
+
*
|
|
9
|
+
* Linked accounts (excluding the "credential" provider) are shown with an unlink control;
|
|
10
|
+
* available providers are shown with a connect 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 connected accounts card
|
|
14
|
+
*/
|
|
15
|
+
export declare function ConnectedAccounts({ className, ...props }: ConnectedAccountsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|