@better-auth-ui/heroui 1.6.7 → 1.6.9
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/additional-field.js +133 -266
- package/dist/components/auth/api-key/api-key-skeleton.js +4 -20
- package/dist/components/auth/api-key/api-key.d.ts +6 -2
- package/dist/components/auth/api-key/api-key.js +16 -54
- package/dist/components/auth/api-key/api-keys-empty.d.ts +3 -1
- package/dist/components/auth/api-key/api-keys-empty.js +8 -34
- package/dist/components/auth/api-key/api-keys.d.ts +10 -2
- package/dist/components/auth/api-key/api-keys.js +21 -40
- package/dist/components/auth/api-key/create-api-key-dialog.d.ts +3 -1
- package/dist/components/auth/api-key/create-api-key-dialog.js +43 -73
- package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +4 -2
- package/dist/components/auth/api-key/delete-api-key-dialog.js +16 -50
- package/dist/components/auth/api-key/new-api-key-dialog.js +23 -59
- package/dist/components/auth/api-key/organization-api-keys.d.ts +14 -0
- package/dist/components/auth/api-key/organization-api-keys.js +22 -0
- package/dist/components/auth/auth-provider.d.ts +1 -1
- package/dist/components/auth/auth-provider.js +10 -16
- package/dist/components/auth/auth.d.ts +3 -3
- package/dist/components/auth/auth.js +86 -61
- package/dist/components/auth/delete-user/danger-zone.d.ts +3 -3
- package/dist/components/auth/delete-user/danger-zone.js +11 -17
- package/dist/components/auth/delete-user/delete-account.d.ts +9 -0
- package/dist/components/auth/delete-user/delete-account.js +50 -0
- package/dist/components/auth/email/email-changed-email.d.ts +2 -2
- package/dist/components/auth/email/email-changed-email.js +22 -29
- package/dist/components/auth/email/email-verification-email.d.ts +2 -2
- package/dist/components/auth/email/email-verification-email.js +22 -29
- package/dist/components/auth/email/magic-link-email.d.ts +2 -2
- package/dist/components/auth/email/magic-link-email.js +22 -29
- package/dist/components/auth/email/new-device-email.d.ts +2 -2
- package/dist/components/auth/email/new-device-email.js +22 -29
- package/dist/components/auth/email/organization-invitation-email.d.ts +3 -0
- package/dist/components/auth/email/organization-invitation-email.js +23 -0
- package/dist/components/auth/email/otp-email.d.ts +2 -2
- package/dist/components/auth/email/otp-email.js +21 -28
- package/dist/components/auth/email/password-changed-email.d.ts +2 -2
- package/dist/components/auth/email/password-changed-email.js +22 -29
- package/dist/components/auth/email/reset-password-email.d.ts +2 -2
- package/dist/components/auth/email/reset-password-email.js +22 -29
- package/dist/components/auth/error-toaster.js +32 -16
- package/dist/components/auth/field-separator.d.ts +1 -1
- package/dist/components/auth/field-separator.js +4 -17
- package/dist/components/auth/forgot-password.d.ts +1 -1
- package/dist/components/auth/forgot-password.js +36 -88
- package/dist/components/auth/magic-link/magic-link-button.d.ts +1 -1
- package/dist/components/auth/magic-link/magic-link-button.js +33 -19
- package/dist/components/auth/magic-link/magic-link.d.ts +2 -2
- package/dist/components/auth/magic-link/magic-link.js +39 -86
- package/dist/components/auth/multi-session/manage-account.d.ts +1 -1
- package/dist/components/auth/multi-session/manage-account.js +32 -48
- package/dist/components/auth/multi-session/manage-accounts.d.ts +1 -1
- package/dist/components/auth/multi-session/manage-accounts.js +32 -33
- package/dist/components/auth/multi-session/switch-account-submenu-content.d.ts +3 -1
- package/dist/components/auth/multi-session/switch-account-submenu-content.js +24 -27
- package/dist/components/auth/multi-session/switch-account-submenu-item.d.ts +3 -2
- package/dist/components/auth/multi-session/switch-account-submenu-item.js +16 -19
- package/dist/components/auth/multi-session/switch-account-submenu.d.ts +2 -1
- package/dist/components/auth/multi-session/switch-account-submenu.js +23 -23
- package/dist/components/auth/organization/change-organization-logo.d.ts +4 -0
- package/dist/components/auth/organization/change-organization-logo.js +69 -0
- package/dist/components/auth/organization/create-organization-dialog.d.ts +13 -0
- package/dist/components/auth/organization/create-organization-dialog.js +37 -0
- package/dist/components/auth/organization/delete-organization-dialog.d.ts +7 -0
- package/dist/components/auth/organization/delete-organization-dialog.js +25 -0
- package/dist/components/auth/organization/delete-organization-skeleton.d.ts +4 -0
- package/dist/components/auth/organization/delete-organization-skeleton.js +8 -0
- package/dist/components/auth/organization/delete-organization.d.ts +5 -0
- package/dist/components/auth/organization/delete-organization.js +27 -0
- package/dist/components/auth/organization/invite-member-dialog.d.ts +13 -0
- package/dist/components/auth/organization/invite-member-dialog.js +47 -0
- package/dist/components/auth/organization/leave-organization-dialog.d.ts +7 -0
- package/dist/components/auth/organization/leave-organization-dialog.js +21 -0
- package/dist/components/auth/organization/leave-organization.d.ts +4 -0
- package/dist/components/auth/organization/leave-organization.js +16 -0
- package/dist/components/auth/organization/organization-danger-zone.d.ts +16 -0
- package/dist/components/auth/organization/organization-danger-zone.js +23 -0
- package/dist/components/auth/organization/organization-invitation-row-skeleton.d.ts +5 -0
- package/dist/components/auth/organization/organization-invitation-row-skeleton.js +9 -0
- package/dist/components/auth/organization/organization-invitation-row.d.ts +5 -0
- package/dist/components/auth/organization/organization-invitation-row.js +31 -0
- package/dist/components/auth/organization/organization-invitations-empty.d.ts +8 -0
- package/dist/components/auth/organization/organization-invitations-empty.js +13 -0
- package/dist/components/auth/organization/organization-invitations.d.ts +9 -0
- package/dist/components/auth/organization/organization-invitations.js +66 -0
- package/dist/components/auth/organization/organization-logo.d.ts +15 -0
- package/dist/components/auth/organization/organization-logo.js +14 -0
- package/dist/components/auth/organization/organization-member-row-skeleton.d.ts +4 -0
- package/dist/components/auth/organization/organization-member-row-skeleton.js +9 -0
- package/dist/components/auth/organization/organization-member-row.d.ts +9 -0
- package/dist/components/auth/organization/organization-member-row.js +34 -0
- package/dist/components/auth/organization/organization-members.d.ts +9 -0
- package/dist/components/auth/organization/organization-members.js +67 -0
- package/dist/components/auth/organization/organization-people.d.ts +10 -0
- package/dist/components/auth/organization/organization-people.js +11 -0
- package/dist/components/auth/organization/organization-profile.d.ts +9 -0
- package/dist/components/auth/organization/organization-profile.js +34 -0
- package/dist/components/auth/organization/organization-row.d.ts +8 -0
- package/dist/components/auth/organization/organization-row.js +31 -0
- package/dist/components/auth/organization/organization-settings.d.ts +12 -0
- package/dist/components/auth/organization/organization-settings.js +13 -0
- package/dist/components/auth/organization/organization-switcher.d.ts +20 -0
- package/dist/components/auth/organization/organization-switcher.js +48 -0
- package/dist/components/auth/organization/organization-view-skeleton.d.ts +6 -0
- package/dist/components/auth/organization/organization-view-skeleton.js +9 -0
- package/dist/components/auth/organization/organization-view.d.ts +15 -0
- package/dist/components/auth/organization/organization-view.js +31 -0
- package/dist/components/auth/organization/organization.d.ts +17 -0
- package/dist/components/auth/organization/organization.js +60 -0
- package/dist/components/auth/organization/organizations-empty.d.ts +4 -0
- package/dist/components/auth/organization/organizations-empty.js +9 -0
- package/dist/components/auth/organization/organizations-settings.d.ts +17 -0
- package/dist/components/auth/organization/organizations-settings.js +17 -0
- package/dist/components/auth/organization/organizations.d.ts +10 -0
- package/dist/components/auth/organization/organizations.js +21 -0
- package/dist/components/auth/organization/remove-member-dialog.d.ts +9 -0
- package/dist/components/auth/organization/remove-member-dialog.js +20 -0
- package/dist/components/auth/organization/slug-field.d.ts +19 -0
- package/dist/components/auth/organization/slug-field.js +38 -0
- package/dist/components/auth/organization/user-invitation-row-skeleton.d.ts +4 -0
- package/dist/components/auth/organization/user-invitation-row-skeleton.js +8 -0
- package/dist/components/auth/organization/user-invitation-row.d.ts +10 -0
- package/dist/components/auth/organization/user-invitation-row.js +22 -0
- package/dist/components/auth/organization/user-invitations-empty.d.ts +4 -0
- package/dist/components/auth/organization/user-invitations-empty.js +12 -0
- package/dist/components/auth/organization/user-invitations.d.ts +10 -0
- package/dist/components/auth/organization/user-invitations.js +18 -0
- package/dist/components/auth/passkey/add-passkey-dialog.d.ts +5 -0
- package/dist/components/auth/passkey/add-passkey-dialog.js +22 -0
- package/dist/components/auth/passkey/delete-passkey-dialog.d.ts +11 -0
- package/dist/components/auth/passkey/delete-passkey-dialog.js +14 -0
- package/dist/components/auth/passkey/passkey-button.d.ts +1 -1
- package/dist/components/auth/passkey/passkey-button.js +24 -23
- package/dist/components/auth/passkey/passkey-skeleton.d.ts +1 -0
- package/dist/components/auth/passkey/passkey-skeleton.js +5 -0
- package/dist/components/auth/passkey/passkey.d.ts +2 -5
- package/dist/components/auth/passkey/passkey.js +16 -44
- package/dist/components/auth/passkey/passkeys-empty.d.ts +4 -0
- package/dist/components/auth/passkey/passkeys-empty.js +9 -0
- package/dist/components/auth/passkey/passkeys.d.ts +1 -1
- package/dist/components/auth/passkey/passkeys.js +15 -105
- package/dist/components/auth/provider-button.d.ts +2 -2
- package/dist/components/auth/provider-button.js +28 -24
- package/dist/components/auth/provider-buttons.js +28 -18
- package/dist/components/auth/reset-password.d.ts +1 -1
- package/dist/components/auth/reset-password.js +56 -146
- package/dist/components/auth/settings/account/account-settings.d.ts +2 -2
- package/dist/components/auth/settings/account/account-settings.js +21 -19
- package/dist/components/auth/settings/account/change-avatar.js +53 -88
- package/dist/components/auth/settings/account/change-email.d.ts +1 -1
- package/dist/components/auth/settings/account/change-email.js +27 -53
- package/dist/components/auth/settings/account/user-profile.d.ts +1 -1
- package/dist/components/auth/settings/account/user-profile.js +64 -85
- package/dist/components/auth/settings/security/active-session.d.ts +1 -1
- package/dist/components/auth/settings/security/active-session.js +50 -57
- package/dist/components/auth/settings/security/active-sessions.d.ts +1 -1
- package/dist/components/auth/settings/security/active-sessions.js +21 -32
- package/dist/components/auth/settings/security/change-password.d.ts +1 -1
- package/dist/components/auth/settings/security/change-password.js +76 -186
- package/dist/components/auth/settings/security/linked-account.d.ts +1 -1
- package/dist/components/auth/settings/security/linked-account.js +37 -55
- package/dist/components/auth/settings/security/linked-accounts.d.ts +1 -1
- package/dist/components/auth/settings/security/linked-accounts.js +34 -43
- package/dist/components/auth/settings/security/security-settings.d.ts +2 -2
- package/dist/components/auth/settings/security/security-settings.js +19 -21
- package/dist/components/auth/settings/settings.d.ts +4 -4
- package/dist/components/auth/settings/settings.js +52 -44
- package/dist/components/auth/sign-in.d.ts +2 -2
- package/dist/components/auth/sign-in.js +63 -132
- package/dist/components/auth/sign-out.js +32 -26
- package/dist/components/auth/sign-up.d.ts +2 -2
- package/dist/components/auth/sign-up.js +97 -211
- package/dist/components/auth/theme/appearance.d.ts +1 -1
- package/dist/components/auth/theme/appearance.js +23 -71
- package/dist/components/auth/theme/theme-toggle-item.js +56 -61
- package/dist/components/auth/user/user-avatar.d.ts +4 -4
- package/dist/components/auth/user/user-avatar.js +31 -28
- package/dist/components/auth/user/user-button.d.ts +6 -7
- package/dist/components/auth/user/user-button.js +38 -72
- package/dist/components/auth/user/user-view.d.ts +13 -7
- package/dist/components/auth/user/user-view.js +26 -36
- package/dist/components/auth/username/sign-in-username.d.ts +2 -2
- package/dist/components/auth/username/sign-in-username.js +87 -147
- package/dist/components/auth/username/username-field.d.ts +1 -1
- package/dist/components/auth/username/username-field.js +44 -52
- package/dist/email.d.ts +8 -7
- package/dist/email.js +8 -8
- package/dist/index.d.ts +21 -21
- package/dist/index.js +21 -22
- package/dist/lib/auth/api-key-plugin.d.ts +7 -4
- package/dist/lib/auth/api-key-plugin.js +12 -10
- package/dist/lib/auth/auth-plugin.d.ts +10 -6
- package/dist/lib/auth/auth-plugin.js +1 -0
- package/dist/lib/auth/delete-user-plugin.d.ts +6 -6
- package/dist/lib/auth/delete-user-plugin.js +6 -9
- package/dist/lib/auth/magic-link-plugin.d.ts +5 -5
- package/dist/lib/auth/magic-link-plugin.js +17 -12
- package/dist/lib/auth/multi-session-plugin.d.ts +5 -5
- package/dist/lib/auth/multi-session-plugin.js +8 -11
- package/dist/lib/auth/organization-plugin.d.ts +37 -0
- package/dist/lib/auth/organization-plugin.js +19 -0
- package/dist/lib/auth/passkey-plugin.d.ts +9 -6
- package/dist/lib/auth/passkey-plugin.js +8 -11
- package/dist/lib/auth/theme-plugin.d.ts +6 -6
- package/dist/lib/auth/theme-plugin.js +25 -22
- package/dist/lib/auth/username-plugin.d.ts +11 -11
- package/dist/lib/auth/username-plugin.js +18 -17
- package/dist/plugins.d.ts +46 -22
- package/dist/plugins.js +47 -22
- package/package.json +25 -19
- package/src/components/auth/api-key/api-key.tsx +25 -16
- package/src/components/auth/api-key/api-keys-empty.tsx +8 -4
- package/src/components/auth/api-key/api-keys.tsx +52 -14
- package/src/components/auth/api-key/create-api-key-dialog.tsx +17 -4
- package/src/components/auth/api-key/delete-api-key-dialog.tsx +10 -2
- package/src/components/auth/api-key/organization-api-keys.tsx +55 -0
- package/src/components/auth/delete-user/danger-zone.tsx +3 -3
- package/src/components/auth/delete-user/{delete-user.tsx → delete-account.tsx} +9 -9
- package/src/components/auth/email/organization-invitation-email.tsx +38 -0
- package/src/components/auth/error-toaster.tsx +28 -10
- package/src/components/auth/forgot-password.tsx +10 -2
- package/src/components/auth/multi-session/manage-accounts.tsx +1 -1
- package/src/components/auth/multi-session/switch-account-submenu-content.tsx +7 -2
- package/src/components/auth/multi-session/switch-account-submenu-item.tsx +4 -2
- package/src/components/auth/multi-session/switch-account-submenu.tsx +8 -4
- package/src/components/auth/organization/change-organization-logo.tsx +171 -0
- package/src/components/auth/organization/create-organization-dialog.tsx +133 -0
- package/src/components/auth/organization/delete-organization-dialog.tsx +97 -0
- package/src/components/auth/organization/delete-organization-skeleton.tsx +17 -0
- package/src/components/auth/organization/delete-organization.tsx +77 -0
- package/src/components/auth/organization/invite-member-dialog.tsx +177 -0
- package/src/components/auth/organization/leave-organization-dialog.tsx +95 -0
- package/src/components/auth/organization/leave-organization.tsx +59 -0
- package/src/components/auth/organization/organization-danger-zone.tsx +68 -0
- package/src/components/auth/organization/organization-invitation-row-skeleton.tsx +29 -0
- package/src/components/auth/organization/organization-invitation-row.tsx +97 -0
- package/src/components/auth/organization/organization-invitations-empty.tsx +39 -0
- package/src/components/auth/organization/organization-invitations.tsx +351 -0
- package/src/components/auth/organization/organization-logo.tsx +64 -0
- package/src/components/auth/organization/organization-member-row-skeleton.tsx +24 -0
- package/src/components/auth/organization/organization-member-row.tsx +157 -0
- package/src/components/auth/organization/organization-members.tsx +284 -0
- package/src/components/auth/organization/organization-people.tsx +26 -0
- package/src/components/auth/organization/organization-profile.tsx +141 -0
- package/src/components/auth/organization/organization-row.tsx +66 -0
- package/src/components/auth/organization/organization-settings.tsx +38 -0
- package/src/components/auth/organization/organization-switcher.tsx +233 -0
- package/src/components/auth/organization/organization-view-skeleton.tsx +36 -0
- package/src/components/auth/organization/organization-view.tsx +109 -0
- package/src/components/auth/organization/organization.tsx +162 -0
- package/src/components/auth/organization/organizations-empty.tsx +35 -0
- package/src/components/auth/organization/organizations-settings.tsx +36 -0
- package/src/components/auth/organization/organizations.tsx +80 -0
- package/src/components/auth/organization/remove-member-dialog.tsx +95 -0
- package/src/components/auth/organization/slug-field.tsx +111 -0
- package/src/components/auth/organization/user-invitation-row-skeleton.tsx +17 -0
- package/src/components/auth/organization/user-invitation-row.tsx +90 -0
- package/src/components/auth/organization/user-invitations-empty.tsx +30 -0
- package/src/components/auth/organization/user-invitations.tsx +59 -0
- package/src/components/auth/passkey/add-passkey-dialog.tsx +109 -0
- package/src/components/auth/passkey/delete-passkey-dialog.tsx +95 -0
- package/src/components/auth/passkey/passkey-skeleton.tsx +16 -0
- package/src/components/auth/passkey/passkey.tsx +23 -22
- package/src/components/auth/passkey/passkeys-empty.tsx +35 -0
- package/src/components/auth/passkey/passkeys.tsx +33 -154
- package/src/components/auth/settings/account/change-email.tsx +1 -1
- package/src/components/auth/settings/account/user-profile.tsx +5 -5
- package/src/components/auth/settings/security/active-sessions.tsx +6 -4
- package/src/components/auth/settings/security/linked-accounts.tsx +2 -2
- package/src/components/auth/settings/settings.tsx +77 -14
- package/src/components/auth/user/user-avatar.tsx +4 -1
- package/src/components/auth/user/user-button.tsx +14 -11
- package/src/components/auth/user/user-view.tsx +32 -12
- package/src/email.ts +1 -0
- package/src/lib/auth/api-key-plugin.ts +10 -4
- package/src/lib/auth/auth-plugin.ts +6 -1
- package/src/lib/auth/organization-plugin.tsx +32 -0
- package/src/plugins.ts +25 -1
- package/dist/components/auth/delete-user/delete-user.d.ts +0 -9
- package/dist/components/auth/delete-user/delete-user.js +0 -95
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type OrganizationAuthClient,
|
|
3
|
+
useAuth,
|
|
4
|
+
useAuthPlugin,
|
|
5
|
+
useCancelInvitation,
|
|
6
|
+
useHasPermission
|
|
7
|
+
} from "@better-auth-ui/react"
|
|
8
|
+
import { Xmark } from "@gravity-ui/icons"
|
|
9
|
+
import { Button, Chip, Spinner, Table } from "@heroui/react"
|
|
10
|
+
|
|
11
|
+
import type { Invitation } from "better-auth/client"
|
|
12
|
+
|
|
13
|
+
import { organizationPlugin } from "../../../lib/auth/organization-plugin"
|
|
14
|
+
import { OrganizationInvitationRowSkeleton } from "./organization-invitation-row-skeleton"
|
|
15
|
+
|
|
16
|
+
export type OrganizationInvitationTableRowProps = {
|
|
17
|
+
invitation: Invitation
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function OrganizationInvitationTableRow({
|
|
21
|
+
invitation
|
|
22
|
+
}: OrganizationInvitationTableRowProps) {
|
|
23
|
+
const { authClient } = useAuth()
|
|
24
|
+
const { localization: organizationLocalization, roles } =
|
|
25
|
+
useAuthPlugin(organizationPlugin)
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
data: cancelInvitationPermission,
|
|
29
|
+
isPending: cancelPermissionPending
|
|
30
|
+
} = useHasPermission(authClient as OrganizationAuthClient, {
|
|
31
|
+
permissions: { invitation: ["cancel"] }
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const { mutate: cancelInvitation, isPending: cancelPending } =
|
|
35
|
+
useCancelInvitation(authClient as OrganizationAuthClient)
|
|
36
|
+
|
|
37
|
+
const roleLabel = roles?.[invitation.role] ?? invitation.role
|
|
38
|
+
|
|
39
|
+
const statusLabel =
|
|
40
|
+
organizationLocalization[invitation.status] ?? invitation.status
|
|
41
|
+
|
|
42
|
+
const statusColor =
|
|
43
|
+
invitation.status === "pending"
|
|
44
|
+
? "warning"
|
|
45
|
+
: invitation.status === "accepted"
|
|
46
|
+
? "success"
|
|
47
|
+
: invitation.status === "rejected"
|
|
48
|
+
? "danger"
|
|
49
|
+
: "default"
|
|
50
|
+
|
|
51
|
+
if (cancelPermissionPending) {
|
|
52
|
+
return <OrganizationInvitationRowSkeleton />
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Table.Row>
|
|
57
|
+
<Table.Cell className="font-medium text-sm">
|
|
58
|
+
{invitation.email}
|
|
59
|
+
</Table.Cell>
|
|
60
|
+
|
|
61
|
+
<Table.Cell className="text-muted text-xs tabular-nums whitespace-nowrap">
|
|
62
|
+
{new Date(invitation.createdAt).toLocaleString(undefined, {
|
|
63
|
+
dateStyle: "short",
|
|
64
|
+
timeStyle: "short"
|
|
65
|
+
})}
|
|
66
|
+
</Table.Cell>
|
|
67
|
+
|
|
68
|
+
<Table.Cell className="text-sm">{roleLabel}</Table.Cell>
|
|
69
|
+
|
|
70
|
+
<Table.Cell className="text-sm">
|
|
71
|
+
<Chip color={statusColor} size="sm" variant="soft">
|
|
72
|
+
{statusLabel}
|
|
73
|
+
</Chip>
|
|
74
|
+
</Table.Cell>
|
|
75
|
+
|
|
76
|
+
<Table.Cell className="text-end">
|
|
77
|
+
{cancelInvitationPermission?.success &&
|
|
78
|
+
invitation.status === "pending" && (
|
|
79
|
+
<Button
|
|
80
|
+
isIconOnly
|
|
81
|
+
size="sm"
|
|
82
|
+
variant="danger-soft"
|
|
83
|
+
isPending={cancelPending}
|
|
84
|
+
onPress={() => cancelInvitation({ invitationId: invitation.id })}
|
|
85
|
+
aria-label={organizationLocalization.cancelInvitation}
|
|
86
|
+
>
|
|
87
|
+
{cancelPending ? (
|
|
88
|
+
<Spinner color="current" size="sm" />
|
|
89
|
+
) : (
|
|
90
|
+
<Xmark />
|
|
91
|
+
)}
|
|
92
|
+
</Button>
|
|
93
|
+
)}
|
|
94
|
+
</Table.Cell>
|
|
95
|
+
</Table.Row>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useAuthPlugin } from "@better-auth-ui/react"
|
|
2
|
+
import { PaperPlane } from "@gravity-ui/icons"
|
|
3
|
+
import { Button, EmptyState } from "@heroui/react"
|
|
4
|
+
import { organizationPlugin } from "../../../lib/auth/organization-plugin"
|
|
5
|
+
|
|
6
|
+
export type OrganizationInvitationsEmptyProps = {
|
|
7
|
+
onInvitePress: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Empty state for {@link OrganizationInvitations} — mirrors `ApiKeysEmpty`
|
|
12
|
+
* (icon, title, description) with an "Invite member" call to action.
|
|
13
|
+
*/
|
|
14
|
+
export function OrganizationInvitationsEmpty({
|
|
15
|
+
onInvitePress
|
|
16
|
+
}: OrganizationInvitationsEmptyProps) {
|
|
17
|
+
const { localization: organizationLocalization } =
|
|
18
|
+
useAuthPlugin(organizationPlugin)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<EmptyState className="flex flex-col items-center gap-4 text-center p-4">
|
|
22
|
+
<PaperPlane className="size-6 text-muted" />
|
|
23
|
+
|
|
24
|
+
<div className="flex flex-col gap-2">
|
|
25
|
+
<p className="text-sm font-semibold text-foreground">
|
|
26
|
+
{organizationLocalization.noInvitations}
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<span className="text-sm text-muted">
|
|
30
|
+
{organizationLocalization.organizationInvitationsEmptyDescription}
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<Button size="sm" onPress={onInvitePress}>
|
|
35
|
+
{organizationLocalization.inviteMember}
|
|
36
|
+
</Button>
|
|
37
|
+
</EmptyState>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import type { OrganizationLocalization } from "@better-auth-ui/core/plugins"
|
|
2
|
+
import {
|
|
3
|
+
type OrganizationAuthClient,
|
|
4
|
+
useAuth,
|
|
5
|
+
useAuthPlugin,
|
|
6
|
+
useHasPermission,
|
|
7
|
+
useListOrganizationInvitations
|
|
8
|
+
} from "@better-auth-ui/react"
|
|
9
|
+
import { ChevronUp, Funnel, Xmark } from "@gravity-ui/icons"
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
Chip,
|
|
13
|
+
cn,
|
|
14
|
+
Dropdown,
|
|
15
|
+
Label,
|
|
16
|
+
SearchField,
|
|
17
|
+
type SortDescriptor,
|
|
18
|
+
Table
|
|
19
|
+
} from "@heroui/react"
|
|
20
|
+
import { type ComponentProps, type ReactNode, useMemo, useState } from "react"
|
|
21
|
+
|
|
22
|
+
import { organizationPlugin } from "../../../lib/auth/organization-plugin"
|
|
23
|
+
import { InviteMemberDialog } from "./invite-member-dialog"
|
|
24
|
+
import { OrganizationInvitationTableRow } from "./organization-invitation-row"
|
|
25
|
+
import { OrganizationInvitationRowSkeleton } from "./organization-invitation-row-skeleton"
|
|
26
|
+
import { OrganizationInvitationsEmpty } from "./organization-invitations-empty"
|
|
27
|
+
|
|
28
|
+
/** Props for the {@link OrganizationInvitations} component. */
|
|
29
|
+
export type OrganizationInvitationsProps = {
|
|
30
|
+
className?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Organization invitations table with invite control and per-row actions.
|
|
35
|
+
*/
|
|
36
|
+
export function OrganizationInvitations({
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
39
|
+
}: OrganizationInvitationsProps & ComponentProps<"div">) {
|
|
40
|
+
const { authClient, localization } = useAuth()
|
|
41
|
+
const { localization: organizationLocalization, roles } =
|
|
42
|
+
useAuthPlugin(organizationPlugin)
|
|
43
|
+
|
|
44
|
+
const { data: invitations, isPending: invitationsPending } =
|
|
45
|
+
useListOrganizationInvitations(authClient as OrganizationAuthClient)
|
|
46
|
+
|
|
47
|
+
const { isPending: invitationPermissionPending } = useHasPermission(
|
|
48
|
+
authClient as OrganizationAuthClient,
|
|
49
|
+
{
|
|
50
|
+
permissions: { invitation: ["cancel"] }
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const isPending = invitationsPending || invitationPermissionPending
|
|
55
|
+
|
|
56
|
+
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>()
|
|
57
|
+
const [roleFilter, setRoleFilter] = useState("all")
|
|
58
|
+
const [statusFilter, setStatusFilter] = useState("all")
|
|
59
|
+
const [search, setSearch] = useState("")
|
|
60
|
+
|
|
61
|
+
const filteredInvitations = useMemo(() => {
|
|
62
|
+
return invitations?.filter(
|
|
63
|
+
(invitation) =>
|
|
64
|
+
(roleFilter === "all" || invitation.role === roleFilter) &&
|
|
65
|
+
(statusFilter === "all" || invitation.status === statusFilter) &&
|
|
66
|
+
invitation.email.toLowerCase().includes(search.toLowerCase())
|
|
67
|
+
)
|
|
68
|
+
}, [search, invitations, roleFilter, statusFilter])
|
|
69
|
+
|
|
70
|
+
const sortedInvitations = useMemo(() => {
|
|
71
|
+
if (!sortDescriptor) return filteredInvitations
|
|
72
|
+
if (!filteredInvitations) return filteredInvitations
|
|
73
|
+
|
|
74
|
+
return [...filteredInvitations].sort((a, b) => {
|
|
75
|
+
const col = sortDescriptor.column as keyof typeof a
|
|
76
|
+
let cmp = 0
|
|
77
|
+
|
|
78
|
+
if (col === "createdAt") {
|
|
79
|
+
cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
|
80
|
+
} else {
|
|
81
|
+
cmp = String(a[col]).localeCompare(String(b[col]))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (sortDescriptor.direction === "descending") {
|
|
85
|
+
cmp *= -1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return cmp
|
|
89
|
+
})
|
|
90
|
+
}, [sortDescriptor, filteredInvitations])
|
|
91
|
+
|
|
92
|
+
const [inviteOpen, setInviteOpen] = useState(false)
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className={cn("flex flex-col gap-3", className)} {...props}>
|
|
96
|
+
<h3 className="truncate text-sm font-semibold">
|
|
97
|
+
{organizationLocalization.invitations}
|
|
98
|
+
</h3>
|
|
99
|
+
|
|
100
|
+
<div className="flex flex-col gap-4">
|
|
101
|
+
<div className="flex items-center gap-3">
|
|
102
|
+
<SearchField
|
|
103
|
+
className="min-w-0"
|
|
104
|
+
aria-label={organizationLocalization.search}
|
|
105
|
+
value={search}
|
|
106
|
+
onChange={setSearch}
|
|
107
|
+
isDisabled={isPending}
|
|
108
|
+
>
|
|
109
|
+
<SearchField.Group>
|
|
110
|
+
<SearchField.SearchIcon />
|
|
111
|
+
|
|
112
|
+
<SearchField.Input
|
|
113
|
+
placeholder={organizationLocalization.search}
|
|
114
|
+
className="sm:w-[200px]"
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
<SearchField.ClearButton />
|
|
118
|
+
</SearchField.Group>
|
|
119
|
+
</SearchField>
|
|
120
|
+
|
|
121
|
+
<Dropdown>
|
|
122
|
+
<Button size="sm" variant="secondary" isDisabled={isPending}>
|
|
123
|
+
<Funnel />
|
|
124
|
+
|
|
125
|
+
{organizationLocalization.role}
|
|
126
|
+
</Button>
|
|
127
|
+
|
|
128
|
+
<Dropdown.Popover>
|
|
129
|
+
<Dropdown.Menu
|
|
130
|
+
selectionMode="single"
|
|
131
|
+
selectedKeys={new Set([roleFilter])}
|
|
132
|
+
onSelectionChange={(keys) => {
|
|
133
|
+
const key = [...keys][0] as string | undefined
|
|
134
|
+
setRoleFilter(key ?? "all")
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<Dropdown.Item
|
|
138
|
+
id="all"
|
|
139
|
+
textValue={organizationLocalization.all}
|
|
140
|
+
>
|
|
141
|
+
<Label>{organizationLocalization.all}</Label>
|
|
142
|
+
|
|
143
|
+
<Dropdown.ItemIndicator />
|
|
144
|
+
</Dropdown.Item>
|
|
145
|
+
|
|
146
|
+
{Object.entries(roles).map(([key, label]) => (
|
|
147
|
+
<Dropdown.Item key={key} id={key} textValue={label}>
|
|
148
|
+
<Label>{label}</Label>
|
|
149
|
+
|
|
150
|
+
<Dropdown.ItemIndicator />
|
|
151
|
+
</Dropdown.Item>
|
|
152
|
+
))}
|
|
153
|
+
</Dropdown.Menu>
|
|
154
|
+
</Dropdown.Popover>
|
|
155
|
+
</Dropdown>
|
|
156
|
+
|
|
157
|
+
<Dropdown>
|
|
158
|
+
<Button size="sm" variant="secondary" isDisabled={isPending}>
|
|
159
|
+
<Funnel />
|
|
160
|
+
|
|
161
|
+
{organizationLocalization.status}
|
|
162
|
+
</Button>
|
|
163
|
+
|
|
164
|
+
<Dropdown.Popover>
|
|
165
|
+
<Dropdown.Menu
|
|
166
|
+
selectionMode="single"
|
|
167
|
+
selectedKeys={new Set([statusFilter])}
|
|
168
|
+
onSelectionChange={(keys) => {
|
|
169
|
+
const key = [...keys][0] as string | undefined
|
|
170
|
+
setStatusFilter(key ?? "all")
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
<Dropdown.Item
|
|
174
|
+
id="all"
|
|
175
|
+
textValue={organizationLocalization.all}
|
|
176
|
+
>
|
|
177
|
+
<Label>{organizationLocalization.all}</Label>
|
|
178
|
+
|
|
179
|
+
<Dropdown.ItemIndicator />
|
|
180
|
+
</Dropdown.Item>
|
|
181
|
+
|
|
182
|
+
{["pending", "accepted", "rejected", "canceled"].map(
|
|
183
|
+
(status) => (
|
|
184
|
+
<Dropdown.Item
|
|
185
|
+
key={status}
|
|
186
|
+
id={status}
|
|
187
|
+
textValue={
|
|
188
|
+
organizationLocalization[
|
|
189
|
+
status as keyof OrganizationLocalization
|
|
190
|
+
] ?? status
|
|
191
|
+
}
|
|
192
|
+
>
|
|
193
|
+
<Label>
|
|
194
|
+
{organizationLocalization[
|
|
195
|
+
status as keyof OrganizationLocalization
|
|
196
|
+
] ?? status}
|
|
197
|
+
</Label>
|
|
198
|
+
|
|
199
|
+
<Dropdown.ItemIndicator />
|
|
200
|
+
</Dropdown.Item>
|
|
201
|
+
)
|
|
202
|
+
)}
|
|
203
|
+
</Dropdown.Menu>
|
|
204
|
+
</Dropdown.Popover>
|
|
205
|
+
</Dropdown>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{(roleFilter !== "all" || statusFilter !== "all") && (
|
|
209
|
+
<div className="flex flex-wrap gap-2">
|
|
210
|
+
{roleFilter !== "all" && (
|
|
211
|
+
<Chip size="sm" variant="secondary" className="w-fit">
|
|
212
|
+
<Chip.Label>
|
|
213
|
+
{organizationLocalization.role}:{" "}
|
|
214
|
+
<span className="capitalize">
|
|
215
|
+
{roles?.[roleFilter] ?? roleFilter}
|
|
216
|
+
</span>
|
|
217
|
+
</Chip.Label>
|
|
218
|
+
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
aria-label={organizationLocalization.clear}
|
|
222
|
+
className="text-muted hover:text-foreground inline-flex cursor-pointer items-center"
|
|
223
|
+
onClick={() => setRoleFilter("all")}
|
|
224
|
+
>
|
|
225
|
+
<Xmark className="size-3" />
|
|
226
|
+
</button>
|
|
227
|
+
</Chip>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{statusFilter !== "all" && (
|
|
231
|
+
<Chip size="sm" variant="secondary" className="w-fit">
|
|
232
|
+
<Chip.Label>
|
|
233
|
+
{organizationLocalization.status}:{" "}
|
|
234
|
+
{organizationLocalization[
|
|
235
|
+
statusFilter as keyof OrganizationLocalization
|
|
236
|
+
] ?? statusFilter}
|
|
237
|
+
</Chip.Label>
|
|
238
|
+
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
aria-label={organizationLocalization.clear}
|
|
242
|
+
className="text-muted hover:text-foreground inline-flex cursor-pointer items-center"
|
|
243
|
+
onClick={() => setStatusFilter("all")}
|
|
244
|
+
>
|
|
245
|
+
<Xmark className="size-3" />
|
|
246
|
+
</button>
|
|
247
|
+
</Chip>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
|
|
252
|
+
<Table>
|
|
253
|
+
<Table.ScrollContainer>
|
|
254
|
+
<Table.Content
|
|
255
|
+
aria-label={organizationLocalization.invitations}
|
|
256
|
+
sortDescriptor={sortDescriptor}
|
|
257
|
+
onSortChange={(descriptor) => {
|
|
258
|
+
const shouldReset =
|
|
259
|
+
sortDescriptor?.column === descriptor.column &&
|
|
260
|
+
descriptor.direction === "ascending"
|
|
261
|
+
setSortDescriptor(shouldReset ? undefined : descriptor)
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
<Table.Header>
|
|
265
|
+
<Table.Column allowsSorting isRowHeader id="email">
|
|
266
|
+
{({ sortDirection }) => (
|
|
267
|
+
<SortableColumnHeader sortDirection={sortDirection}>
|
|
268
|
+
{localization.auth.email}
|
|
269
|
+
</SortableColumnHeader>
|
|
270
|
+
)}
|
|
271
|
+
</Table.Column>
|
|
272
|
+
|
|
273
|
+
<Table.Column allowsSorting id="createdAt">
|
|
274
|
+
{({ sortDirection }) => (
|
|
275
|
+
<SortableColumnHeader sortDirection={sortDirection}>
|
|
276
|
+
{organizationLocalization.invitedAt}
|
|
277
|
+
</SortableColumnHeader>
|
|
278
|
+
)}
|
|
279
|
+
</Table.Column>
|
|
280
|
+
|
|
281
|
+
<Table.Column allowsSorting id="role">
|
|
282
|
+
{({ sortDirection }) => (
|
|
283
|
+
<SortableColumnHeader sortDirection={sortDirection}>
|
|
284
|
+
{organizationLocalization.role}
|
|
285
|
+
</SortableColumnHeader>
|
|
286
|
+
)}
|
|
287
|
+
</Table.Column>
|
|
288
|
+
|
|
289
|
+
<Table.Column allowsSorting id="status">
|
|
290
|
+
{({ sortDirection }) => (
|
|
291
|
+
<SortableColumnHeader sortDirection={sortDirection}>
|
|
292
|
+
{organizationLocalization.status}
|
|
293
|
+
</SortableColumnHeader>
|
|
294
|
+
)}
|
|
295
|
+
</Table.Column>
|
|
296
|
+
|
|
297
|
+
<Table.Column className="text-end">
|
|
298
|
+
{organizationLocalization.actions}
|
|
299
|
+
</Table.Column>
|
|
300
|
+
</Table.Header>
|
|
301
|
+
|
|
302
|
+
<Table.Body
|
|
303
|
+
renderEmptyState={() => (
|
|
304
|
+
<OrganizationInvitationsEmpty
|
|
305
|
+
onInvitePress={() => setInviteOpen(true)}
|
|
306
|
+
/>
|
|
307
|
+
)}
|
|
308
|
+
>
|
|
309
|
+
{isPending ? (
|
|
310
|
+
<OrganizationInvitationRowSkeleton />
|
|
311
|
+
) : (
|
|
312
|
+
sortedInvitations?.map((invitation) => (
|
|
313
|
+
<OrganizationInvitationTableRow
|
|
314
|
+
key={invitation.id}
|
|
315
|
+
invitation={invitation}
|
|
316
|
+
/>
|
|
317
|
+
))
|
|
318
|
+
)}
|
|
319
|
+
</Table.Body>
|
|
320
|
+
</Table.Content>
|
|
321
|
+
</Table.ScrollContainer>
|
|
322
|
+
</Table>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<InviteMemberDialog isOpen={inviteOpen} onOpenChange={setInviteOpen} />
|
|
326
|
+
</div>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function SortableColumnHeader({
|
|
331
|
+
children,
|
|
332
|
+
sortDirection
|
|
333
|
+
}: {
|
|
334
|
+
children: ReactNode
|
|
335
|
+
sortDirection?: "ascending" | "descending"
|
|
336
|
+
}) {
|
|
337
|
+
return (
|
|
338
|
+
<span className="flex items-center justify-between">
|
|
339
|
+
{children}
|
|
340
|
+
|
|
341
|
+
{!!sortDirection && (
|
|
342
|
+
<ChevronUp
|
|
343
|
+
className={cn(
|
|
344
|
+
"size-3 transform transition-transform duration-100 ease-out",
|
|
345
|
+
sortDirection === "descending" ? "rotate-180" : ""
|
|
346
|
+
)}
|
|
347
|
+
/>
|
|
348
|
+
)}
|
|
349
|
+
</span>
|
|
350
|
+
)
|
|
351
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Briefcase } from "@gravity-ui/icons"
|
|
2
|
+
import { Avatar, type AvatarProps, cn, Skeleton } from "@heroui/react"
|
|
3
|
+
import type { Organization } from "better-auth/client"
|
|
4
|
+
import type { ReactNode } from "react"
|
|
5
|
+
|
|
6
|
+
export type OrganizationLogoProps = {
|
|
7
|
+
className?: string
|
|
8
|
+
fallback?: ReactNode
|
|
9
|
+
isPending?: boolean
|
|
10
|
+
organization?: Partial<Organization>
|
|
11
|
+
size?: AvatarProps["size"]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Renders an organization logo image, initials fallback, or a briefcase icon;
|
|
16
|
+
* shows a circular skeleton while loading when `isPending` is set and `organization` is undefined.
|
|
17
|
+
*/
|
|
18
|
+
export function OrganizationLogo({
|
|
19
|
+
className,
|
|
20
|
+
fallback,
|
|
21
|
+
isPending,
|
|
22
|
+
organization,
|
|
23
|
+
size = "sm",
|
|
24
|
+
style,
|
|
25
|
+
...props
|
|
26
|
+
}: OrganizationLogoProps & AvatarProps) {
|
|
27
|
+
if (isPending && !organization) {
|
|
28
|
+
return (
|
|
29
|
+
<Skeleton
|
|
30
|
+
className={cn(
|
|
31
|
+
"rounded-full",
|
|
32
|
+
size === "sm" ? "size-8" : size === "md" ? "size-10" : "size-12",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
style={style}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const initials = organization?.name?.slice(0, 2).toUpperCase()
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Avatar
|
|
44
|
+
size={size}
|
|
45
|
+
className={cn("rounded-full", className)}
|
|
46
|
+
style={style}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
<Avatar.Image
|
|
50
|
+
alt={organization?.name ?? "Organization"}
|
|
51
|
+
src={organization?.logo ?? undefined}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<Avatar.Fallback
|
|
55
|
+
className={cn(
|
|
56
|
+
size === "lg" ? "text-xl" : size === "md" ? "text-base" : "text-sm"
|
|
57
|
+
)}
|
|
58
|
+
delayMs={organization?.logo ? 600 : undefined}
|
|
59
|
+
>
|
|
60
|
+
{fallback || initials || <Briefcase className="size-4" />}
|
|
61
|
+
</Avatar.Fallback>
|
|
62
|
+
</Avatar>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Skeleton, Table } from "@heroui/react"
|
|
2
|
+
|
|
3
|
+
import { UserView } from "../user/user-view"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Placeholder row matching {@link OrganizationMemberRow} while members load.
|
|
7
|
+
*/
|
|
8
|
+
export function OrganizationMemberRowSkeleton() {
|
|
9
|
+
return (
|
|
10
|
+
<Table.Row>
|
|
11
|
+
<Table.Cell>
|
|
12
|
+
<UserView isPending />
|
|
13
|
+
</Table.Cell>
|
|
14
|
+
|
|
15
|
+
<Table.Cell>
|
|
16
|
+
<Skeleton className="h-4 w-18 rounded-lg" />
|
|
17
|
+
</Table.Cell>
|
|
18
|
+
|
|
19
|
+
<Table.Cell className="flex justify-end">
|
|
20
|
+
<Skeleton className="size-8 rounded-full" />
|
|
21
|
+
</Table.Cell>
|
|
22
|
+
</Table.Row>
|
|
23
|
+
)
|
|
24
|
+
}
|