mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge branch 'main' into refactor/create-token
This commit is contained in:
		
						commit
						dd64f7110f
					
				| @ -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; | ||||
| @ -1,31 +0,0 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     helperText: { marginBottom: '1rem' }, | ||||
|     formHeader: { | ||||
|         fontWeight: 'bold', | ||||
|         fontSize: '1rem', | ||||
|         marginTop: '2rem', | ||||
|     }, | ||||
|     radioGroup: { | ||||
|         flexDirection: 'row', | ||||
|     }, | ||||
|     environmentDetailsContainer: { | ||||
|         display: 'flex', | ||||
|         flexDirection: 'column', | ||||
|         margin: '1rem 0', | ||||
|     }, | ||||
|     submitButton: { | ||||
|         marginTop: '1rem', | ||||
|         width: '150px', | ||||
|         marginRight: '1rem', | ||||
|     }, | ||||
|     btnContainer: { | ||||
|         display: 'flex', | ||||
|         justifyContent: 'center', | ||||
|     }, | ||||
|     inputField: { | ||||
|         width: '100%', | ||||
|         marginTop: '1rem', | ||||
|     }, | ||||
| })); | ||||
| @ -1,193 +1,141 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import { FormControl, Button } from '@material-ui/core'; | ||||
| import HeaderTitle from '../../common/HeaderTitle'; | ||||
| import PageContent from '../../common/PageContent'; | ||||
| 
 | ||||
| import { useStyles } from './CreateEnvironment.styles'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import CreateEnvironmentSuccess from './CreateEnvironmentSuccess/CreateEnvironmentSuccess'; | ||||
| import useLoading from '../../../hooks/useLoading'; | ||||
| import useEnvironmentForm from '../hooks/useEnvironmentForm'; | ||||
| import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useToast from '../../../hooks/useToast'; | ||||
| import EnvironmentTypeSelector from '../form/EnvironmentTypeSelector/EnvironmentTypeSelector'; | ||||
| import Input from '../../common/Input/Input'; | ||||
| import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; | ||||
| import EnvironmentForm from '../EnvironmentForm/EnvironmentForm'; | ||||
| import FormTemplate from '../../common/FormTemplate/FormTemplate'; | ||||
| import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { Button } from '@material-ui/core'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender'; | ||||
| import PageContent from '../../common/PageContent'; | ||||
| import HeaderTitle from '../../common/HeaderTitle'; | ||||
| import PermissionButton from '../../common/PermissionButton/PermissionButton'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; | ||||
| 
 | ||||
| const NAME_EXISTS_ERROR = 'Error: Environment'; | ||||
| 
 | ||||
