| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- 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 (
- <ConfirmationModal
- size="medium"
- visible={visible}
- loading={saving}
- title="Confirm to change roles of member"
- confirmLabel="Update roles"
- confirmLabelLoading="Updating"
- onCancel={() => onClose()}
- onConfirm={onConfirmUpdateMemberRoles}
- >
- <div className="flex flex-col gap-y-3">
- <p className="text-sm text-foreground-light">
- You are making the following changes to the role of{' '}
- <span className="text-foreground">{member.username}</span> in the organization{' '}
- <span className="text-foreground">{organization?.name}</span>:
- </p>
- <div className="flex flex-col gap-y-2">
- {changesToRoles.removed.length !== 0 && (
- <div>
- <p className="text-sm">
- Removing {changesToRoles.removed.length} role
- {changesToRoles.removed.length > 1 ? 's' : ''} for user:
- </p>
- <ul className="list-disc pl-6">
- {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 (
- <li key={`update-${i}`} className="text-sm text-foreground-light">
- <span className="text-foreground">{roleName}</span> on{' '}
- <span className="text-foreground">{x?.name ?? 'organization'}</span>
- </li>
- )
- })}
- </ul>
- </div>
- )}
- {changesToRoles.added.length !== 0 && (
- <div>
- <p className="text-sm">
- Adding {changesToRoles.added.length} role
- {changesToRoles.added.length > 1 ? 's' : ''} for user:
- </p>
- <ul className="list-disc pl-6">
- {changesToRoles.added.map((x, i) => {
- const role = availableRoles.find((y) => y.id === x.roleId)
- return (
- <li key={`update-${i}`} className="text-sm text-foreground-light">
- <span className="text-foreground">{role?.name}</span> on{' '}
- <span className="text-foreground">{x?.name ?? 'organization'}</span>
- </li>
- )
- })}
- </ul>
- </div>
- )}
- {changesToRoles.updated.length !== 0 && (
- <div>
- <p className="text-sm">
- Updating {changesToRoles.updated.length} role
- {changesToRoles.updated.length > 1 ? 's' : ''} for user:
- </p>
- <ul className="list-disc pl-6">
- {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 (
- <li key={`update-${i}`} className="text-sm text-foreground-light">
- From <span className="text-foreground">{originalRoleName}</span> to{' '}
- <span className="text-foreground">{updatedRole?.name ?? 'Unknown'}</span> on{' '}
- <span className="text-foreground">{x?.name ?? 'organization'}</span>
- </li>
- )
- })}
- </ul>
- </div>
- )}
- </div>
- <p className="text-sm text-foreground">
- By changing the role of this member their permissions will change.
- </p>
- </div>
- </ConfirmationModal>
- )
- }
|