mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-19 01:17:18 +02:00
228 lines
7.9 KiB
TypeScript
228 lines
7.9 KiB
TypeScript
import React, { ChangeEvent, useEffect, useState } from 'react';
|
|
import {
|
|
TextField,
|
|
CircularProgress,
|
|
Grid,
|
|
Button,
|
|
InputAdornment,
|
|
SelectChangeEvent,
|
|
} from '@mui/material';
|
|
import { Search } from '@mui/icons-material';
|
|
import Autocomplete from '@mui/material/Autocomplete';
|
|
import { ProjectRoleSelect } from '../ProjectRoleSelect/ProjectRoleSelect';
|
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
|
import useToast from 'hooks/useToast';
|
|
import useProjectAccess, {
|
|
IProjectAccessUser,
|
|
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
|
import { IProjectRole } from 'interfaces/role';
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|
|
|
interface IProjectAccessAddUserProps {
|
|
roles: IProjectRole[];
|
|
}
|
|
|
|
export const ProjectAccessAddUser = ({ roles }: IProjectAccessAddUserProps) => {
|
|
const projectId = useRequiredPathParam('projectId');
|
|
const [user, setUser] = useState<IProjectAccessUser | undefined>();
|
|
const [role, setRole] = useState<IProjectRole | undefined>();
|
|
const [options, setOptions] = useState<IProjectAccessUser[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const { setToastData } = useToast();
|
|
const { refetchProjectAccess, access } = useProjectAccess(projectId);
|
|
|
|
const { searchProjectUser, addUserToRole } = useProjectApi();
|
|
|
|
useEffect(() => {
|
|
if (roles.length > 0) {
|
|
const regularRole = roles.find(
|
|
r => r.name.toLowerCase() === 'regular'
|
|
);
|
|
setRole(regularRole || roles[0]);
|
|
}
|
|
}, [roles]);
|
|
|
|
const search = async (query: string) => {
|
|
if (query.length > 1) {
|
|
setLoading(true);
|
|
|
|
const result = await searchProjectUser(query);
|
|
const userSearchResults = await result.json();
|
|
|
|
const filteredUsers = userSearchResults.filter(
|
|
(selectedUser: IProjectAccessUser) => {
|
|
const selected = access.users.find(
|
|
(user: IProjectAccessUser) =>
|
|
user.id === selectedUser.id
|
|
);
|
|
return !selected;
|
|
}
|
|
);
|
|
setOptions(filteredUsers);
|
|
} else {
|
|
setOptions([]);
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const handleQueryUpdate = (evt: { target: { value: string } }) => {
|
|
const q = evt.target.value;
|
|
search(q);
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
if (options.length > 0) {
|
|
const user = options[0];
|
|
setUser(user);
|
|
}
|
|
};
|
|
|
|
const handleSelectUser = (
|
|
evt: ChangeEvent<{}>,
|
|
selectedUser: string | IProjectAccessUser | null
|
|
) => {
|
|
setOptions([]);
|
|
|
|
if (typeof selectedUser === 'string' || selectedUser === null) {
|
|
return;
|
|
}
|
|
|
|
if (selectedUser?.id) {
|
|
setUser(selectedUser);
|
|
}
|
|
};
|
|
|
|
const handleRoleChange = (evt: SelectChangeEvent) => {
|
|
const roleId = Number(evt.target.value);
|
|
const role = roles.find(role => role.id === roleId);
|
|
if (role) {
|
|
setRole(role);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (evt: React.SyntheticEvent) => {
|
|
evt.preventDefault();
|
|
if (!role || !user) {
|
|
setToastData({
|
|
type: 'error',
|
|
title: 'Invalid selection',
|
|
text: `The selected user or role does not exist`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await addUserToRole(projectId, role.id, user.id);
|
|
refetchProjectAccess();
|
|
setUser(undefined);
|
|
setOptions([]);
|
|
setToastData({
|
|
type: 'success',
|
|
title: 'Added user to project',
|
|
text: `User added to the project with the role of ${role.name}`,
|
|
});
|
|
} catch (e: any) {
|
|
let error;
|
|
|
|
if (
|
|
e
|
|
.toString()
|
|
.includes(`User already has access to project=${projectId}`)
|
|
) {
|
|
error = `User already has access to project ${projectId}`;
|
|
} else {
|
|
error = e.toString() || 'Server problems when adding users.';
|
|
}
|
|
setToastData({
|
|
type: 'error',
|
|
title: error,
|
|
});
|
|
}
|
|
};
|
|
|
|
const getOptionLabel = (option: IProjectAccessUser | string) => {
|
|
if (option && typeof option !== 'string') {
|
|
return `${option.name || option.username || '(Empty name)'} <${
|
|
option.email || option.username
|
|
}>`;
|
|
} else return '';
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Grid container spacing={3} alignItems="flex-end">
|
|
<Grid item>
|
|
<Autocomplete
|
|
id="add-user-component"
|
|
style={{ width: 300 }}
|
|
noOptionsText="No users found."
|
|
onChange={handleSelectUser}
|
|
onBlur={() => handleBlur()}
|
|
value={user || ''}
|
|
freeSolo
|
|
isOptionEqualToValue={() => true}
|
|
filterOptions={o => o}
|
|
getOptionLabel={getOptionLabel}
|
|
options={options}
|
|
loading={loading}
|
|
renderInput={params => (
|
|
<TextField
|
|
{...params}
|
|
label="User"
|
|
variant="outlined"
|
|
size="small"
|
|
name="search"
|
|
onChange={handleQueryUpdate}
|
|
InputProps={{
|
|
...params.InputProps,
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<Search />
|
|
</InputAdornment>
|
|
),
|
|
endAdornment: (
|
|
<>
|
|
<ConditionallyRender
|
|
condition={loading}
|
|
show={
|
|
<CircularProgress
|
|
color="inherit"
|
|
size={20}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{params.InputProps.endAdornment}
|
|
</>
|
|
),
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
</Grid>
|
|
<Grid item>
|
|
<ProjectRoleSelect
|
|
labelId="add-user-select-role-label"
|
|
id="add-user-select-role"
|
|
placeholder="Project role"
|
|
value={role?.id || -1}
|
|
onChange={handleRoleChange}
|
|
roles={roles}
|
|
/>
|
|
</Grid>
|
|
<Grid item>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
disabled={!user}
|
|
onClick={handleSubmit}
|
|
>
|
|
Add user
|
|
</Button>
|
|
</Grid>
|
|
</Grid>
|
|
</>
|
|
);
|
|
};
|