mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: create edit and creat feature screen
This commit is contained in:
		
							parent
							
								
									53cff04349
								
							
						
					
					
						commit
						8c82a6bceb
					
				| @ -24,7 +24,9 @@ const BreadcrumbNav = () => { | ||||
|                 item !== 'strategies' && | ||||
|                 item !== 'features' && | ||||
|                 item !== 'features2' &&  | ||||
|                 item !== 'create-toggle' | ||||
|                 item !== 'create-toggle'&&  | ||||
|                 item !== 'settings'   | ||||
| 
 | ||||
|         ); | ||||
| 
 | ||||
|     return ( | ||||
|  | ||||
| @ -0,0 +1,103 @@ | ||||
| import FormTemplate from '../../../common/FormTemplate/FormTemplate'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import FeatureForm from '../FeatureForm/FeatureForm'; | ||||
| import useFeatureForm from '../hooks/useFeatureForm'; | ||||
| import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions'; | ||||
| import PermissionButton from '../../../common/PermissionButton/PermissionButton'; | ||||
| 
 | ||||
| const CreateFeature = () => { | ||||
|     /* @ts-ignore */ | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
|     const { | ||||
|         type, | ||||
|         setType, | ||||
|         name, | ||||
|         setName, | ||||
|         project, | ||||
|         setProject, | ||||
|         description, | ||||
|         setDescription, | ||||
|         getTogglePayload, | ||||
|         validateName, | ||||
|         clearErrors, | ||||
|         errors, | ||||
|     } = useFeatureForm(); | ||||
| 
 | ||||
|     const { createFeatureToggle, loading } = useFeatureApi(); | ||||
| 
 | ||||
|     const handleSubmit = async (e: Event) => { | ||||
|         e.preventDefault(); | ||||
|         clearErrors(); | ||||
|         await validateName(name); | ||||
|         const payload = getTogglePayload(); | ||||
|         try { | ||||
|             await createFeatureToggle(project, payload); | ||||
|             history.push(`/projects/${project}/features2/${name}`); | ||||
|             setToastData({ | ||||
|                 title: 'Toggle created successfully', | ||||
|                 text: 'Now you can start using your toggle.', | ||||
|                 confetti: true, | ||||
|                 type: 'success', | ||||
|             }); | ||||
|         } catch (e: any) { | ||||
|             setToastApiError(e.toString()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const formatApiCode = () => { | ||||
|         return `curl --location --request POST '${ | ||||
|             uiConfig.unleashUrl | ||||
|         }/api/admin/projects/${project}/features' \\ | ||||
|     --header 'Authorization: INSERT_API_KEY' \\ | ||||
|     --header 'Content-Type: application/json' \\ | ||||
|     --data-raw '${JSON.stringify(getTogglePayload(), undefined, 2)}'`;
 | ||||
|     }; | ||||
| 
 | ||||
|     const handleCancel = () => { | ||||
|         history.goBack(); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <FormTemplate | ||||
|             loading={loading} | ||||
|             title="Create Feature toggle" | ||||
|             description="Feature toggles support different use cases, each with their own specific needs such as simple static routing or more complex routing. | ||||
|             The feature toggle is disabled when created and you decide when to enable" | ||||
|             documentationLink="https://docs.getunleash.io/" | ||||
|             formatApiCode={formatApiCode} | ||||
|         > | ||||
|             <FeatureForm | ||||
|                 type={type} | ||||
|                 name={name} | ||||
|                 project={project} | ||||
|                 description={description} | ||||
|                 setType={setType} | ||||
|                 setName={setName} | ||||
|                 setProject={setProject} | ||||
|                 setDescription={setDescription} | ||||
|                 errors={errors} | ||||
|                 handleSubmit={handleSubmit} | ||||
|                 handleCancel={handleCancel} | ||||
|                 mode="Create" | ||||
|                 clearErrors={clearErrors} | ||||
|             > | ||||
|                 <PermissionButton | ||||
|                     onClick={handleSubmit} | ||||
|                     permission={CREATE_FEATURE} | ||||
|                     projectId={project} | ||||
|                     type="submit" | ||||
|                 > | ||||
|                     Create toggle | ||||
|                 </PermissionButton> | ||||
|             </FeatureForm> | ||||
|         </FormTemplate> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default CreateFeature; | ||||
| @ -0,0 +1,115 @@ | ||||
| import FormTemplate from '../../../common/FormTemplate/FormTemplate'; | ||||
| import { useHistory, useParams } from 'react-router-dom'; | ||||
| import FeatureForm from '../FeatureForm/FeatureForm'; | ||||
| import useFeatureForm from '../hooks/useFeatureForm'; | ||||
| import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import useFeature from '../../../../hooks/api/getters/useFeature/useFeature'; | ||||
| import { IFeatureViewParams } from '../../../../interfaces/params'; | ||||
| import * as jsonpatch from 'fast-json-patch'; | ||||
| import PermissionButton from '../../../common/PermissionButton/PermissionButton'; | ||||
| import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions'; | ||||
| 
 | ||||
| const EditFeature = () => { | ||||
|     /* @ts-ignore */ | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const history = useHistory(); | ||||
|     const { projectId, featureId } = useParams<IFeatureViewParams>(); | ||||
|     const { patchFeatureToggle, loading } = useFeatureApi(); | ||||
|     const { feature } = useFeature(projectId, featureId); | ||||
| 
 | ||||
|     const { | ||||
|         type, | ||||
|         setType, | ||||
|         name, | ||||
|         setName, | ||||
|         project, | ||||
|         setProject, | ||||
|         description, | ||||
|         setDescription, | ||||
|         getTogglePayload, | ||||
|         clearErrors, | ||||
|         errors, | ||||
|     } = useFeatureForm( | ||||
|         feature?.name, | ||||
|         feature?.type, | ||||
|         feature?.project, | ||||
|         feature?.description | ||||
|     ); | ||||
| 
 | ||||
|     const createPatch = () => { | ||||
|         const comparison = { ...feature, type, description }; | ||||
|         const patch = jsonpatch.compare(feature, comparison); | ||||
|         return patch; | ||||
|     }; | ||||
| 
 | ||||
|     const handleSubmit = async (e: Event) => { | ||||
|         e.preventDefault(); | ||||
|         clearErrors(); | ||||
|         const patch = createPatch(); | ||||
|         try { | ||||
|             await patchFeatureToggle(project, featureId, patch); | ||||
|             history.push(`/projects/${project}/features2/${name}`); | ||||
|             setToastData({ | ||||
|                 title: 'Toggle updated successfully', | ||||
|                 text: 'Now you can start using your toggle.', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|         } catch (e: any) { | ||||
|             setToastApiError(e.toString()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const formatApiCode = () => { | ||||
|         return `curl --location --request PUT '${ | ||||
|             uiConfig.unleashUrl | ||||
|         }/api/admin/projects/${projectId}/features/${featureId}' \\ | ||||
|     --header 'Authorization: INSERT_API_KEY' \\ | ||||
|     --header 'Content-Type: application/json' \\ | ||||
|     --data-raw '${JSON.stringify(getTogglePayload(), undefined, 2)}'`;
 | ||||
|     }; | ||||
| 
 | ||||
|     const handleCancel = () => { | ||||
|         history.goBack(); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <FormTemplate | ||||
|             loading={loading} | ||||
|             title="Create Feature toggle" | ||||
|             description="Feature toggles support different use cases, each with their own specific needs such as simple static routing or more complex routing. | ||||
|             The feature toggle is disabled when created and you decide when to enable" | ||||
|             documentationLink="https://docs.getunleash.io/" | ||||
|             formatApiCode={formatApiCode} | ||||
|         > | ||||
|             <FeatureForm | ||||
|                 type={type} | ||||
|                 name={name} | ||||
|                 project={project} | ||||
|                 description={description} | ||||
|                 setType={setType} | ||||
|                 setName={setName} | ||||
|                 setProject={setProject} | ||||
|                 setDescription={setDescription} | ||||
|                 errors={errors} | ||||
|                 handleSubmit={handleSubmit} | ||||
|                 handleCancel={handleCancel} | ||||
|                 mode="Edit" | ||||
|                 clearErrors={clearErrors} | ||||
|             > | ||||
|                 <PermissionButton | ||||
|                     onClick={handleSubmit} | ||||
|                     permission={UPDATE_FEATURE} | ||||
|                     projectId={project} | ||||
|                     type="submit" | ||||
|                 > | ||||
|                     Edit toggle | ||||
|                 </PermissionButton> | ||||
|             </FeatureForm> | ||||
|         </FormTemplate> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default EditFeature; | ||||
| @ -0,0 +1,61 @@ | ||||
| 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' }, | ||||
|     selectInput: { | ||||
|         marginBottom: '1rem', | ||||
|         minWidth: '400px', | ||||
|         [theme.breakpoints.down(600)]: { | ||||
|             minWidth: '379px', | ||||
|         }, | ||||
|     }, | ||||
|     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', | ||||
|     }, | ||||
|     typeDescription: { | ||||
|         //@ts-ignore
 | ||||
|         fontSize: theme.fontSizes.smallBody, | ||||
|         color: theme.palette.grey[600], | ||||
|         top: '-13px', | ||||
|         position: 'relative', | ||||
|     }, | ||||
|     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,145 @@ | ||||
| import { | ||||
|     CREATE_FEATURE, | ||||
|     UPDATE_FEATURE, | ||||
| } from '../../../providers/AccessProvider/permissions'; | ||||
| import Input from '../../../common/Input/Input'; | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { useStyles } from './FeatureForm.styles'; | ||||
| import FeatureTypeSelect from '../../FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect'; | ||||
| import { CF_DESC_ID, CF_TYPE_ID } from '../../../../testIds'; | ||||
| import useFeatureTypes from '../../../../hooks/api/getters/useFeatureTypes/useFeatureTypes'; | ||||
| import { KeyboardArrowDownOutlined } from '@material-ui/icons'; | ||||
| import { useContext } from 'react'; | ||||
| import useUser from '../../../../hooks/api/getters/useUser/useUser'; | ||||
| import { projectFilterGenerator } from '../../../../utils/project-filter-generator'; | ||||
| import FeatureProjectSelect from '../../FeatureView2/FeatureSettings/FeatureSettingsProject/FeatureProjectSelect/FeatureProjectSelect'; | ||||
| import AccessContext from '../../../../contexts/AccessContext'; | ||||
| import ConditionallyRender from '../../../common/ConditionallyRender'; | ||||
| import { trim } from '../../../common/util'; | ||||
| 
 | ||||
| interface IFeatureToggleForm { | ||||
|     type: string; | ||||
|     name: string; | ||||
|     description: string; | ||||
|     project: string; | ||||
|     setType: React.Dispatch<React.SetStateAction<string>>; | ||||
|     setName: React.Dispatch<React.SetStateAction<string>>; | ||||
|     setDescription: React.Dispatch<React.SetStateAction<string>>; | ||||
|     setProject: React.Dispatch<React.SetStateAction<string>>; | ||||
|     handleSubmit: (e: any) => void; | ||||
|     handleCancel: () => void; | ||||
|     errors: { [key: string]: string }; | ||||
|     mode: string; | ||||
|     clearErrors: () => void; | ||||
| } | ||||
| 
 | ||||
| const FeatureForm: React.FC<IFeatureToggleForm> = ({ | ||||
|     children, | ||||
|     type, | ||||
|     name, | ||||
|     description, | ||||
|     project, | ||||
|     setType, | ||||
|     setName, | ||||
|     setDescription, | ||||
|     setProject, | ||||
|     handleSubmit, | ||||
|     handleCancel, | ||||
|     errors, | ||||
|     mode, | ||||
|     clearErrors, | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
|     const { featureTypes } = useFeatureTypes(); | ||||
|     const { permissions } = useUser(); | ||||
|     const editable = hasAccess(UPDATE_FEATURE, project); | ||||
| 
 | ||||
|     const renderToggleDescription = () => { | ||||
|         return featureTypes.find(toggle => toggle.id === type)?.description; | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <form onSubmit={handleSubmit} className={styles.form}> | ||||
|             <div className={styles.container}> | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     What kind of feature toggle do you want to create? | ||||
|                 </p> | ||||
|                 <FeatureTypeSelect | ||||
|                     value={type} | ||||
|                     onChange={e => setType(e.target.value)} | ||||
|                     label={'Toggle type'} | ||||
|                     id="feature-type-select" | ||||
|                     editable | ||||
|                     inputProps={{ | ||||
|                         'data-test': CF_TYPE_ID, | ||||
|                     }} | ||||
|                     IconComponent={KeyboardArrowDownOutlined} | ||||
|                     className={styles.selectInput} | ||||
|                 /> | ||||
|                 <p className={styles.typeDescription}> | ||||
|                     {renderToggleDescription()} | ||||
|                 </p> | ||||
| 
 | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     What would you like to call your toggle? | ||||
|                 </p> | ||||
|                 <Input | ||||
|                     disabled={mode === 'Edit'} | ||||
|                     className={styles.input} | ||||
|                     label="Name" | ||||
|                     error={Boolean(errors.name)} | ||||
|                     errorText={errors.name} | ||||
|                     onFocus={() => clearErrors()} | ||||
|                     value={name} | ||||
|                     onChange={e => setName(trim(e.target.value))} | ||||
|                 /> | ||||
|                 <ConditionallyRender | ||||
|                     condition={editable} | ||||
|                     show={ | ||||
|                         <p className={styles.inputDescription}> | ||||
|                             In which project do you want to save the toggle? | ||||
|                         </p> | ||||
|                     } | ||||
|                 /> | ||||
|                 <FeatureProjectSelect | ||||
|                     value={project} | ||||
|                     onChange={e => setProject(e.target.value)} | ||||
|                     enabled={editable} | ||||
|                     label="Project" | ||||
|                     filter={projectFilterGenerator( | ||||
|                         { permissions }, | ||||
|                         CREATE_FEATURE | ||||
|                     )} | ||||
|                     IconComponent={KeyboardArrowDownOutlined} | ||||
|                     className={styles.selectInput} | ||||
|                 /> | ||||
| 
 | ||||
|                 <p className={styles.inputDescription}> | ||||
|                     How would you describe your feature toggle? | ||||
|                 </p> | ||||
|                 <Input | ||||
|                     className={styles.input} | ||||
|                     multiline | ||||
|                     rows={4} | ||||
|                     label="Description" | ||||
|                     placeholder="A short description of the feature toggle" | ||||
|                     value={description} | ||||
|                     inputProps={{ | ||||
|                         'data-test': CF_DESC_ID, | ||||
|                     }} | ||||
|                     onChange={e => setDescription(e.target.value)} | ||||
|                 /> | ||||
|             </div> | ||||
| 
 | ||||
|             <div className={styles.buttonContainer}> | ||||
|                 <Button onClick={handleCancel} className={styles.cancelButton}> | ||||
|                     Cancel | ||||
|                 </Button> | ||||
|                 {children} | ||||
|             </div> | ||||
|         </form> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default FeatureForm; | ||||
| @ -0,0 +1,90 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { useParams } from 'react-router-dom'; | ||||
| import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import useQueryParams from '../../../../hooks/useQueryParams'; | ||||
| import { IFeatureViewParams } from '../../../../interfaces/params'; | ||||
| 
 | ||||
| const useFeatureForm = ( | ||||
|     initialName = '', | ||||
|     initialType = 'release', | ||||
|     initialProject = 'default', | ||||
|     initialDescription = '' | ||||
| ) => { | ||||
|     const { projectId } = useParams<IFeatureViewParams>(); | ||||
|     const params = useQueryParams(); | ||||
|     const { validateFeatureToggleName } = useFeatureApi(); | ||||
|     const toggleQueryName = params.get('name'); | ||||
|     const [type, setType] = useState(initialType); | ||||
|     const [name, setName] = useState(toggleQueryName || initialName); | ||||
|     const [project, setProject] = useState(projectId || initialProject); | ||||
|     const [description, setDescription] = useState(initialDescription); | ||||
|     const [errors, setErrors] = useState({}); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setType(initialType); | ||||
|     }, [initialType]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (!toggleQueryName) setName(initialName); | ||||
|         else setName(toggleQueryName); | ||||
|     }, [initialName, toggleQueryName]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (!projectId) setProject(initialProject); | ||||
|         else setProject(projectId); | ||||
|     }, [initialProject, projectId]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setDescription(initialDescription); | ||||
|     }, [initialDescription]); | ||||
| 
 | ||||
|     const getTogglePayload = () => { | ||||
|         return { | ||||
|             type: type, | ||||
|             name: name, | ||||
|             projectId: project, | ||||
|             description: description, | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     const validateName = async (name: string) => { | ||||
|         if (name.length === 0) { | ||||
|             setErrors(prev => ({ ...prev, name: 'Name can not be empty.' })); | ||||
|             return false; | ||||
|         } | ||||
|         if (name.length > 0) { | ||||
|             try { | ||||
|                 await validateFeatureToggleName(name); | ||||
|             } catch (err: any) { | ||||
|                 setErrors(prev => ({ | ||||
|                     ...prev, | ||||
|                     name: | ||||
|                         err && err.message | ||||
|                             ? err.message | ||||
|                             : 'Could not check name', | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const clearErrors = () => { | ||||
|         setErrors({}); | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         type, | ||||
|         setType, | ||||
|         name, | ||||
|         setName, | ||||
|         project, | ||||
|         setProject, | ||||
|         description, | ||||
|         setDescription, | ||||
|         getTogglePayload, | ||||
|         validateName, | ||||
|         clearErrors, | ||||
|         errors, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export default useFeatureForm; | ||||
| @ -31,13 +31,13 @@ import RedirectFeatureViewPage from '../../page/features/redirect'; | ||||
| import RedirectArchive from '../feature/RedirectArchive/RedirectArchive'; | ||||
| import EnvironmentList from '../environments/EnvironmentList/EnvironmentList'; | ||||
| 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'; | ||||
| <<<<<<< HEAD | ||||
| import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment'; | ||||
| import EditEnvironment from '../environments/EditEnvironment/EditEnvironment'; | ||||
| import CreateContext from '../context/CreateContext/CreateContext'; | ||||
| @ -46,6 +46,10 @@ import EditTagType from '../tagTypes/EditTagType/EditTagType'; | ||||
| import CreateTagType from '../tagTypes/CreateTagType/CreateTagType'; | ||||
| import EditProject from '../project/Project/EditProject/EditProject'; | ||||
| import CreateProject from '../project/Project/CreateProject/CreateProject'; | ||||
| ======= | ||||
| import CreateFeature from '../feature/CreateFeature/CreateFeature/CreateFeature'; | ||||
| import EditFeature from '../feature/CreateFeature/EditFeature/EditFeature'; | ||||
| >>>>>>> 937e090b (feat: create edit and creat feature screen) | ||||
| 
 | ||||
| export const routes = [ | ||||
|     // Project
 | ||||
| @ -95,6 +99,15 @@ export const routes = [ | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:projectId/features2/:featureId/settings', | ||||
|         parent: '/projects', | ||||
|         title: 'Edit Feature', | ||||
|         component: EditFeature, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/projects/:projectId/features2/:featureId', | ||||
|         parent: '/projects', | ||||
| @ -118,7 +131,7 @@ export const routes = [ | ||||
|         path: '/projects/:projectId/create-toggle', | ||||
|         parent: '/projects/:id/features', | ||||
|         title: 'Create feature toggle', | ||||
|         component: FeatureCreate, | ||||
|         component: CreateFeature, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: {}, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user