@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.
Files changed (191) hide show
  1. package/dist/components/auth/additional-field.d.ts +2 -2
  2. package/dist/components/auth/api-key/api-key.d.ts +1 -1
  3. package/dist/components/auth/api-key/api-keys.d.ts +1 -1
  4. package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +1 -1
  5. package/dist/components/auth/api-key/organization-api-keys.d.ts +1 -1
  6. package/dist/components/auth/auth-provider.d.ts +1 -1
  7. package/dist/components/auth/auth.d.ts +3 -3
  8. package/dist/components/auth/delete-user/danger-zone.d.ts +2 -2
  9. package/dist/components/auth/delete-user/delete-account.d.ts +1 -1
  10. package/dist/components/auth/email/email-changed-email.d.ts +2 -2
  11. package/dist/components/auth/email/email-verification-email.d.ts +2 -2
  12. package/dist/components/auth/email/magic-link-email.d.ts +2 -2
  13. package/dist/components/auth/email/new-device-email.d.ts +2 -2
  14. package/dist/components/auth/email/organization-invitation-email.d.ts +2 -2
  15. package/dist/components/auth/email/otp-email.d.ts +2 -2
  16. package/dist/components/auth/email/password-changed-email.d.ts +2 -2
  17. package/dist/components/auth/email/reset-password-email.d.ts +2 -2
  18. package/dist/components/auth/field-separator.d.ts +1 -1
  19. package/dist/components/auth/forgot-password.d.ts +1 -1
  20. package/dist/components/auth/magic-link/magic-link-button.d.ts +1 -1
  21. package/dist/components/auth/magic-link/magic-link.d.ts +2 -2
  22. package/dist/components/auth/multi-session/manage-account.d.ts +1 -1
  23. package/dist/components/auth/multi-session/manage-accounts.d.ts +1 -1
  24. package/dist/components/auth/multi-session/switch-account-submenu-item.d.ts +1 -1
  25. package/dist/components/auth/organization/delete-organization-dialog.d.ts +1 -1
  26. package/dist/components/auth/organization/leave-organization-dialog.d.ts +1 -1
  27. package/dist/components/auth/organization/organization-danger-zone.d.ts +2 -2
  28. package/dist/components/auth/organization/organization-invitation-row.d.ts +1 -1
  29. package/dist/components/auth/organization/organization-invitations.d.ts +1 -1
  30. package/dist/components/auth/organization/organization-logo.d.ts +3 -3
  31. package/dist/components/auth/organization/organization-member-row.d.ts +1 -1
  32. package/dist/components/auth/organization/organization-members.d.ts +1 -1
  33. package/dist/components/auth/organization/organization-people.d.ts +1 -1
  34. package/dist/components/auth/organization/organization-profile.d.ts +1 -1
  35. package/dist/components/auth/organization/organization-row.d.ts +1 -1
  36. package/dist/components/auth/organization/organization-settings.d.ts +2 -2
  37. package/dist/components/auth/organization/organization-switcher.d.ts +3 -3
  38. package/dist/components/auth/organization/organization-view-skeleton.d.ts +2 -2
  39. package/dist/components/auth/organization/organization-view.d.ts +3 -3
  40. package/dist/components/auth/organization/organization.d.ts +3 -3
  41. package/dist/components/auth/organization/organizations-settings.d.ts +2 -2
  42. package/dist/components/auth/organization/organizations.d.ts +1 -1
  43. package/dist/components/auth/organization/remove-member-dialog.d.ts +1 -1
  44. package/dist/components/auth/organization/slug-field.d.ts +1 -1
  45. package/dist/components/auth/organization/user-invitation-row.d.ts +1 -1
  46. package/dist/components/auth/organization/user-invitations.d.ts +1 -1
  47. package/dist/components/auth/passkey/passkey-button.d.ts +1 -1
  48. package/dist/components/auth/passkey/passkey.d.ts +1 -1
  49. package/dist/components/auth/passkey/passkeys.d.ts +1 -1
  50. package/dist/components/auth/provider-button.d.ts +2 -2
  51. package/dist/components/auth/reset-password.d.ts +1 -1
  52. package/dist/components/auth/settings/account/account-settings.d.ts +2 -2
  53. package/dist/components/auth/settings/account/change-email.d.ts +1 -1
  54. package/dist/components/auth/settings/account/user-profile.d.ts +1 -1
  55. package/dist/components/auth/settings/security/active-session.d.ts +1 -1
  56. package/dist/components/auth/settings/security/active-sessions.d.ts +1 -1
  57. package/dist/components/auth/settings/security/change-password.d.ts +1 -1
  58. package/dist/components/auth/settings/security/linked-account.d.ts +1 -1
  59. package/dist/components/auth/settings/security/linked-accounts.d.ts +1 -1
  60. package/dist/components/auth/settings/security/security-settings.d.ts +2 -2
  61. package/dist/components/auth/settings/settings.d.ts +3 -3
  62. package/dist/components/auth/sign-in.d.ts +2 -2
  63. package/dist/components/auth/sign-up.d.ts +2 -2
  64. package/dist/components/auth/theme/appearance.d.ts +1 -1
  65. package/dist/components/auth/user/user-avatar.d.ts +3 -3
  66. package/dist/components/auth/user/user-button.d.ts +2 -2
  67. package/dist/components/auth/user/user-view.d.ts +3 -3
  68. package/dist/components/auth/username/sign-in-username.d.ts +2 -2
  69. package/dist/components/auth/username/username-field.d.ts +1 -1
  70. package/dist/email.d.ts +8 -8
  71. package/dist/email.js +211 -8
  72. package/dist/index.d.ts +21 -21
  73. package/dist/index.js +1682 -21
  74. package/dist/lib/auth/api-key-plugin.d.ts +3 -3
  75. package/dist/lib/auth/auth-plugin.d.ts +5 -5
  76. package/dist/lib/auth/delete-user-plugin.d.ts +2 -2
  77. package/dist/lib/auth/magic-link-plugin.d.ts +3 -3
  78. package/dist/lib/auth/multi-session-plugin.d.ts +3 -3
  79. package/dist/lib/auth/organization-plugin.d.ts +2 -2
  80. package/dist/lib/auth/passkey-plugin.d.ts +3 -3
  81. package/dist/lib/auth/theme-plugin.d.ts +3 -3
  82. package/dist/lib/auth/username-plugin.d.ts +3 -3
  83. package/dist/plugins.d.ts +46 -46
  84. package/dist/plugins.js +2824 -47
  85. package/dist/user-view--47tVtJ3.js +113 -0
  86. package/package.json +2 -3
  87. package/dist/components/auth/additional-field.js +0 -137
  88. package/dist/components/auth/api-key/api-key-skeleton.js +0 -5
  89. package/dist/components/auth/api-key/api-key.js +0 -17
  90. package/dist/components/auth/api-key/api-keys-empty.js +0 -9
  91. package/dist/components/auth/api-key/api-keys.js +0 -22
  92. package/dist/components/auth/api-key/create-api-key-dialog.js +0 -44
  93. package/dist/components/auth/api-key/delete-api-key-dialog.js +0 -17
  94. package/dist/components/auth/api-key/new-api-key-dialog.js +0 -24
  95. package/dist/components/auth/api-key/organization-api-keys.js +0 -22
  96. package/dist/components/auth/auth-provider.js +0 -11
  97. package/dist/components/auth/auth.js +0 -88
  98. package/dist/components/auth/delete-user/danger-zone.js +0 -12
  99. package/dist/components/auth/delete-user/delete-account.js +0 -50
  100. package/dist/components/auth/email/email-changed-email.js +0 -23
  101. package/dist/components/auth/email/email-verification-email.js +0 -23
  102. package/dist/components/auth/email/magic-link-email.js +0 -23
  103. package/dist/components/auth/email/new-device-email.js +0 -23
  104. package/dist/components/auth/email/organization-invitation-email.js +0 -23
  105. package/dist/components/auth/email/otp-email.js +0 -22
  106. package/dist/components/auth/email/password-changed-email.js +0 -23
  107. package/dist/components/auth/email/reset-password-email.js +0 -23
  108. package/dist/components/auth/error-toaster.js +0 -33
  109. package/dist/components/auth/field-separator.js +0 -5
  110. package/dist/components/auth/forgot-password.js +0 -37
  111. package/dist/components/auth/magic-link/magic-link-button.js +0 -34
  112. package/dist/components/auth/magic-link/magic-link.js +0 -40
  113. package/dist/components/auth/multi-session/manage-account.js +0 -33
  114. package/dist/components/auth/multi-session/manage-accounts.js +0 -33
  115. package/dist/components/auth/multi-session/switch-account-submenu-content.js +0 -25
  116. package/dist/components/auth/multi-session/switch-account-submenu-item.js +0 -17
  117. package/dist/components/auth/multi-session/switch-account-submenu.js +0 -24
  118. package/dist/components/auth/organization/change-organization-logo.js +0 -69
  119. package/dist/components/auth/organization/create-organization-dialog.js +0 -37
  120. package/dist/components/auth/organization/delete-organization-dialog.js +0 -25
  121. package/dist/components/auth/organization/delete-organization-skeleton.js +0 -8
  122. package/dist/components/auth/organization/delete-organization.js +0 -27
  123. package/dist/components/auth/organization/invite-member-dialog.js +0 -47
  124. package/dist/components/auth/organization/leave-organization-dialog.js +0 -21
  125. package/dist/components/auth/organization/leave-organization.js +0 -16
  126. package/dist/components/auth/organization/organization-danger-zone.js +0 -23
  127. package/dist/components/auth/organization/organization-invitation-row-skeleton.js +0 -9
  128. package/dist/components/auth/organization/organization-invitation-row.js +0 -31
  129. package/dist/components/auth/organization/organization-invitations-empty.js +0 -13
  130. package/dist/components/auth/organization/organization-invitations.js +0 -66
  131. package/dist/components/auth/organization/organization-logo.js +0 -14
  132. package/dist/components/auth/organization/organization-member-row-skeleton.js +0 -9
  133. package/dist/components/auth/organization/organization-member-row.js +0 -34
  134. package/dist/components/auth/organization/organization-members.js +0 -67
  135. package/dist/components/auth/organization/organization-people.js +0 -11
  136. package/dist/components/auth/organization/organization-profile.js +0 -34
  137. package/dist/components/auth/organization/organization-row.js +0 -31
  138. package/dist/components/auth/organization/organization-settings.js +0 -13
  139. package/dist/components/auth/organization/organization-switcher.js +0 -48
  140. package/dist/components/auth/organization/organization-view-skeleton.js +0 -9
  141. package/dist/components/auth/organization/organization-view.js +0 -31
  142. package/dist/components/auth/organization/organization.js +0 -60
  143. package/dist/components/auth/organization/organizations-empty.js +0 -9
  144. package/dist/components/auth/organization/organizations-settings.js +0 -17
  145. package/dist/components/auth/organization/organizations.js +0 -21
  146. package/dist/components/auth/organization/remove-member-dialog.js +0 -20
  147. package/dist/components/auth/organization/slug-field.js +0 -38
  148. package/dist/components/auth/organization/user-invitation-row-skeleton.js +0 -8
  149. package/dist/components/auth/organization/user-invitation-row.js +0 -22
  150. package/dist/components/auth/organization/user-invitations-empty.js +0 -12
  151. package/dist/components/auth/organization/user-invitations.js +0 -18
  152. package/dist/components/auth/passkey/add-passkey-dialog.js +0 -22
  153. package/dist/components/auth/passkey/delete-passkey-dialog.js +0 -14
  154. package/dist/components/auth/passkey/passkey-button.js +0 -25
  155. package/dist/components/auth/passkey/passkey-skeleton.js +0 -5
  156. package/dist/components/auth/passkey/passkey.js +0 -17
  157. package/dist/components/auth/passkey/passkeys-empty.js +0 -9
  158. package/dist/components/auth/passkey/passkeys.js +0 -16
  159. package/dist/components/auth/provider-button.js +0 -29
  160. package/dist/components/auth/provider-buttons.js +0 -29
  161. package/dist/components/auth/reset-password.js +0 -57
  162. package/dist/components/auth/settings/account/account-settings.js +0 -22
  163. package/dist/components/auth/settings/account/change-avatar.js +0 -54
  164. package/dist/components/auth/settings/account/change-email.js +0 -28
  165. package/dist/components/auth/settings/account/user-profile.js +0 -65
  166. package/dist/components/auth/settings/security/active-session.js +0 -52
  167. package/dist/components/auth/settings/security/active-sessions.js +0 -23
  168. package/dist/components/auth/settings/security/change-password.js +0 -79
  169. package/dist/components/auth/settings/security/linked-account.js +0 -38
  170. package/dist/components/auth/settings/security/linked-accounts.js +0 -36
  171. package/dist/components/auth/settings/security/security-settings.js +0 -20
  172. package/dist/components/auth/settings/settings.js +0 -53
  173. package/dist/components/auth/sign-in.js +0 -64
  174. package/dist/components/auth/sign-out.js +0 -33
  175. package/dist/components/auth/sign-up.js +0 -98
  176. package/dist/components/auth/theme/appearance.js +0 -24
  177. package/dist/components/auth/theme/theme-toggle-item.js +0 -57
  178. package/dist/components/auth/user/user-avatar.js +0 -32
  179. package/dist/components/auth/user/user-button.js +0 -40
  180. package/dist/components/auth/user/user-view.js +0 -27
  181. package/dist/components/auth/username/sign-in-username.js +0 -88
  182. package/dist/components/auth/username/username-field.js +0 -45
  183. package/dist/lib/auth/api-key-plugin.js +0 -12
  184. package/dist/lib/auth/auth-plugin.js +0 -1
  185. package/dist/lib/auth/delete-user-plugin.js +0 -7
  186. package/dist/lib/auth/magic-link-plugin.js +0 -18
  187. package/dist/lib/auth/multi-session-plugin.js +0 -9
  188. package/dist/lib/auth/organization-plugin.js +0 -19
  189. package/dist/lib/auth/passkey-plugin.js +0 -9
  190. package/dist/lib/auth/theme-plugin.js +0 -26
  191. 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
- }