1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: add edit and create user screen (NEW) (#601)

* feat: add edit and create user screen

* refactor: rename create user component

* fix: add missing documentation link

* fix: remove unused dependencies

* feat: add confirm screen

* refactor: change UserForm and delete unused components

* refactor: remove toast when create new user

* fix: add margin top to form elements

Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
Youssef Khedher 2022-01-18 12:05:48 +01:00 committed by GitHub
parent 1b097f85d6
commit 80e80805f7
17 changed files with 627 additions and 479 deletions

View File

@ -1,93 +0,0 @@
import { useState } from 'react';
import Dialogue from '../../../common/Dialogue';
import { IUserApiErrors } from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
import IRole from '../../../../interfaces/role';
import AddUserForm from './AddUserForm/AddUserForm';
interface IAddUserProps {
showDialog: boolean;
closeDialog: () => void;
addUser: (data: any) => any;
validatePassword: () => boolean;
userLoading: boolean;
userApiErrors: IUserApiErrors;
roles: IRole[];
}
interface IAddUserFormData {
name: string;
email: string;
rootRole: number;
sendEmail: boolean;
}
const EDITOR_ROLE_ID = 2;
const initialData = { email: '', name: '', rootRole: EDITOR_ROLE_ID, sendEmail: true };
const AddUser = ({
showDialog,
closeDialog,
userLoading,
addUser,
userApiErrors,
roles,
}: IAddUserProps) => {
const [data, setData] = useState<IAddUserFormData>(initialData);
const [error, setError] = useState({});
const submit = async (e: Event) => {
e.preventDefault();
if (!data.email) {
setError({ general: 'You must specify the email address' });
return;
}
if (!data.rootRole) {
setError({ general: 'You must specify a role for the user' });
return;
}
await addUser(data);
setData(initialData);
setError({});
};
const onCancel = (e: Event) => {
e.preventDefault();
setData(initialData);
setError({});
closeDialog();
};
const formId = 'add-user-dialog-form';
return (
<Dialogue
onClick={e => {
submit(e);
}}
formId={formId}
open={showDialog}
onClose={onCancel}
primaryButtonText="Add user"
secondaryButtonText="Cancel"
title="Add team member"
fullWidth
>
<AddUserForm
formId={formId}
userApiErrors={userApiErrors}
data={data}
setData={setData}
roles={roles}
submit={submit}
error={error}
userLoading={userLoading}
/>
</Dialogue>
);
};
export default AddUser;

View File

@ -1,217 +0,0 @@
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {
DialogContent,
FormControl,
FormControlLabel,
Radio,
RadioGroup,
Switch,
TextField,
Typography,
} from '@material-ui/core';
import { trim } from '../../../../common/util';
import { useCommonStyles } from '../../../../../common.styles';
import ConditionallyRender from '../../../../common/ConditionallyRender';
import { useStyles } from './AddUserForm.styles';
import useLoading from '../../../../../hooks/useLoading';
import {
ADD_USER_ERROR,
UPDATE_USER_ERROR,
} from '../../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
import { Alert } from '@material-ui/lab';
function AddUserForm({
submit,
data,
error,
setData,
roles,
userLoading,
userApiErrors,
formId,
}) {
const ref = useLoading(userLoading);
const commonStyles = useCommonStyles();
const styles = useStyles();
const updateField = e => {
setData({
...data,
[e.target.name]: e.target.value,
});
};
const updateFieldWithTrim = e => {
setData({
...data,
[e.target.name]: trim(e.target.value),
});
};
const toggleBooleanField = e => {
setData({
...data,
[e.target.name]: !data[e.target.name],
});
};
const updateNumberField = e => {
setData({
...data,
[e.target.name]: +e.target.value,
});
};
const sortRoles = (a, b) => {
if (b.name[0] < a.name[0]) {
return 1;
} else if (a.name[0] < b.name[0]) {
return -1;
}
return 0;
};
const apiError =
userApiErrors[ADD_USER_ERROR] || userApiErrors[UPDATE_USER_ERROR];
return (
<div ref={ref}>
<form id={formId} onSubmit={submit}>
<DialogContent>
<ConditionallyRender
condition={apiError}
show={
<Alert
className={styles.errorAlert}
severity="error"
data-loading
>
{apiError}
</Alert>
}
/>
<div
className={classnames(
commonStyles.contentSpacingY,
commonStyles.flexColumn,
styles.userInfoContainer
)}
>
<Typography variant="subtitle1" data-loading>
Who is your team member?
</Typography>
<ConditionallyRender
condition={error.general}
show={
<p data-loading style={{ color: 'red' }}>
{error.general}
</p>
}
/>
<TextField
autoFocus
label="Full name"
data-loading
name="name"
value={data.name || ''}
error={error.name !== undefined}
helperText={error.name}
type="name"
variant="outlined"
size="small"
onChange={updateField}
/>
<TextField
label="Email"
data-loading
name="email"
required
value={data.email || ''}
error={error.email !== undefined}
helperText={error.email}
variant="outlined"
size="small"
type="email"
onChange={updateFieldWithTrim}
/>
<br />
<br />
</div>
<FormControl>
<Typography
variant="subtitle1"
className={styles.roleSubtitle}
data-loading
>
What is your team member allowed to do?
</Typography>
<RadioGroup
name="rootRole"
value={data.rootRole || ''}
onChange={updateNumberField}
data-loading
>
{roles.sort(sortRoles).map(role => (
<FormControlLabel
key={`role-${role.id}`}
labelPlacement="end"
className={styles.roleBox}
label={
<div>
<strong>{role.name}</strong>
<Typography variant="body2">
{role.description}
</Typography>
</div>
}
control={
<Radio
checked={role.id === data.rootRole}
className={styles.roleRadio}
/>
}
value={role.id}
/>
))}
</RadioGroup>
</FormControl>
<br />
<br />
<div className={commonStyles.flexRow}>
<FormControl>
<Typography
variant="subtitle1"
className={styles.roleSubtitle}
data-loading
>
Should we send an email to your new team member
</Typography>
<div className={commonStyles.flexRow}>
<Switch
name="sendEmail"
onChange={toggleBooleanField}
checked={data.sendEmail}
/>
<Typography>
{data.sendEmail ? 'Yes' : 'No'}
</Typography>
</div>
</FormControl>
</div>
</DialogContent>
</form>
</div>
);
}
AddUserForm.propTypes = {
data: PropTypes.object.isRequired,
error: PropTypes.object.isRequired,
submit: PropTypes.func.isRequired,
setData: PropTypes.func.isRequired,
roles: PropTypes.array.isRequired,
};
export default AddUserForm;

View File

@ -1,21 +0,0 @@
import { makeStyles } from '@material-ui/styles';
export const useStyles = makeStyles(theme => ({
roleBox: {
margin: '3px 0',
border: '1px solid #EFEFEF',
padding: '1rem',
},
userInfoContainer: {
margin: '-20px 0',
},
roleRadio: {
marginRight: '15px',
},
roleSubtitle: {
marginBottom: '0.5rem',
},
errorAlert: {
marginBottom: '1rem',
},
}));

View File

@ -0,0 +1,119 @@
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
import { useHistory } from 'react-router-dom';
import UserForm from '../UserForm/UserForm';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
import useToast from '../../../../hooks/useToast';
import useAddUserForm from '../hooks/useAddUserForm';
import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded';
import { useState } from 'react';
import { scrollToTop } from '../../../common/util';
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../../providers/AccessProvider/permissions';
const CreateUser = () => {
/* @ts-ignore */
const { setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const history = useHistory();
const {
name,
setName,
email,
setEmail,
sendEmail,
setSendEmail,
rootRole,
setRootRole,
getAddUserPayload,
validateName,
validateEmail,
errors,
clearErrors,
} = useAddUserForm();
const [showConfirm, setShowConfirm] = useState(false);
const [inviteLink, setInviteLink] = useState('');
const { addUser, userLoading: loading } = useAdminUsersApi();
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const validName = validateName();
const validEmail = validateEmail();
if (validName && validEmail) {
const payload = getAddUserPayload();
try {
await addUser(payload)
.then(res => res.json())
.then(user => {
scrollToTop();
setInviteLink(user.inviteLink);
setShowConfirm(true);
});
} catch (e: any) {
setToastApiError(e.toString());
}
}
};
const closeConfirm = () => {
setShowConfirm(false);
history.push('/admin/user-admin');
};
const formatApiCode = () => {
return `curl --location --request POST '${
uiConfig.unleashUrl
}/api/admin/user-admin' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getAddUserPayload(), undefined, 2)}'`;
};
const handleCancel = () => {
history.goBack();
};
return (
<FormTemplate
loading={loading}
title="Create Unleash user"
description="In order to get access to Unleash needs to have an Unleash root role as Admin, Editor or Viewer.
You can also add the user to projects as member or owner in the specific projects."
documentationLink="https://docs.getunleash.io/user_guide/user-management"
formatApiCode={formatApiCode}
>
<UserForm
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
name={name}
setName={setName}
email={email}
setEmail={setEmail}
sendEmail={sendEmail}
setSendEmail={setSendEmail}
rootRole={rootRole}
setRootRole={setRootRole}
clearErrors={clearErrors}
>
<PermissionButton
onClick={handleSubmit}
permission={ADMIN}
type="submit"
>
Create user
</PermissionButton>
</UserForm>
<ConfirmUserAdded
open={showConfirm}
closeConfirm={closeConfirm}
emailSent={sendEmail}
inviteLink={inviteLink}
/>
</FormTemplate>
);
};
export default CreateUser;

View File

@ -0,0 +1,114 @@
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
import { useHistory, useParams } from 'react-router-dom';
import UserForm from '../UserForm/UserForm';
import useAddUserForm from '../hooks/useAddUserForm';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
import useToast from '../../../../hooks/useToast';
import useAdminUsersApi from '../../../../hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
import useUserInfo from '../../../../hooks/api/getters/useUserInfo/useUserInfo';
import { scrollToTop } from '../../../common/util';
import { useEffect } from 'react';
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../../providers/AccessProvider/permissions';
import { EDIT } from '../../../../constants/misc';
const EditUser = () => {
useEffect(() => {
scrollToTop();
}, []);
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { id } = useParams<{ id: string }>();
const { user, refetch } = useUserInfo(id);
const { updateUser, userLoading: loading } = useAdminUsersApi();
const history = useHistory();
const {
name,
setName,
email,
setEmail,
sendEmail,
setSendEmail,
rootRole,
setRootRole,
getAddUserPayload,
validateName,
errors,
clearErrors,
} = useAddUserForm(
user?.name,
user?.email,
user?.sendEmail,
user?.rootRole
);
const formatApiCode = () => {
return `curl --location --request PUT '${
uiConfig.unleashUrl
}/api/admin/user-admin/${id}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getAddUserPayload(), undefined, 2)}'`;
};
const handleSubmit = async (e: Event) => {
e.preventDefault();
const payload = getAddUserPayload();
const validName = validateName();
if (validName) {
try {
await updateUser({ ...payload, id });
refetch();
history.push('/admin/users');
setToastData({
title: 'User information updated',
type: 'success',
});
} catch (e: any) {
setToastApiError(e.toString());
}
}
};
const handleCancel = () => {
history.goBack();
};
return (
<FormTemplate
loading={loading}
title="Edit user"
description="In order to get access to Unleash needs to have an Unleash root role as Admin, Editor or Viewer.
You can also add the user to projects as member or owner in the specific projects."
documentationLink="https://docs.getunleash.io/user_guide/user-management"
formatApiCode={formatApiCode}
>
<UserForm
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
name={name}
setName={setName}
email={email}
setEmail={setEmail}
sendEmail={sendEmail}
setSendEmail={setSendEmail}
rootRole={rootRole}
setRootRole={setRootRole}
clearErrors={clearErrors}
mode={EDIT}
>
<PermissionButton
onClick={handleSubmit}
permission={ADMIN}
type="submit"
>
Edit user
</PermissionButton>
</UserForm>
</FormTemplate>
);
};
export default EditUser;

View File

@ -0,0 +1,68 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
container: {
maxWidth: '400px',
},
form: {
display: 'flex',
flexDirection: 'column',
height: '100%',
},
input: { width: '100%', marginBottom: '1rem' },
label: {
minWidth: '300px',
[theme.breakpoints.down(600)]: {
minWidth: 'auto',
},
},
buttonContainer: {
marginTop: 'auto',
display: 'flex',
justifyContent: 'flex-end',
},
cancelButton: {
marginRight: '1.5rem',
},
inputDescription: {
marginBottom: '0.5rem',
},
formHeader: {
fontWeight: 'normal',
marginTop: '0',
},
header: {
fontWeight: 'normal',
},
permissionErrorContainer: {
position: 'relative',
},
errorMessage: {
//@ts-ignore
fontSize: theme.fontSizes.smallBody,
color: theme.palette.error.main,
position: 'absolute',
top: '-8px',
},
roleBox: {
margin: '3px 0',
border: '1px solid #EFEFEF',
padding: '1rem',
},
userInfoContainer: {
margin: '-20px 0',
},
roleRadio: {
marginRight: '15px',
},
roleSubtitle: {
margin: '0.5rem 0',
},
errorAlert: {
marginBottom: '1rem',
},
flexRow: {
display: 'flex',
alignItems: 'center',
},
}));

