Home / Function/ UpdateRolesPanel() — supabase Function Reference

UpdateRolesPanel() — supabase Function Reference

Architecture documentation for the UpdateRolesPanel() function in UpdateRolesPanel.tsx from the supabase codebase.

Entity Profile

Dependency Diagram

graph TD
  26567e94_e158_8cd1_b1e4_49300baf745f["UpdateRolesPanel()"]
  f6ac5702_c235_901f_8a41_8cfe80ab3f6f["useGetRolesManagementPermissions()"]
  26567e94_e158_8cd1_b1e4_49300baf745f -->|calls| f6ac5702_c235_901f_8a41_8cfe80ab3f6f
  85313509_9893_a7f1_0325_abddf0cf38b3["formatMemberRoleToProjectRoleConfiguration()"]
  26567e94_e158_8cd1_b1e4_49300baf745f -->|calls| 85313509_9893_a7f1_0325_abddf0cf38b3
  style 26567e94_e158_8cd1_b1e4_49300baf745f fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

apps/studio/components/interfaces/Organization/TeamSettings/UpdateRolesPanel/UpdateRolesPanel.tsx lines 54–378

export const UpdateRolesPanel = ({ visible, member, onClose }: UpdateRolesPanelProps) => {
  const { slug } = useParams()
  const { data: organization } = useSelectedOrganizationQuery()
  const isOptedIntoProjectLevelPermissions = useHasAccessToProjectLevelPermissions(slug as string)

  const { data: permissions } = usePermissionsQuery()
  const { data: allRoles, isSuccess: isSuccessRoles } = useOrganizationRolesV2Query({ slug })

  const { data: projectsData } = useOrgProjectsInfiniteQuery({ slug })
  const totalNumOrgProjects = projectsData?.pages[0].pagination.count ?? 0
  const orgProjects =
    useMemo(() => projectsData?.pages.flatMap((page) => page.projects), [projectsData?.pages]) || []

  // [Joshen] We use the org scoped roles as the source for available roles
  const orgScopedRoles = allRoles?.org_scoped_roles ?? []
  const projectScopedRoles = allRoles?.project_scoped_roles ?? []

  const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions(
    organization?.slug,
    orgScopedRoles.concat(projectScopedRoles),
    permissions ?? []
  )
  const cannotAddAnyRoles = orgScopedRoles.every((r) => !rolesAddable.includes(r.id))

  const [showConfirmation, setShowConfirmation] = useState(false)
  const [showProjectDropdown, setShowProjectDropdown] = useState(false)
  const [projectsRoleConfiguration, setProjectsRoleConfiguration] = useState<
    ProjectRoleConfiguration[]
  >([])

  const originalConfiguration =
    allRoles !== undefined ? formatMemberRoleToProjectRoleConfiguration(member, allRoles) : []
  const originalConfigurationType =
    originalConfiguration.length === 1 &&
    !!orgScopedRoles.find((r) => r.id === originalConfiguration[0].roleId)
      ? 'org-scope'
      : 'project-scope'

  const isApplyingRoleToAllProjects =
    projectsRoleConfiguration.length === 1 && projectsRoleConfiguration[0]?.ref === undefined
  const canSaveRoles = projectsRoleConfiguration.length > 0

  const lowerPermissionsRole = orgScopedRoles.find((r) => r.name === 'Developer')?.id
  const noAccessProjects = orgProjects.filter((project) => {
    return !projectsRoleConfiguration.some((p) => p.ref === project.ref)
  })
  const numberOfProjectsWithAccess = orgProjects.length - noAccessProjects.length
  const hasNoChanges = isEqual(projectsRoleConfiguration, originalConfiguration)

  const onSelectProject = (project: OrgProject) => {
    setProjectsRoleConfiguration(
      projectsRoleConfiguration.concat({
        ref: project.ref,
        name: project.name,
        roleId: lowerPermissionsRole ?? orgScopedRoles[0].id,
      })
    )
    setShowProjectDropdown(false)
  }

  const onRemoveProject = (ref?: string) => {
    if (ref === undefined) return
    setProjectsRoleConfiguration(projectsRoleConfiguration.filter((p) => p.ref !== ref))
  }

  const onSelectRole = (value: string, project: ProjectRoleConfiguration) => {
    if (project.ref !== undefined) {
      setProjectsRoleConfiguration(
        projectsRoleConfiguration.map((p) => {
          if (p.ref === project.ref) {
            return { ref: p.ref, name: p.name, roleId: Number(value) }
          } else {
            return p
          }
        })
      )
    } else {
      setProjectsRoleConfiguration([{ ref: undefined, roleId: Number(value) }])
    }
  }

  const onToggleApplyToAllProjects = (isApplyAllProjects: boolean) => {
    const roleIdToApply = lowerPermissionsRole ?? orgScopedRoles[0].id

    if (isApplyAllProjects) {
      if (originalConfigurationType === 'org-scope') {
        setProjectsRoleConfiguration(originalConfiguration)
      } else {
        setProjectsRoleConfiguration([{ ref: undefined, name: undefined, roleId: roleIdToApply }])
      }
    } else {
      if (originalConfigurationType === 'project-scope') {
        setProjectsRoleConfiguration(originalConfiguration)
      } else {
        setProjectsRoleConfiguration(
          orgProjects.map((p) => {
            return { ref: p.ref, name: p.name, roleId: roleIdToApply }
          })
        )
      }
    }
  }

  useEffect(() => {
    if (visible && isSuccessRoles) {
      const roleConfiguration = formatMemberRoleToProjectRoleConfiguration(member, allRoles)
      setProjectsRoleConfiguration(roleConfiguration)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [visible, isSuccessRoles])

  return (
    <>
      <Sheet open={visible} onOpenChange={() => onClose()}>
        <SheetContent
          showClose={false}
          size="default"
          className="bg-surface-200 p-0 flex flex-row gap-0 md:w-[600px] lg:w-[600px] w-full"
        >
          <div className="flex flex-col grow w-full">
            <SheetHeader className="py-3 flex flex-row justify-between gap-x-4 items-center border-b bg-transparent">
              <p className="truncate" title={`Manage access for ${member.username}`}>
                Manage access for {member.username}
              </p>
              <DocsButton href={`${DOCS_URL}/guides/platform/access-control`} />
            </SheetHeader>

            <SheetSection className="h-full overflow-auto flex flex-col">
              {isOptedIntoProjectLevelPermissions && (
                <div className="flex items-center gap-x-4 border-b border-border pb-4">
                  <Switch
                    disabled={cannotAddAnyRoles}
                    checked={isApplyingRoleToAllProjects}
                    onCheckedChange={onToggleApplyToAllProjects}
                  />
                  <p className="text-sm">Apply roles to all projects in the organization</p>
                </div>
              )}

              {projectsRoleConfiguration.length === 0 && (
                <Alert_Shadcn_>
                  <WarningIcon />
                  <AlertTitle_Shadcn_>
                    Team members need to be assigned at least one role
                  </AlertTitle_Shadcn_>
                  <AlertDescription_Shadcn_>
                    You may not remove all roles from a team member
                  </AlertDescription_Shadcn_>
                </Alert_Shadcn_>
              )}

              {!isApplyingRoleToAllProjects &&
                projectsRoleConfiguration.length > 0 &&
                projectsRoleConfiguration.length < totalNumOrgProjects && (
                  <Collapsible_Shadcn_ className="bg-alternative border rounded-lg py-4 group">
                    <CollapsibleTrigger_Shadcn_ className="w-full text-left px-4 flex items-center justify-between">
                      <span className="text-sm">
                        {hasNoChanges
                          ? `This member only has access to ${numberOfProjectsWithAccess} project${numberOfProjectsWithAccess > 1 ? 's' : ''}`
                          : `This member will only have access to ${numberOfProjectsWithAccess} project${numberOfProjectsWithAccess > 1 ? 's' : ''}`}
                      </span>
                      <ChevronDown
                        size={14}
                        className="transition group-data-[state=open]:-rotate-180"
                      />
                    </CollapsibleTrigger_Shadcn_>
                    <CollapsibleContent_Shadcn_ className="text-foreground-light text-sm px-4">
                      <p>
                        {member.username} {hasNoChanges ? 'does' : 'will'} not have access to the
                        following {noAccessProjects.length} project
                        {noAccessProjects.length > 1 ? 's' : ''}:
                      </p>
                      <ul className="list-disc pl-6">
                        {noAccessProjects.map((project) => {
                          return <li key={project.ref}>{project.name}</li>
                        })}
                      </ul>
                    </CollapsibleContent_Shadcn_>
                  </Collapsible_Shadcn_>
                )}

              <div className="flex flex-col divide-y divide-border">
                {projectsRoleConfiguration.map((project) => {
                  const name = project.ref === undefined ? 'All projects' : project.name
                  const role = orgScopedRoles.find((r) => {
                    if (project.baseRoleId !== undefined) return r.id === project.baseRoleId
                    else return r.id === project.roleId
                  })
                  const canRemoveRole = rolesRemovable.includes(role?.id ?? 0)

                  return (
                    <div
                      key={`${project.ref}-${project.roleId}`}
                      className="flex items-center justify-between py-2"
                    >
                      <p className="text-sm">{name}</p>

                      <div className="flex items-center gap-x-2">
                        {cannotAddAnyRoles ? (
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <div className="flex items-center justify-between rounded-md border border-button bg-button px-3 py-2 text-sm h-10 w-56 text-foreground-light">
                                {role?.name ?? 'Unknown'}
                              </div>
                            </TooltipTrigger>
                            <TooltipContent side="bottom">
                              Additional permissions required to update role
                            </TooltipContent>
                          </Tooltip>
                        ) : (
                          <Select_Shadcn_
                            value={(project?.baseRoleId ?? project.roleId).toString()}
                            onValueChange={(value) => onSelectRole(value, project)}
                          >
                            <SelectTrigger_Shadcn_
                              className={cn(
                                ' w-40',
                                role?.name === undefined && 'text-foreground-light'
                              )}
                            >
                              {role?.name ?? 'Please select a role'}
                            </SelectTrigger_Shadcn_>
                            <SelectContent_Shadcn_>
                              <SelectGroup_Shadcn_>
                                {(orgScopedRoles ?? []).map((role) => {
                                  const canAssignRole = rolesAddable.includes(role.id)

                                  return (
                                    <SelectItem_Shadcn_
                                      key={role.id}
                                      value={role.id.toString()}
                                      className="text-sm hover:bg-selection cursor-pointer"
                                      disabled={!canAssignRole}
                                    >
                                      {role.name}
                                    </SelectItem_Shadcn_>
                                  )
                                })}
                              </SelectGroup_Shadcn_>
                            </SelectContent_Shadcn_>
                          </Select_Shadcn_>
                        )}

                        {!isApplyingRoleToAllProjects && (
                          <ButtonTooltip
                            type="text"
                            disabled={!canRemoveRole}
                            className="px-1"
                            icon={<X />}
                            onClick={() => onRemoveProject(project?.ref)}
                            tooltip={{
                              content: {
                                side: 'bottom',
                                text: !canRemoveRole
                                  ? 'Additional permission required to remove role from member'
                                  : 'Remove access to project',
                              },
                            }}
                          />
                        )}
                      </div>
                    </div>
                  )
                })}
              </div>

              {!isApplyingRoleToAllProjects && (
                <OrganizationProjectSelector
                  open={showProjectDropdown}
                  setOpen={setShowProjectDropdown}
                  modal={true}
                  onSelect={onSelectProject}
                  renderTrigger={() => (
                    <Button type="default" className="w-min">
                      Add project
                    </Button>
                  )}
                  renderRow={(project) => {
                    const hasRoleAssigned = projectsRoleConfiguration.some(
                      (p) => p.ref === project.ref
                    )
                    return (
                      <div className="w-full flex items-center justify-between">
                        <span className="truncate">{project.name}</span>
                        {hasRoleAssigned && <p className="w-[45%] text-right">Already assigned</p>}
                      </div>
                    )
                  }}
                  isOptionDisabled={(project) =>
                    projectsRoleConfiguration.some((p) => p.ref === project.ref)
                  }
                />
              )}
            </SheetSection>

            <SheetFooter className="flex items-center !justify-end px-5 py-4 w-full border-t">
              <Button type="default" disabled={false} onClick={() => onClose()}>
                Cancel
              </Button>
              <Button
                loading={false}
                disabled={!canSaveRoles || hasNoChanges}
                onClick={() => {
                  setShowConfirmation(true)
                }}
              >
                Save roles
              </Button>
            </SheetFooter>
          </div>
        </SheetContent>
      </Sheet>

      <UpdateRolesConfirmationModal
        visible={showConfirmation}
        member={member}
        projectsRoleConfiguration={projectsRoleConfiguration}
        onClose={(success) => {
          setShowConfirmation(false)
          if (success) onClose()
        }}
      />
    </>
  )
}

Subdomains

Frequently Asked Questions

What does UpdateRolesPanel() do?
UpdateRolesPanel() is a function in the supabase codebase.
What does UpdateRolesPanel() call?
UpdateRolesPanel() calls 2 function(s): formatMemberRoleToProjectRoleConfiguration, useGetRolesManagementPermissions.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free