import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'common' import { useState } from 'react' import { toast } from 'sonner' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { deriveChanges, deriveRoleChangeActions, formatMemberRoleToProjectRoleConfiguration, ProjectRoleConfiguration, } from './UpdateRolesPanel.utils' import { organizationKeys } from '@/data/organization-members/keys' import { useOrganizationMemberAssignRoleMutation } from '@/data/organization-members/organization-member-role-assign-mutation' import { useOrganizationMemberUnassignRoleMutation } from '@/data/organization-members/organization-member-role-unassign-mutation' import { useOrganizationMemberUpdateRoleMutation } from '@/data/organization-members/organization-member-role-update-mutation' import { OrganizationRole, useOrganizationRolesV2Query, } from '@/data/organization-members/organization-roles-query' import { organizationKeys as organizationKeysV1 } from '@/data/organizations/keys' import { OrganizationMember } from '@/data/organizations/organization-members-query' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' interface UpdateRolesConfirmationModal { visible: boolean member: OrganizationMember projectsRoleConfiguration: ProjectRoleConfiguration[] onClose: (success?: boolean) => void } export const UpdateRolesConfirmationModal = ({ visible, member, projectsRoleConfiguration, onClose, }: UpdateRolesConfirmationModal) => { const { slug } = useParams() const queryClient = useQueryClient() const { data: organization } = useSelectedOrganizationQuery() const { data: allRoles } = useOrganizationRolesV2Query({ slug: organization?.slug }) // [Joshen] Separate saving state instead of using RQ due to several successive steps const [saving, setSaving] = useState(false) const { mutateAsync: assignRole } = useOrganizationMemberAssignRoleMutation() const { mutateAsync: removeRole } = useOrganizationMemberUnassignRoleMutation({ onError: () => {}, }) const { mutateAsync: updateRole } = useOrganizationMemberUpdateRoleMutation() const availableRoles = allRoles?.org_scoped_roles ?? [] const { org_scoped_roles, project_scoped_roles } = allRoles ?? { org_scoped_roles: [], project_scoped_roles: [], } const originalConfiguration = allRoles !== undefined ? formatMemberRoleToProjectRoleConfiguration(member, allRoles) : [] const changesToRoles = deriveChanges(originalConfiguration, projectsRoleConfiguration) const onConfirmUpdateMemberRoles = async () => { if (slug === undefined) return console.error('Slug is required') setSaving(true) const gotrueId = member.gotrue_id const existingRoles = member.role_ids .map((id) => { return [...org_scoped_roles, ...project_scoped_roles].find((r) => r.id === id) }) .filter(Boolean) as OrganizationRole[] const isChangeWithinOrgScope = projectsRoleConfiguration.length === 1 && projectsRoleConfiguration[0].ref === undefined // Early return if we're just updating org level roles // Everything else below is just project level role changes then if (isChangeWithinOrgScope) { try { await assignRole({ slug, gotrueId, roleId: projectsRoleConfiguration[0].roleId, }) toast.success(`Successfully updated role for ${member.username}`) onClose(true) } catch (error: any) { toast.error(`Failed to update role: ${error.message}`) } finally { setSaving(false) return } } const { toRemove, toAssign, toUpdate } = deriveRoleChangeActions(existingRoles, changesToRoles) try { for (const { roleId, refs } of toAssign) { await assignRole({ slug, gotrueId, roleId, projects: refs, skipInvalidation: true, }) } for (const roleId of toRemove) { await removeRole({ slug, gotrueId, roleId, skipInvalidation: true }) } for (const { roleId, refs } of toUpdate) { await updateRole({ slug, gotrueId, roleId, roleName: project_scoped_roles.find((r) => r.id === roleId)?.name as string, projects: refs, skipInvalidation: true, }) } await Promise.all([ queryClient.invalidateQueries({ queryKey: organizationKeys.rolesV2(slug) }), queryClient.invalidateQueries({ queryKey: organizationKeysV1.members(slug) }), ]) toast.success(`Successfully updated role for ${member.username}`) onClose(true) } catch (error: any) { toast.error(`Failed to update role: ${error.message}`) } finally { setSaving(false) return } } return ( onClose()} onConfirm={onConfirmUpdateMemberRoles} >

You are making the following changes to the role of{' '} {member.username} in the organization{' '} {organization?.name}:

{changesToRoles.removed.length !== 0 && (

Removing {changesToRoles.removed.length} role {changesToRoles.removed.length > 1 ? 's' : ''} for user:

    {changesToRoles.removed.map((x, i) => { const role = org_scoped_roles.find((y) => y.id === x.roleId) ?? project_scoped_roles.find((y) => y.id === x.roleId) const roleName = (role?.name ?? 'Unknown').split('_')[0] return (
  • {roleName} on{' '} {x?.name ?? 'organization'}
  • ) })}
)} {changesToRoles.added.length !== 0 && (

Adding {changesToRoles.added.length} role {changesToRoles.added.length > 1 ? 's' : ''} for user:

    {changesToRoles.added.map((x, i) => { const role = availableRoles.find((y) => y.id === x.roleId) return (
  • {role?.name} on{' '} {x?.name ?? 'organization'}
  • ) })}
)} {changesToRoles.updated.length !== 0 && (

Updating {changesToRoles.updated.length} role {changesToRoles.updated.length > 1 ? 's' : ''} for user:

    {changesToRoles.updated.map((x, i) => { const originalRole = org_scoped_roles.find((y) => y.id === x.originalRole) ?? project_scoped_roles.find((y) => y.id === x.originalRole) const updatedRole = org_scoped_roles.find((y) => y.id === x.updatedRole) const originalRoleName = (originalRole?.name ?? 'Unknown').split('_')[0] return (
  • From {originalRoleName} to{' '} {updatedRole?.name ?? 'Unknown'} on{' '} {x?.name ?? 'organization'}
  • ) })}
)}

By changing the role of this member their permissions will change.

) }