import React, { FormEvent, useState } from 'react'; import { Autocomplete, Button, capitalize, Checkbox, styled, TextField, } from '@mui/material'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; import useToast from 'hooks/useToast'; import useProjectAccess, { ENTITY_TYPE, IProjectAccess, } from 'hooks/api/getters/useProjectAccess/useProjectAccess'; import { IProjectRole } from 'interfaces/role'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; import { formatUnknownError } from 'utils/formatUnknownError'; import { IUser } from 'interfaces/user'; import { IGroup } from 'interfaces/group'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ProjectRoleDescription } from './ProjectRoleDescription/ProjectRoleDescription'; import { useNavigate } from 'react-router-dom'; import { GO_BACK } from 'constants/navigate'; import { PA_ASSIGN_CREATE_ID, PA_ROLE_ID, PA_USERS_GROUPS_ID, PA_USERS_GROUPS_TITLE_ID, } from 'utils/testIds'; const StyledForm = styled('form')(() => ({ display: 'flex', flexDirection: 'column', height: '100%', })); const StyledInputDescription = styled('p')(({ theme }) => ({ color: theme.palette.text.secondary, marginBottom: theme.spacing(1), })); const StyledAutocompleteWrapper = styled('div')(({ theme }) => ({ '& > div:first-of-type': { width: '100%', maxWidth: theme.spacing(50), marginBottom: theme.spacing(2), }, })); const StyledButtonContainer = styled('div')(({ theme }) => ({ marginTop: theme.spacing(6), display: 'flex', justifyContent: 'flex-end', })); const StyledCancelButton = styled(Button)(({ theme }) => ({ marginLeft: theme.spacing(3), })); const StyledGroupOption = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', '& > span:last-of-type': { color: theme.palette.text.secondary, }, })); const StyledUserOption = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', '& > span:first-of-type': { color: theme.palette.text.secondary, }, })); const StyledRoleOption = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', '& > span:last-of-type': { fontSize: theme.fontSizes.smallerBody, color: theme.palette.text.secondary, }, })); interface IAccessOption { id: number; entity: IUser | IGroup; type: ENTITY_TYPE; } interface IProjectAccessAssignProps { selected?: IProjectAccess; accesses: IProjectAccess[]; users: IUser[]; groups: IGroup[]; roles: IProjectRole[]; } export const ProjectAccessAssign = ({ selected, accesses, users, groups, roles, }: IProjectAccessAssignProps) => { const { uiConfig } = useUiConfig(); const { flags } = uiConfig; const entityType = flags.UG ? 'user / group' : 'user'; const projectId = useRequiredPathParam('projectId'); const { refetchProjectAccess } = useProjectAccess(projectId); const { addAccessToProject, changeUserRole, changeGroupRole, loading } = useProjectApi(); const edit = Boolean(selected); const { setToastData, setToastApiError } = useToast(); const navigate = useNavigate(); const options = [ ...groups .filter( (group: IGroup) => edit || !accesses.some( ({ entity: { id }, type }) => group.id === id && type === ENTITY_TYPE.GROUP ) ) .map((group: IGroup) => ({ id: group.id, entity: group, type: ENTITY_TYPE.GROUP, })), ...users .filter( (user: IUser) => edit || !accesses.some( ({ entity: { id }, type }) => user.id === id && type === ENTITY_TYPE.USER ) ) .map((user: IUser) => ({ id: user.id, entity: user, type: ENTITY_TYPE.USER, })), ]; const [selectedOptions, setSelectedOptions] = useState( () => options.filter( ({ id, type }) => id === selected?.entity.id && type === selected?.type ) ); const [role, setRole] = useState( roles.find(({ id }) => id === selected?.entity.roleId) ?? null ); const payload = { users: selectedOptions ?.filter(({ type }) => type === ENTITY_TYPE.USER) .map(({ id }) => ({ id })), groups: selectedOptions ?.filter(({ type }) => type === ENTITY_TYPE.GROUP) .map(({ id }) => ({ id })), }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); if (!isValid) return; try { if (!edit) { await addAccessToProject(projectId, role.id, payload); } else if (selected?.type === ENTITY_TYPE.USER) { await changeUserRole(projectId, role.id, selected.entity.id); } else if (selected?.type === ENTITY_TYPE.GROUP) { await changeGroupRole(projectId, role.id, selected.entity.id); } refetchProjectAccess(); navigate(GO_BACK); setToastData({ title: `${selectedOptions.length} ${ selectedOptions.length === 1 ? 'access' : 'accesses' } ${!edit ? 'assigned' : 'edited'} successfully`, type: 'success', }); } catch (error: unknown) { setToastApiError(formatUnknownError(error)); } }; const formatApiCode = () => { if (edit) { return `curl --location --request ${edit ? 'PUT' : 'POST'} '${ uiConfig.unleashUrl }/api/admin/projects/${projectId}/${ selected?.type === ENTITY_TYPE.USER ? 'users' : 'groups' }/${selected?.entity.id}/roles/${role?.id}' \\ --header 'Authorization: INSERT_API_KEY'`; } return `curl --location --request ${edit ? 'PUT' : 'POST'} '${ uiConfig.unleashUrl }/api/admin/projects/${projectId}/role/${role?.id}/access' \\ --header 'Authorization: INSERT_API_KEY' \\ --header 'Content-Type: application/json' \\ --data-raw '${JSON.stringify(payload, undefined, 2)}'`; }; const renderOption = ( props: React.HTMLAttributes, option: IAccessOption, selected: boolean ) => { let optionGroup; let optionUser; if (option.type === ENTITY_TYPE.GROUP) { optionGroup = option.entity as IGroup; } else { optionUser = option.entity as IUser; } return (
  • } checkedIcon={} style={{ marginRight: 8 }} checked={selected} /> {optionGroup?.name} {optionGroup?.userCount} users } elseShow={ {optionUser?.name || optionUser?.username} {optionUser?.email} } />
  • ); }; const renderRoleOption = ( props: React.HTMLAttributes, option: IProjectRole ) => (
  • {option.name} {option.description}
  • ); const isValid = selectedOptions.length > 0 && role; return ( navigate(GO_BACK)} label={`${!edit ? 'Assign' : 'Edit'} ${entityType} access`} >
    Select the {entityType} { if ( event.type === 'keydown' && (event as React.KeyboardEvent).key === 'Backspace' && reason === 'removeOption' ) { return; } setSelectedOptions(newValue); }} options={options} groupBy={option => option.type} renderOption={(props, option, { selected }) => renderOption(props, option, selected) } getOptionLabel={(option: IAccessOption) => { if (option.type === ENTITY_TYPE.USER) { const optionUser = option.entity as IUser; return ( optionUser.email || optionUser.name || optionUser.username || '' ); } else { return option.entity.name; } }} isOptionEqualToValue={(option, value) => option.type === value.type && option.entity.id === value.entity.id } renderInput={params => ( )} /> Select the role to assign for this project setRole(newValue)} options={roles} renderOption={renderRoleOption} getOptionLabel={option => option.name} renderInput={params => ( )} /> } />
    navigate(GO_BACK)}> Cancel
    ); };