@better-auth-ui/heroui 1.6.9 → 1.6.10
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/auth/additional-field.d.ts +2 -2
- package/dist/components/auth/api-key/api-key.d.ts +1 -1
- package/dist/components/auth/api-key/api-keys.d.ts +1 -1
- package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +1 -1
- package/dist/components/auth/api-key/organization-api-keys.d.ts +1 -1
- package/dist/components/auth/auth-provider.d.ts +1 -1
- package/dist/components/auth/auth.d.ts +3 -3
- package/dist/components/auth/delete-user/danger-zone.d.ts +2 -2
- package/dist/components/auth/delete-user/delete-account.d.ts +1 -1
- package/dist/components/auth/email/email-changed-email.d.ts +2 -2
- package/dist/components/auth/email/email-verification-email.d.ts +2 -2
- package/dist/components/auth/email/magic-link-email.d.ts +2 -2
- package/dist/components/auth/email/new-device-email.d.ts +2 -2
- package/dist/components/auth/email/organization-invitation-email.d.ts +2 -2
- package/dist/components/auth/email/otp-email.d.ts +2 -2
- package/dist/components/auth/email/password-changed-email.d.ts +2 -2
- package/dist/components/auth/email/reset-password-email.d.ts +2 -2
- package/dist/components/auth/field-separator.d.ts +1 -1
- package/dist/components/auth/forgot-password.d.ts +1 -1
- package/dist/components/auth/magic-link/magic-link-button.d.ts +1 -1
- package/dist/components/auth/magic-link/magic-link.d.ts +2 -2
- package/dist/components/auth/multi-session/manage-account.d.ts +1 -1
- package/dist/components/auth/multi-session/manage-accounts.d.ts +1 -1
- package/dist/components/auth/multi-session/switch-account-submenu-item.d.ts +1 -1
- package/dist/components/auth/organization/delete-organization-dialog.d.ts +1 -1
- package/dist/components/auth/organization/leave-organization-dialog.d.ts +1 -1
- package/dist/components/auth/organization/organization-danger-zone.d.ts +2 -2
- package/dist/components/auth/organization/organization-invitation-row.d.ts +1 -1
- package/dist/components/auth/organization/organization-invitations.d.ts +1 -1
- package/dist/components/auth/organization/organization-logo.d.ts +3 -3
- package/dist/components/auth/organization/organization-member-row.d.ts +1 -1
- package/dist/components/auth/organization/organization-members.d.ts +1 -1
- package/dist/components/auth/organization/organization-people.d.ts +1 -1
- package/dist/components/auth/organization/organization-profile.d.ts +1 -1
- package/dist/components/auth/organization/organization-row.d.ts +1 -1
- package/dist/components/auth/organization/organization-settings.d.ts +2 -2
- package/dist/components/auth/organization/organization-switcher.d.ts +3 -3
- package/dist/components/auth/organization/organization-view-skeleton.d.ts +2 -2
- package/dist/components/auth/organization/organization-view.d.ts +3 -3
- package/dist/components/auth/organization/organization.d.ts +3 -3
- package/dist/components/auth/organization/organizations-settings.d.ts +2 -2
- package/dist/components/auth/organization/organizations.d.ts +1 -1
- package/dist/components/auth/organization/remove-member-dialog.d.ts +1 -1
- package/dist/components/auth/organization/slug-field.d.ts +1 -1
- package/dist/components/auth/organization/user-invitation-row.d.ts +1 -1
- package/dist/components/auth/organization/user-invitations.d.ts +1 -1
- package/dist/components/auth/passkey/passkey-button.d.ts +1 -1
- package/dist/components/auth/passkey/passkey.d.ts +1 -1
- package/dist/components/auth/passkey/passkeys.d.ts +1 -1
- package/dist/components/auth/provider-button.d.ts +2 -2
- package/dist/components/auth/reset-password.d.ts +1 -1
- package/dist/components/auth/settings/account/account-settings.d.ts +2 -2
- package/dist/components/auth/settings/account/change-email.d.ts +1 -1
- package/dist/components/auth/settings/account/user-profile.d.ts +1 -1
- package/dist/components/auth/settings/security/active-session.d.ts +1 -1
- package/dist/components/auth/settings/security/active-sessions.d.ts +1 -1
- package/dist/components/auth/settings/security/change-password.d.ts +1 -1
- package/dist/components/auth/settings/security/linked-account.d.ts +1 -1
- package/dist/components/auth/settings/security/linked-accounts.d.ts +1 -1
- package/dist/components/auth/settings/security/security-settings.d.ts +2 -2
- package/dist/components/auth/settings/settings.d.ts +3 -3
- package/dist/components/auth/sign-in.d.ts +2 -2
- package/dist/components/auth/sign-up.d.ts +2 -2
- package/dist/components/auth/theme/appearance.d.ts +1 -1
- package/dist/components/auth/user/user-avatar.d.ts +3 -3
- package/dist/components/auth/user/user-button.d.ts +2 -2
- package/dist/components/auth/user/user-view.d.ts +3 -3
- package/dist/components/auth/username/sign-in-username.d.ts +2 -2
- package/dist/components/auth/username/username-field.d.ts +1 -1
- package/dist/email.d.ts +8 -8
- package/dist/email.js +211 -8
- package/dist/index.d.ts +21 -21
- package/dist/index.js +1682 -21
- package/dist/lib/auth/api-key-plugin.d.ts +3 -3
- package/dist/lib/auth/auth-plugin.d.ts +5 -5
- package/dist/lib/auth/delete-user-plugin.d.ts +2 -2
- package/dist/lib/auth/magic-link-plugin.d.ts +3 -3
- package/dist/lib/auth/multi-session-plugin.d.ts +3 -3
- package/dist/lib/auth/organization-plugin.d.ts +2 -2
- package/dist/lib/auth/passkey-plugin.d.ts +3 -3
- package/dist/lib/auth/theme-plugin.d.ts +3 -3
- package/dist/lib/auth/username-plugin.d.ts +3 -3
- package/dist/plugins.d.ts +46 -46
- package/dist/plugins.js +2824 -47
- package/dist/user-view--47tVtJ3.js +113 -0
- package/package.json +2 -3
- package/dist/components/auth/additional-field.js +0 -137
- package/dist/components/auth/api-key/api-key-skeleton.js +0 -5
- package/dist/components/auth/api-key/api-key.js +0 -17
- package/dist/components/auth/api-key/api-keys-empty.js +0 -9
- package/dist/components/auth/api-key/api-keys.js +0 -22
- package/dist/components/auth/api-key/create-api-key-dialog.js +0 -44
- package/dist/components/auth/api-key/delete-api-key-dialog.js +0 -17
- package/dist/components/auth/api-key/new-api-key-dialog.js +0 -24
- package/dist/components/auth/api-key/organization-api-keys.js +0 -22
- package/dist/components/auth/auth-provider.js +0 -11
- package/dist/components/auth/auth.js +0 -88
- package/dist/components/auth/delete-user/danger-zone.js +0 -12
- package/dist/components/auth/delete-user/delete-account.js +0 -50
- package/dist/components/auth/email/email-changed-email.js +0 -23
- package/dist/components/auth/email/email-verification-email.js +0 -23
- package/dist/components/auth/email/magic-link-email.js +0 -23
- package/dist/components/auth/email/new-device-email.js +0 -23
- package/dist/components/auth/email/organization-invitation-email.js +0 -23
- package/dist/components/auth/email/otp-email.js +0 -22
- package/dist/components/auth/email/password-changed-email.js +0 -23
- package/dist/components/auth/email/reset-password-email.js +0 -23
- package/dist/components/auth/error-toaster.js +0 -33
- package/dist/components/auth/field-separator.js +0 -5
- package/dist/components/auth/forgot-password.js +0 -37
- package/dist/components/auth/magic-link/magic-link-button.js +0 -34
- package/dist/components/auth/magic-link/magic-link.js +0 -40
- package/dist/components/auth/multi-session/manage-account.js +0 -33
- package/dist/components/auth/multi-session/manage-accounts.js +0 -33
- package/dist/components/auth/multi-session/switch-account-submenu-content.js +0 -25
- package/dist/components/auth/multi-session/switch-account-submenu-item.js +0 -17
- package/dist/components/auth/multi-session/switch-account-submenu.js +0 -24
- package/dist/components/auth/organization/change-organization-logo.js +0 -69
- package/dist/components/auth/organization/create-organization-dialog.js +0 -37
- package/dist/components/auth/organization/delete-organization-dialog.js +0 -25
- package/dist/components/auth/organization/delete-organization-skeleton.js +0 -8
- package/dist/components/auth/organization/delete-organization.js +0 -27
- package/dist/components/auth/organization/invite-member-dialog.js +0 -47
- package/dist/components/auth/organization/leave-organization-dialog.js +0 -21
- package/dist/components/auth/organization/leave-organization.js +0 -16
- package/dist/components/auth/organization/organization-danger-zone.js +0 -23
- package/dist/components/auth/organization/organization-invitation-row-skeleton.js +0 -9
- package/dist/components/auth/organization/organization-invitation-row.js +0 -31
- package/dist/components/auth/organization/organization-invitations-empty.js +0 -13
- package/dist/components/auth/organization/organization-invitations.js +0 -66
- package/dist/components/auth/organization/organization-logo.js +0 -14
- package/dist/components/auth/organization/organization-member-row-skeleton.js +0 -9
- package/dist/components/auth/organization/organization-member-row.js +0 -34
- package/dist/components/auth/organization/organization-members.js +0 -67
- package/dist/components/auth/organization/organization-people.js +0 -11
- package/dist/components/auth/organization/organization-profile.js +0 -34
- package/dist/components/auth/organization/organization-row.js +0 -31
- package/dist/components/auth/organization/organization-settings.js +0 -13
- package/dist/components/auth/organization/organization-switcher.js +0 -48
- package/dist/components/auth/organization/organization-view-skeleton.js +0 -9
- package/dist/components/auth/organization/organization-view.js +0 -31
- package/dist/components/auth/organization/organization.js +0 -60
- package/dist/components/auth/organization/organizations-empty.js +0 -9
- package/dist/components/auth/organization/organizations-settings.js +0 -17
- package/dist/components/auth/organization/organizations.js +0 -21
- package/dist/components/auth/organization/remove-member-dialog.js +0 -20
- package/dist/components/auth/organization/slug-field.js +0 -38
- package/dist/components/auth/organization/user-invitation-row-skeleton.js +0 -8
- package/dist/components/auth/organization/user-invitation-row.js +0 -22
- package/dist/components/auth/organization/user-invitations-empty.js +0 -12
- package/dist/components/auth/organization/user-invitations.js +0 -18
- package/dist/components/auth/passkey/add-passkey-dialog.js +0 -22
- package/dist/components/auth/passkey/delete-passkey-dialog.js +0 -14
- package/dist/components/auth/passkey/passkey-button.js +0 -25
- package/dist/components/auth/passkey/passkey-skeleton.js +0 -5
- package/dist/components/auth/passkey/passkey.js +0 -17
- package/dist/components/auth/passkey/passkeys-empty.js +0 -9
- package/dist/components/auth/passkey/passkeys.js +0 -16
- package/dist/components/auth/provider-button.js +0 -29
- package/dist/components/auth/provider-buttons.js +0 -29
- package/dist/components/auth/reset-password.js +0 -57
- package/dist/components/auth/settings/account/account-settings.js +0 -22
- package/dist/components/auth/settings/account/change-avatar.js +0 -54
- package/dist/components/auth/settings/account/change-email.js +0 -28
- package/dist/components/auth/settings/account/user-profile.js +0 -65
- package/dist/components/auth/settings/security/active-session.js +0 -52
- package/dist/components/auth/settings/security/active-sessions.js +0 -23
- package/dist/components/auth/settings/security/change-password.js +0 -79
- package/dist/components/auth/settings/security/linked-account.js +0 -38
- package/dist/components/auth/settings/security/linked-accounts.js +0 -36
- package/dist/components/auth/settings/security/security-settings.js +0 -20
- package/dist/components/auth/settings/settings.js +0 -53
- package/dist/components/auth/sign-in.js +0 -64
- package/dist/components/auth/sign-out.js +0 -33
- package/dist/components/auth/sign-up.js +0 -98
- package/dist/components/auth/theme/appearance.js +0 -24
- package/dist/components/auth/theme/theme-toggle-item.js +0 -57
- package/dist/components/auth/user/user-avatar.js +0 -32
- package/dist/components/auth/user/user-button.js +0 -40
- package/dist/components/auth/user/user-view.js +0 -27
- package/dist/components/auth/username/sign-in-username.js +0 -88
- package/dist/components/auth/username/username-field.js +0 -45
- package/dist/lib/auth/api-key-plugin.js +0 -12
- package/dist/lib/auth/auth-plugin.js +0 -1
- package/dist/lib/auth/delete-user-plugin.js +0 -7
- package/dist/lib/auth/magic-link-plugin.js +0 -18
- package/dist/lib/auth/multi-session-plugin.js +0 -9
- package/dist/lib/auth/organization-plugin.js +0 -19
- package/dist/lib/auth/passkey-plugin.js +0 -9
- package/dist/lib/auth/theme-plugin.js +0 -26
- package/dist/lib/auth/username-plugin.js +0 -19
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useChangeEmail, useSession } from "@better-auth-ui/react";
|
|
3
|
-
import { Button, Card, cn, FieldError, Fieldset, Form, Input, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
|
|
4
|
-
/**
|
|
5
|
-
* Render a card containing a form to view and update the authenticated user's email.
|
|
6
|
-
*
|
|
7
|
-
* Shows a loading skeleton until session data is available, displays the current
|
|
8
|
-
* email as the form's default value, and sends a verification email to the
|
|
9
|
-
* new address upon successful submission.
|
|
10
|
-
*
|
|
11
|
-
* @returns A JSX element rendering the change-email card and form
|
|
12
|
-
*/
|
|
13
|
-
export function ChangeEmail({ className, variant, ...props }) {
|
|
14
|
-
const { authClient, localization, baseURL, viewPaths } = useAuth();
|
|
15
|
-
const { data: session } = useSession(authClient);
|
|
16
|
-
const { mutate: changeEmail, isPending } = useChangeEmail(authClient, {
|
|
17
|
-
onSuccess: () => toast.success(localization.settings.changeEmailSuccess)
|
|
18
|
-
});
|
|
19
|
-
function handleSubmit(e) {
|
|
20
|
-
e.preventDefault();
|
|
21
|
-
const formData = new FormData(e.currentTarget);
|
|
22
|
-
changeEmail({
|
|
23
|
-
newEmail: formData.get("email"),
|
|
24
|
-
callbackURL: `${baseURL}/${viewPaths.settings.account}`
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.changeEmail }), _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(Fieldset.Group, { children: _jsxs(TextField, { name: "email", type: "email", defaultValue: session?.user.email, isDisabled: isPending || !session, children: [_jsx(Label, { children: localization.auth.email }), session ? (_jsx(Input, { required: true, variant: variant === "transparent" ? "primary" : "secondary", autoComplete: "email", placeholder: localization.auth.emailPlaceholder })) : (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }, `${session?.user.id}-${session?.user.email}-email`) }), _jsx(Fieldset.Actions, { children: _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !session, size: "sm", children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.settings.updateEmail] }) })] }) }) }) })] }));
|
|
28
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { parseAdditionalFieldValue } from "@better-auth-ui/core";
|
|
3
|
-
import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react";
|
|
4
|
-
import { Button, Card, cn, FieldError, Form, Input, Label, Skeleton, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
-
import { AdditionalField } from "../../additional-field";
|
|
6
|
-
import { ChangeAvatar } from "./change-avatar";
|
|
7
|
-
/**
|
|
8
|
-
* Render a profile card that lets the authenticated user view and update their
|
|
9
|
-
* display name, avatar, and any plugin- or user-supplied additional fields.
|
|
10
|
-
*/
|
|
11
|
-
export function UserProfile({ className, variant, ...props }) {
|
|
12
|
-
const { additionalFields, authClient, localization } = useAuth();
|
|
13
|
-
const { data: session } = useSession(authClient);
|
|
14
|
-
const { mutate: updateUser, isPending } = useUpdateUser(authClient, {
|
|
15
|
-
onSuccess: () => toast.success(localization.settings.profileUpdatedSuccess)
|
|
16
|
-
});
|
|
17
|
-
async function handleSubmit(e) {
|
|
18
|
-
e.preventDefault();
|
|
19
|
-
const formData = new FormData(e.currentTarget);
|
|
20
|
-
const name = formData.get("name");
|
|
21
|
-
const additionalFieldValues = {};
|
|
22
|
-
for (const field of additionalFields ?? []) {
|
|
23
|
-
if (field.profile === false || field.readOnly)
|
|
24
|
-
continue;
|
|
25
|
-
const value = parseAdditionalFieldValue(field, formData.get(field.name));
|
|
26
|
-
if (field.validate) {
|
|
27
|
-
try {
|
|
28
|
-
await field.validate(value);
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
toast.danger(error instanceof Error ? error.message : String(error));
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// `null` = explicit clear (forward to backend); `undefined` = omitted.
|
|
36
|
-
if (value !== undefined) {
|
|
37
|
-
additionalFieldValues[field.name] = value;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
updateUser({
|
|
41
|
-
name,
|
|
42
|
-
...additionalFieldValues
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return (_jsxs("div", { children: [_jsx("h2", { className: cn("text-sm font-semibold mb-3"), children: localization.settings.userProfile }), _jsx(Card, { className: cn("p-4 gap-4", className), variant: variant, ...props, children: _jsx(Card.Content, { children: _jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsx(ChangeAvatar, {}), _jsxs(TextField, { name: "name", defaultValue: session?.user.name, isDisabled: isPending || !session, children: [_jsx(Label, { children: localization.auth.name }), _jsx(Input, { className: cn(!session && "hidden"), autoComplete: "name", placeholder: localization.auth.name, variant: variant === "transparent" ? "primary" : "secondary" }), !session && (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" })), _jsx(FieldError, {})] }, `${session?.user?.id}-${session?.user?.name}-name`), additionalFields
|
|
46
|
-
?.filter((field) => field.profile !== false)
|
|
47
|
-
.map((field) => {
|
|
48
|
-
if (!session) {
|
|
49
|
-
if (field.inputType === "hidden") {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
return (_jsx(Skeleton, { className: "h-10 md:h-9 w-full rounded-xl" }, field.name));
|
|
53
|
-
}
|
|
54
|
-
const value = session.user[field.name];
|
|
55
|
-
// Re-mount when the user or the session value changes so the
|
|
56
|
-
// field's uncontrolled `defaultValue` reflects the latest data.
|
|
57
|
-
const key = `${session.user.id}-${field.name}-${value instanceof Date
|
|
58
|
-
? value.toISOString()
|
|
59
|
-
: String(value ?? "")}`;
|
|
60
|
-
return (_jsx(AdditionalField, { name: field.name, field: {
|
|
61
|
-
...field,
|
|
62
|
-
defaultValue: value
|
|
63
|
-
}, isPending: isPending, variant: variant }, key));
|
|
64
|
-
}), _jsxs(Button, { type: "submit", isPending: isPending, isDisabled: !session, size: "sm", className: "self-start mt-1", children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.settings.saveChanges] })] }) }) })] }));
|
|
65
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
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 { authClient, basePaths, localization, viewPaths, navigate } = useAuth();
|
|
36
|
-
const { data: session } = useSession(authClient, { refetchOnMount: false });
|
|
37
|
-
const { mutate: revokeSession, isPending: isRevoking } = useRevokeSession(authClient, {
|
|
38
|
-
onSuccess: () => toast.success(localization.settings.revokeSessionSuccess)
|
|
39
|
-
});
|
|
40
|
-
const isCurrentSession = activeSession.token === session?.session.token;
|
|
41
|
-
const ua = Bowser.parse(activeSession.userAgent || "");
|
|
42
|
-
const isMobile = ua.platform.type === "mobile" || ua.platform.type === "tablet";
|
|
43
|
-
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
|
|
44
|
-
? navigate({
|
|
45
|
-
to: `${basePaths.auth}/${viewPaths.auth.signOut}`
|
|
46
|
-
})
|
|
47
|
-
: revokeSession(activeSession), isPending: isRevoking, "aria-label": isCurrentSession
|
|
48
|
-
? localization.auth.signOut
|
|
49
|
-
: localization.settings.revokeSession, children: [isRevoking ? (_jsx(Spinner, { color: "current", size: "sm" })) : isCurrentSession ? (_jsx(ArrowRightFromSquare, {})) : (_jsx(Xmark, {})), isCurrentSession
|
|
50
|
-
? localization.auth.signOut
|
|
51
|
-
: localization.settings.revoke] })] }));
|
|
52
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
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 } 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 { authClient, localization } = useAuth();
|
|
15
|
-
const { data: session } = useSession(authClient);
|
|
16
|
-
const { data: sessions, isPending } = useListSessions(authClient);
|
|
17
|
-
const activeSessions = sessions &&
|
|
18
|
-
[...sessions].sort((a, b) => a.id === session?.session.id ? -1 : b.id === session?.session.id ? 1 : 0);
|
|
19
|
-
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 my-4" })), _jsx(ActiveSession, { activeSession: activeSession })] }, activeSession.id)))) }) })] }));
|
|
20
|
-
}
|
|
21
|
-
function SessionRowSkeleton() {
|
|
22
|
-
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" })] })] }) }));
|
|
23
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
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 { authClient, emailAndPassword, localization } = useAuth();
|
|
17
|
-
const { data: session } = useSession(authClient);
|
|
18
|
-
const { data: accounts, isPending: isAccountsPending } = useListAccounts(authClient);
|
|
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 { authClient, localization } = useAuth();
|
|
27
|
-
const { data: session } = useSession(authClient);
|
|
28
|
-
const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset(authClient, {
|
|
29
|
-
onSuccess: () => toast.success(localization.auth.passwordResetEmailSent)
|
|
30
|
-
});
|
|
31
|
-
const handleSetPassword = () => {
|
|
32
|
-
if (!session?.user.email)
|
|
33
|
-
return;
|
|
34
|
-
requestPasswordReset({ email: session.user.email });
|
|
35
|
-
};
|
|
36
|
-
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] })] }) })] }));
|
|
37
|
-
}
|
|
38
|
-
function ChangePasswordForm({ className, variant, emailAndPassword, localization, session, ...props }) {
|
|
39
|
-
const { authClient } = useAuth();
|
|
40
|
-
const [currentPassword, setCurrentPassword] = useState("");
|
|
41
|
-
const [newPassword, setNewPassword] = useState("");
|
|
42
|
-
const [confirmPassword, setConfirmPassword] = useState("");
|
|
43
|
-
const { mutate: changePassword, isPending } = useChangePassword(authClient, {
|
|
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
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
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 { authClient, baseURL, localization } = useAuth();
|
|
18
|
-
const { data: accountInfo, isPending: isLoadingInfo } = useAccountInfo(authClient, { query: { accountId: account?.accountId } });
|
|
19
|
-
const { mutate: linkSocial, isPending: isLinking } = useLinkSocial(authClient);
|
|
20
|
-
const { mutate: unlinkAccount, isPending: isUnlinking } = useUnlinkAccount(authClient, {
|
|
21
|
-
onSuccess: () => toast.success(localization.settings.accountUnlinked)
|
|
22
|
-
});
|
|
23
|
-
const ProviderIcon = providerIcons[provider];
|
|
24
|
-
const providerName = getProviderName(provider);
|
|
25
|
-
const displayName = accountInfo?.data?.login ||
|
|
26
|
-
accountInfo?.data?.username ||
|
|
27
|
-
accountInfo?.user?.email ||
|
|
28
|
-
accountInfo?.user?.name ||
|
|
29
|
-
account?.accountId;
|
|
30
|
-
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: "size-4.5" })) : (_jsx(PlugConnection, { className: "size-4.5" })) }), _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
|
|
31
|
-
? displayName
|
|
32
|
-
: 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
|
|
33
|
-
.replace("{{provider}}", "")
|
|
34
|
-
.trim()] })) : (_jsxs(Button, { className: "ml-auto shrink-0", variant: "outline", size: "sm", onPress: () => linkSocial({
|
|
35
|
-
provider,
|
|
36
|
-
callbackURL: `${baseURL}${window.location.pathname}`
|
|
37
|
-
}), isPending: isLinking, "aria-label": localization.settings.linkProvider.replace("{{provider}}", providerName), children: [isLinking ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Link, {}), localization.settings.link] }))] }));
|
|
38
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
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 } 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 { authClient, localization, socialProviders } = useAuth();
|
|
16
|
-
const { data: accounts, isPending } = useListAccounts(authClient);
|
|
17
|
-
const linkedAccounts = accounts?.filter((account) => account.providerId !== "credential");
|
|
18
|
-
const allRows = [
|
|
19
|
-
...(linkedAccounts?.map((account) => ({
|
|
20
|
-
key: account.id,
|
|
21
|
-
account,
|
|
22
|
-
provider: account.providerId
|
|
23
|
-
})) ?? []),
|
|
24
|
-
...(socialProviders?.map((provider) => ({
|
|
25
|
-
key: provider,
|
|
26
|
-
account: undefined,
|
|
27
|
-
provider
|
|
28
|
-
})) ?? [])
|
|
29
|
-
];
|
|
30
|
-
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
|
|
31
|
-
? socialProviders?.map((provider, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 my-4" })), _jsx(AccountRowSkeleton, {})] }, provider)))
|
|
32
|
-
: allRows.map((row, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 my-4" })), _jsx(LinkedAccount, { account: row.account, provider: row.provider })] }, row.key))) }) })] }));
|
|
33
|
-
}
|
|
34
|
-
function AccountRowSkeleton() {
|
|
35
|
-
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" })] })] }) }));
|
|
36
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
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 { LinkedAccounts } from "./linked-accounts";
|
|
7
|
-
/**
|
|
8
|
-
* Renders the security settings layout including password management, linked accounts, and active sessions.
|
|
9
|
-
*
|
|
10
|
-
* ChangePassword is rendered when password authentication is enabled; LinkedAccounts is rendered when social providers are present.
|
|
11
|
-
* Each registered auth plugin may contribute `securityCards` (for example delete-user, passkeys).
|
|
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 function SecuritySettings({ className, variant, ...props }) {
|
|
18
|
-
const { emailAndPassword, plugins, socialProviders } = useAuth();
|
|
19
|
-
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 }), plugins.flatMap((plugin) => plugin.securityCards?.map((Card, index) => (_jsx(Card, { variant: variant }, `${plugin.id}-${index.toString()}`))))] }));
|
|
20
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthenticate } from "@better-auth-ui/react";
|
|
3
|
-
import { Person, Shield } from "@gravity-ui/icons";
|
|
4
|
-
import { cn, Tabs } from "@heroui/react";
|
|
5
|
-
import { useMemo } from "react";
|
|
6
|
-
import { AccountSettings } from "./account/account-settings";
|
|
7
|
-
import { SecuritySettings } from "./security/security-settings";
|
|
8
|
-
/**
|
|
9
|
-
* Renders the settings UI and activates the appropriate settings view based on `view` or `path`.
|
|
10
|
-
*
|
|
11
|
-
* @param className - Additional CSS class names applied to the root container
|
|
12
|
-
* @param path - Route path used to resolve which settings view to activate when `view` is not provided
|
|
13
|
-
* @param view - Explicit settings view to activate, e.g. `"account"` or `"security"`
|
|
14
|
-
* @param hideNav - When `true`, hide the navigation tabs
|
|
15
|
-
* @param variant - Card variant forwarded to each settings section card
|
|
16
|
-
* @returns A DOM tree containing the responsive settings tabs and the currently selected settings panel
|
|
17
|
-
*
|
|
18
|
-
* @throws Error if neither `view` nor `path` is provided, or if `path` does not match any settings view
|
|
19
|
-
*/
|
|
20
|
-
export function Settings({ className, hideNav, path, variant, view, ...props }) {
|
|
21
|
-
if (!view && !path) {
|
|
22
|
-
throw new Error("[Better Auth UI] Either `view` or `path` must be provided");
|
|
23
|
-
}
|
|
24
|
-
const { authClient, basePaths, localization, viewPaths, plugins } = useAuth();
|
|
25
|
-
useAuthenticate(authClient);
|
|
26
|
-
// Built-ins first, then plugin segments
|
|
27
|
-
const currentView = useMemo(() => {
|
|
28
|
-
if (view)
|
|
29
|
-
return view;
|
|
30
|
-
if (!path)
|
|
31
|
-
return undefined;
|
|
32
|
-
const match = [
|
|
33
|
-
viewPaths.settings,
|
|
34
|
-
...plugins.map((plugin) => plugin.viewPaths?.settings)
|
|
35
|
-
]
|
|
36
|
-
.flatMap((source) => Object.entries(source ?? {}))
|
|
37
|
-
.find(([, segment]) => segment === path);
|
|
38
|
-
return match?.[0];
|
|
39
|
-
}, [view, path, viewPaths.settings, plugins]);
|
|
40
|
-
if (!currentView) {
|
|
41
|
-
const validPaths = [
|
|
42
|
-
viewPaths.settings,
|
|
43
|
-
...plugins.map((plugin) => plugin.viewPaths?.settings)
|
|
44
|
-
]
|
|
45
|
-
.flatMap((source) => Object.values(source ?? {}))
|
|
46
|
-
.join(", ");
|
|
47
|
-
throw new Error(`[Better Auth UI] Unknown settings path "${path}". Valid paths are: ${validPaths}`);
|
|
48
|
-
}
|
|
49
|
-
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: "max-w-fit overflow-x-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: [_jsxs(Tabs.Tab, { id: "account", href: `${basePaths.settings}/${viewPaths.settings.account}`, className: "gap-2", onPress: (e) => e.target.scrollIntoView({ behavior: "smooth", inline: "center" }), children: [_jsx(Person, { className: "text-muted" }), localization.settings.account, _jsx(Tabs.Indicator, {})] }), _jsxs(Tabs.Tab, { id: "security", href: `${basePaths.settings}/${viewPaths.settings.security}`, className: "gap-2", onPress: (e) => e.target.scrollIntoView({ behavior: "smooth", inline: "center" }), children: [_jsx(Shield, { className: "text-muted" }), localization.settings.security, _jsx(Tabs.Indicator, {})] }), plugins.flatMap((plugin) => plugin.settingsTabs?.map((settingsTab, index) => (_jsxs(Tabs.Tab, { id: settingsTab.view, href: `${basePaths.settings}/${plugin.viewPaths?.settings?.[settingsTab.view]}`, className: "gap-2", onPress: (e) => e.target.scrollIntoView({
|
|
50
|
-
behavior: "smooth",
|
|
51
|
-
inline: "center"
|
|
52
|
-
}), children: [settingsTab.label, _jsx(Tabs.Indicator, {})] }, `${plugin.id}-${index.toString()}`))) ?? [])] }) }), _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 }) }), plugins.flatMap((plugin) => plugin.settingsTabs?.map((settingsTab, index) => (_jsx(Tabs.Panel, { id: settingsTab.view, className: "px-0", children: _jsx(settingsTab.component, { variant: variant }) }, `${plugin.id}-${index.toString()}`))))] }));
|
|
53
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { authMutationKeys } from "@better-auth-ui/core";
|
|
3
|
-
import { useAuth, useFetchOptions, useSendVerificationEmail, useSignInEmail } from "@better-auth-ui/react";
|
|
4
|
-
import { Button, Card, Checkbox, cn, Description, FieldError, Form, Input, Label, Link, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
-
import { useIsMutating } from "@tanstack/react-query";
|
|
6
|
-
import { useState } from "react";
|
|
7
|
-
import { FieldSeparator } from "./field-separator";
|
|
8
|
-
import { ProviderButtons } from "./provider-buttons";
|
|
9
|
-
/**
|
|
10
|
-
* Render the sign-in UI using auth context for configuration and localization.
|
|
11
|
-
*
|
|
12
|
-
* @returns The sign-in JSX element containing email/password fields, optional magic-link button, and social provider buttons.
|
|
13
|
-
*/
|
|
14
|
-
export function SignIn({ className, socialLayout, socialPosition = "bottom", variant, ...props }) {
|
|
15
|
-
const { authClient, basePaths, baseURL, emailAndPassword, localization, plugins, redirectTo, socialProviders, viewPaths, navigate } = useAuth();
|
|
16
|
-
const { fetchOptions, resetFetchOptions } = useFetchOptions();
|
|
17
|
-
const [password, setPassword] = useState("");
|
|
18
|
-
const { mutate: sendVerificationEmail } = useSendVerificationEmail(authClient, {
|
|
19
|
-
onSuccess: () => toast.success(localization.auth.verificationEmailSent)
|
|
20
|
-
});
|
|
21
|
-
const { mutate: signInEmail, isPending: signInEmailPending } = useSignInEmail(authClient, {
|
|
22
|
-
onError: (error, { email }) => {
|
|
23
|
-
setPassword("");
|
|
24
|
-
if (error.error?.code === "EMAIL_NOT_VERIFIED") {
|
|
25
|
-
toast.danger(error.error?.message || error.message, {
|
|
26
|
-
actionProps: {
|
|
27
|
-
children: localization.auth.resend,
|
|
28
|
-
onClick: () => sendVerificationEmail({
|
|
29
|
-
email,
|
|
30
|
-
callbackURL: `${baseURL}${redirectTo}`
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
toast.danger(error.error?.message || error.message);
|
|
37
|
-
}
|
|
38
|
-
resetFetchOptions();
|
|
39
|
-
},
|
|
40
|
-
onSuccess: () => navigate({ to: redirectTo })
|
|
41
|
-
});
|
|
42
|
-
const handleSubmit = (e) => {
|
|
43
|
-
e.preventDefault();
|
|
44
|
-
const formData = new FormData(e.currentTarget);
|
|
45
|
-
const email = formData.get("email");
|
|
46
|
-
const rememberMe = formData.get("rememberMe") === "on";
|
|
47
|
-
signInEmail({
|
|
48
|
-
email,
|
|
49
|
-
password,
|
|
50
|
-
...(emailAndPassword?.rememberMe ? { rememberMe } : {}),
|
|
51
|
-
fetchOptions
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
const signInMutating = useIsMutating({
|
|
55
|
-
mutationKey: authMutationKeys.signIn.all
|
|
56
|
-
});
|
|
57
|
-
const signUpMutating = useIsMutating({
|
|
58
|
-
mutationKey: authMutationKeys.signUp.all
|
|
59
|
-
});
|
|
60
|
-
const isPending = signInMutating + signUpMutating > 0;
|
|
61
|
-
const Captcha = plugins.find((plugin) => plugin.captchaComponent)?.captchaComponent;
|
|
62
|
-
const showSeparator = emailAndPassword?.enabled && !!socialProviders?.length;
|
|
63
|
-
return (_jsxs(Card, { className: cn("w-full max-w-sm gap-4 md:p-6", className), variant: variant, ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl font-semibold mb-1", children: localization.auth.signIn }) }), _jsxs(Card.Content, { className: "gap-4", children: [socialPosition === "top" && (_jsxs(_Fragment, { children: [!!socialProviders?.length && (_jsx(ProviderButtons, { socialLayout: socialLayout })), showSeparator && (_jsx(FieldSeparator, { children: localization.auth.or }))] })), emailAndPassword?.enabled && (_jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs(TextField, { name: "email", type: "email", autoComplete: "email", isDisabled: isPending, children: [_jsx(Label, { children: localization.auth.email }), _jsx(Input, { placeholder: localization.auth.emailPlaceholder, variant: variant === "transparent" ? "primary" : "secondary", required: true }), _jsx(FieldError, {})] }), _jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, name: "password", type: "password", autoComplete: "current-password", isDisabled: isPending, value: password, onChange: setPassword, children: [_jsx(Label, { children: localization.auth.password }), _jsx(Input, { placeholder: localization.auth.passwordPlaceholder, variant: variant === "transparent" ? "primary" : "secondary", required: true }), _jsx(FieldError, {})] }), emailAndPassword?.rememberMe && (_jsxs(Checkbox, { name: "rememberMe", isDisabled: isPending, variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, {}) }), _jsx(Checkbox.Content, { children: _jsx(Label, { children: localization.auth.rememberMe }) })] })), Captcha && _jsx("div", { className: "flex justify-center", children: Captcha }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs(Button, { type: "submit", className: "w-full", isPending: isPending, children: [signInEmailPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.auth.signIn] }), plugins.flatMap((plugin) => plugin.authButtons?.map((AuthButton, index) => (_jsx(AuthButton, { view: "signIn" }, `${plugin.id}-${index.toString()}`))))] })] })), socialPosition === "bottom" && (_jsxs(_Fragment, { children: [showSeparator && (_jsx(FieldSeparator, { children: localization.auth.or })), !!socialProviders?.length && (_jsx(ProviderButtons, { socialLayout: socialLayout }))] }))] }), _jsxs(Card.Footer, { className: "flex-col gap-3", children: [emailAndPassword?.forgotPassword && (_jsx(Link, { href: `${basePaths.auth}/${viewPaths.auth.forgotPassword}`, className: "no-underline hover:underline", children: localization.auth.forgotPasswordLink })), emailAndPassword?.enabled && (_jsxs(Description, { className: "text-sm", children: [localization.auth.needToCreateAnAccount, " ", _jsx(Link, { href: `${basePaths.auth}/${viewPaths.auth.signUp}`, className: "text-accent no-underline hover:underline decoration-accent-hover", children: localization.auth.signUp })] }))] })] }));
|
|
64
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useSignOut } from "@better-auth-ui/react";
|
|
3
|
-
import { cn, Spinner, toast } from "@heroui/react";
|
|
4
|
-
import { useEffect, useRef } from "react";
|
|
5
|
-
/**
|
|
6
|
-
* Initiates sign-out on mount and renders a loading card while sign-out proceeds.
|
|
7
|
-
*
|
|
8
|
-
* @returns A Card containing a centered Spinner shown during the sign-out process
|
|
9
|
-
*/
|
|
10
|
-
export function SignOut({ className }) {
|
|
11
|
-
const { authClient, basePaths, navigate, viewPaths } = useAuth();
|
|
12
|
-
const { mutate: signOut } = useSignOut(authClient, {
|
|
13
|
-
onError: (error) => {
|
|
14
|
-
toast.danger(error.error?.message || error.message);
|
|
15
|
-
navigate({
|
|
16
|
-
to: `${basePaths.auth}/${viewPaths.auth.signIn}`,
|
|
17
|
-
replace: true
|
|
18
|
-
});
|
|
19
|
-
},
|
|
20
|
-
onSuccess: () => navigate({
|
|
21
|
-
to: `${basePaths.auth}/${viewPaths.auth.signIn}`,
|
|
22
|
-
replace: true
|
|
23
|
-
})
|
|
24
|
-
});
|
|
25
|
-
const hasSignedOut = useRef(false);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (hasSignedOut.current)
|
|
28
|
-
return;
|
|
29
|
-
hasSignedOut.current = true;
|
|
30
|
-
signOut();
|
|
31
|
-
}, [signOut]);
|
|
32
|
-
return (_jsx(Spinner, { className: cn("mx-auto my-auto", className), color: "current" }));
|
|
33
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { authMutationKeys, parseAdditionalFieldValue } from "@better-auth-ui/core";
|
|
3
|
-
import { useAuth, useFetchOptions, useSignUpEmail } from "@better-auth-ui/react";
|
|
4
|
-
import { Eye, EyeSlash } from "@gravity-ui/icons";
|
|
5
|
-
import { Button, Card, cn, Description, FieldError, Form, Input, InputGroup, Label, Link, Spinner, TextField, toast } from "@heroui/react";
|
|
6
|
-
import { useIsMutating } from "@tanstack/react-query";
|
|
7
|
-
import { useState } from "react";
|
|
8
|
-
import { AdditionalField } from "./additional-field";
|
|
9
|
-
import { FieldSeparator } from "./field-separator";
|
|
10
|
-
import { ProviderButtons } from "./provider-buttons";
|
|
11
|
-
/**
|
|
12
|
-
* Render a sign-up form with name, email, password (and optional confirm password) fields, optional social provider buttons, and password visibility controls.
|
|
13
|
-
*
|
|
14
|
-
* The component reflects request state by disabling inputs and showing a pending indicator during sign-up or social sign-in.
|
|
15
|
-
*
|
|
16
|
-
* @param className - Additional CSS classes applied to the outer card container
|
|
17
|
-
* @param socialLayout - Social layout to apply to the provider buttons component
|
|
18
|
-
* @param socialPosition - Position of social provider buttons relative to the form; `"top"` or `"bottom"` (default `"bottom"`)
|
|
19
|
-
* @returns The sign-up form React element
|
|
20
|
-
*/
|
|
21
|
-
export function SignUp({ className, socialLayout, socialPosition = "bottom", variant, ...props }) {
|
|
22
|
-
const { additionalFields, authClient, basePaths, emailAndPassword, localization, plugins, redirectTo, socialProviders, viewPaths, navigate } = useAuth();
|
|
23
|
-
const { fetchOptions, resetFetchOptions } = useFetchOptions();
|
|
24
|
-
const [password, setPassword] = useState("");
|
|
25
|
-
const [confirmPassword, setConfirmPassword] = useState("");
|
|
26
|
-
const { mutate: signUpEmail, isPending: signUpEmailPending } = useSignUpEmail(authClient, {
|
|
27
|
-
onError: (error) => {
|
|
28
|
-
setPassword("");
|
|
29
|
-
setConfirmPassword("");
|
|
30
|
-
toast.danger(error.error?.message || error.message);
|
|
31
|
-
resetFetchOptions();
|
|
32
|
-
},
|
|
33
|
-
onSuccess: () => {
|
|
34
|
-
if (emailAndPassword?.requireEmailVerification) {
|
|
35
|
-
toast.success(localization.auth.verifyYourEmail);
|
|
36
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` });
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
navigate({ to: redirectTo });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
|
44
|
-
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] = useState(false);
|
|
45
|
-
const signInMutating = useIsMutating({
|
|
46
|
-
mutationKey: authMutationKeys.signIn.all
|
|
47
|
-
});
|
|
48
|
-
const signUpMutating = useIsMutating({
|
|
49
|
-
mutationKey: authMutationKeys.signUp.all
|
|
50
|
-
});
|
|
51
|
-
const isPending = signInMutating + signUpMutating > 0;
|
|
52
|
-
const Captcha = plugins.find((plugin) => plugin.captchaComponent)?.captchaComponent;
|
|
53
|
-
const handleSubmit = async (e) => {
|
|
54
|
-
e.preventDefault();
|
|
55
|
-
const formData = new FormData(e.currentTarget);
|
|
56
|
-
// `emailAndPassword.name === false` hides the name field and submits "".
|
|
57
|
-
const name = formData.get("name") ?? "";
|
|
58
|
-
const email = formData.get("email");
|
|
59
|
-
if (emailAndPassword?.confirmPassword && password !== confirmPassword) {
|
|
60
|
-
toast.danger(localization.auth.passwordsDoNotMatch);
|
|
61
|
-
setPassword("");
|
|
62
|
-
setConfirmPassword("");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const additionalFieldValues = {};
|
|
66
|
-
for (const field of additionalFields ?? []) {
|
|
67
|
-
if (!field.signUp || field.readOnly)
|
|
68
|
-
continue;
|
|
69
|
-
const value = parseAdditionalFieldValue(field, formData.get(field.name));
|
|
70
|
-
if (field.validate) {
|
|
71
|
-
try {
|
|
72
|
-
await field.validate(value);
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
toast.danger(error instanceof Error ? error.message : String(error));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (value !== undefined) {
|
|
80
|
-
additionalFieldValues[field.name] = value;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
signUpEmail({
|
|
84
|
-
name,
|
|
85
|
-
email,
|
|
86
|
-
password,
|
|
87
|
-
...additionalFieldValues,
|
|
88
|
-
fetchOptions
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
const showSeparator = emailAndPassword?.enabled && !!socialProviders?.length;
|
|
92
|
-
return (_jsxs(Card, { className: cn("w-full max-w-sm gap-4 md:p-6", className), variant: variant, ...props, children: [_jsx(Card.Header, { children: _jsx(Card.Title, { className: "text-xl font-semibold mb-1", children: localization.auth.signUp }) }), _jsxs(Card.Content, { className: "gap-4", children: [socialPosition === "top" && (_jsxs(_Fragment, { children: [!!socialProviders?.length && (_jsx(ProviderButtons, { socialLayout: socialLayout })), showSeparator && (_jsx(FieldSeparator, { children: localization.auth.or }))] })), emailAndPassword?.enabled && (_jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [emailAndPassword.name !== false && (_jsxs(TextField, { name: "name", type: "text", autoComplete: "name", isDisabled: isPending, children: [_jsx(Label, { children: localization.auth.name }), _jsx(Input, { placeholder: localization.auth.namePlaceholder, required: true, variant: variant === "transparent" ? "primary" : "secondary" }), _jsx(FieldError, {})] })), _jsxs(TextField, { name: "email", type: "email", autoComplete: "email", isDisabled: isPending, children: [_jsx(Label, { children: localization.auth.email }), _jsx(Input, { placeholder: localization.auth.emailPlaceholder, required: true, variant: variant === "transparent" ? "primary" : "secondary" }), _jsx(FieldError, {})] }), additionalFields?.map((field) => field.signUp === "above" && (_jsx(AdditionalField, { name: field.name, field: field, isPending: isPending, variant: variant }, field.name))), _jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, name: "password", autoComplete: "new-password", isDisabled: isPending, value: password, onChange: setPassword, children: [_jsx(Label, { children: localization.auth.password }), _jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Input, { placeholder: localization.auth.passwordPlaceholder, type: isPasswordVisible ? "text" : "password", name: "password", required: true }), _jsx(InputGroup.Suffix, { className: "px-0", children: _jsx(Button, { isIconOnly: true, "aria-label": isPasswordVisible
|
|
93
|
-
? localization.auth.hidePassword
|
|
94
|
-
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsPasswordVisible(!isPasswordVisible), isDisabled: isPending, children: isPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] }), _jsx(FieldError, {})] }), emailAndPassword?.confirmPassword && (_jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, name: "confirmPassword", autoComplete: "new-password", isDisabled: isPending, value: confirmPassword, onChange: setConfirmPassword, children: [_jsx(Label, { children: localization.auth.confirmPassword }), _jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Input, { name: "confirmPassword", placeholder: localization.auth.confirmPasswordPlaceholder, type: isConfirmPasswordVisible ? "text" : "password", required: true }), _jsx(InputGroup.Suffix, { className: "px-0", children: _jsx(Button, { isIconOnly: true, "aria-label": isConfirmPasswordVisible
|
|
95
|
-
? localization.auth.hidePassword
|
|
96
|
-
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsConfirmPasswordVisible(!isConfirmPasswordVisible), isDisabled: isPending, children: isConfirmPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] }), _jsx(FieldError, {})] })), additionalFields?.map((field) => field.signUp &&
|
|
97
|
-
field.signUp !== "above" && (_jsx(AdditionalField, { name: field.name, field: field, isPending: isPending, variant: variant }, field.name))), Captcha && _jsx("div", { className: "flex justify-center", children: Captcha }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs(Button, { type: "submit", className: "w-full", isPending: isPending, children: [signUpEmailPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.auth.signUp] }), plugins.flatMap((plugin) => plugin.authButtons?.map((AuthButton, index) => (_jsx(AuthButton, { view: "signUp" }, `${plugin.id}-${index.toString()}`))))] })] })), socialPosition === "bottom" && (_jsxs(_Fragment, { children: [showSeparator && (_jsx(FieldSeparator, { children: localization.auth.or })), !!socialProviders?.length && (_jsx(ProviderButtons, { socialLayout: socialLayout }))] }))] }), _jsx(Card.Footer, { className: "flex-col gap-3", children: _jsxs(Description, { className: "text-sm", children: [localization.auth.alreadyHaveAnAccount, " ", _jsx(Link, { href: `${basePaths.auth}/${viewPaths.auth.signIn}`, className: "text-accent no-underline hover:underline decoration-accent-hover", children: localization.auth.signIn })] }) })] }));
|
|
98
|
-
}
|