mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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:
		
							parent
							
								
									1b097f85d6
								
							
						
					
					
						commit
						80e80805f7
					
				| @ -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; | ||||
| @ -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; | ||||
| @ -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', | ||||
|     }, | ||||
| })); | ||||
							
								
								
									
										119
									
								
								frontend/src/component/admin/users/CreateUser/CreateUser.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								frontend/src/component/admin/users/CreateUser/CreateUser.tsx
									
									
									
									
									
										Normal 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; | ||||
							
								
								
									
										114
									
								
								frontend/src/component/admin/users/EditUser/EditUser.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								frontend/src/component/admin/users/EditUser/EditUser.tsx
									
									
									
									
									
										Normal 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; | ||||
| @ -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', | ||||
|     }, | ||||
| })); | ||||
							
								
								
									
										161
									
								
								frontend/src/component/admin/users/UserForm/UserForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								frontend/src/component/admin/users/UserForm/UserForm.tsx
									
									
									
									
									
										Normal 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; | ||||
| @ -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> | ||||
|  | ||||
| @ -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} | ||||
|  | ||||
| @ -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} | ||||
|  | ||||
							
								
								
									
										84
									
								
								frontend/src/component/admin/users/hooks/useAddUserForm.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								frontend/src/component/admin/users/hooks/useAddUserForm.ts
									
									
									
									
									
										Normal 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; | ||||
| @ -40,7 +40,9 @@ const UsersAdmin = ({ history }) => { | ||||
|                                     <Button | ||||
|                                         variant="contained" | ||||
|                                         color="primary" | ||||
|                                         onClick={openDialog} | ||||
|                                         onClick={() => | ||||
|                                             history.push('/admin/create-user') | ||||
|                                         } | ||||
|                                     > | ||||
|                                         Add new user | ||||
|                                     </Button> | ||||
|  | ||||
| @ -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; | ||||
| @ -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), | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
							
								
								
									
										2
									
								
								frontend/src/constants/misc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								frontend/src/constants/misc.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| export const EDIT = 'Edit'; | ||||
| export const CREATE = 'Create'; | ||||
							
								
								
									
										35
									
								
								frontend/src/hooks/api/getters/useUserInfo/useUserInfo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								frontend/src/hooks/api/getters/useUserInfo/useUserInfo.ts
									
									
									
									
									
										Normal 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; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user