import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { MoreVertical, Redo2, Trash } from 'lucide-react' import { useState } from 'react' import { toast } from 'sonner' import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuTrigger, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { LeaveTeamButton } from './LeaveTeamButton' import { useGetRolesManagementPermissions } from './TeamSettings.utils' import { UpdateRolesPanel } from './UpdateRolesPanel/UpdateRolesPanel' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip' import { useOrganizationCreateInvitationMutation } from '@/data/organization-members/organization-invitation-create-mutation' import { useOrganizationDeleteInvitationMutation } from '@/data/organization-members/organization-invitation-delete-mutation' import { useOrganizationRolesV2Query } from '@/data/organization-members/organization-roles-query' import { useOrganizationMemberDeleteMutation } from '@/data/organizations/organization-member-delete-mutation' import { useOrganizationMembersQuery, type OrganizationMember, } from '@/data/organizations/organization-members-query' import { usePermissionsQuery } from '@/data/permissions/permissions-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useProfile } from '@/lib/profile' interface MemberActionsProps { member: OrganizationMember } export const MemberActions = ({ member }: MemberActionsProps) => { const { slug } = useParams() const { profile } = useProfile() const [showAccessModal, setShowAccessModal] = useState(false) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const organizationMembersDeletionEnabled = useIsFeatureEnabled('organization_members:delete') const { data: selectedOrganization } = useSelectedOrganizationQuery() const { data: permissions } = usePermissionsQuery() const { data: allRoles } = useOrganizationRolesV2Query({ slug }) const { data: members } = useOrganizationMembersQuery({ slug }) const memberIsUser = member.gotrue_id == profile?.gotrue_id const orgScopedRoles = allRoles?.org_scoped_roles ?? [] const projectScopedRoles = allRoles?.project_scoped_roles ?? [] const isPendingInviteAcceptance = !!member.invited_id const userMemberData = members?.find((m) => m.gotrue_id === profile?.gotrue_id) const hasOrgRole = (userMemberData?.role_ids ?? []).length === 1 && orgScopedRoles.some((r) => r.id === userMemberData?.role_ids[0]) const { rolesRemovable } = useGetRolesManagementPermissions( selectedOrganization?.slug, orgScopedRoles.concat(projectScopedRoles), permissions ?? [] ) const roleId = member.role_ids?.[0] ?? -1 const canRemoveMember = member.role_ids.every((id) => rolesRemovable.includes(id)) const { can: canCreateUserInvites } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_invites', { resource: { role_id: roleId } } ) const canResendInvite = canCreateUserInvites && hasOrgRole const { can: canDeleteUserInvites } = useAsyncCheckPermissions( PermissionAction.DELETE, 'user_invites', { resource: { role_id: roleId } } ) const canRevokeInvite = canDeleteUserInvites && hasOrgRole const { mutate: deleteOrganizationMember, isPending: isDeletingMember } = useOrganizationMemberDeleteMutation({ onSuccess: () => { toast.success(`Successfully removed ${member.primary_email}`) setIsDeleteModalOpen(false) }, }) const { mutate: inviteMember, isPending: isCreatingInvite } = useOrganizationCreateInvitationMutation({ onSuccess: () => { toast.success('Resent the invitation.') }, onError: (error) => { toast.error(`Failed to resend invitation: ${error.message}`) }, }) const { mutate: deleteInvitation, isPending: isDeletingInvite } = useOrganizationDeleteInvitationMutation() const isLoading = isDeletingMember || isDeletingInvite || isCreatingInvite const handleMemberDelete = () => { if (!slug) return console.error('slug is required') if (!member.gotrue_id) return console.error('gotrue_id is required') deleteOrganizationMember({ slug, gotrueId: member.gotrue_id }) } const handleResendInvite = (member: OrganizationMember) => { const roleId = (member?.role_ids ?? [])[0] const invitedId = member.invited_id if (!slug) return console.error('Slug is required') if (!invitedId) return console.error('Member invited ID is required') deleteInvitation( { slug, id: invitedId, skipInvalidation: true }, { onSuccess: () => { if (!member.primary_email) return toast.error('Email is required') const projectScopedRole = projectScopedRoles.find((role) => role.id === roleId) if (projectScopedRole !== undefined) { const projects = projectScopedRole.projects.map(({ ref }) => ref) inviteMember({ slug, emails: [member.primary_email], roleId: projectScopedRole.base_role_id, projects, }) } else { inviteMember({ slug, emails: [member.primary_email], roleId }) } }, } ) } const handleRevokeInvitation = (member: OrganizationMember) => { const invitedId = member.invited_id if (!slug) return console.error('Slug is required') if (!invitedId) return console.error('Member invited ID is required') deleteInvitation( { slug, id: invitedId }, { onSuccess: () => toast.success('Successfully canceled the invitation') } ) } if (memberIsUser) { return (
) } return ( <>
setShowAccessModal(true)} tooltip={{ content: { side: 'bottom', text: isPendingInviteAcceptance ? 'Access can be managed after the invite is accepted' : !canRemoveMember ? 'You need additional permissions to manage this team member' : undefined, }, }} > Manage access
Removing a member will delete all of the user's saved content in all projects of this organization, which includes:

If you'd like to retain the member's shared SQL snippets, right click on them and "Duplicate query" in the SQL Editor before removing this member.

), }} onCancel={() => setIsDeleteModalOpen(false)} onConfirm={() => { handleMemberDelete() }} >

Are you sure you want to remove{' '} {member.primary_email} from{' '} {selectedOrganization?.name}?

setShowAccessModal(false)} /> ) }