View File

@ -0,0 +1,161 @@
import Input from '../../../common/Input/Input';
import {
FormControlLabel,
Button,
RadioGroup,
FormControl,
Typography,
Radio,
Switch,
} from '@material-ui/core';
import { useStyles } from './UserForm.styles';
import React from 'react';
import useUsers from '../../../../hooks/api/getters/useUsers/useUsers';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
import { EDIT } from '../../../../constants/misc';
interface IUserForm {
email: string;
name: string;
rootRole: number;
sendEmail: boolean;
setEmail: React.Dispatch<React.SetStateAction<string>>;
setName: React.Dispatch<React.SetStateAction<string>>;
setSendEmail: React.Dispatch<React.SetStateAction<boolean>>;
setRootRole: React.Dispatch<React.SetStateAction<number>>;
handleSubmit: (e: any) => void;
handleCancel: () => void;
errors: { [key: string]: string };
clearErrors: () => void;
mode?: string;
}
const UserForm: React.FC<IUserForm> = ({
children,
email,
name,
rootRole,
sendEmail,
setEmail,
setName,
setSendEmail,
setRootRole,
handleSubmit,
handleCancel,
errors,
clearErrors,
mode,
}) => {
const styles = useStyles();
const { roles } = useUsers();
const sortRoles = (a, b) => {
if (b.name[0] < a.name[0]) {
return 1;
} else if (a.name[0] < b.name[0]) {
return -1;
}
return 0;
};
return (
<form onSubmit={handleSubmit} className={styles.form}>
<h3 className={styles.formHeader}>User information</h3>
<div className={styles.container}>
<p className={styles.inputDescription}>
Who is the new Unleash user?
</p>
<Input
className={styles.input}
label="Full name"
value={name}
onChange={e => setName(e.target.value)}
error={Boolean(errors.name)}
errorText={errors.name}
onFocus={() => clearErrors()}
/>
<Input
className={styles.input}
label="Email"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
error={Boolean(errors.email)}
errorText={errors.email}
onFocus={() => clearErrors()}
/>
<FormControl>
<Typography
variant="subtitle1"
className={styles.roleSubtitle}
data-loading
>
What is your team member allowed to do?
</Typography>
<RadioGroup
name="rootRole"
value={rootRole || ''}
onChange={e => setRootRole(+e.target.value)}
data-loading
>
{roles.sort(sortRoles).map(role => (
<FormControlLabel
key={`role-${role.id}`}
labelPlacement="end"
className={styles.roleBox}
label={
<div>
<strong>{role.name}</strong>
<Typography variant="body2">
{role.description}
</Typography>
</div>
}
control={
<Radio
checked={role.id === rootRole}
className={styles.roleRadio}
/>
}
value={role.id}
/>
))}
</RadioGroup>
</FormControl>
<ConditionallyRender
condition={mode !== EDIT}
show={
<FormControl>
<Typography
variant="subtitle1"
className={styles.roleSubtitle}
data-loading
>
Should we send an email to your new team member
</Typography>
<div className={styles.flexRow}>
<Switch
name="sendEmail"
onChange={() => setSendEmail(!sendEmail)}
checked={sendEmail}
/>
<Typography>
{sendEmail ? 'Yes' : 'No'}
</Typography>
</div>
</FormControl>
}
/>
</div>
<div className={styles.buttonContainer}>
<Button onClick={handleCancel} className={styles.cancelButton}>
Cancel
</Button>
{children}
</div>
</form>
);
};
export default UserForm;

