@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,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 { ConnectedAccount } from "./connected-account";
|
|
5
|
+
/**
|
|
6
|
+
* Render a card showing linked social accounts and available social providers to connect.
|
|
7
|
+
*
|
|
8
|
+
* Linked accounts (excluding the "credential" provider) are shown with an unlink control;
|
|
9
|
+
* available providers are shown with a connect 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 connected accounts card
|
|
13
|
+
*/
|
|
14
|
+
export function ConnectedAccounts({ 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, { timeout: 3000 });
|
|
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.connectedAccounts }) }), _jsx(Card.Content, { className: "gap-3", children: isPending ? (_jsx(AccountRowSkeleton, {})) : (_jsxs(_Fragment, { children: [accounts
|
|
24
|
+
?.filter((account) => account.providerId !== "credential")
|
|
25
|
+
.map((account) => (_jsx(ConnectedAccount, { account: account, provider: account.providerId }, account.id))), socialProviders?.map((provider) => {
|
|
26
|
+
return _jsx(ConnectedAccount, { 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,11 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
export type DangerZoneProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
variant?: CardProps["variant"];
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Renders the danger zone heading and {@link DeleteUser}.
|
|
9
|
+
* Gate with `deleteUser.enabled` at the call site (e.g. {@link SecuritySettings}).
|
|
10
|
+
*/
|
|
11
|
+
export declare function DangerZone({ className, variant, ...props }: DangerZoneProps & ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth } from "@better-auth-ui/react";
|
|
3
|
+
import { cn } from "@heroui/react";
|
|
4
|
+
import { DeleteUser } from "./delete-user";
|
|
5
|
+
/**
|
|
6
|
+
* Renders the danger zone heading and {@link DeleteUser}.
|
|
7
|
+
* Gate with `deleteUser.enabled` at the call site (e.g. {@link SecuritySettings}).
|
|
8
|
+
*/
|
|
9
|
+
export function DangerZone({ className, variant, ...props }) {
|
|
10
|
+
const { localization } = useAuth();
|
|
11
|
+
return (_jsxs("div", { className: cn("flex w-full flex-col", className), ...props, children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.dangerZone }), _jsx(DeleteUser, { variant: variant })] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type DeleteUserProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
variant?: CardProps["variant"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Danger-zone card to delete the authenticated account, with a confirmation dialog and toasts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function DeleteUser({ className, variant, ...props }: DeleteUserProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useDeleteUser, useListAccounts } from "@better-auth-ui/react";
|
|
3
|
+
import { TriangleExclamation } from "@gravity-ui/icons";
|
|
4
|
+
import { AlertDialog, Button, Card, cn, FieldError, Form, Input, Label, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
/**
|
|
7
|
+
* Danger-zone card to delete the authenticated account, with a confirmation dialog and toasts.
|
|
8
|
+
*/
|
|
9
|
+
export function DeleteUser({ className, variant, ...props }) {
|
|
10
|
+
const { basePaths, deleteUser: deleteUserConfig, localization, navigate, viewPaths } = useAuth();
|
|
11
|
+
const { data: accounts } = useListAccounts();
|
|
12
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
13
|
+
const [password, setPassword] = useState("");
|
|
14
|
+
const hasCredentialAccount = accounts?.some((account) => account.providerId === "credential");
|
|
15
|
+
const needsPassword = !deleteUserConfig?.sendDeleteAccountVerification && hasCredentialAccount;
|
|
16
|
+
const { mutate: deleteUser, isPending } = useDeleteUser();
|
|
17
|
+
const handleDialogOpenChange = (open) => {
|
|
18
|
+
setConfirmOpen(open);
|
|
19
|
+
setPassword("");
|
|
20
|
+
};
|
|
21
|
+
const handleSubmit = async (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
const params = {
|
|
24
|
+
...(needsPassword ? { password } : {})
|
|
25
|
+
};
|
|
26
|
+
deleteUser(params, {
|
|
27
|
+
onSuccess: () => {
|
|
28
|
+
setConfirmOpen(false);
|
|
29
|
+
setPassword("");
|
|
30
|
+
if (deleteUserConfig?.sendDeleteAccountVerification) {
|
|
31
|
+
toast.success(localization.settings.deleteUserVerificationSent);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
toast.success(localization.settings.deleteUserSuccess);
|
|
35
|
+
navigate({
|
|
36
|
+
to: `${basePaths.auth}/${viewPaths.auth.signIn}`,
|
|
37
|
+
replace: true
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
onError: (error) => {
|
|
42
|
+
toast.danger(error.error?.message || error.message);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
return (_jsx(Card, { className: cn("border border-danger", 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.deleteUser }), _jsx("p", { className: "text-muted text-xs mt-0.5", children: localization.settings.deleteUserDescription })] }), _jsxs(AlertDialog, { children: [_jsx(Button, { isDisabled: !accounts, size: "sm", variant: "danger", onPress: () => setConfirmOpen(true), children: localization.settings.deleteUser }), _jsx(AlertDialog.Backdrop, { isOpen: confirmOpen, onOpenChange: handleDialogOpenChange, children: _jsx(AlertDialog.Container, { children: _jsx(AlertDialog.Dialog, { children: _jsxs(Form, { onSubmit: handleSubmit, children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger", children: _jsx(TriangleExclamation, {}) }), _jsx(AlertDialog.Heading, { children: localization.settings.deleteUser })] }), _jsxs(AlertDialog.Body, { className: "overflow-visible", children: [_jsx("p", { className: "text-muted text-sm", children: localization.settings.deleteUserDescription }), needsPassword && (_jsxs(TextField, { className: "mt-4", name: "password", type: "password", isDisabled: isPending, value: password, onChange: setPassword, children: [_jsx(Label, { children: localization.auth.password }), _jsx(Input, { autoComplete: "current-password", placeholder: localization.auth.passwordPlaceholder, required: true, variant: "secondary" }), _jsx(FieldError, {})] }))] }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "tertiary", isDisabled: isPending, children: localization.settings.cancel }), _jsxs(Button, { type: "submit", variant: "danger", isPending: isPending, children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.settings.deleteUser] })] })] }) }) }) })] })] }) }));
|
|
47
|
+
}
|
|
@@ -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 row with provider info and link/unlink control.
|
|
8
|
+
*
|
|
9
|
+
* Fetches additional account information from the provider using the accountInfo API
|
|
10
|
+
* and displays the provider name, account details, and a link/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 row
|
|
15
|
+
*/
|
|
16
|
+
export declare function LinkedAccount({ account, provider }: LinkedAccountProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
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, LinkSlash, PlugConnection } from "@gravity-ui/icons";
|
|
5
|
+
import { Button, cn, Skeleton, Spinner, toast } from "@heroui/react";
|
|
6
|
+
/**
|
|
7
|
+
* Render a single linked social account row with provider info and link/unlink control.
|
|
8
|
+
*
|
|
9
|
+
* Fetches additional account information from the provider using the accountInfo API
|
|
10
|
+
* and displays the provider name, account details, and a link/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 row
|
|
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("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn("flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary"), children: ProviderIcon ? (_jsx(ProviderIcon, { className: cn("size-4.5", !account && "opacity-50") })) : (_jsx(PlugConnection, { className: cn("size-4.5", !account && "opacity-50") })) }), _jsxs("div", { className: "flex flex-col min-w-0", 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 truncate", children: account
|
|
41
|
+
? displayName
|
|
42
|
+
: localization.settings.linkProvider.replace("{{provider}}", providerName) }))] }), account ? (_jsxs(Button, { className: "ml-auto shrink-0", variant: "outline", 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(LinkSlash, {}), localization.settings.unlinkProvider
|
|
43
|
+
.replace("{{provider}}", "")
|
|
44
|
+
.trim()] })) : (_jsxs(Button, { className: "ml-auto shrink-0", variant: "outline", size: "sm", onPress: () => linkSocial({
|
|
45
|
+
provider,
|
|
46
|
+
callbackURL: `${baseURL}${window.location.pathname}`
|
|
47
|
+
}), isPending: isLinking, "aria-label": localization.settings.linkProvider.replace("{{provider}}", providerName), children: [isLinking ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Link, {}), localization.settings.link] }))] }));
|
|
48
|
+
}
|
|
@@ -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, variant, ...props }: LinkedAccountsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, 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, variant, ...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
|
+
const linkedAccounts = accounts?.filter((account) => account.providerId !== "credential");
|
|
24
|
+
const allRows = [
|
|
25
|
+
...(linkedAccounts?.map((account) => ({
|
|
26
|
+
key: account.id,
|
|
27
|
+
account,
|
|
28
|
+
provider: account.providerId
|
|
29
|
+
})) ?? []),
|
|
30
|
+
...(socialProviders?.map((provider) => ({
|
|
31
|
+
key: provider,
|
|
32
|
+
account: undefined,
|
|
33
|
+
provider
|
|
34
|
+
})) ?? [])
|
|
35
|
+
];
|
|
36
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.linkedAccounts }), _jsx(Card, { className: cn(className), variant: variant, ...props, children: _jsx(Card.Content, { className: "gap-0", children: isPending
|
|
37
|
+
? socialProviders?.map((provider, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 md:-mx-6 my-4" })), _jsx(AccountRowSkeleton, {})] }, provider)))
|
|
38
|
+
: allRows.map((row, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 md:-mx-6 my-4" })), _jsx(LinkedAccount, { account: row.account, provider: row.provider })] }, row.key))) }) })] }));
|
|
39
|
+
}
|
|
40
|
+
function AccountRowSkeleton() {
|
|
41
|
+
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-20 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-32 rounded-lg" })] })] }) }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
* DangerZone is rendered when `deleteUser.enabled` is true in auth config.
|
|
12
|
+
*
|
|
13
|
+
* @param className - Optional additional CSS class names for the outer container.
|
|
14
|
+
* @param variant - Card variant forwarded to each security settings card.
|
|
15
|
+
* @returns The security settings container as a JSX element.
|
|
16
|
+
*/
|
|
17
|
+
export declare function SecuritySettings({ className, variant, ...props }: SecuritySettingsProps & ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth } from "@better-auth-ui/react";
|
|
3
|
+
import { cn } from "@heroui/react";
|
|
4
|
+
import { ActiveSessions } from "./active-sessions";
|
|
5
|
+
import { ChangePassword } from "./change-password";
|
|
6
|
+
import { DangerZone } from "./danger-zone";
|
|
7
|
+
import { LinkedAccounts } from "./linked-accounts";
|
|
8
|
+
/**
|
|
9
|
+
* Renders the security settings layout including password management, linked accounts, and active sessions.
|
|
10
|
+
*
|
|
11
|
+
* ChangePassword is rendered when password authentication is enabled; LinkedAccounts is rendered when social providers are present.
|
|
12
|
+
* DangerZone is rendered when `deleteUser.enabled` is true in auth config.
|
|
13
|
+
*
|
|
14
|
+
* @param className - Optional additional CSS class names for the outer container.
|
|
15
|
+
* @param variant - Card variant forwarded to each security settings card.
|
|
16
|
+
* @returns The security settings container as a JSX element.
|
|
17
|
+
*/
|
|
18
|
+
export function SecuritySettings({ className, variant, ...props }) {
|
|
19
|
+
const { deleteUser, emailAndPassword, socialProviders } = useAuth();
|
|
20
|
+
return (_jsxs("div", { className: cn("flex w-full flex-col gap-4 md:gap-6", className), ...props, children: [emailAndPassword?.enabled && _jsx(ChangePassword, { variant: variant }), !!socialProviders?.length && _jsx(LinkedAccounts, { variant: variant }), _jsx(ActiveSessions, { variant: variant }), deleteUser?.enabled && _jsx(DangerZone, { variant: variant })] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CardProps } from "@heroui/react";
|
|
2
|
+
export type SessionsProps = {
|
|
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 Sessions({ className, ...props }: SessionsProps & CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useListSessions, useRevokeSession, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { ArrowRightFromSquare, Display, Smartphone } from "@gravity-ui/icons";
|
|
4
|
+
import { Button, Card, Chip, cn, Skeleton, Spinner } from "@heroui/react";
|
|
5
|
+
import { UAParser } from "ua-parser-js";
|
|
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 function Sessions({ className, ...props }) {
|
|
15
|
+
const { basePaths, localization, viewPaths, navigate } = useAuth();
|
|
16
|
+
const { data: sessionData } = useSession();
|
|
17
|
+
const { data: sessions, isPending } = useListSessions();
|
|
18
|
+
const { revokingSession, revokeSession } = useRevokeSession();
|
|
19
|
+
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.sessions }) }), _jsx(Card.Content, { className: "gap-3", children: isPending ? (_jsx(SessionRowSkeleton, {})) : (sessions
|
|
20
|
+
?.toSorted((session) => session.id === sessionData?.session.id ? -1 : 1)
|
|
21
|
+
.map((session) => {
|
|
22
|
+
const isCurrentSession = session.token === sessionData?.session.token;
|
|
23
|
+
const isRevoking = revokingSession === session.token;
|
|
24
|
+
const ua = new UAParser(session.userAgent || "").getResult();
|
|
25
|
+
const isMobile = ua.device.type === "mobile" ||
|
|
26
|
+
ua.device.type === "tablet" ||
|
|
27
|
+
ua.device.type === "wearable";
|
|
28
|
+
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([], {
|
|
29
|
+
dateStyle: "short",
|
|
30
|
+
timeStyle: "short"
|
|
31
|
+
}) })] }))] })] }), _jsx(Button, { isIconOnly: true, className: "ml-auto", variant: "ghost", size: "sm", onPress: () => isCurrentSession
|
|
32
|
+
? navigate({
|
|
33
|
+
to: `${basePaths.auth}/${viewPaths.auth.signOut}`
|
|
34
|
+
})
|
|
35
|
+
: revokeSession(session.token), isPending: !!revokingSession, "aria-label": localization.settings.revokeSession, children: isRevoking ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(ArrowRightFromSquare, {})) })] }, session.id));
|
|
36
|
+
})) })] }));
|
|
37
|
+
}
|
|
38
|
+
function SessionRowSkeleton() {
|
|
39
|
+
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" })] })] }) }));
|
|
40
|
+
}
|
|
@@ -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,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useUpdateUser } from "@better-auth-ui/react";
|
|
3
|
+
import { CheckIcon, PencilIcon } from "@heroicons/react/24/outline";
|
|
4
|
+
import { Button, Card, Description, FieldError, Fieldset, Form, Input, Label, Skeleton, Spinner, TextField } from "@heroui/react";
|
|
5
|
+
import { UserAvatar } from "../../user/user-avatar";
|
|
6
|
+
export function UserProfile({ className, ...config }) {
|
|
7
|
+
const { authClient, localization } = useAuth(config);
|
|
8
|
+
const { data: sessionData, isPending: isSessionPending } = authClient.useSession();
|
|
9
|
+
const [state, formAction, isPending] = useUpdateUser(config);
|
|
10
|
+
if (isSessionPending) {
|
|
11
|
+
return _jsx(UserProfileSkeleton, {});
|
|
12
|
+
}
|
|
13
|
+
return (_jsx(Card, { className: "p-4 md:p-6", children: _jsx(Form, { action: formAction, children: _jsxs(Fieldset, { className: "w-full gap-4 md:gap-6", children: [_jsx(Fieldset.Legend, { className: "text-xl", children: localization.settings.profile }), _jsx(Description, {}), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Button, { isIconOnly: true, variant: "ghost", className: "p-0 h-auto w-auto rounded-full", children: [_jsx(UserAvatar, { size: "lg" }), _jsx("span", { className: "absolute right-0 bottom-0 size-3.5 rounded-full bg-background ring-2 ring-surface-quaternary flex items-center justify-center", children: _jsx(PencilIcon, { className: "size-2 text-muted" }) })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-sm font-medium", children: sessionData?.user?.displayUsername ||
|
|
14
|
+
sessionData?.user?.name ||
|
|
15
|
+
sessionData?.user?.email }), (sessionData?.user?.displayUsername ||
|
|
16
|
+
sessionData?.user?.name) && (_jsx("p", { className: "text-muted text-xs leading-none", children: sessionData?.user?.email }))] })] }), _jsx(Fieldset.Group, { children: _jsxs(TextField, { name: "name", defaultValue: sessionData?.user?.name || state.name, isDisabled: isPending, children: [_jsx(Label, { children: localization.auth.name }), _jsx(Input, { placeholder: localization.auth.name }), _jsx(FieldError, {})] }) }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, children: [isPending ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(CheckIcon, { className: "size-4" })), localization.settings.saveChanges] }) })] }) }) }));
|
|
17
|
+
}
|
|
18
|
+
function UserProfileSkeleton() {
|
|
19
|
+
return (_jsxs(Card, { className: "p-4 md:p-6 gap-4 md:gap-6", children: [_jsx(Skeleton, { className: "h-7 w-16 rounded-xl" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-12 rounded-full" }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-4 mt-0.5 w-24 rounded-lg" }), _jsx(Skeleton, { className: "h-3 mt-0.5 w-32 rounded-lg" })] })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-4 w-12 my-0.5 rounded-lg" }), _jsx(Skeleton, { className: "h-9 w-full rounded-xl" })] }), _jsx(Skeleton, { className: "h-10 md:h-9 w-36 rounded-full mt-1" })] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SettingsView } from "@better-auth-ui/react/core";
|
|
2
|
+
import { type CardProps } from "@heroui/react";
|
|
3
|
+
import { type ComponentProps } from "react";
|
|
4
|
+
export type SettingsProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
hideNav?: boolean;
|
|
7
|
+
path?: string;
|
|
8
|
+
variant?: CardProps["variant"];
|
|
9
|
+
/** @remarks `SettingsView` */
|
|
10
|
+
view?: SettingsView;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Renders the settings UI and activates the appropriate settings view based on `view` or `path`.
|
|
14
|
+
*
|
|
15
|
+
* @param className - Additional CSS class names applied to the root container
|
|
16
|
+
* @param path - Route path used to resolve which settings view to activate when `view` is not provided
|
|
17
|
+
* @param view - Explicit settings view to activate, e.g. `"account"` or `"security"`
|
|
18
|
+
* @param hideNav - When `true`, hide the navigation tabs
|
|
19
|
+
* @param variant - Card variant forwarded to each settings section card
|
|
20
|
+
* @returns A DOM tree containing the responsive settings tabs and the currently selected settings panel
|
|
21
|
+
*
|
|
22
|
+
* @throws Error if neither `view` nor `path` is provided
|
|
23
|
+
*/
|
|
24
|
+
export declare function Settings({ className, hideNav, path, variant, view, ...props }: SettingsProps & ComponentProps<"div">): 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, useAuthenticate } from "@better-auth-ui/react";
|
|
3
|
+
import { cn, Tabs } from "@heroui/react";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { AccountSettings } from "./account/account-settings";
|
|
6
|
+
import { SecuritySettings } from "./security/security-settings";
|
|
7
|
+
/**
|
|
8
|
+
* Renders the settings UI and activates the appropriate settings view based on `view` or `path`.
|
|
9
|
+
*
|
|
10
|
+
* @param className - Additional CSS class names applied to the root container
|
|
11
|
+
* @param path - Route path used to resolve which settings view to activate when `view` is not provided
|
|
12
|
+
* @param view - Explicit settings view to activate, e.g. `"account"` or `"security"`
|
|
13
|
+
* @param hideNav - When `true`, hide the navigation tabs
|
|
14
|
+
* @param variant - Card variant forwarded to each settings section card
|
|
15
|
+
* @returns A DOM tree containing the responsive settings tabs and the currently selected settings panel
|
|
16
|
+
*
|
|
17
|
+
* @throws Error if neither `view` nor `path` is provided
|
|
18
|
+
*/
|
|
19
|
+
export function Settings({ className, hideNav, path, variant, view, ...props }) {
|
|
20
|
+
const { basePaths, localization, viewPaths } = useAuth();
|
|
21
|
+
useAuthenticate();
|
|
22
|
+
if (!view && !path) {
|
|
23
|
+
throw new Error("[Better Auth UI] Either `view` or `path` must be provided");
|
|
24
|
+
}
|
|
25
|
+
const settingsPathViews = useMemo(() => Object.fromEntries(Object.entries(viewPaths.settings).map(([k, v]) => [v, k])), [viewPaths.settings]);
|
|
26
|
+
const currentView = view || (path ? settingsPathViews[path] : undefined);
|
|
27
|
+
return (_jsxs(Tabs, { className: cn(className), orientation: "horizontal", selectedKey: currentView, ...props, children: [_jsx(Tabs.ListContainer, { children: _jsxs(Tabs.List, { "aria-label": localization.settings.settings, className: "overflow-auto w-fit", children: [_jsxs(Tabs.Tab, { id: "account", href: `${basePaths.settings}/${viewPaths.settings.account}`, children: [localization.settings.account, _jsx(Tabs.Indicator, {})] }), _jsxs(Tabs.Tab, { id: "security", href: `${basePaths.settings}/${viewPaths.settings.security}`, children: [localization.settings.security, _jsx(Tabs.Indicator, {})] })] }) }), _jsx(Tabs.Panel, { id: "account", className: "px-0", children: _jsx(AccountSettings, { variant: variant }) }), _jsx(Tabs.Panel, { id: "security", className: "px-0", children: _jsx(SecuritySettings, { variant: variant }) })] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useUpdateUser } from "@better-auth-ui/react";
|
|
3
|
+
import { CheckIcon, PencilIcon } from "@heroicons/react/24/outline";
|
|
4
|
+
import { Button, Card, Description, FieldError, Fieldset, Form, Input, Label, Skeleton, Spinner, TextField } from "@heroui/react";
|
|
5
|
+
import { UserAvatar } from "../user/user-avatar";
|
|
6
|
+
export function UserProfile({ className, ...config }) {
|
|
7
|
+
const { authClient, localization } = useAuth(config);
|
|
8
|
+
const { data: sessionData, isPending: isSessionPending } = authClient.useSession();
|
|
9
|
+
const [state, formAction, isPending] = useUpdateUser(config);
|
|
10
|
+
if (isSessionPending) {
|
|
11
|
+
return _jsx(UserProfileSkeleton, {});
|
|
12
|
+
}
|
|
13
|
+
return (_jsx(Card, { className: "p-4 md:p-6", children: _jsx(Form, { action: formAction, children: _jsxs(Fieldset, { className: "w-full gap-4 md:gap-6", children: [_jsx(Fieldset.Legend, { className: "text-xl", children: localization.settings.profile }), _jsx(Description, {}), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Button, { type: "button", isIconOnly: true, variant: "ghost", className: "p-0 h-auto w-auto rounded-full", children: [_jsx(UserAvatar, { ...config, size: "lg" }), _jsx("span", { className: "absolute right-0 bottom-0 size-3.5 rounded-full bg-background ring-2 ring-surface-quaternary flex items-center justify-center", children: _jsx(PencilIcon, { className: "size-2 text-muted" }) })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-sm font-medium", children: sessionData?.user?.displayUsername ||
|
|
14
|
+
sessionData?.user?.name ||
|
|
15
|
+
sessionData?.user?.email }), (sessionData?.user?.displayUsername ||
|
|
16
|
+
sessionData?.user?.name) && (_jsx("p", { className: "text-muted text-xs leading-none", children: sessionData?.user?.email }))] })] }), _jsx(Fieldset.Group, { children: _jsxs(TextField, { name: "name", defaultValue: sessionData?.user?.name || state.name, isDisabled: isPending, children: [_jsx(Label, { children: localization.auth.name }), _jsx(Input, { placeholder: localization.auth.name }), _jsx(FieldError, {})] }) }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, children: [isPending ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(CheckIcon, { className: "size-4" })), localization.settings.saveChanges] }) })] }) }) }));
|
|
17
|
+
}
|
|
18
|
+
function UserProfileSkeleton() {
|
|
19
|
+
return (_jsxs(Card, { className: "p-4 md:p-6 gap-4 md:gap-6", children: [_jsx(Skeleton, { className: "h-7 w-16 rounded-xl" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-12 rounded-full" }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-4 mt-0.5 w-24 rounded-lg" }), _jsx(Skeleton, { className: "h-3 mt-0.5 w-32 rounded-lg" })] })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-4 w-12 my-0.5 rounded-lg" }), _jsx(Skeleton, { className: "h-9 w-full rounded-xl" })] }), _jsx(Skeleton, { className: "h-10 md:h-9 w-36 rounded-full mt-1" })] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Session, User } from "better-auth";
|
|
2
|
+
type DeviceSession = {
|
|
3
|
+
session: Session;
|
|
4
|
+
user: User;
|
|
5
|
+
};
|
|
6
|
+
export type SwitchAccountItemProps = {
|
|
7
|
+
deviceSession: DeviceSession;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Render a dropdown item for switching to a different authenticated session.
|
|
11
|
+
*
|
|
12
|
+
* @param deviceSession - The device session to display and switch to when pressed
|
|
13
|
+
* @returns The switch account dropdown item as a JSX element
|
|
14
|
+
*/
|
|
15
|
+
export declare function SwitchAccountItem({ deviceSession }: SwitchAccountItemProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useSetActiveSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Dropdown, Spinner, toast } from "@heroui/react";
|
|
4
|
+
import { UserView } from "./user-view";
|
|
5
|
+
/**
|
|
6
|
+
* Render a dropdown item for switching to a different authenticated session.
|
|
7
|
+
*
|
|
8
|
+
* @param deviceSession - The device session to display and switch to when pressed
|
|
9
|
+
* @returns The switch account dropdown item as a JSX element
|
|
10
|
+
*/
|
|
11
|
+
export function SwitchAccountItem({ deviceSession }) {
|
|
12
|
+
const { mutate: setActiveSession, isPending } = useSetActiveSession({
|
|
13
|
+
onError: (error) => toast.danger(error.error?.message || error.message)
|
|
14
|
+
});
|
|
15
|
+
return (_jsxs(Dropdown.Item, { className: "px-2", isDisabled: isPending, onPress: () => setActiveSession({ sessionToken: deviceSession.session.token }), children: [_jsx(UserView, { user: deviceSession.user }), isPending && _jsx(Spinner, { color: "current", size: "sm", className: "ml-auto" })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the menu content for switching between multiple authenticated sessions.
|
|
3
|
+
*
|
|
4
|
+
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
5
|
+
* and provides an option to add a new account. This component should be rendered inside a
|
|
6
|
+
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
7
|
+
*
|
|
8
|
+
* @returns The switch account menu content as a JSX element
|
|
9
|
+
*/
|
|
10
|
+
export declare function SwitchAccountMenu(): 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, useListDeviceSessions, useSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Check, CirclePlus } from "@gravity-ui/icons";
|
|
4
|
+
import { Dropdown, Label, Separator, toast } from "@heroui/react";
|
|
5
|
+
import { SwitchAccountItem } from "./switch-account-item";
|
|
6
|
+
import { UserView } from "./user-view";
|
|
7
|
+
/**
|
|
8
|
+
* Render the menu content for switching between multiple authenticated sessions.
|
|
9
|
+
*
|
|
10
|
+
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
11
|
+
* and provides an option to add a new account. This component should be rendered inside a
|
|
12
|
+
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
13
|
+
*
|
|
14
|
+
* @returns The switch account menu content as a JSX element
|
|
15
|
+
*/
|
|
16
|
+
export function SwitchAccountMenu() {
|
|
17
|
+
const { basePaths, viewPaths, localization } = useAuth();
|
|
18
|
+
const { data: session } = useSession();
|
|
19
|
+
const { data: deviceSessions, isPending } = useListDeviceSessions({
|
|
20
|
+
throwOnError: (error) => {
|
|
21
|
+
if (error.error)
|
|
22
|
+
toast.danger(error.error.message);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return (_jsx(Dropdown.Popover, { className: "min-w-40 md:min-w-56 max-w-[48svw]", children: _jsxs(Dropdown.Menu, { children: [_jsxs(Dropdown.Item, { className: "px-2", children: [_jsx(UserView, { isPending: isPending }), !isPending && _jsx(Check, { className: "ml-auto" })] }), deviceSessions
|
|
27
|
+
?.filter((deviceSession) => deviceSession.session.id !== session?.session?.id)
|
|
28
|
+
.map((deviceSession) => (_jsx(SwitchAccountItem, { deviceSession: deviceSession }, deviceSession.session.id))), _jsx(Separator, {}), _jsxs(Dropdown.Item, { textValue: localization.auth.addAccount, href: `${basePaths.auth}/${viewPaths.auth.signIn}`, children: [_jsx(CirclePlus, { className: "text-muted" }), _jsx(Label, { children: localization.auth.addAccount })] })] }) }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the popover content for switching between multiple authenticated sessions.
|
|
3
|
+
*
|
|
4
|
+
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
5
|
+
* and provides an option to add a new account. This component should be rendered inside a
|
|
6
|
+
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
7
|
+
*
|
|
8
|
+
* @returns The switch account popover content as a JSX element
|
|
9
|
+
*/
|
|
10
|
+
export declare function SwitchAccountPopover(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuth, useListDeviceSessions, useSession, useSetActiveSession } from "@better-auth-ui/react";
|
|
3
|
+
import { Check, CirclePlus } from "@gravity-ui/icons";
|
|
4
|
+
import { Dropdown, Label, Separator } from "@heroui/react";
|
|
5
|
+
import { UserView } from "./user-view";
|
|
6
|
+
/**
|
|
7
|
+
* Render the popover content for switching between multiple authenticated sessions.
|
|
8
|
+
*
|
|
9
|
+
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
10
|
+
* and provides an option to add a new account. This component should be rendered inside a
|
|
11
|
+
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
12
|
+
*
|
|
13
|
+
* @returns The switch account popover content as a JSX element
|
|
14
|
+
*/
|
|
15
|
+
export function SwitchAccountPopover() {
|
|
16
|
+
const { basePaths, viewPaths, localization } = useAuth();
|
|
17
|
+
const { settingActiveSession, setActiveSession } = useSetActiveSession();
|
|
18
|
+
const { data: sessionData } = useSession();
|
|
19
|
+
const { data: deviceSessions } = useListDeviceSessions();
|
|
20
|
+
return (_jsxs(Dropdown.Menu, { children: [_jsxs(Dropdown.Item, { className: "px-2", children: [_jsx(UserView, {}), _jsx(Check, { className: "ml-auto" })] }), deviceSessions
|
|
21
|
+
?.filter((deviceSession) => deviceSession.session.id !== sessionData?.session?.id)
|
|
22
|
+
.map((deviceSession) => {
|
|
23
|
+
return (_jsx(Dropdown.Item, { className: "px-2", isDisabled: !!settingActiveSession, onPress: () => setActiveSession(deviceSession.session.token), children: _jsx(UserView, { user: deviceSession.user }) }, deviceSession.session.id));
|
|
24
|
+
}), _jsx(Separator, {}), _jsxs(Dropdown.Item, { textValue: localization.auth.addAccount, href: `${basePaths.auth}/${viewPaths.auth.signIn}`, children: [_jsx(CirclePlus, { className: "text-muted" }), _jsx(Label, { children: localization.auth.addAccount })] })] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the popover content for switching between multiple authenticated sessions.
|
|
3
|
+
*
|
|
4
|
+
* Shows the current session with a checkmark, lists other device sessions that can be activated,
|
|
5
|
+
* and provides an option to add a new account. This component should be rendered inside a
|
|
6
|
+
* Dropdown.SubmenuTrigger to defer the useListDeviceSessions query until the submenu is opened.
|
|
7
|
+
*
|
|
8
|
+
* @returns The switch account submenu popover content as a JSX element
|
|
9
|
+
*/
|
|
10
|
+
export declare function SwitchAccountSubmenu(): import("react/jsx-runtime").JSX.Element;
|