| const CreateEnvironment = () => { | ||||
|     const [type, setType] = useState('development'); | ||||
|     const [envName, setEnvName] = useState(''); | ||||
|     const [nameError, setNameError] = useState(''); | ||||
|     const [createSuccess, setCreateSucceess] = useState(false); | ||||
|     /* @ts-ignore */ | ||||
|     const { setToastApiError, setToastData } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const history = useHistory(); | ||||
|     const styles = useStyles(); | ||||
|     const { validateEnvName, createEnvironment, loading } = useEnvironmentApi(); | ||||
|     const { environments } = useEnvironments(); | ||||
|     const ref = useLoading(loading); | ||||
|     const { setToastApiError } = useToast(); | ||||
|     const { refetch } = useProjectRolePermissions(); | ||||
| 
 | ||||
|     const handleTypeChange = (event: React.FormEvent<HTMLInputElement>) => { | ||||
|         setType(event.currentTarget.value); | ||||
|     }; | ||||
| 
 | ||||
|     const handleEnvNameChange = (e: React.FormEvent<HTMLInputElement>) => { | ||||
|         setEnvName(e.currentTarget.value); | ||||
|     }; | ||||
| 
 | ||||
|     const goBack = () => history.goBack(); | ||||
| 
 | ||||
|     const canCreateMoreEnvs = environments.length < 7; | ||||
|     const { createEnvironment, loading } = useEnvironmentApi(); | ||||
|     const { refetch } = useProjectRolePermissions(); | ||||
|     const { | ||||
|         name, | ||||
|         setName, | ||||
|         type, | ||||
|         setType, | ||||
|         getEnvPayload, | ||||
|         validateEnvironmentName, | ||||
|         clearErrors, | ||||
|         errors, | ||||
|     } = useEnvironmentForm(); | ||||
| 
 | ||||
|     const validateEnvironmentName = async () => { | ||||
|         if (envName.length === 0) { | ||||
|             setNameError('Environment Id can not be empty.'); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await validateEnvName(envName); | ||||
|         } catch (e) { | ||||
|             if (e.toString().includes(NAME_EXISTS_ERROR)) { | ||||
|                 setNameError('Name already exists'); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     const clearNameError = () => setNameError(''); | ||||
| 
 | ||||
|     const handleSubmit = async (e: React.FormEvent) => { | ||||
|     const handleSubmit = async (e: Event) => { | ||||
|         e.preventDefault(); | ||||
|         clearErrors(); | ||||
|         const validName = await validateEnvironmentName(); | ||||
| 
 | ||||
|         if (validName) { | ||||
|             const environment = { | ||||
|                 name: envName, | ||||
|                 type, | ||||
|             }; | ||||
| 
 | ||||
|             const payload = getEnvPayload(); | ||||
|             try { | ||||
|                 await createEnvironment(environment); | ||||
|                 await createEnvironment(payload); | ||||
|                 refetch(); | ||||
|                 setCreateSucceess(true); | ||||
|             } catch (e) { | ||||
|                 setToastData({ | ||||
|                     title: 'Environment created', | ||||
|                     type: 'success', | ||||
|                     confetti: true, | ||||
|                 }); | ||||
|                 history.push('/environments'); | ||||
|             } catch (e: any) { | ||||
|                 setToastApiError(e.toString()); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const formatApiCode = () => { | ||||
|         return `curl --location --request POST '${ | ||||
|             uiConfig.unleashUrl | ||||
|         }/api/admin/environments' \\ | ||||
| --header 'Authorization: INSERT_API_KEY' \\ | ||||
| --header 'Content-Type: application/json' \\ | ||||
| --data-raw '${JSON.stringify(getEnvPayload(), undefined, 2)}'`;
 | ||||
|     }; | ||||
| 
 | ||||
|     const handleCancel = () => { | ||||
|         history.goBack(); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent headerContent={<HeaderTitle title="Create environment" />}> | ||||
|             <ConditionallyRender | ||||
|                 condition={createSuccess} | ||||
|                 show={<CreateEnvironmentSuccess name={envName} type={type} />} | ||||
|                 elseShow={ | ||||
|                     <ConditionallyRender | ||||
|                         condition={canCreateMoreEnvs} | ||||
|                         show={ | ||||
|                             <div ref={ref}> | ||||
|                                 <p className={styles.helperText} data-loading> | ||||
|                                     Environments allow you to manage your | ||||
|                                     product lifecycle from local development | ||||
|                                     through production. Your projects and | ||||
|                                     feature toggles are accessible in all your | ||||
|                                     environments, but they can take different | ||||
|                                     configurations per environment. This means | ||||
|                                     that you can enable a feature toggle in a | ||||
|                                     development or test environment without | ||||
|                                     enabling the feature toggle in the | ||||
|                                     production environment. | ||||
|                                 </p> | ||||
| 
 | ||||
|                                 <form onSubmit={handleSubmit}> | ||||
|                                     <FormControl component="fieldset"> | ||||
|                                         <h3 | ||||
|                                             className={styles.formHeader} | ||||
|                                             data-loading | ||||
|                                         > | ||||
|                                             Environment Id and name | ||||
|                                         </h3> | ||||
| 
 | ||||
|                                         <div | ||||
|                                             data-loading | ||||
|                                             className={ | ||||
|                                                 styles.environmentDetailsContainer | ||||
|                                             } | ||||
|                                         > | ||||
|                                             <p> | ||||
|                                                 Unique env name for SDK | ||||
|                                                 configurations. | ||||
|                                             </p> | ||||
|                                             <Input | ||||
|                                                 label="Environment Id" | ||||
|                                                 onFocus={clearNameError} | ||||
|                                                 placeholder="A unique name for your environment" | ||||
|                                                 onBlur={validateEnvironmentName} | ||||
|                                                 error={Boolean(nameError)} | ||||
|                                                 errorText={nameError} | ||||
|                                                 value={envName} | ||||
|                                                 onChange={handleEnvNameChange} | ||||
|                                                 className={styles.inputField} | ||||
|                                             /> | ||||
|                                         </div> | ||||
| 
 | ||||
|                                         <EnvironmentTypeSelector | ||||
|                                             onChange={handleTypeChange} | ||||
|                                             value={type} | ||||
|                                         /> | ||||
|                                     </FormControl> | ||||
|                                     <div className={styles.btnContainer}> | ||||
|                                         <Button | ||||
|                                             className={styles.submitButton} | ||||
|                                             variant="contained" | ||||
|                                             color="primary" | ||||
|                                             type="submit" | ||||
|                                             data-loading | ||||
|                                         > | ||||
|                                             Submit | ||||
|                                         </Button>{' '} | ||||
|                                         <Button | ||||
|                                             className={styles.submitButton} | ||||
|                                             variant="outlined" | ||||
|                                             color="secondary" | ||||
|                                             onClick={goBack} | ||||
|                                             data-loading | ||||
|                                         > | ||||
|                                             Cancel | ||||
|                                         </Button> | ||||
|                                     </div> | ||||
|                                 </form> | ||||
|                             </div> | ||||
|         <ConditionallyRender | ||||
|             condition={canCreateMoreEnvs} | ||||
|             show={ | ||||
|                 <FormTemplate | ||||
|                     loading={loading} | ||||
|                     title="Create Environment" | ||||
|                     description="Environments allow you to manage your  | ||||
|                             product lifecycle from local development | ||||
|                             through production. Your projects and | ||||
|                             feature toggles are accessible in all your | ||||
|                             environments, but they can take different | ||||
|                             configurations per environment. This means | ||||
|                             that you can enable a feature toggle in a | ||||
|                             development or test environment without | ||||
|                             enabling the feature toggle in the | ||||
|                             production environment." | ||||
|                     documentationLink="https://docs.getunleash.io/user_guide/environments" | ||||
|                     formatApiCode={formatApiCode} | ||||
|                 > | ||||
|                     <EnvironmentForm | ||||
|                         errors={errors} | ||||
|                         handleSubmit={handleSubmit} | ||||
|                         handleCancel={handleCancel} | ||||
|                         validateEnvironmentName={validateEnvironmentName} | ||||
|                         name={name} | ||||
|                         type={type} | ||||
|                         setName={setName} | ||||
|                         setType={setType} | ||||
|                         mode="Create" | ||||
|                         clearErrors={clearErrors} | ||||
|                     > | ||||
|                         <PermissionButton | ||||
|                             onClick={handleSubmit} | ||||
|                             permission={ADMIN} | ||||
|                             type="submit" | ||||
|                         > | ||||
|                             Create environment | ||||
|                         </PermissionButton> | ||||
|                     </EnvironmentForm> | ||||
|                 </FormTemplate> | ||||
|             } | ||||
|             elseShow={ | ||||
|                 <> | ||||
|                     <PageContent | ||||
|                         headerContent={ | ||||
|                             <HeaderTitle title="Create environment" /> | ||||
|                         } | ||||
|                         elseShow={ | ||||
|                             <> | ||||
|                                 <Alert severity="error"> | ||||
|                                     <p> | ||||
|                                         Currently Unleash does not support more | ||||
|                                         than 5 environments. If you need more | ||||
|                                         please reach out. | ||||
|                                     </p> | ||||
|                                 </Alert> | ||||
|                                 <br /> | ||||
|                                 <Button | ||||
|                                     onClick={goBack} | ||||
|                                     variant="contained" | ||||
|                                     color="primary" | ||||
|                                 > | ||||
|                                     Go back | ||||
|                                 </Button> | ||||
|                             </> | ||||
|                         } | ||||
|                     /> | ||||
|                 } | ||||
|             /> | ||||
|         </PageContent> | ||||
|                     > | ||||
|                         <Alert severity="error"> | ||||
|                             <p> | ||||
|                                 Currently Unleash does not support more than 7 | ||||
|                                 environments. If you need more please reach out. | ||||
|                             </p> | ||||
|                         </Alert> | ||||
|                         <br /> | ||||
|                         <Button | ||||
|                             onClick={handleCancel} | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|                         > | ||||
|                             Go back | ||||
|                         </Button> | ||||
|                     </PageContent> | ||||
|                 </> | ||||
|             } | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -1,41 +0,0 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     subheader: { | ||||
|         fontSize: theme.fontSizes.subHeader, | ||||
|         fontWeight: 'normal', | ||||
|         marginTop: '2rem', | ||||
|     }, | ||||
|     container: { | ||||
|         display: 'flex', | ||||
|         justifyContent: 'center', | ||||
|         flexDirection: 'column', | ||||
|         alignItems: 'center', | ||||
|     }, | ||||
|     nextSteps: { | ||||
|         display: 'flex', | ||||
|     }, | ||||
|     step: { maxWidth: '350px', margin: '0 1.5rem', position: 'relative' }, | ||||
|     stepBadge: { | ||||
|         backgroundColor: theme.palette.primary.main, | ||||
|         width: '30px', | ||||
|         height: '30px', | ||||
|         borderRadius: '25px', | ||||
|         color: '#fff', | ||||
|         display: 'flex', | ||||
|         justifyContent: 'center', | ||||
|         alignItems: 'center', | ||||
|         fontWeight: 'bold', | ||||
|         margin: '2rem auto', | ||||
|     }, | ||||
|     stepParagraph: { | ||||
|         marginBottom: '1rem', | ||||
|     }, | ||||
|     button: { | ||||
|         marginTop: '2.5rem', | ||||
|         minWidth: '150px', | ||||
|     }, | ||||
|     link: { | ||||
|         color: theme.palette.primary.main, | ||||
|     }, | ||||
| })); | ||||
| @ -1,89 +0,0 @@ | ||||
| /* eslint-disable react/jsx-no-target-blank */ | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import CheckMarkBadge from '../../../common/CheckmarkBadge/CheckMarkBadge'; | ||||
| import { useStyles } from './CreateEnvironmentSuccess.styles'; | ||||
| import CreateEnvironmentSuccessCard from './CreateEnvironmentSuccessCard/CreateEnvironmentSuccessCard'; | ||||
| 
 | ||||
| export interface ICreateEnvironmentSuccessProps { | ||||
|     name: string; | ||||
|     type: string; | ||||
| } | ||||
| 
 | ||||
| const CreateEnvironmentSuccess = ({ | ||||
|     name, | ||||
|     type, | ||||
| }: ICreateEnvironmentSuccessProps) => { | ||||
|     const history = useHistory(); | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     const navigateToEnvironmentList = () => { | ||||
|         history.push('/environments'); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={styles.container}> | ||||
|             <CheckMarkBadge /> | ||||
|             <h2 className={styles.subheader}>Environment created</h2> | ||||
|             <CreateEnvironmentSuccessCard | ||||
|                 name={name} | ||||
|                 type={type} | ||||
|             /> | ||||
|             <h2 className={styles.subheader}>Next steps</h2> | ||||
|             <div className={styles.nextSteps}> | ||||
|                 <div className={styles.step}> | ||||
|                     <div> | ||||
|                         <div className={styles.stepBadge}>1</div> | ||||
|                         <h3 className={styles.subheader}> | ||||
|                             Update SDK version and provide the environment id to | ||||
|                             the SDK | ||||
|                         </h3> | ||||
|                         <p className={styles.stepParagraph}> | ||||
|                             By providing the environment id in the SDK the SDK | ||||
|                             will only retrieve activation strategies for | ||||
|                             specified environment | ||||
|                         </p> | ||||
|                         <a | ||||
|                             href="https://docs.getunleash.io/user_guide/environments" | ||||
|                             target="_blank" | ||||
|                             className={styles.link} | ||||
|                         > | ||||
|                             Learn more | ||||
|                         </a> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div className={styles.step}> | ||||
|                     <div> | ||||
|                         <div className={styles.stepBadge}>2</div> | ||||
|                         <h3 className={styles.subheader}> | ||||
|                             Add environment specific activation strategies | ||||
|                         </h3> | ||||
| 
 | ||||
|                         <p className={styles.stepParagraph}> | ||||
|                             You can now select this environment when you are | ||||
|                             adding new activation strategies on feature toggles. | ||||
|                         </p> | ||||
|                         <a | ||||
|                             href="https://docs.getunleash.io/user_guide/environments" | ||||
|                             target="_blank" | ||||
|                             className={styles.link} | ||||
|                         > | ||||
|                             Learn more | ||||
|                         </a> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <Button | ||||
|                 variant="contained" | ||||
|                 color="primary" | ||||
|                 className={styles.button} | ||||
|                 onClick={navigateToEnvironmentList} | ||||
|             > | ||||
|                 Got it! | ||||
|             </Button> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default CreateEnvironmentSuccess; | ||||
| @ -1,54 +0,0 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     container: { | ||||
|         minWidth: '300px', | ||||
|         position: 'absolute', | ||||
|         right: '80px', | ||||
|         bottom: '-475px', | ||||
|         zIndex: 9999, | ||||
|         opacity: 0, | ||||
|         transform: 'translateY(100px)', | ||||
|     }, | ||||
|     inputField: { | ||||
|         width: '100%', | ||||
|     }, | ||||
|     header: { | ||||
|         fontSize: theme.fontSizes.subHeader, | ||||
|         fontWeight: 'normal', | ||||
|         borderBottom: `1px solid ${theme.palette.grey[300]}`, | ||||
|         padding: '1rem', | ||||
|     }, | ||||
|     body: { padding: '1rem' }, | ||||
|     subheader: { | ||||
|         display: 'flex', | ||||
|         alignItems: 'center', | ||||
|         fontSize: theme.fontSizes.bodySize, | ||||
|         fontWeight: 'normal', | ||||
|     }, | ||||
|     icon: { | ||||
|         marginRight: '0.5rem', | ||||
|         fill: theme.palette.grey[600], | ||||
|     }, | ||||
|     formHeader: { | ||||
|         fontSize: theme.fontSizes.bodySize, | ||||
|     }, | ||||
|     buttonGroup: { | ||||
|         marginTop: '2rem', | ||||
|         display: 'flex', | ||||
|         justifyContent: 'space-between', | ||||
|     }, | ||||
|     editEnvButton: { | ||||
|         width: '150px', | ||||
|     }, | ||||
|     fadeInBottomEnter: { | ||||
|         transform: 'translateY(0)', | ||||
|         opacity: '1', | ||||
|         transition: 'transform 0.4s ease, opacity .4s ease', | ||||
|     }, | ||||
|     fadeInBottomLeave: { | ||||
|         transform: 'translateY(100px)', | ||||
|         opacity: '0', | ||||
|         transition: 'transform 0.4s ease, opacity 0.4s ease', | ||||
|     }, | ||||
| })); | ||||
| @ -1,108 +1,99 @@ | ||||
| import { CloudCircle } from '@material-ui/icons'; | ||||
| import { useEffect, useState } from 'react'; | ||||
| import EnvironmentTypeSelector from '../form/EnvironmentTypeSelector/EnvironmentTypeSelector'; | ||||
| import { useStyles } from './EditEnvironment.styles'; | ||||
| import { IEnvironment } from '../../../interfaces/environments'; | ||||
| import { useHistory, useParams } from 'react-router-dom'; | ||||
| import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; | ||||
| import useLoading from '../../../hooks/useLoading'; | ||||
| import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments'; | ||||
| import Dialogue from '../../common/Dialogue'; | ||||
| import useEnvironment from '../../../hooks/api/getters/useEnvironment/useEnvironment'; | ||||
| import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; | ||||
| import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useToast from '../../../hooks/useToast'; | ||||
| import FormTemplate from '../../common/FormTemplate/FormTemplate'; | ||||
| import PermissionButton from '../../common/PermissionButton/PermissionButton'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import EnvironmentForm from '../EnvironmentForm/EnvironmentForm'; | ||||
| import useEnvironmentForm from '../hooks/useEnvironmentForm'; | ||||
| 
 | ||||
| interface IEditEnvironmentProps { | ||||
|     env: IEnvironment; | ||||
|     setEditEnvironment: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     editEnvironment: boolean; | ||||
|     setToastData: React.Dispatch<React.SetStateAction<any>>; | ||||
| } | ||||
| const EditEnvironment = () => { | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
| 
 | ||||
| const EditEnvironment = ({ | ||||
|     env, | ||||
|     setEditEnvironment, | ||||
|     editEnvironment, | ||||
|     setToastData, | ||||
| }: IEditEnvironmentProps) => { | ||||
|     const styles = useStyles(); | ||||
|     const [type, setType] = useState(env.type); | ||||
|     const { updateEnvironment, loading } = useEnvironmentApi(); | ||||
|     const { refetch } = useEnvironments(); | ||||
|     const ref = useLoading(loading); | ||||
|     const { id } = useParams<{ id: string }>(); | ||||
|     const { environment } = useEnvironment(id); | ||||
|     const { updateEnvironment } = useEnvironmentApi(); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setType(env.type); | ||||
|     }, [env.type]); | ||||
|     const history = useHistory(); | ||||
|     const { name, type, setName, setType, errors, clearErrors } = | ||||
|         useEnvironmentForm(environment.name, environment.type); | ||||
|     const { refetch } = useProjectRolePermissions(); | ||||
| 
 | ||||
|     const handleTypeChange = (event: React.FormEvent<HTMLInputElement>) => { | ||||
|         setType(event.currentTarget.value); | ||||
|     }; | ||||
| 
 | ||||
|     const isDisabled = () => { | ||||
|         return type === env.type; | ||||
|     }; | ||||
| 
 | ||||
|     const handleCancel = () => { | ||||
|         setEditEnvironment(false); | ||||
|         resetFields(); | ||||
|     }; | ||||
| 
 | ||||
|     const handleSubmit = async (e: React.FormEvent) => { | ||||
|         e.preventDefault(); | ||||
|         const updatedEnv = { | ||||
|             sortOrder: env.sortOrder, | ||||
|     const editPayload = () => { | ||||
|         return { | ||||
|             type, | ||||
|             sortOrder: environment.sortOrder, | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     const formatApiCode = () => { | ||||
|         return `curl --location --request PUT '${ | ||||
|             uiConfig.unleashUrl | ||||
|         }/api/admin/environments/update/${id}' \\ | ||||
| --header 'Authorization: INSERT_API_KEY' \\ | ||||
| --header 'Content-Type: application/json' \\ | ||||
| --data-raw '${JSON.stringify(editPayload(), undefined, 2)}'`;
 | ||||
|     }; | ||||
| 
 | ||||
|     const handleSubmit = async (e: Event) => { | ||||
|         e.preventDefault(); | ||||
|         try { | ||||
|             await updateEnvironment(env.name, updatedEnv); | ||||
|             await updateEnvironment(id, editPayload()); | ||||
|             refetch(); | ||||
|             history.push('/environments'); | ||||
|             setToastData({ | ||||
|                 type: 'success', | ||||
|                 show: true, | ||||
|                 text: 'Successfully updated environment.', | ||||
|                 title: 'Successfully updated environment.', | ||||
|             }); | ||||
|             resetFields(); | ||||
|             refetch(); | ||||
|         } catch (e) { | ||||
|             setToastData({ | ||||
|                 show: true, | ||||
|                 type: 'error', | ||||
|                 text: e.toString(), | ||||
|             }); | ||||
|         } finally { | ||||
|             setEditEnvironment(false); | ||||
|         } catch (e: any) { | ||||
|             setToastApiError(e.toString()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const resetFields = () => { | ||||
|         setType(env.type); | ||||
|     const handleCancel = () => { | ||||
|         history.goBack(); | ||||
|     }; | ||||
| 
 | ||||
|     const formId = 'edit-environment-form'; | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             open={editEnvironment} | ||||
|         <FormTemplate | ||||
|             title="Edit environment" | ||||
|             onClose={handleCancel} | ||||
|             onClick={handleSubmit} | ||||
|             primaryButtonText="Save" | ||||
|             secondaryButtonText="Cancel" | ||||
|             disabledPrimaryButton={isDisabled()} | ||||
|             formId={formId} | ||||
|             description="Environments allow you to manage your  | ||||
|             product lifecycle from local development | ||||
|             through production. Your projects and | ||||
|             feature toggles are accessible in all your | ||||
|             environments, but they can take different | ||||
|             configurations per environment. This means | ||||
|             that you can enable a feature toggle in a | ||||
|             development or test environment without | ||||
|             enabling the feature toggle in the | ||||
|             production environment." | ||||
|             documentationLink="https://docs.getunleash.io/user_guide/environments" | ||||
|             formatApiCode={formatApiCode} | ||||
|         > | ||||
|             <div className={styles.body} ref={ref}> | ||||
|                 <h3 className={styles.formHeader} data-loading> | ||||
|                     Environment Id | ||||
|                 </h3> | ||||
|                 <h3 className={styles.subheader} data-loading> | ||||
|                     <CloudCircle className={styles.icon} /> {env.name} | ||||
|                 </h3> | ||||
|                 <form id={formId}> | ||||
|                     <EnvironmentTypeSelector | ||||
|                         onChange={handleTypeChange} | ||||
|                         value={type} | ||||
|                     /> | ||||
|                 </form> | ||||
|             </div> | ||||
|         </Dialogue> | ||||
|             <EnvironmentForm | ||||
|                 handleSubmit={handleSubmit} | ||||
|                 handleCancel={handleCancel} | ||||
|                 name={name} | ||||
|                 type={type} | ||||
|                 setName={setName} | ||||
|                 setType={setType} | ||||
|                 mode="Edit" | ||||
|                 errors={errors} | ||||
|                 clearErrors={clearErrors} | ||||
|             > | ||||
|                 <PermissionButton | ||||
|                     onClick={handleSubmit} | ||||
|                     permission={ADMIN} | ||||
|                     type="submit" | ||||
|                 > | ||||
|                     Edit environment | ||||
|                 </PermissionButton> | ||||
|             </EnvironmentForm> | ||||
|         </FormTemplate> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,47 @@ | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| 
 | ||||
| export const useStyles = makeStyles(theme => ({ | ||||
|     container: { | ||||
|         maxWidth: '440px', | ||||
|     }, | ||||
|     form: { | ||||
|         display: 'flex', | ||||
|         flexDirection: 'column', | ||||
|         height: '100%', | ||||
|     }, | ||||
|     input: { width: '100%', marginBottom: '1rem' }, | ||||
|     label: { | ||||
|         minWidth: '30px', | ||||
|         [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', | ||||
|     }, | ||||
| })); | ||||
| @ -0,0 +1,74 @@ | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { useStyles } from './EnvironmentForm.styles'; | ||||
| import React from 'react'; | ||||
| import Input from '../../common/Input/Input'; | ||||
| import EnvironmentTypeSelector from './EnvironmentTypeSelector/EnvironmentTypeSelector'; | ||||
| import { trim } from '../../common/util'; | ||||
| 
 | ||||
| interface IEnvironmentForm { | ||||
|     name: string; | ||||
|     type: string; | ||||
|     setName: React.Dispatch<React.SetStateAction<string>>; | ||||
|     setType: React.Dispatch<React.SetStateAction<string>>; | ||||
|     validateEnvironmentName?: (e: any) => void; | ||||
|     handleSubmit: (e: any) => void; | ||||
|     handleCancel: () => void; | ||||
|     errors: { [key: string]: string }; | ||||
|     mode: string; | ||||
|     clearErrors: () => void; | ||||
| } | ||||
| 
 | ||||
| const EnvironmentForm: React.FC<IEnvironmentForm> = ({ | ||||
|     children, | ||||
|     handleSubmit, | ||||
|     handleCancel, | ||||
|     name, | ||||
|     type, | ||||
|     setName, | ||||
|     setType, | ||||
|     validateEnvironmentName, | ||||
|     errors, | ||||
|     mode, | ||||
|     clearErrors, | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     return ( | ||||
|         <form onSubmit={handleSubmit} className={styles.form}> | ||||
|             <h3 className={styles.formHeader}>Environment information</h3> | ||||
| 
 | ||||
|             <div className={styles.container}> | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     What is your environment name? (Can't be changed later) | ||||
|                 </p> | ||||
|                 <Input | ||||
|                     className={styles.input} | ||||
|                     label="Environment name" | ||||
|                     value={name} | ||||
|                     onChange={e => setName(trim(e.target.value))} | ||||
|                     error={Boolean(errors.name)} | ||||
|                     errorText={errors.name} | ||||
|                     onFocus={() => clearErrors()} | ||||
|                     onBlur={validateEnvironmentName} | ||||
|                     disabled={mode === 'Edit'} | ||||
|                 /> | ||||
| 
 | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     What type of environment do you want to create? | ||||
|                 </p> | ||||
|                 <EnvironmentTypeSelector | ||||
|                     onChange={e => setType(e.currentTarget.value)} | ||||
|                     value={type} | ||||
|                 /> | ||||
|             </div> | ||||
|             <div className={styles.buttonContainer}> | ||||
|                 <Button onClick={handleCancel} className={styles.cancelButton}> | ||||
|                     Cancel | ||||
|                 </Button> | ||||
|                 {children} | ||||
|             </div> | ||||
|         </form> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default EnvironmentForm; | ||||
| @ -6,6 +6,7 @@ export const useStyles = makeStyles(theme => ({ | ||||
|     }, | ||||
|     formHeader: { | ||||
|         fontWeight: 'bold', | ||||
|         //@ts-ignore
 | ||||
|         fontSize: theme.fontSizes.bodySize, | ||||
|         marginTop: '1.5rem', | ||||
|         marginBottom: '0.5rem', | ||||
| @ -18,10 +18,6 @@ const EnvironmentTypeSelector = ({ | ||||
|     const styles = useStyles(); | ||||
|     return ( | ||||
|         <FormControl component="fieldset"> | ||||
|             <h3 className={styles.formHeader} data-loading> | ||||
|                 Environment Type | ||||
|             </h3> | ||||
| 
 | ||||
|             <RadioGroup | ||||
|                 data-loading | ||||
|                 value={value} | ||||
| @ -1,12 +1,13 @@ | ||||
| import { CloudCircle } from '@material-ui/icons'; | ||||
| import StringTruncator from '../../../../common/StringTruncator/StringTruncator'; | ||||
| import { ICreateEnvironmentSuccessProps } from '../CreateEnvironmentSuccess'; | ||||
| import { useStyles } from './CreateEnvironmentSuccessCard.styles'; | ||||
| import StringTruncator from '../../../common/StringTruncator/StringTruncator'; | ||||
| import { useStyles } from './EnvironmentCard.styles'; | ||||
| 
 | ||||
| const CreateEnvironmentSuccessCard = ({ | ||||
|     name, | ||||
|     type, | ||||
| }: ICreateEnvironmentSuccessProps) => { | ||||
| interface IEnvironmentProps { | ||||
|     name: string; | ||||
|     type: string; | ||||
| } | ||||
| 
 | ||||
| const EnvironmentCard = ({ name, type }: IEnvironmentProps) => { | ||||
|     const styles = useStyles(); | ||||
|     return ( | ||||
|         <div className={styles.container}> | ||||
| @ -30,4 +31,4 @@ const CreateEnvironmentSuccessCard = ({ | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default CreateEnvironmentSuccessCard; | ||||
| export default EnvironmentCard; | ||||
| @ -3,7 +3,7 @@ import React from 'react'; | ||||
| import { IEnvironment } from '../../../../interfaces/environments'; | ||||
| import Dialogue from '../../../common/Dialogue'; | ||||
| import Input from '../../../common/Input/Input'; | ||||
| import CreateEnvironmentSuccessCard from '../../CreateEnvironment/CreateEnvironmentSuccess/CreateEnvironmentSuccessCard/CreateEnvironmentSuccessCard'; | ||||
| import EnvironmentCard from '../EnvironmentCard/EnvironmentCard'; | ||||
| import { useStyles } from './EnvironmentDeleteConfirm.styles'; | ||||
| 
 | ||||
| interface IEnviromentDeleteConfirmProps { | ||||
| @ -52,7 +52,7 @@ const EnvironmentDeleteConfirm = ({ | ||||
|                 strategies that are active in this environment across all | ||||
|                 feature toggles. | ||||
|             </Alert> | ||||
|             <CreateEnvironmentSuccessCard name={env?.name} type={env?.type} /> | ||||
|             <EnvironmentCard name={env?.name} type={env?.type} /> | ||||
| 
 | ||||
|             <p className={styles.deleteParagraph}> | ||||
|                 In order to delete this environment, please enter the id of the | ||||
|  | ||||
| @ -17,7 +17,6 @@ import useToast from '../../../hooks/useToast'; | ||||
| import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; | ||||
| import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem'; | ||||
| import { mutate } from 'swr'; | ||||
| import EditEnvironment from '../EditEnvironment/EditEnvironment'; | ||||
| import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm'; | ||||
| import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; | ||||
| 
 | ||||
| @ -31,7 +30,6 @@ const EnvironmentList = () => { | ||||
|         protected: false, | ||||
|     }; | ||||
|     const { environments, refetch } = useEnvironments(); | ||||
|     const [editEnvironment, setEditEnvironment] = useState(false); | ||||
|     const { refetch: refetchProjectRolePermissions } = | ||||
|         useProjectRolePermissions(); | ||||
| 
 | ||||
| @ -151,7 +149,6 @@ const EnvironmentList = () => { | ||||
|             <EnvironmentListItem | ||||
|                 key={env.name} | ||||
|                 env={env} | ||||
|                 setEditEnvironment={setEditEnvironment} | ||||
|                 setDeldialogue={setDeldialogue} | ||||
|                 setSelectedEnv={setSelectedEnv} | ||||
|                 setToggleDialog={setToggleDialog} | ||||
| @ -195,13 +192,6 @@ const EnvironmentList = () => { | ||||
|                 confirmName={confirmName} | ||||
|                 setConfirmName={setConfirmName} | ||||
|             /> | ||||
| 
 | ||||
|             <EditEnvironment | ||||
|                 env={selectedEnv} | ||||
|                 setEditEnvironment={setEditEnvironment} | ||||
|                 editEnvironment={editEnvironment} | ||||
|                 setToastData={setToastData} | ||||
|             /> | ||||
|             <EnvironmentToggleConfirm | ||||
|                 env={selectedEnv} | ||||
|                 open={toggleDialog} | ||||
|  | ||||
| @ -25,12 +25,12 @@ import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd'; | ||||
| import { XYCoord } from 'dnd-core'; | ||||
| import DisabledIndicator from '../../../common/DisabledIndicator/DisabledIndicator'; | ||||
| import StringTruncator from '../../../common/StringTruncator/StringTruncator'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| 
 | ||||
| interface IEnvironmentListItemProps { | ||||
|     env: IEnvironment; | ||||
|     setSelectedEnv: React.Dispatch<React.SetStateAction<IEnvironment>>; | ||||
|     setDeldialogue: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     setEditEnvironment: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     setToggleDialog: React.Dispatch<React.SetStateAction<boolean>>; | ||||
|     index: number; | ||||
|     moveListItem: (dragIndex: number, hoverIndex: number) => IEnvironment[]; | ||||
| @ -51,8 +51,8 @@ const EnvironmentListItem = ({ | ||||
|     moveListItem, | ||||
|     moveListItemApi, | ||||
|     setToggleDialog, | ||||
|     setEditEnvironment, | ||||
| }: IEnvironmentListItemProps) => { | ||||
|     const history = useHistory(); | ||||
|     const ref = useRef<HTMLDivElement>(null); | ||||
|     const ACCEPT_TYPE = 'LIST_ITEM'; | ||||
|     const [{ isDragging }, drag] = useDrag({ | ||||
| @ -178,8 +178,7 @@ const EnvironmentListItem = ({ | ||||
|                             aria-label="update" | ||||
|                             disabled={env.protected} | ||||
|                             onClick={() => { | ||||
|                                 setSelectedEnv(env); | ||||
|                                 setEditEnvironment(prev => !prev); | ||||
|                                 history.push(`/environments/${env.name}`); | ||||
|                             }} | ||||
|                         > | ||||
|                             <Edit /> | ||||
|  | ||||
| @ -4,7 +4,7 @@ import React from 'react'; | ||||
| import { IEnvironment } from '../../../../interfaces/environments'; | ||||
| import ConditionallyRender from '../../../common/ConditionallyRender'; | ||||
| import Dialogue from '../../../common/Dialogue'; | ||||
| import CreateEnvironmentSuccessCard from '../../CreateEnvironment/CreateEnvironmentSuccess/CreateEnvironmentSuccessCard/CreateEnvironmentSuccessCard'; | ||||
| import EnvironmentCard from '../EnvironmentCard/EnvironmentCard'; | ||||
| 
 | ||||
| interface IEnvironmentToggleConfirmProps { | ||||
|     env: IEnvironment; | ||||
| @ -52,10 +52,7 @@ const EnvironmentToggleConfirm = ({ | ||||
|                 } | ||||
|             /> | ||||
| 
 | ||||
|             <CreateEnvironmentSuccessCard | ||||
|                 name={env?.name} | ||||
|                 type={env?.type} | ||||
|             /> | ||||
|             <EnvironmentCard name={env?.name} type={env?.type} /> | ||||
|         </Dialogue> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,69 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; | ||||
| 
 | ||||
| const useEnvironmentForm = ( | ||||
|     initialName = '', | ||||
|     initialType = 'development' | ||||
| ) => { | ||||
|     const NAME_EXISTS_ERROR = 'Error: Environment'; | ||||
|     const [name, setName] = useState(initialName); | ||||
|     const [type, setType] = useState(initialType); | ||||
|     const [errors, setErrors] = useState({}); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setName(initialName); | ||||
|     }, [initialName]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setType(initialType); | ||||
|     }, [initialType]); | ||||
| 
 | ||||
|     const { validateEnvName } = useEnvironmentApi(); | ||||
| 
 | ||||
|     const getEnvPayload = () => { | ||||
|         return { | ||||
|             name, | ||||
|             type, | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     const validateEnvironmentName = async () => { | ||||
|         if (name.length === 0) { | ||||
|             setErrors(prev => ({ | ||||
|                 ...prev, | ||||
|                 name: 'Environment name can not be empty', | ||||
|             })); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await validateEnvName(name); | ||||
|         } catch (e: any) { | ||||
|             if (e.toString().includes(NAME_EXISTS_ERROR)) { | ||||
|                 setErrors(prev => ({ | ||||
|                     ...prev, | ||||
|                     name: 'Name already exists', | ||||
|                 })); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     const clearErrors = () => { | ||||
|         setErrors({}); | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         name, | ||||
|         setName, | ||||
|         type, | ||||
|         setType, | ||||
|         getEnvPayload, | ||||
|         validateEnvironmentName, | ||||
|         clearErrors, | ||||
|         errors, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export default useEnvironmentForm; | ||||
| @ -214,6 +214,14 @@ Array [ | ||||
|     "title": "Environments", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|     "menu": Object {}, | ||||
|     "path": "/environments/:id", | ||||
|     "title": "Edit", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": [Function], | ||||
|     "flag": "EEA", | ||||
| @ -380,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), | ||||
|  | ||||
| @ -37,13 +37,17 @@ import Project from '../project/Project/Project'; | ||||
| import RedirectFeatureViewPage from '../../page/features/redirect'; | ||||
| import RedirectArchive from '../feature/RedirectArchive/RedirectArchive'; | ||||
| import EnvironmentList from '../environments/EnvironmentList/EnvironmentList'; | ||||
| import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment'; | ||||
| import FeatureView2 from '../feature/FeatureView2/FeatureView2'; | ||||
| 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'; | ||||
| 
 | ||||
| 
 | ||||
| export const routes = [ | ||||
|     // Project
 | ||||
| @ -255,6 +259,14 @@ export const routes = [ | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/environments/:id', | ||||
|         title: 'Edit', | ||||
|         component: EditEnvironment, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/environments', | ||||
|         title: 'Environments', | ||||
| @ -406,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', | ||||
| @ -424,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'; | ||||
| @ -0,0 +1,10 @@ | ||||
| import { IEnvironment } from '../../../../interfaces/environments'; | ||||
| 
 | ||||
| export const defaultEnvironment: IEnvironment = { | ||||
|     name: '', | ||||
|     type: '', | ||||
|     createdAt: '', | ||||
|     sortOrder: 0, | ||||
|     enabled: false, | ||||
|     protected: false | ||||
| }; | ||||
| @ -0,0 +1,49 @@ | ||||
| import useSWR, { mutate, SWRConfiguration } from 'swr'; | ||||
| import { useState, useEffect } from 'react'; | ||||
| 
 | ||||
| import { formatApiPath } from '../../../../utils/format-path'; | ||||
| import { IEnvironment } from '../../../../interfaces/environments'; | ||||
| import handleErrorResponses from '../httpErrorResponseHandler'; | ||||
| import { defaultEnvironment } from './defaultEnvironment'; | ||||
| 
 | ||||
| const useEnvironment = ( | ||||
|     id: string, | ||||
|     options: SWRConfiguration = {} | ||||
| ) => { | ||||
|     const fetcher = async () => { | ||||
|         const path = formatApiPath( | ||||
|             `api/admin/environments/${id}` | ||||
|         ); | ||||
|         return fetch(path, { | ||||
|             method: 'GET', | ||||
|         }) | ||||
|             .then(handleErrorResponses('Environment data')) | ||||
|             .then(res => res.json()); | ||||
|     }; | ||||
| 
 | ||||
|     const FEATURE_CACHE_KEY = `api/admin/environments/${id}`; | ||||
| 
 | ||||
|     const { data, error } = useSWR<IEnvironment>(FEATURE_CACHE_KEY, fetcher, { | ||||
|         ...options, | ||||
|     }); | ||||
| 
 | ||||
|     const [loading, setLoading] = useState(!error && !data); | ||||
| 
 | ||||
|     const refetch = () => { | ||||
|         mutate(FEATURE_CACHE_KEY); | ||||
|     }; | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setLoading(!error && !data); | ||||
|     }, [data, error]); | ||||
| 
 | ||||
|     return { | ||||
|         environment: data || defaultEnvironment, | ||||
|         error, | ||||
|         loading, | ||||
|         refetch, | ||||
|         FEATURE_CACHE_KEY, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export default useEnvironment; | ||||
							
								
								
									
										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