View File

@ -13,6 +13,7 @@ import { formatDateWithLocale } from '../../../../common/util';
import AccessContext from '../../../../../contexts/AccessContext';
import { IUser } from '../../../../../interfaces/user';
import { useStyles } from './UserListItem.styles';
import { useHistory } from 'react-router-dom';
interface IUserListItemProps {
user: IUser;
@ -36,6 +37,7 @@ const UserListItem = ({
location,
}: IUserListItemProps) => {
const { hasAccess } = useContext(AccessContext);
const history = useHistory()
const styles = useStyles();
return (
@ -78,7 +80,7 @@ const UserListItem = ({
data-loading
aria-label="Edit"
title="Edit"
onClick={openUpdateDialog(user)}
onClick={()=> history.push(`/admin/users/${user.id}/edit`)}
>
<Edit />
</IconButton>

View File

@ -8,9 +8,7 @@ import {
TableHead,
TableRow,
} from '@material-ui/core';
import AddUser from '../AddUser/AddUser';
import ChangePassword from '../change-password-component';
import UpdateUser from '../update-user-component';
import DelUser from '../del-user-component';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
import AccessContext from '../../../../contexts/AccessContext';
@ -27,9 +25,7 @@ import PaginateUI from '../../../common/PaginateUI/PaginateUI';
function UsersList({ location, closeDialog, showDialog }) {
const { users, roles, refetch, loading } = useUsers();
const {
addUser,
removeUser,
updateUser,
changePassword,
validatePassword,
userLoading,
@ -42,7 +38,6 @@ function UsersList({ location, closeDialog, showDialog }) {
const [emailSent, setEmailSent] = useState(false);
const [inviteLink, setInviteLink] = useState('');
const [delUser, setDelUser] = useState();
const [updateDialog, setUpdateDialog] = useState({ open: false });
const ref = useLoading(loading);
const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } =
usePagination(users, 50);
@ -66,28 +61,6 @@ function UsersList({ location, closeDialog, showDialog }) {
setPwDialog({ open: false });
};
const openUpdateDialog = user => e => {
e.preventDefault();
setUpdateDialog({ open: true, user });
};
const closeUpdateDialog = () => {
setUpdateDialog({ open: false });
};
const onAddUser = data => {
addUser(data)
.then(res => res.json())
.then(user => {
setEmailSent(user.emailSent);
setInviteLink(user.inviteLink);
closeDialog();
refetch();
setShowConfirm(true);
})
.catch(handleCatch);
};
const onDeleteUser = () => {
removeUser(delUser)
.then(() => {
@ -97,15 +70,6 @@ function UsersList({ location, closeDialog, showDialog }) {
.catch(handleCatch);
};
const onUpdateUser = data => {
updateUser(data)
.then(() => {
refetch();
closeUpdateDialog();
})
.catch(handleCatch);
};
const handleCatch = () =>
console.log('An exception was thrown and handled.');
@ -126,7 +90,6 @@ function UsersList({ location, closeDialog, showDialog }) {
<UserListItem
key={user.id}
user={user}
openUpdateDialog={openUpdateDialog}
openPwDialog={openPwDialog}
openDelDialog={openDelDialog}
location={location}
@ -140,7 +103,6 @@ function UsersList({ location, closeDialog, showDialog }) {
<UserListItem
key={user.id}
user={user}
openUpdateDialog={openUpdateDialog}
openPwDialog={openPwDialog}
openDelDialog={openDelDialog}
location={location}
@ -185,26 +147,6 @@ function UsersList({ location, closeDialog, showDialog }) {
inviteLink={inviteLink}
/>
<AddUser
showDialog={showDialog}
closeDialog={closeDialog}
addUser={onAddUser}
userLoading={userLoading}
validatePassword={validatePassword}
userApiErrors={userApiErrors}
roles={roles}
/>
<UpdateUser
showDialog={updateDialog.open}
closeDialog={closeUpdateDialog}
updateUser={onUpdateUser}
userLoading={userLoading}
userApiErrors={userApiErrors}
user={updateDialog.user}
roles={roles}
/>
<ChangePassword
showDialog={pwDialog.open}
closeDialog={closePwDialog}

View File

@ -1,7 +1,7 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Typography, Avatar } from '@material-ui/core';
import { TextField, Typography, Avatar } from '@material-ui/core';
import { trim } from '../../common/util';
import { modalStyles } from './util';
import Dialogue from '../../common/Dialogue/Dialogue';
@ -10,7 +10,6 @@ import { useCommonStyles } from '../../../common.styles';
import PasswordMatcher from '../../user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
import ConditionallyRender from '../../common/ConditionallyRender';
import { Alert } from '@material-ui/lab';
import PasswordField from '../../common/PasswordField/PasswordField';
function ChangePassword({
showDialog,
@ -110,20 +109,26 @@ function ChangePassword({
/>
<p style={{ color: 'red' }}>{error.general}</p>
<PasswordField
<TextField
label="New password"
name="password"
type="password"
value={data.password}
helperText={error.password}
onChange={updateField}
variant="outlined"
size="small"
/>
<PasswordField
<TextField
label="Confirm password"
name="confirm"
type="password"
value={data.confirm}
error={error.confirm !== undefined}
helperText={error.confirm}
onChange={updateField}
variant="outlined"
size="small"
/>
<PasswordMatcher
started={data.password && data.confirm}

View File

@ -0,0 +1,84 @@
import { useEffect, useState } from 'react';
import useUsers from '../../../../hooks/api/getters/useUsers/useUsers';
const useProjectRoleForm = (
initialName = '',
initialEmail = '',
initialSendEmail = false,
initialRootRole = 1
) => {
const [name, setName] = useState(initialName);
const [email, setEmail] = useState(initialEmail);
const [sendEmail, setSendEmail] = useState(initialSendEmail);
const [rootRole, setRootRole] = useState(initialRootRole);
const [errors, setErrors] = useState({});
const { users } = useUsers();
useEffect(() => {
setName(initialName);
}, [initialName]);
useEffect(() => {
setEmail(initialEmail);
}, [initialEmail]);
useEffect(() => {
setSendEmail(initialSendEmail);
}, [initialSendEmail]);
useEffect(() => {
setRootRole(initialRootRole);
}, [initialRootRole]);
const getAddUserPayload = () => {
return {
name: name,
email: email,
sendEmail: sendEmail,
rootRole: rootRole,
};
};
const validateName = () => {
if (name.length === 0) {
setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
return false;
}
if (email.length === 0) {
setErrors(prev => ({ ...prev, email: 'Email can not be empty.' }));
return false;
}
return true;
};
const validateEmail = () => {
if (users.some(user => user['email'] === email)) {
setErrors(prev => ({ ...prev, email: 'Email already exists' }));
return false;
}
return true;
};
const clearErrors = () => {
setErrors({});
};
return {
name,
setName,
email,
setEmail,
sendEmail,
setSendEmail,
rootRole,
setRootRole,
getAddUserPayload,
validateName,
validateEmail,
clearErrors,
errors,
};
};
export default useProjectRoleForm;

View File

@ -40,7 +40,9 @@ const UsersAdmin = ({ history }) => {
<Button
variant="contained"
color="primary"
onClick={openDialog}
onClick={() =>
history.push('/admin/create-user')
}
>
Add new user
</Button>

View File

@ -1,84 +0,0 @@
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Dialogue from '../../common/Dialogue';
import UserForm from './AddUser/AddUserForm/AddUserForm';
function AddUser({
user,
showDialog,
closeDialog,
updateUser,
roles,
userApiErrors,
userLoading,
}) {
const [data, setData] = useState({});
const [error, setError] = useState({});
useEffect(() => {
setData({
...user,
});
}, [user]);
if (!user) {
return null;
}
const submit = async e => {
e.preventDefault();
try {
await updateUser(data);
setData({});
setError({});
} catch (error) {
setError({ general: 'Could not update user' });
}
};
const onCancel = e => {
e.preventDefault();
setData({});
setError({});
closeDialog();
};
const formId = 'update-user-dialog-form'
return (
<Dialogue
onClick={e => {
submit(e);
}}
formId={formId}
open={showDialog}
onClose={onCancel}
primaryButtonText="Update user"
secondaryButtonText="Cancel"
title="Update team member"
fullWidth
>
<UserForm
data={data}
setData={setData}
roles={roles}
submit={submit}
error={error}
userApiErrors={userApiErrors}
userLoading={userLoading}
formId={formId}
/>
</Dialogue>
);
}
AddUser.propTypes = {
showDialog: PropTypes.bool.isRequired,
closeDialog: PropTypes.func.isRequired,
updateUser: PropTypes.func.isRequired,
user: PropTypes.object,
roles: PropTypes.array.isRequired,
};
export default AddUser;

View File

@ -388,6 +388,15 @@ Array [
"title": "Users",
"type": "protected",
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/admin",
"path": "/admin/create-user",
"title": "Users",
"type": "protected",
},
Object {
"component": Object {
"$$typeof": Symbol(react.memo),

View File

@ -42,6 +42,8 @@ import FeatureCreate from '../feature/FeatureCreate/FeatureCreate';
import ProjectRoles from '../admin/project-roles/ProjectRoles/ProjectRoles';
import CreateProjectRole from '../admin/project-roles/CreateProjectRole/CreateProjectRole';
import EditProjectRole from '../admin/project-roles/EditProjectRole/EditProjectRole';
import CreateUser from '../admin/users/CreateUser/CreateUser';
import EditUser from '../admin/users/EditUser/EditUser';
import CreateApiToken from '../admin/api-token/CreateApiToken/CreateApiToken';
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
import EditEnvironment from '../environments/EditEnvironment/EditEnvironment';
@ -416,6 +418,15 @@ export const routes = [
menu: {},
flag: RE,
},
{
path: '/admin/users/:id/edit',
title: 'Edit',
component: EditUser,
type: 'protected',
layout: 'main',
menu: {},
hidden: true,
},
{
path: '/admin/api',
parent: '/admin',
@ -434,6 +445,15 @@ export const routes = [
layout: 'main',
menu: { adminSettings: true },
},
{
path: '/admin/create-user',
parent: '/admin',
title: 'Users',
component: CreateUser,
type: 'protected',
layout: 'main',
menu: {},
},
{
path: '/admin/auth',
parent: '/admin',

View File

@ -0,0 +1,2 @@
export const EDIT = 'Edit';
export const CREATE = 'Create';

View File

@ -0,0 +1,35 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useState, useEffect } from 'react';
import { formatApiPath } from '../../../../utils/format-path';
import handleErrorResponses from '../httpErrorResponseHandler';
const useUserInfo = (id: string, options: SWRConfiguration = {}) => {
const fetcher = () => {
const path = formatApiPath(`api/admin/user-admin/${id}`);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Users'))
.then(res => res.json());
};
const { data, error } = useSWR(`api/admin/user-admin/${id}`, fetcher, options);
const [loading, setLoading] = useState(!error && !data);
const refetch = () => {
mutate(`api/admin/user-admin/${id}`);
};
useEffect(() => {
setLoading(!error && !data);
}, [data, error]);
return {
user: data || {},
error,
loading,
refetch,
};
};
export default useUserInfo;