@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,31 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useActiveOrganization, useAuth, useAuthPlugin, useListOrganizationMembers, useSession } from "@better-auth-ui/react";
|
|
3
|
-
import { Chip, cn } from "@heroui/react";
|
|
4
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
5
|
-
import { OrganizationLogo } from "./organization-logo";
|
|
6
|
-
import { OrganizationViewSkeleton } from "./organization-view-skeleton";
|
|
7
|
-
/**
|
|
8
|
-
* Compact organization row: logo, primary name, secondary slug — analogous to {@link UserView}.
|
|
9
|
-
*/
|
|
10
|
-
export function OrganizationView({ className, isPending, size = "md", hideSlug, hideRole, organization, ...props }) {
|
|
11
|
-
const { authClient } = useAuth();
|
|
12
|
-
const { roles } = useAuthPlugin(organizationPlugin);
|
|
13
|
-
const { data: session } = useSession(authClient);
|
|
14
|
-
const { data: activeOrganization, isPending: activeOrganizationPending } = useActiveOrganization(authClient, {
|
|
15
|
-
enabled: !organization && !isPending
|
|
16
|
-
});
|
|
17
|
-
const resolvedOrganization = organization ?? activeOrganization;
|
|
18
|
-
const { data: membersList, isPending: membersPending } = useListOrganizationMembers(authClient, {
|
|
19
|
-
query: {
|
|
20
|
-
organizationId: resolvedOrganization?.id
|
|
21
|
-
},
|
|
22
|
-
enabled: !!resolvedOrganization?.id && !hideRole
|
|
23
|
-
});
|
|
24
|
-
const membership = membersList?.members?.find((member) => member.userId === session?.user.id);
|
|
25
|
-
if (isPending ||
|
|
26
|
-
(!organization && activeOrganizationPending) ||
|
|
27
|
-
(!hideRole && !!resolvedOrganization?.id && membersPending)) {
|
|
28
|
-
return (_jsx(OrganizationViewSkeleton, { className: className, hideSlug: hideSlug, size: size, ...props }));
|
|
29
|
-
}
|
|
30
|
-
return (_jsxs("div", { className: cn("flex min-w-0 items-center gap-2", className), ...props, children: [_jsx(OrganizationLogo, { organization: resolvedOrganization, className: size === "sm" ? "size-5 [&>span]:text-xs" : undefined, size: size === "lg" ? "md" : "sm" }), _jsxs("div", { className: "flex flex-col min-w-0", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("p", { className: "text-foreground text-sm font-medium truncate leading-tight", children: resolvedOrganization?.name }), !hideRole && !!membership && (_jsx(Chip, { className: "shrink-0 -my-0.5", size: "sm", children: roles?.[membership.role] ?? membership.role }))] }), !hideSlug && (_jsx("p", { className: "text-muted text-xs truncate overflow-x-hidden font-mono leading-tight", children: resolvedOrganization?.slug }))] })] }));
|
|
31
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useActiveOrganization, useAuth, useAuthenticate, useAuthPlugin } from "@better-auth-ui/react";
|
|
3
|
-
import { Gear, Person } from "@gravity-ui/icons";
|
|
4
|
-
import { cn, Tabs } from "@heroui/react";
|
|
5
|
-
import { useEffect, useMemo } from "react";
|
|
6
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
7
|
-
import { OrganizationPeople } from "./organization-people";
|
|
8
|
-
import { OrganizationSettings } from "./organization-settings";
|
|
9
|
-
/**
|
|
10
|
-
* Organization management shell: tabs for profile / danger zone and for
|
|
11
|
-
* people (members / invitations). Path segments come from
|
|
12
|
-
* `useAuthPlugin(organizationPlugin).viewPaths.organization`.
|
|
13
|
-
*/
|
|
14
|
-
export function Organization({ className, hideNav, path, variant, view, ...props }) {
|
|
15
|
-
if (!view && !path) {
|
|
16
|
-
throw new Error("[Better Auth UI] Either `view` or `path` must be provided");
|
|
17
|
-
}
|
|
18
|
-
const { authClient, basePaths, localization, navigate } = useAuth();
|
|
19
|
-
useAuthenticate(authClient);
|
|
20
|
-
const { localization: organizationLocalization, viewPaths: organizationViewPaths, slug } = useAuthPlugin(organizationPlugin);
|
|
21
|
-
const { data: activeOrganization, isPending } = useActiveOrganization(authClient);
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (!isPending && !activeOrganization) {
|
|
24
|
-
navigate({
|
|
25
|
-
to: `${basePaths.settings}/${organizationViewPaths.settings?.organizations}`,
|
|
26
|
-
replace: true
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}, [
|
|
30
|
-
basePaths.settings,
|
|
31
|
-
isPending,
|
|
32
|
-
navigate,
|
|
33
|
-
organizationViewPaths.settings?.organizations,
|
|
34
|
-
activeOrganization
|
|
35
|
-
]);
|
|
36
|
-
const currentView = useMemo(() => {
|
|
37
|
-
if (view)
|
|
38
|
-
return view;
|
|
39
|
-
const match = Object.entries(organizationViewPaths.organization).find(([, segment]) => segment === path);
|
|
40
|
-
return match?.[0];
|
|
41
|
-
}, [view, path, organizationViewPaths.organization]);
|
|
42
|
-
if (!currentView) {
|
|
43
|
-
const validPaths = Object.values(organizationViewPaths.organization).join(", ");
|
|
44
|
-
throw new Error(`[Better Auth UI] Unknown organization path "${path}". Valid paths are: ${validPaths}`);
|
|
45
|
-
}
|
|
46
|
-
if (!isPending && !activeOrganization) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return (_jsxs(Tabs, { className: cn(className), orientation: "horizontal", selectedKey: currentView, ...props, children: [!hideNav && (_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: "settings", href: slug
|
|
50
|
-
? `${basePaths.organization}/${slug}/${organizationViewPaths.organization.settings}`
|
|
51
|
-
: `${basePaths.organization}/${organizationViewPaths.organization.settings}`, className: "gap-2", onPress: (e) => e.target.scrollIntoView({
|
|
52
|
-
behavior: "smooth",
|
|
53
|
-
inline: "center"
|
|
54
|
-
}), children: [_jsx(Gear, { className: "text-muted" }), localization.settings.settings, _jsx(Tabs.Indicator, {})] }), _jsxs(Tabs.Tab, { id: "people", href: slug
|
|
55
|
-
? `${basePaths.organization}/${slug}/${organizationViewPaths.organization.people}`
|
|
56
|
-
: `${basePaths.organization}/${organizationViewPaths.organization.people}`, className: "gap-2", onPress: (e) => e.target.scrollIntoView({
|
|
57
|
-
behavior: "smooth",
|
|
58
|
-
inline: "center"
|
|
59
|
-
}), children: [_jsx(Person, { className: "text-muted" }), organizationLocalization.people, _jsx(Tabs.Indicator, {})] })] }) })), _jsx(Tabs.Panel, { id: "settings", className: "px-0", children: _jsx(OrganizationSettings, { variant: variant }) }), _jsx(Tabs.Panel, { id: "people", className: "px-0", children: _jsx(OrganizationPeople, {}) })] }));
|
|
60
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuthPlugin } from "@better-auth-ui/react";
|
|
3
|
-
import { Briefcase } from "@gravity-ui/icons";
|
|
4
|
-
import { Button, EmptyState } from "@heroui/react";
|
|
5
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
6
|
-
export function OrganizationsEmpty({ onCreatePress }) {
|
|
7
|
-
const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
|
|
8
|
-
return (_jsxs(EmptyState, { className: "flex flex-col items-center gap-4 text-center", children: [_jsx("div", { className: "flex size-12 items-center justify-center rounded-full bg-surface-secondary", children: _jsx(Briefcase, { className: "size-5" }) }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-semibold text-foreground", children: organizationLocalization.noOrganizations }), _jsx("span", { className: "text-sm text-muted", children: organizationLocalization.organizationsDescription })] }), _jsx(Button, { size: "sm", onPress: onCreatePress, children: organizationLocalization.createOrganization })] }));
|
|
9
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "@heroui/react";
|
|
3
|
-
import { Organizations } from "./organizations";
|
|
4
|
-
import { UserInvitations } from "./user-invitations";
|
|
5
|
-
/**
|
|
6
|
-
* Renders the organizations settings panel.
|
|
7
|
-
*
|
|
8
|
-
* Displays all organizations the user belongs to with an empty state and
|
|
9
|
-
* create button, followed by a card for invitations to the user.
|
|
10
|
-
*
|
|
11
|
-
* @param className - Optional additional CSS class names for the outer container.
|
|
12
|
-
* @param variant - Card variant forwarded to each card.
|
|
13
|
-
* @returns The organizations settings UI as a JSX element.
|
|
14
|
-
*/
|
|
15
|
-
export function OrganizationsSettings({ className, variant, ...props }) {
|
|
16
|
-
return (_jsxs("div", { className: cn("flex w-full flex-col gap-4 md:gap-6", className), ...props, children: [_jsx(Organizations, { variant: variant }), _jsx(UserInvitations, { variant: variant })] }));
|
|
17
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin, useListOrganizations } from "@better-auth-ui/react";
|
|
3
|
-
import { Button, Card } from "@heroui/react";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
6
|
-
import { CreateOrganizationDialog } from "./create-organization-dialog";
|
|
7
|
-
import { OrganizationRow } from "./organization-row";
|
|
8
|
-
import { OrganizationViewSkeleton } from "./organization-view-skeleton";
|
|
9
|
-
import { OrganizationsEmpty } from "./organizations-empty";
|
|
10
|
-
/**
|
|
11
|
-
* Lists organizations the user belongs to (via {@link useListOrganizations}): loading skeleton,
|
|
12
|
-
* empty state with create, or a card of rows with a Manage control per organization.
|
|
13
|
-
* Owns {@link CreateOrganizationDialog} open state and the create actions.
|
|
14
|
-
*/
|
|
15
|
-
export function Organizations({ variant }) {
|
|
16
|
-
const { authClient } = useAuth();
|
|
17
|
-
const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
|
|
18
|
-
const [createOpen, setCreateOpen] = useState(false);
|
|
19
|
-
const { data: organizations, isPending: organizationsPending } = useListOrganizations(authClient);
|
|
20
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("h2", { className: "truncate text-sm font-semibold", children: organizationLocalization.organizations }), _jsx(Button, { className: "shrink-0", size: "sm", isDisabled: organizationsPending, onPress: () => setCreateOpen(true), children: organizationLocalization.createOrganization })] }), _jsx(Card, { variant: variant, children: _jsx(Card.Content, { className: "gap-0", children: organizationsPending ? (_jsx(OrganizationViewSkeleton, {})) : !organizations?.length ? (_jsx(OrganizationsEmpty, { onCreatePress: () => setCreateOpen(true) })) : (organizations.map((organization, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "-mx-4 my-4 border-b border-dashed" })), _jsx(OrganizationRow, { organization: organization })] }, organization.id)))) }) })] }), _jsx(CreateOrganizationDialog, { isOpen: createOpen, onOpenChange: setCreateOpen })] }));
|
|
21
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin, useRemoveMember } from "@better-auth-ui/react";
|
|
3
|
-
import { TrashBin } from "@gravity-ui/icons";
|
|
4
|
-
import { AlertDialog, Button, Card, Chip, Spinner, toast } from "@heroui/react";
|
|
5
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
6
|
-
import { UserView } from "../user/user-view";
|
|
7
|
-
export function RemoveMemberDialog({ isOpen, onOpenChange, member }) {
|
|
8
|
-
const { authClient, localization } = useAuth();
|
|
9
|
-
const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
|
|
10
|
-
const { mutate: removeMember, isPending } = useRemoveMember(authClient, {
|
|
11
|
-
onSuccess: () => {
|
|
12
|
-
onOpenChange(false);
|
|
13
|
-
toast.success(organizationLocalization.memberRemoved);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
return (_jsx(AlertDialog.Backdrop, { isOpen: isOpen, onOpenChange: onOpenChange, children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger", children: _jsx(TrashBin, {}) }), _jsx(AlertDialog.Heading, { children: organizationLocalization.removeMember })] }), _jsxs(AlertDialog.Body, { className: "flex flex-col gap-4 overflow-visible", children: [_jsx("p", { className: "text-muted text-sm", children: organizationLocalization.removeMemberWarning }), _jsx(Card, { variant: "secondary", children: _jsxs(Card.Content, { className: "justify-between flex-row items-center gap-2", children: [_jsx(UserView, { user: member.user }), _jsx(Chip, { size: "sm", variant: "tertiary", children: _jsx(Chip.Label, { children: roles?.[member.role] ?? member.role }) })] }) })] }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "tertiary", isDisabled: isPending, children: localization.settings.cancel }), _jsxs(Button, { variant: "danger", isPending: isPending, onPress: () => removeMember({
|
|
17
|
-
memberIdOrEmail: member.id,
|
|
18
|
-
organizationId: member.organizationId
|
|
19
|
-
}), children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), organizationLocalization.removeMember] })] })] }) }) }));
|
|
20
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin, useCheckSlug } from "@better-auth-ui/react";
|
|
3
|
-
import { Check, Xmark } from "@gravity-ui/icons";
|
|
4
|
-
import { FieldError, InputGroup, Label, Spinner, TextField } from "@heroui/react";
|
|
5
|
-
import { useDebouncer } from "@tanstack/react-pacer";
|
|
6
|
-
import { useEffect } from "react";
|
|
7
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
8
|
-
/**
|
|
9
|
-
* Sanitize a slug value so it only contains lowercase alphanumeric characters
|
|
10
|
-
* and dashes. Runs of disallowed characters are collapsed to a single dash, but
|
|
11
|
-
* leading/trailing dashes are preserved while the user is still typing.
|
|
12
|
-
*/
|
|
13
|
-
export function sanitizeSlug(value) {
|
|
14
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Organization slug field with debounced availability checking.
|
|
18
|
-
*/
|
|
19
|
-
export function SlugField({ value, onChange, currentSlug, variant, ...props }) {
|
|
20
|
-
const { authClient } = useAuth();
|
|
21
|
-
const { localization, checkSlug: checkSlugEnabled } = useAuthPlugin(organizationPlugin);
|
|
22
|
-
const { mutate: checkSlug, data: checkSlugData, error: checkSlugError, reset: resetCheckSlug } = useCheckSlug(authClient);
|
|
23
|
-
const debouncer = useDebouncer((value) => {
|
|
24
|
-
if (!checkSlugEnabled || !value.trim() || value.trim() === currentSlug)
|
|
25
|
-
return;
|
|
26
|
-
checkSlug({ slug: value.trim() });
|
|
27
|
-
}, { wait: 500 });
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (!checkSlugEnabled)
|
|
30
|
-
return;
|
|
31
|
-
resetCheckSlug();
|
|
32
|
-
debouncer.maybeExecute(value);
|
|
33
|
-
}, [checkSlugEnabled, value, debouncer.maybeExecute, resetCheckSlug]);
|
|
34
|
-
const handleChange = (next) => {
|
|
35
|
-
onChange(sanitizeSlug(next));
|
|
36
|
-
};
|
|
37
|
-
return (_jsxs(TextField, { id: "slug", name: "slug", ...props, value: value, onChange: handleChange, children: [_jsx(Label, { children: localization.slug }), _jsxs(InputGroup, { variant: variant, children: [_jsx(InputGroup.Input, { placeholder: localization.slugPlaceholder, required: true }), checkSlugEnabled && !!value.trim() && value.trim() !== currentSlug && (_jsx(InputGroup.Suffix, { className: "px-2", children: checkSlugData?.status ? (_jsx(Check, { className: "text-success" })) : checkSlugError ? (_jsx(Xmark, { className: "text-danger" })) : (_jsx(Spinner, { size: "sm", color: "current" })) }))] }), _jsx(FieldError, {})] }));
|
|
38
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Skeleton } from "@heroui/react";
|
|
3
|
-
/**
|
|
4
|
-
* Placeholder row matching {@link UserInvitationRow} while invitations load.
|
|
5
|
-
*/
|
|
6
|
-
export function UserInvitationRowSkeleton() {
|
|
7
|
-
return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-10 shrink-0 rounded-xl" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Skeleton, { className: "h-4 w-40 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-28 rounded-lg" })] })] }));
|
|
8
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAcceptInvitation, useAuth, useAuthPlugin, useRejectInvitation } from "@better-auth-ui/react";
|
|
3
|
-
import { Check, Clock, Xmark } from "@gravity-ui/icons";
|
|
4
|
-
import { Button, Chip, Spinner } from "@heroui/react";
|
|
5
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
6
|
-
/**
|
|
7
|
-
* Single invitation row with accept/reject actions for the current user.
|
|
8
|
-
*/
|
|
9
|
-
export function UserInvitationRow({ invitation }) {
|
|
10
|
-
const { authClient } = useAuth();
|
|
11
|
-
const { localization: organizationLocalization, roles } = useAuthPlugin(organizationPlugin);
|
|
12
|
-
const { mutate: acceptInvitation, isPending: isAccepting } = useAcceptInvitation(authClient);
|
|
13
|
-
const { mutate: rejectInvitation, isPending: isRejecting } = useRejectInvitation(authClient);
|
|
14
|
-
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: _jsx(Clock, { className: "size-4.5" }) }), _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "truncate text-sm font-medium leading-tight", children: invitation.organizationName }), _jsx(Chip, { size: "sm", children: roles?.[invitation.role] ?? invitation.role })] }), _jsx("span", { className: "truncate text-muted text-xs", children: new Date(invitation.createdAt).toLocaleString(undefined, {
|
|
15
|
-
dateStyle: "medium",
|
|
16
|
-
timeStyle: "short"
|
|
17
|
-
}) })] }), _jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", isPending: isAccepting, isDisabled: isRejecting, onPress: () => acceptInvitation({
|
|
18
|
-
invitationId: invitation.id
|
|
19
|
-
}), children: [isAccepting ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Check, {}), organizationLocalization.accept] }), _jsx(Button, { variant: "danger-soft", size: "sm", isIconOnly: true, isPending: isRejecting, isDisabled: isAccepting, onPress: () => rejectInvitation({
|
|
20
|
-
invitationId: invitation.id
|
|
21
|
-
}), "aria-label": organizationLocalization.rejectInvitation, children: isRejecting ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Xmark, {}) })] })] }));
|
|
22
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuthPlugin } from "@better-auth-ui/react";
|
|
3
|
-
import { PaperPlane } from "@gravity-ui/icons";
|
|
4
|
-
import { EmptyState } from "@heroui/react";
|
|
5
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
6
|
-
/**
|
|
7
|
-
* Empty state for `UserInvitations`
|
|
8
|
-
*/
|
|
9
|
-
export function UserInvitationsEmpty() {
|
|
10
|
-
const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
|
|
11
|
-
return (_jsxs(EmptyState, { className: "flex flex-col items-center gap-4 text-center", children: [_jsx("div", { className: "flex size-12 items-center justify-center rounded-full bg-surface-secondary", children: _jsx(PaperPlane, { className: "size-5" }) }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm font-semibold text-foreground", children: organizationLocalization.noInvitations }), _jsx("span", { className: "text-sm text-muted", children: organizationLocalization.userInvitationsEmptyDescription })] })] }));
|
|
12
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin, useListUserInvitations } from "@better-auth-ui/react";
|
|
3
|
-
import { Card } from "@heroui/react";
|
|
4
|
-
import { organizationPlugin } from "../../../lib/auth/organization-plugin";
|
|
5
|
-
import { UserInvitationRow } from "./user-invitation-row";
|
|
6
|
-
import { UserInvitationRowSkeleton } from "./user-invitation-row-skeleton";
|
|
7
|
-
import { UserInvitationsEmpty } from "./user-invitations-empty";
|
|
8
|
-
/**
|
|
9
|
-
* Organization invitations for the signed-in user (from
|
|
10
|
-
* {@link useListUserInvitations}). Always renders the section card; uses
|
|
11
|
-
* {@link UserInvitationsEmpty} when there are no pending invitations.
|
|
12
|
-
*/
|
|
13
|
-
export function UserInvitations({ variant }) {
|
|
14
|
-
const { authClient } = useAuth();
|
|
15
|
-
const { localization: organizationLocalization } = useAuthPlugin(organizationPlugin);
|
|
16
|
-
const { data: invitations, isPending } = useListUserInvitations(authClient);
|
|
17
|
-
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("h2", { className: "truncate text-sm font-semibold", children: organizationLocalization.invitations }), _jsx(Card, { variant: variant, children: _jsx(Card.Content, { children: isPending ? (_jsx(UserInvitationRowSkeleton, {})) : !invitations?.length ? (_jsx(UserInvitationsEmpty, {})) : (invitations?.map((invitation, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 my-4" })), _jsx(UserInvitationRow, { invitation: invitation })] }, invitation.id)))) }) })] }));
|
|
18
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAddPasskey, useAuth, useAuthPlugin } from "@better-auth-ui/react";
|
|
3
|
-
import { Fingerprint } from "@gravity-ui/icons";
|
|
4
|
-
import { AlertDialog, Button, FieldError, Form, Input, Label, Spinner, TextField } from "@heroui/react";
|
|
5
|
-
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin";
|
|
6
|
-
export function AddPasskeyDialog({ isOpen, onOpenChange }) {
|
|
7
|
-
const { authClient, localization } = useAuth();
|
|
8
|
-
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin);
|
|
9
|
-
const { mutate: addPasskey, isPending: isAdding } = useAddPasskey(authClient);
|
|
10
|
-
const handleOpenChange = (open) => {
|
|
11
|
-
onOpenChange(open);
|
|
12
|
-
};
|
|
13
|
-
const handleSubmit = (e) => {
|
|
14
|
-
e.preventDefault();
|
|
15
|
-
const formData = new FormData(e.target);
|
|
16
|
-
const name = formData.get("name")?.trim();
|
|
17
|
-
addPasskey(name ? { name } : undefined, {
|
|
18
|
-
onSuccess: () => handleOpenChange(false)
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
return (_jsx(AlertDialog.Backdrop, { isOpen: isOpen, onOpenChange: handleOpenChange, children: _jsx(AlertDialog.Container, { children: _jsx(AlertDialog.Dialog, { children: _jsxs(Form, { onSubmit: handleSubmit, children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "default", children: _jsx(Fingerprint, {}) }), _jsx(AlertDialog.Heading, { children: passkeyLocalization.addPasskey })] }), _jsxs(AlertDialog.Body, { className: "overflow-visible", children: [_jsx("p", { className: "text-muted text-sm", children: passkeyLocalization.passkeysDescription }), _jsxs(TextField, { className: "mt-4", id: "name", name: "name", isDisabled: isAdding, children: [_jsx(Label, { children: passkeyLocalization.name }), _jsx(Input, { autoFocus: true, placeholder: localization.settings.optional, variant: "secondary" }), _jsx(FieldError, {})] })] }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "tertiary", isDisabled: isAdding, children: localization.settings.cancel }), _jsxs(Button, { type: "submit", isPending: isAdding, children: [isAdding && _jsx(Spinner, { color: "current", size: "sm" }), passkeyLocalization.addPasskey] })] })] }) }) }) }));
|
|
22
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin, useDeletePasskey } from "@better-auth-ui/react";
|
|
3
|
-
import { Fingerprint } from "@gravity-ui/icons";
|
|
4
|
-
import { AlertDialog, Button, Input, Label, Spinner, TextField } from "@heroui/react";
|
|
5
|
-
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin";
|
|
6
|
-
export function DeletePasskeyDialog({ isOpen, onOpenChange, passkey }) {
|
|
7
|
-
const { authClient, localization } = useAuth();
|
|
8
|
-
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin);
|
|
9
|
-
const passkeyName = passkey.name || passkeyLocalization.passkey;
|
|
10
|
-
const { mutate: deletePasskey, isPending: isDeleting } = useDeletePasskey(authClient, {
|
|
11
|
-
onSuccess: () => onOpenChange(false)
|
|
12
|
-
});
|
|
13
|
-
return (_jsx(AlertDialog.Backdrop, { isOpen: isOpen, onOpenChange: onOpenChange, children: _jsx(AlertDialog.Container, { children: _jsxs(AlertDialog.Dialog, { children: [_jsx(AlertDialog.CloseTrigger, {}), _jsxs(AlertDialog.Header, { children: [_jsx(AlertDialog.Icon, { status: "danger", children: _jsx(Fingerprint, {}) }), _jsx(AlertDialog.Heading, { children: passkeyLocalization.deletePasskeyTitle })] }), _jsxs(AlertDialog.Body, { className: "flex flex-col gap-4 overflow-visible", children: [_jsx("p", { className: "text-muted text-sm", children: passkeyLocalization.deletePasskeyWarning }), _jsxs(TextField, { value: passkeyName, variant: "secondary", children: [_jsx(Label, { children: passkey.name || passkeyLocalization.passkey }), _jsx(Input, { readOnly: true })] })] }), _jsxs(AlertDialog.Footer, { children: [_jsx(Button, { slot: "close", variant: "tertiary", isDisabled: isDeleting, children: localization.settings.cancel }), _jsxs(Button, { variant: "danger", onPress: () => deletePasskey({ id: passkey.id }), isPending: isDeleting, children: [isDeleting && _jsx(Spinner, { color: "current", size: "sm" }), passkeyLocalization.deletePasskeyTitle] })] })] }) }) }));
|
|
14
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { authMutationKeys } from "@better-auth-ui/core";
|
|
3
|
-
import { useAuth, useAuthPlugin, useSignInPasskey } from "@better-auth-ui/react";
|
|
4
|
-
import { Fingerprint } from "@gravity-ui/icons";
|
|
5
|
-
import { Button, Spinner } from "@heroui/react";
|
|
6
|
-
import { useIsMutating } from "@tanstack/react-query";
|
|
7
|
-
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin";
|
|
8
|
-
export function PasskeyButton({ view }) {
|
|
9
|
-
const { authClient, localization, redirectTo, navigate } = useAuth();
|
|
10
|
-
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin);
|
|
11
|
-
const { mutate: signInPasskey, isPending: passkeyPending } = useSignInPasskey(authClient, {
|
|
12
|
-
onSuccess: () => navigate({ to: redirectTo })
|
|
13
|
-
});
|
|
14
|
-
const signInMutating = useIsMutating({
|
|
15
|
-
mutationKey: authMutationKeys.signIn.all
|
|
16
|
-
});
|
|
17
|
-
const signUpMutating = useIsMutating({
|
|
18
|
-
mutationKey: authMutationKeys.signUp.all
|
|
19
|
-
});
|
|
20
|
-
const isPending = signInMutating + signUpMutating > 0;
|
|
21
|
-
// Passkey sign-in isn't relevant on the sign-up flow.
|
|
22
|
-
if (view === "signUp")
|
|
23
|
-
return null;
|
|
24
|
-
return (_jsxs(Button, { className: "w-full", variant: "tertiary", isDisabled: isPending, isPending: passkeyPending, onPress: () => signInPasskey(), children: [passkeyPending ? _jsx(Spinner, { color: "current", size: "sm" }) : _jsx(Fingerprint, {}), localization.auth.continueWith.replace("{{provider}}", passkeyLocalization.passkey)] }));
|
|
25
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Skeleton } from "@heroui/react";
|
|
3
|
-
export function PasskeySkeleton() {
|
|
4
|
-
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-28 rounded-lg" }), _jsx(Skeleton, { className: "h-3 w-32 rounded-lg" })] })] }) }));
|
|
5
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin } from "@better-auth-ui/react";
|
|
3
|
-
import { Fingerprint, Xmark } from "@gravity-ui/icons";
|
|
4
|
-
import { Button } from "@heroui/react";
|
|
5
|
-
import { useState } from "react";
|
|
6
|
-
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin";
|
|
7
|
-
import { DeletePasskeyDialog } from "./delete-passkey-dialog";
|
|
8
|
-
export function Passkey({ passkey }) {
|
|
9
|
-
const { localization } = useAuth();
|
|
10
|
-
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin);
|
|
11
|
-
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
12
|
-
const passkeyName = passkey.name || passkeyLocalization.passkey;
|
|
13
|
-
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: _jsx(Fingerprint, { className: "size-4.5" }) }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium leading-tight", children: passkeyName }), _jsx("span", { className: "text-xs text-muted", children: new Date(passkey.createdAt).toLocaleString(undefined, {
|
|
14
|
-
dateStyle: "medium",
|
|
15
|
-
timeStyle: "short"
|
|
16
|
-
}) })] }), _jsxs(Button, { className: "ml-auto shrink-0", variant: "outline", size: "sm", onPress: () => setDeleteOpen(true), "aria-label": passkeyLocalization.deletePasskey.replace("{{name}}", passkeyName), children: [_jsx(Xmark, {}), localization.settings.delete] }), _jsx(DeletePasskeyDialog, { isOpen: deleteOpen, onOpenChange: setDeleteOpen, passkey: passkey })] }));
|
|
17
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuthPlugin } from "@better-auth-ui/react";
|
|
3
|
-
import { Fingerprint } from "@gravity-ui/icons";
|
|
4
|
-
import { Button } from "@heroui/react";
|
|
5
|
-
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin";
|
|
6
|
-
export function PasskeysEmpty({ onAddPress }) {
|
|
7
|
-
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin);
|
|
8
|
-
return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-4", children: [_jsx("div", { className: "flex size-10 items-center justify-center rounded-xl bg-surface-secondary", children: _jsx(Fingerprint, { className: "size-4.5" }) }), _jsxs("div", { className: "flex flex-col items-center justify-center gap-1 text-center", children: [_jsx("p", { className: "text-sm font-semibold", children: passkeyLocalization.noPasskeys }), _jsx("p", { className: "text-muted text-xs", children: passkeyLocalization.passkeysDescription })] }), _jsx(Button, { size: "sm", onPress: onAddPress, children: passkeyLocalization.addPasskey })] }));
|
|
9
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useAuthPlugin, useListPasskeys } from "@better-auth-ui/react";
|
|
3
|
-
import { Button, Card, cn } from "@heroui/react";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin";
|
|
6
|
-
import { AddPasskeyDialog } from "./add-passkey-dialog";
|
|
7
|
-
import { Passkey } from "./passkey";
|
|
8
|
-
import { PasskeySkeleton } from "./passkey-skeleton";
|
|
9
|
-
import { PasskeysEmpty } from "./passkeys-empty";
|
|
10
|
-
export function Passkeys({ className, variant, ...props }) {
|
|
11
|
-
const { authClient } = useAuth();
|
|
12
|
-
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin);
|
|
13
|
-
const { data: passkeys, isPending } = useListPasskeys(authClient);
|
|
14
|
-
const [addOpen, setAddOpen] = useState(false);
|
|
15
|
-
return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [_jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("h2", { className: "text-sm font-semibold truncate", children: passkeyLocalization.passkeys }), _jsx(Button, { className: "shrink-0", size: "sm", isDisabled: isPending, onPress: () => setAddOpen(true), children: passkeyLocalization.addPasskey })] }), _jsx(Card, { variant: variant, ...props, children: _jsx(Card.Content, { children: isPending ? (_jsx(PasskeySkeleton, {})) : !passkeys?.length ? (_jsx(PasskeysEmpty, { onAddPress: () => setAddOpen(true) })) : (passkeys.map((passkey, index) => (_jsxs("div", { children: [index > 0 && (_jsx("div", { className: "border-b border-dashed -mx-4 my-4" })), _jsx(Passkey, { passkey: passkey })] }, passkey.id)))) }) }), _jsx(AddPasskeyDialog, { isOpen: addOpen, onOpenChange: setAddOpen })] }));
|
|
16
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { authMutationKeys, getProviderName } from "@better-auth-ui/core";
|
|
3
|
-
import { providerIcons, useAuth, useSignInSocial } from "@better-auth-ui/react";
|
|
4
|
-
import { Button, Spinner } from "@heroui/react";
|
|
5
|
-
import { useIsMutating } from "@tanstack/react-query";
|
|
6
|
-
/**
|
|
7
|
-
* Social provider sign-in button.
|
|
8
|
-
*
|
|
9
|
-
* @param provider - Provider to sign in with.
|
|
10
|
-
* @param display - `"full"` (e.g. "Continue with Google"), `"name"` (just the provider name), or `"icon"` (icon only).
|
|
11
|
-
*/
|
|
12
|
-
export function ProviderButton({ provider, display = "full", variant = "tertiary", ...props }) {
|
|
13
|
-
const { authClient, baseURL, localization, redirectTo } = useAuth();
|
|
14
|
-
const callbackURL = `${baseURL}${redirectTo}`;
|
|
15
|
-
const { mutate: signInSocial, isPending: signInSocialPending } = useSignInSocial(authClient);
|
|
16
|
-
const ProviderIcon = providerIcons[provider];
|
|
17
|
-
const signInMutating = useIsMutating({
|
|
18
|
-
mutationKey: authMutationKeys.signIn.all
|
|
19
|
-
});
|
|
20
|
-
const signUpMutating = useIsMutating({
|
|
21
|
-
mutationKey: authMutationKeys.signUp.all
|
|
22
|
-
});
|
|
23
|
-
const isPending = signInMutating + signUpMutating > 0;
|
|
24
|
-
return (_jsxs(Button, { variant: variant, isPending: isPending, onPress: () => signInSocial({ provider, callbackURL }), ...props, "aria-label": getProviderName(provider), children: [signInSocialPending ? (_jsx(Spinner, { color: "current", size: "sm" })) : (_jsx(ProviderIcon, {})), display === "full"
|
|
25
|
-
? localization.auth.continueWith.replace("{{provider}}", getProviderName(provider))
|
|
26
|
-
: display === "name"
|
|
27
|
-
? getProviderName(provider)
|
|
28
|
-
: null] }));
|
|
29
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth } from "@better-auth-ui/react";
|
|
3
|
-
import { cn } from "@heroui/react";
|
|
4
|
-
import { useMemo } from "react";
|
|
5
|
-
import { ProviderButton } from "./provider-button";
|
|
6
|
-
/**
|
|
7
|
-
* Render social provider sign-in buttons. Each button owns its own sign-in
|
|
8
|
-
* mutation and reads shared auth pending state from React Query.
|
|
9
|
-
*
|
|
10
|
-
* @param socialLayout - Preferred layout for the buttons; `"auto"` picks `"horizontal"` when there are four or more providers, otherwise `"vertical"`.
|
|
11
|
-
* @returns The JSX element that renders the configured social provider buttons.
|
|
12
|
-
*/
|
|
13
|
-
export function ProviderButtons({ socialLayout = "auto" }) {
|
|
14
|
-
const { socialProviders } = useAuth();
|
|
15
|
-
const resolvedSocialLayout = useMemo(() => {
|
|
16
|
-
if (socialLayout === "auto") {
|
|
17
|
-
if (socialProviders?.length && socialProviders.length >= 4) {
|
|
18
|
-
return "horizontal";
|
|
19
|
-
}
|
|
20
|
-
return "vertical";
|
|
21
|
-
}
|
|
22
|
-
return socialLayout;
|
|
23
|
-
}, [socialLayout, socialProviders?.length]);
|
|
24
|
-
return (_jsx("div", { className: cn("gap-3", resolvedSocialLayout === "grid" && "grid grid-cols-2", resolvedSocialLayout === "vertical" && "flex flex-col", resolvedSocialLayout === "horizontal" && "flex flex-wrap"), children: socialProviders?.map((provider) => (_jsx(ProviderButton, { provider: provider, display: resolvedSocialLayout === "vertical"
|
|
25
|
-
? "full"
|
|
26
|
-
: resolvedSocialLayout === "grid"
|
|
27
|
-
? "name"
|
|
28
|
-
: "icon", className: cn("w-full", resolvedSocialLayout === "horizontal" && "flex-1") }, provider))) }));
|
|
29
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAuth, useResetPassword } from "@better-auth-ui/react";
|
|
3
|
-
import { Eye, EyeSlash } from "@gravity-ui/icons";
|
|
4
|
-
import { Button, Card, cn, Description, FieldError, Form, InputGroup, Label, Link, Spinner, TextField, toast } from "@heroui/react";
|
|
5
|
-
import { useEffect, useState } from "react";
|
|
6
|
-
/**
|
|
7
|
-
* Renders a reset password form that verifies a "token" URL parameter on mount and redirects to sign-in if absent.
|
|
8
|
-
*
|
|
9
|
-
* Renders password (and optional confirm-password) inputs with visibility toggles, applies min/max length constraints from the auth configuration, shows field errors, and submits the new password to the auth client.
|
|
10
|
-
*/
|
|
11
|
-
export function ResetPassword({ className, variant, ...props }) {
|
|
12
|
-
const { authClient, basePaths, emailAndPassword, localization, viewPaths, navigate } = useAuth();
|
|
13
|
-
const { mutate: resetPassword, isPending } = useResetPassword(authClient, {
|
|
14
|
-
onSuccess: () => {
|
|
15
|
-
toast.success(localization.auth.passwordResetSuccess);
|
|
16
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` });
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
|
20
|
-
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] = useState(false);
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
23
|
-
const token = searchParams.get("token");
|
|
24
|
-
if (!token) {
|
|
25
|
-
toast.danger(localization.auth.invalidResetPasswordToken);
|
|
26
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` });
|
|
27
|
-
}
|
|
28
|
-
}, [
|
|
29
|
-
basePaths.auth,
|
|
30
|
-
localization.auth.invalidResetPasswordToken,
|
|
31
|
-
viewPaths.auth.signIn,
|
|
32
|
-
navigate
|
|
33
|
-
]);
|
|
34
|
-
function handleSubmit(e) {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
const searchParams = new URLSearchParams(window.location.search);
|
|
37
|
-
const token = searchParams.get("token");
|
|
38
|
-
if (!token) {
|
|
39
|
-
toast.danger(localization.auth.invalidResetPasswordToken);
|
|
40
|
-
navigate({ to: `${basePaths.auth}/${viewPaths.auth.signIn}` });
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const formData = new FormData(e.currentTarget);
|
|
44
|
-
const password = formData.get("password");
|
|
45
|
-
const confirmPassword = formData.get("confirmPassword");
|
|
46
|
-
if (emailAndPassword?.confirmPassword && password !== confirmPassword) {
|
|
47
|
-
toast.danger(localization.auth.passwordsDoNotMatch);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
resetPassword({ token, newPassword: password });
|
|
51
|
-
}
|
|
52
|
-
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.resetPassword }) }), _jsx(Card.Content, { className: "gap-4", children: _jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs(TextField, { minLength: emailAndPassword?.minPasswordLength, maxLength: emailAndPassword?.maxPasswordLength, name: "password", autoComplete: "new-password", isDisabled: isPending, children: [_jsx(Label, { children: localization.auth.password }), _jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Input, { name: "password", placeholder: localization.auth.newPasswordPlaceholder, type: isPasswordVisible ? "text" : "password", required: true }), _jsx(InputGroup.Suffix, { className: "px-0", children: _jsx(Button, { isIconOnly: true, "aria-label": isPasswordVisible
|
|
53
|
-
? localization.auth.hidePassword
|
|
54
|
-
: 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, children: [_jsx(Label, { children: localization.auth.confirmPassword }), _jsxs(InputGroup, { variant: variant === "transparent" ? "primary" : "secondary", children: [_jsx(InputGroup.Input, { placeholder: localization.auth.confirmPasswordPlaceholder, type: isConfirmPasswordVisible ? "text" : "password", required: true, name: "confirmPassword" }), _jsx(InputGroup.Suffix, { className: "px-0", children: _jsx(Button, { isIconOnly: true, "aria-label": isConfirmPasswordVisible
|
|
55
|
-
? localization.auth.hidePassword
|
|
56
|
-
: localization.auth.showPassword, size: "sm", variant: "ghost", onPress: () => setIsConfirmPasswordVisible(!isConfirmPasswordVisible), isDisabled: isPending, children: isConfirmPasswordVisible ? _jsx(EyeSlash, {}) : _jsx(Eye, {}) }) })] }), _jsx(FieldError, {})] })), _jsx("div", { className: "flex flex-col gap-3", children: _jsxs(Button, { type: "submit", className: "w-full", isPending: isPending, children: [isPending && _jsx(Spinner, { color: "current", size: "sm" }), localization.auth.resetPassword] }) })] }) }), _jsx(Card.Footer, { className: "flex-col gap-3", children: _jsxs(Description, { className: "text-sm", children: [localization.auth.rememberYourPassword, " ", _jsx(Link, { href: `${basePaths.auth}/${viewPaths.auth.signIn}`, className: "text-accent no-underline hover:underline decoration-accent-hover", children: localization.auth.signIn })] }) })] }));
|
|
57
|
-
}
|
|
@@ -1,22 +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 { ChangeEmail } from "./change-email";
|
|
5
|
-
import { UserProfile } from "./user-profile";
|
|
6
|
-
/**
|
|
7
|
-
* Renders the account settings layout.
|
|
8
|
-
*
|
|
9
|
-
* `UserProfile` always renders. `ChangeEmail` renders when password auth is
|
|
10
|
-
* enabled or the `magicLink` plugin is registered. Plugin-contributed account
|
|
11
|
-
* cards (e.g. `Appearance` from the theme plugin, multi-session accounts) are
|
|
12
|
-
* rendered via the plugins array.
|
|
13
|
-
*
|
|
14
|
-
* @param className - Optional additional CSS class names for the outer container.
|
|
15
|
-
* @param variant - Card variant forwarded to each account settings card.
|
|
16
|
-
* @returns The account settings container as a JSX element.
|
|
17
|
-
*/
|
|
18
|
-
export function AccountSettings({ className, variant, ...props }) {
|
|
19
|
-
const { emailAndPassword, plugins } = useAuth();
|
|
20
|
-
const hasMagicLink = plugins.some((plugin) => plugin.id === "magicLink");
|
|
21
|
-
return (_jsxs("div", { className: cn("flex w-full flex-col gap-4 md:gap-6", className), ...props, children: [_jsx(UserProfile, { variant: variant }), (emailAndPassword?.enabled || hasMagicLink) && (_jsx(ChangeEmail, { variant: variant })), plugins.flatMap((plugin) => plugin.accountCards?.map((Card, index) => (_jsx(Card, { variant: variant }, `${plugin.id}-${index.toString()}`))))] }));
|
|
22
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { fileToBase64 } from "@better-auth-ui/core";
|
|
3
|
-
import { useAuth, useSession, useUpdateUser } from "@better-auth-ui/react";
|
|
4
|
-
import { CloudArrowUpIn, TrashBin } from "@gravity-ui/icons";
|
|
5
|
-
import { Button, cn, Dropdown, Label, Spinner, toast } from "@heroui/react";
|
|
6
|
-
import { useRef, useState } from "react";
|
|
7
|
-
import { UserAvatar } from "../../user/user-avatar";
|
|
8
|
-
export function ChangeAvatar({ className }) {
|
|
9
|
-
const { authClient, localization, avatar } = useAuth();
|
|
10
|
-
const { data: session } = useSession(authClient);
|
|
11
|
-
const { mutate: updateUser, isPending: updatePending } = useUpdateUser(authClient);
|
|
12
|
-
const fileInputRef = useRef(null);
|
|
13
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
14
|
-
const [isDeleting, setIsDeleting] = useState(false);
|
|
15
|
-
const isPending = updatePending || isUploading || isDeleting;
|
|
16
|
-
async function handleFileChange(e) {
|
|
17
|
-
const file = e.target.files?.[0];
|
|
18
|
-
if (!file)
|
|
19
|
-
return;
|
|
20
|
-
e.target.value = "";
|
|
21
|
-
setIsUploading(true);
|
|
22
|
-
try {
|
|
23
|
-
const resized = (await avatar.resize?.(file, avatar.size, avatar.extension)) || file;
|
|
24
|
-
const image = (await avatar.upload?.(resized)) || (await fileToBase64(resized));
|
|
25
|
-
updateUser({ image }, {
|
|
26
|
-
onSuccess: () => toast.success(localization.settings.avatarChangedSuccess)
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
if (error instanceof Error) {
|
|
31
|
-
toast.danger(error.message);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
setIsUploading(false);
|
|
35
|
-
}
|
|
36
|
-
async function handleDelete() {
|
|
37
|
-
const currentImage = session?.user.image;
|
|
38
|
-
updateUser({ image: null }, {
|
|
39
|
-
onSuccess: async () => {
|
|
40
|
-
if (currentImage) {
|
|
41
|
-
setIsDeleting(true);
|
|
42
|
-
try {
|
|
43
|
-
await avatar.delete?.(currentImage);
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
setIsDeleting(false);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
toast.success(localization.settings.avatarDeletedSuccess);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return (_jsxs("div", { className: cn("flex flex-col gap-1", className), children: [_jsx(Label, { isDisabled: !session, children: localization.settings.avatar }), _jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", className: "hidden", onChange: handleFileChange }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Button, { type: "button", isIconOnly: true, variant: "ghost", className: "p-0 h-auto w-auto rounded-full", isDisabled: !session || isPending, onPress: () => fileInputRef.current?.click(), children: _jsx(UserAvatar, { size: "lg", isPending: isPending }) }), _jsxs(Dropdown, { children: [_jsxs(Button, { isDisabled: !session || isPending, size: "sm", variant: "secondary", children: [isPending && _jsx(Spinner, { size: "sm" }), localization.settings.changeAvatar] }), _jsx(Dropdown.Popover, { className: "min-w-fit", children: _jsxs(Dropdown.Menu, { children: [_jsxs(Dropdown.Item, { textValue: localization.settings.uploadAvatar, onAction: () => fileInputRef.current?.click(), children: [_jsx(CloudArrowUpIn, { className: "text-muted" }), _jsx(Label, { children: localization.settings.uploadAvatar })] }), _jsxs(Dropdown.Item, { textValue: localization.settings.deleteAvatar, isDisabled: !session?.user.image, onAction: handleDelete, variant: "danger", children: [_jsx(TrashBin, { className: "text-danger" }), _jsx(Label, { children: localization.settings.deleteAvatar })] })] }) })] })] })] }));
|
|
54
|
-
}
|