mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Refactor: convert jsx files to typescript (#881)
* refactor: convert remaining js files to typescript * refactor: conditionally render remove index * refactor: dialog component to tsx * refactor: migrate some files from jsx to tsx * refactor: convert dropdown element to tsx * refactor: feature toggle list to tsx * refactor: update context name in use overrides * refactor: variant overrides to tsx refactor: remove unused strategy constraint file * fix: tsx imports * fix: update refectored components after rebase * refactor: rename report list files to tsx * fix: project health list types * refactor: addon form - add types * refactor: copy feature component types * fix: projects toggle style after tsx refactor * refactor: update ts types from openapi * fix: ts refactor changes after review * fix: header title prop * fix: update after PR comments * add test to useoverrides hook * fix conditionally render time ago * fix: toggle list empty tooltip * fix: remove unused variable * remove unused variable * fix: remove faulty snapshot
This commit is contained in:
		
							parent
							
								
									00341c6d67
								
							
						
					
					
						commit
						23a874d051
					
				| @ -1,4 +1,6 @@ | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { createElement } from 'react'; | ||||
| import { Redirect, Route, Switch } from 'react-router-dom'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { FeedbackNPS } from 'component/feedback/FeedbackNPS/FeedbackNPS'; | ||||
| import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker'; | ||||
| import Loader from 'component/common/Loader/Loader'; | ||||
| @ -6,12 +8,12 @@ import NotFound from 'component/common/NotFound/NotFound'; | ||||
| import ProtectedRoute from 'component/common/ProtectedRoute/ProtectedRoute'; | ||||
| import SWRProvider from 'component/providers/SWRProvider/SWRProvider'; | ||||
| import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer'; | ||||
| import styles from 'component/styles.module.scss'; | ||||
| import { Redirect, Route, Switch } from 'react-router-dom'; | ||||
| import { routes } from 'component/menu/routes'; | ||||
| import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails'; | ||||
| import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; | ||||
| import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect'; | ||||
| import { IRoute } from 'interfaces/route'; | ||||
| import styles from 'component/styles.module.scss'; | ||||
| 
 | ||||
| export const App = () => { | ||||
|     const { authDetails } = useAuthDetails(); | ||||
| @ -23,8 +25,7 @@ export const App = () => { | ||||
|         return !isLoggedIn; | ||||
|     }; | ||||
| 
 | ||||
|     // Change this to IRoute once snags with HashRouter and TS is worked out
 | ||||
|     const renderRoute = (route: any) => { | ||||
|     const renderRoute = (route: IRoute) => { | ||||
|         if (route.type === 'protected') { | ||||
|             const unauthorized = isUnauthorized(); | ||||
|             return ( | ||||
| @ -40,13 +41,7 @@ export const App = () => { | ||||
|             <Route | ||||
|                 key={route.path} | ||||
|                 path={route.path} | ||||
|                 render={props => ( | ||||
|                     <route.component | ||||
|                         {...props} | ||||
|                         isUnauthorized={isUnauthorized} | ||||
|                         authDetails={authDetails} | ||||
|                     /> | ||||
|                 )} | ||||
|                 render={() => createElement(route.component, {}, null)} | ||||
|             /> | ||||
|         ); | ||||
|     }; | ||||
| @ -65,8 +60,9 @@ export const App = () => { | ||||
|                                     exact | ||||
|                                     path="/" | ||||
|                                     unauthorized={isUnauthorized()} | ||||
|                                     component={Redirect} | ||||
|                                     renderProps={{ to: '/features' }} | ||||
|                                     component={() => ( | ||||
|                                         <Redirect to="/features" /> | ||||
|                                     )} | ||||
|                                 /> | ||||
|                                 {routes.map(renderRoute)} | ||||
|                                 <Route path="/404" component={NotFound} /> | ||||
|  | ||||
| @ -2,7 +2,7 @@ import classnames from 'classnames'; | ||||
| import { Paper } from '@material-ui/core'; | ||||
| import CheckIcon from '@material-ui/icons/Check'; | ||||
| import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import styles from './ReportCard.module.scss'; | ||||
| import ReactTimeAgo from 'react-timeago'; | ||||
| import { IProjectHealthReport } from 'interfaces/project'; | ||||
|  | ||||
| @ -1,25 +1,34 @@ | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { useState, useEffect, VFC } from 'react'; | ||||
| import { Paper, MenuItem } from '@material-ui/core'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ReportToggleListItem from './ReportToggleListItem/ReportToggleListItem'; | ||||
| import ReportToggleListHeader from './ReportToggleListHeader/ReportToggleListHeader'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useFeaturesSort } from 'hooks/useFeaturesSort'; | ||||
| import { IFeatureToggleListItem } from 'interfaces/featureToggle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import DropdownMenu from 'component/common/DropdownMenu/DropdownMenu'; | ||||
| import { | ||||
|     getObjectProperties, | ||||
|     getCheckedState, | ||||
|     applyCheckedToFeatures, | ||||
| } from '../utils'; | ||||
| import { ReportToggleListItem } from './ReportToggleListItem/ReportToggleListItem'; | ||||
| import { ReportToggleListHeader } from './ReportToggleListHeader/ReportToggleListHeader'; | ||||
| import { useStyles } from './ReportToggleList.styles'; | ||||
| import { useFeaturesSort } from 'hooks/useFeaturesSort'; | ||||
| 
 | ||||
| /* FLAG TO TOGGLE UNFINISHED BULK ACTIONS FEATURE */ | ||||
| const BULK_ACTIONS_ON = false; | ||||
| 
 | ||||
| const ReportToggleList = ({ features, selectedProject }) => { | ||||
| interface IReportToggleListProps { | ||||
|     selectedProject: string; | ||||
|     features: IFeatureToggleListItem[]; | ||||
| } | ||||
| 
 | ||||
| export const ReportToggleList: VFC<IReportToggleListProps> = ({ | ||||
|     features, | ||||
|     selectedProject, | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
|     const [checkAll, setCheckAll] = useState(false); | ||||
|     const [localFeatures, setFeatures] = useState([]); | ||||
|     const [localFeatures, setFeatures] = useState<IFeatureToggleListItem[]>([]); | ||||
|     // @ts-expect-error
 | ||||
|     const { setSort, sorted } = useFeaturesSort(localFeatures); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
| @ -32,10 +41,12 @@ const ReportToggleList = ({ features, selectedProject }) => { | ||||
|                 'stale', | ||||
|                 'type' | ||||
|             ), | ||||
|             // @ts-expect-error
 | ||||
|             checked: getCheckedState(feature.name, features), | ||||
|             setFeatures, | ||||
|         })); | ||||
| 
 | ||||
|         // @ts-expect-error
 | ||||
|         setFeatures(formattedFeatures); | ||||
|     }, [features, selectedProject]); | ||||
| 
 | ||||
| @ -50,6 +61,7 @@ const ReportToggleList = ({ features, selectedProject }) => { | ||||
| 
 | ||||
|     const renderListRows = () => | ||||
|         sorted.map(feature => ( | ||||
|             // @ts-expect-error
 | ||||
|             <ReportToggleListItem | ||||
|                 key={feature.name} | ||||
|                 {...feature} | ||||
| @ -85,6 +97,7 @@ const ReportToggleList = ({ features, selectedProject }) => { | ||||
|                     <ReportToggleListHeader | ||||
|                         handleCheckAll={handleCheckAll} | ||||
|                         checkAll={checkAll} | ||||
|                         // @ts-expect-error
 | ||||
|                         setSort={setSort} | ||||
|                         bulkActionsOn={BULK_ACTIONS_ON} | ||||
|                     /> | ||||
| @ -95,10 +108,3 @@ const ReportToggleList = ({ features, selectedProject }) => { | ||||
|         </Paper> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ReportToggleList.propTypes = { | ||||
|     selectedProject: PropTypes.string.isRequired, | ||||
|     features: PropTypes.array.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default ReportToggleList; | ||||
| @ -1,25 +1,27 @@ | ||||
| import React from 'react'; | ||||
| import { Dispatch, SetStateAction, VFC } from 'react'; | ||||
| import { Checkbox } from '@material-ui/core'; | ||||
| import UnfoldMoreOutlinedIcon from '@material-ui/icons/UnfoldMoreOutlined'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     NAME, | ||||
|     LAST_SEEN, | ||||
|     CREATED, | ||||
|     EXPIRED, | ||||
|     STATUS, | ||||
| } from 'component/Reporting/constants'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ReportingSortType } from 'component/Reporting/constants'; | ||||
| import { useStyles } from '../ReportToggleList.styles'; | ||||
| 
 | ||||
| const ReportToggleListHeader = ({ | ||||
| interface IReportToggleListHeaderProps { | ||||
|     checkAll: boolean; | ||||
|     setSort: Dispatch< | ||||
|         SetStateAction<{ type: ReportingSortType; desc?: boolean }> | ||||
|     >; | ||||
|     bulkActionsOn: boolean; | ||||
|     handleCheckAll: () => void; | ||||
| } | ||||
| 
 | ||||
| export const ReportToggleListHeader: VFC<IReportToggleListHeaderProps> = ({ | ||||
|     handleCheckAll, | ||||
|     checkAll, | ||||
|     setSort, | ||||
|     bulkActionsOn, | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
|     const handleSort = type => { | ||||
|     const handleSort = (type: ReportingSortType) => { | ||||
|         setSort(prev => ({ | ||||
|             type, | ||||
|             desc: !prev.desc, | ||||
| @ -47,7 +49,7 @@ const ReportToggleListHeader = ({ | ||||
|                     role="button" | ||||
|                     tabIndex={0} | ||||
|                     style={{ width: '150px' }} | ||||
|                     onClick={() => handleSort(NAME)} | ||||
|                     onClick={() => handleSort('name')} | ||||
|                 > | ||||
|                     Name | ||||
|                     <UnfoldMoreOutlinedIcon className={styles.sortIcon} /> | ||||
| @ -56,7 +58,7 @@ const ReportToggleListHeader = ({ | ||||
|                     role="button" | ||||
|                     className={styles.hideColumnLastSeen} | ||||
|                     tabIndex={0} | ||||
|                     onClick={() => handleSort(LAST_SEEN)} | ||||
|                     onClick={() => handleSort('last-seen')} | ||||
|                 > | ||||
|                     Last seen | ||||
|                     <UnfoldMoreOutlinedIcon className={styles.sortIcon} /> | ||||
| @ -65,7 +67,7 @@ const ReportToggleListHeader = ({ | ||||
|                     role="button" | ||||
|                     tabIndex={0} | ||||
|                     className={styles.hideColumn} | ||||
|                     onClick={() => handleSort(CREATED)} | ||||
|                     onClick={() => handleSort('created')} | ||||
|                 > | ||||
|                     Created | ||||
|                     <UnfoldMoreOutlinedIcon className={styles.sortIcon} /> | ||||
| @ -74,7 +76,7 @@ const ReportToggleListHeader = ({ | ||||
|                     role="button" | ||||
|                     tabIndex={0} | ||||
|                     className={styles.hideColumn} | ||||
|                     onClick={() => handleSort(EXPIRED)} | ||||
|                     onClick={() => handleSort('expired')} | ||||
|                 > | ||||
|                     Expired | ||||
|                     <UnfoldMoreOutlinedIcon className={styles.sortIcon} /> | ||||
| @ -83,7 +85,7 @@ const ReportToggleListHeader = ({ | ||||
|                     role="button" | ||||
|                     tabIndex={0} | ||||
|                     className={styles.hideColumnStatus} | ||||
|                     onClick={() => handleSort(STATUS)} | ||||
|                     onClick={() => handleSort('status')} | ||||
|                 > | ||||
|                     Status | ||||
|                     <UnfoldMoreOutlinedIcon className={styles.sortIcon} /> | ||||
| @ -91,7 +93,7 @@ const ReportToggleListHeader = ({ | ||||
|                 <th | ||||
|                     role="button" | ||||
|                     tabIndex={0} | ||||
|                     onClick={() => handleSort(EXPIRED)} | ||||
|                     onClick={() => handleSort('expired')} | ||||
|                 > | ||||
|                     Report | ||||
|                     <UnfoldMoreOutlinedIcon className={styles.sortIcon} /> | ||||
| @ -100,12 +102,3 @@ const ReportToggleListHeader = ({ | ||||
|         </thead> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ReportToggleListHeader.propTypes = { | ||||
|     checkAll: PropTypes.bool.isRequired, | ||||
|     setSort: PropTypes.func.isRequired, | ||||
|     bulkActionsOn: PropTypes.bool.isRequired, | ||||
|     handleCheckAll: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default ReportToggleListHeader; | ||||
| @ -1,159 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import classnames from 'classnames'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { Checkbox } from '@material-ui/core'; | ||||
| import CheckIcon from '@material-ui/icons/Check'; | ||||
| import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus'; | ||||
| import { | ||||
|     pluralize, | ||||
|     getDates, | ||||
|     expired, | ||||
|     toggleExpiryByTypeMap, | ||||
|     getDiffInDays, | ||||
| } from 'component/Reporting/utils'; | ||||
| import { KILLSWITCH, PERMISSION } from 'constants/featureToggleTypes'; | ||||
| import { useStyles } from '../ReportToggleList.styles'; | ||||
| import { getTogglePath } from 'utils/routePathHelpers'; | ||||
| 
 | ||||
| const ReportToggleListItem = ({ | ||||
|     name, | ||||
|     stale, | ||||
|     lastSeenAt, | ||||
|     createdAt, | ||||
|     project, | ||||
|     type, | ||||
|     checked, | ||||
|     bulkActionsOn, | ||||
|     setFeatures, | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
|     const nameMatches = feature => feature.name === name; | ||||
| 
 | ||||
|     const handleChange = () => { | ||||
|         setFeatures(prevState => { | ||||
|             const newState = [...prevState]; | ||||
| 
 | ||||
|             return newState.map(feature => { | ||||
|                 if (nameMatches(feature)) { | ||||
|                     return { ...feature, checked: !feature.checked }; | ||||
|                 } | ||||
|                 return feature; | ||||
|             }); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const formatCreatedAt = () => { | ||||
|         const [date, now] = getDates(createdAt); | ||||
| 
 | ||||
|         const diff = getDiffInDays(date, now); | ||||
|         if (diff === 0) return '1 day'; | ||||
| 
 | ||||
|         const formatted = pluralize(diff, 'day'); | ||||
| 
 | ||||
|         return `${formatted} ago`; | ||||
|     }; | ||||
| 
 | ||||
|     const formatExpiredAt = () => { | ||||
|         if (type === KILLSWITCH || type === PERMISSION) { | ||||
|             return 'N/A'; | ||||
|         } | ||||
| 
 | ||||
|         const [date, now] = getDates(createdAt); | ||||
|         const diff = getDiffInDays(date, now); | ||||
| 
 | ||||
|         if (expired(diff, type)) { | ||||
|             const result = diff - toggleExpiryByTypeMap[type]; | ||||
|             if (result === 0) return '1 day'; | ||||
| 
 | ||||
|             return pluralize(result, 'day'); | ||||
|         } | ||||
|         return 'N/A'; | ||||
|     }; | ||||
| 
 | ||||
|     const formatLastSeenAt = () => { | ||||
|         return ( | ||||
|             <FeatureStatus lastSeenAt={lastSeenAt} tooltipPlacement="bottom" /> | ||||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|     const renderStatus = (icon, text) => ( | ||||
|         <span className={styles.reportStatus}> | ||||
|             {icon} | ||||
|             {text} | ||||
|         </span> | ||||
|     ); | ||||
| 
 | ||||
|     const formatReportStatus = () => { | ||||
|         if (type === KILLSWITCH || type === PERMISSION) { | ||||
|             return renderStatus( | ||||
|                 <CheckIcon className={styles.reportIcon} />, | ||||
|                 'Healthy' | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         const [date, now] = getDates(createdAt); | ||||
|         const diff = getDiffInDays(date, now); | ||||
| 
 | ||||
|         if (expired(diff, type)) { | ||||
|             return renderStatus( | ||||
|                 <ReportProblemOutlinedIcon className={styles.reportIcon} />, | ||||
|                 'Potentially stale' | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return renderStatus( | ||||
|             <CheckIcon className={styles.reportIcon} />, | ||||
|             'Healthy' | ||||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|     const statusClasses = classnames(styles.active, styles.hideColumnStatus, { | ||||
|         [styles.stale]: stale, | ||||
|     }); | ||||
| 
 | ||||
|     return ( | ||||
|         <tr className={styles.tableRow}> | ||||
|             <ConditionallyRender | ||||
|                 condition={bulkActionsOn} | ||||
|                 show={ | ||||
|                     <td> | ||||
|                         <Checkbox | ||||
|                             checked={checked} | ||||
|                             value={checked} | ||||
|                             onChange={handleChange} | ||||
|                             className={styles.checkbox} | ||||
|                         /> | ||||
|                     </td> | ||||
|                 } | ||||
|             /> | ||||
|             <td> | ||||
|                 <Link to={getTogglePath(project, name)} className={styles.link}> | ||||
|                     {name} | ||||
|                 </Link> | ||||
|             </td> | ||||
|             <td className={styles.hideColumnLastSeen}>{formatLastSeenAt()}</td> | ||||
|             <td className={styles.hideColumn}>{formatCreatedAt()}</td> | ||||
|             <td className={`${styles.expired} ${styles.hideColumn}`}> | ||||
|                 {formatExpiredAt()} | ||||
|             </td> | ||||
|             <td className={statusClasses}>{stale ? 'Stale' : 'Active'}</td> | ||||
|             <td>{formatReportStatus()}</td> | ||||
|         </tr> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ReportToggleListItem.propTypes = { | ||||
|     name: PropTypes.string.isRequired, | ||||
|     stale: PropTypes.bool.isRequired, | ||||
|     lastSeenAt: PropTypes.string, | ||||
|     createdAt: PropTypes.string.isRequired, | ||||
|     type: PropTypes.string.isRequired, | ||||
|     checked: PropTypes.bool.isRequired, | ||||
|     bulkActionsOn: PropTypes.bool.isRequired, | ||||
|     setFeatures: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default React.memo(ReportToggleListItem); | ||||
| @ -0,0 +1,173 @@ | ||||
| import { memo, ReactNode } from 'react'; | ||||
| import classnames from 'classnames'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { Checkbox } from '@material-ui/core'; | ||||
| import CheckIcon from '@material-ui/icons/Check'; | ||||
| import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus'; | ||||
| import { | ||||
|     pluralize, | ||||
|     getDates, | ||||
|     expired, | ||||
|     toggleExpiryByTypeMap, | ||||
|     getDiffInDays, | ||||
| } from 'component/Reporting/utils'; | ||||
| import { KILLSWITCH, PERMISSION } from 'constants/featureToggleTypes'; | ||||
| import { useStyles } from '../ReportToggleList.styles'; | ||||
| import { getTogglePath } from 'utils/routePathHelpers'; | ||||
| 
 | ||||
| interface IReportToggleListItemProps { | ||||
|     name: string; | ||||
|     stale: boolean; | ||||
|     project: string; | ||||
|     lastSeenAt?: string; | ||||
|     createdAt: string; | ||||
|     type: string; | ||||
|     checked: boolean; | ||||
|     bulkActionsOn: boolean; | ||||
|     setFeatures: () => void; | ||||
| } | ||||
| 
 | ||||
| export const ReportToggleListItem = memo<IReportToggleListItemProps>( | ||||
|     ({ | ||||
|         name, | ||||
|         stale, | ||||
|         lastSeenAt, | ||||
|         createdAt, | ||||
|         project, | ||||
|         type, | ||||
|         checked, | ||||
|         bulkActionsOn, | ||||
|         setFeatures, | ||||
|     }) => { | ||||
|         const styles = useStyles(); | ||||
|         const nameMatches = (feature: { name: string }) => | ||||
|             feature.name === name; | ||||
| 
 | ||||
|         const handleChange = () => { | ||||
|             // @ts-expect-error
 | ||||
|             setFeatures(prevState => { | ||||
|                 const newState = [...prevState]; | ||||
| 
 | ||||
|                 return newState.map(feature => { | ||||
|                     if (nameMatches(feature)) { | ||||
|                         return { ...feature, checked: !feature.checked }; | ||||
|                     } | ||||
|                     return feature; | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
| 
 | ||||
|         const formatCreatedAt = () => { | ||||
|             const [date, now] = getDates(createdAt); | ||||
| 
 | ||||
|             const diff = getDiffInDays(date, now); | ||||
|             if (diff === 0) return '1 day'; | ||||
| 
 | ||||
|             const formatted = pluralize(diff, 'day'); | ||||
| 
 | ||||
|             return `${formatted} ago`; | ||||
|         }; | ||||
| 
 | ||||
|         const formatExpiredAt = () => { | ||||
|             if (type === KILLSWITCH || type === PERMISSION) { | ||||
|                 return 'N/A'; | ||||
|             } | ||||
| 
 | ||||
|             const [date, now] = getDates(createdAt); | ||||
|             const diff = getDiffInDays(date, now); | ||||
| 
 | ||||
|             if (expired(diff, type)) { | ||||
|                 const result = diff - toggleExpiryByTypeMap[type]; | ||||
|                 if (result === 0) return '1 day'; | ||||
| 
 | ||||
|                 return pluralize(result, 'day'); | ||||
|             } | ||||
|             return 'N/A'; | ||||
|         }; | ||||
| 
 | ||||
|         const formatLastSeenAt = () => { | ||||
|             return ( | ||||
|                 <FeatureStatus | ||||
|                     lastSeenAt={lastSeenAt} | ||||
|                     tooltipPlacement="bottom" | ||||
|                 /> | ||||
|             ); | ||||
|         }; | ||||
| 
 | ||||
|         const renderStatus = (icon: ReactNode, text: ReactNode) => ( | ||||
|             <span className={styles.reportStatus}> | ||||
|                 {icon} | ||||
|                 {text} | ||||
|             </span> | ||||
|         ); | ||||
| 
 | ||||
|         const formatReportStatus = () => { | ||||
|             if (type === KILLSWITCH || type === PERMISSION) { | ||||
|                 return renderStatus( | ||||
|                     <CheckIcon className={styles.reportIcon} />, | ||||
|                     'Healthy' | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             const [date, now] = getDates(createdAt); | ||||
|             const diff = getDiffInDays(date, now); | ||||
| 
 | ||||
|             if (expired(diff, type)) { | ||||
|                 return renderStatus( | ||||
|                     <ReportProblemOutlinedIcon className={styles.reportIcon} />, | ||||
|                     'Potentially stale' | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             return renderStatus( | ||||
|                 <CheckIcon className={styles.reportIcon} />, | ||||
|                 'Healthy' | ||||
|             ); | ||||
|         }; | ||||
| 
 | ||||
|         const statusClasses = classnames( | ||||
|             styles.active, | ||||
|             styles.hideColumnStatus, | ||||
|             { | ||||
|                 [styles.stale]: stale, | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         return ( | ||||
|             <tr className={styles.tableRow}> | ||||
|                 <ConditionallyRender | ||||
|                     condition={bulkActionsOn} | ||||
|                     show={ | ||||
|                         <td> | ||||
|                             <Checkbox | ||||
|                                 checked={checked} | ||||
|                                 value={checked} | ||||
|                                 onChange={handleChange} | ||||
|                                 className={styles.checkbox} | ||||
|                             /> | ||||
|                         </td> | ||||
|                     } | ||||
|                 /> | ||||
|                 <td> | ||||
|                     <Link | ||||
|                         to={getTogglePath(project, name)} | ||||
|                         className={styles.link} | ||||
|                     > | ||||
|                         {name} | ||||
|                     </Link> | ||||
|                 </td> | ||||
|                 <td className={styles.hideColumnLastSeen}> | ||||
|                     {formatLastSeenAt()} | ||||
|                 </td> | ||||
|                 <td className={styles.hideColumn}>{formatCreatedAt()}</td> | ||||
|                 <td className={`${styles.expired} ${styles.hideColumn}`}> | ||||
|                     {formatExpiredAt()} | ||||
|                 </td> | ||||
|                 <td className={statusClasses}>{stale ? 'Stale' : 'Active'}</td> | ||||
|                 <td>{formatReportStatus()}</td> | ||||
|             </tr> | ||||
|         ); | ||||
|     } | ||||
| ); | ||||
| @ -1,11 +1,7 @@ | ||||
| /* SORT TYPES */ | ||||
| export const NAME = 'name'; | ||||
| export const LAST_SEEN = 'last-seen'; | ||||
| export const CREATED = 'created'; | ||||
| export const EXPIRED = 'expired'; | ||||
| export const STATUS = 'status'; | ||||
| export const REPORT = 'report'; | ||||
| 
 | ||||
| /* DAYS */ | ||||
| export const FOURTYDAYS = 40; | ||||
| export const SEVENDAYS = 7; | ||||
| export type ReportingSortType = | ||||
|     | 'name' | ||||
|     | 'last-seen' | ||||
|     | 'created' | ||||
|     | 'expired' | ||||
|     | 'status' | ||||
|     | 'report'; | ||||
|  | ||||
| @ -3,13 +3,15 @@ import differenceInDays from 'date-fns/differenceInDays'; | ||||
| 
 | ||||
| import { EXPERIMENT, OPERATIONAL, RELEASE } from 'constants/featureToggleTypes'; | ||||
| 
 | ||||
| import { FOURTYDAYS, SEVENDAYS } from './constants'; | ||||
| import { IFeatureToggleListItem } from 'interfaces/featureToggle'; | ||||
| 
 | ||||
| const FORTY_DAYS = 40; | ||||
| const SEVEN_DAYS = 7; | ||||
| 
 | ||||
| export const toggleExpiryByTypeMap: Record<string, number> = { | ||||
|     [EXPERIMENT]: FOURTYDAYS, | ||||
|     [RELEASE]: FOURTYDAYS, | ||||
|     [OPERATIONAL]: SEVENDAYS, | ||||
|     [EXPERIMENT]: FORTY_DAYS, | ||||
|     [RELEASE]: FORTY_DAYS, | ||||
|     [OPERATIONAL]: SEVEN_DAYS, | ||||
| }; | ||||
| 
 | ||||
| export interface IFeatureToggleListItemCheck extends IFeatureToggleListItem { | ||||
|  | ||||
| @ -5,12 +5,12 @@ import { styles as commonStyles } from 'component/common'; | ||||
| import { IAddonProvider } from 'interfaces/addons'; | ||||
| 
 | ||||
| interface IAddonProps { | ||||
|     provider: IAddonProvider; | ||||
|     provider?: IAddonProvider; | ||||
|     checkedEvents: string[]; | ||||
|     setEventValue: ( | ||||
|         name: string | ||||
|     ) => (event: React.ChangeEvent<HTMLInputElement>) => void; | ||||
|     error: Record<string, string>; | ||||
|     error?: string; | ||||
| } | ||||
| 
 | ||||
| export const AddonEvents = ({ | ||||
|  | ||||
| @ -1,8 +1,17 @@ | ||||
| import { useState, useEffect } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { | ||||
|     useState, | ||||
|     useEffect, | ||||
|     ChangeEvent, | ||||
|     VFC, | ||||
|     ChangeEventHandler, | ||||
|     FormEventHandler, | ||||
|     MouseEventHandler, | ||||
| } from 'react'; | ||||
| import { TextField, FormControlLabel, Switch, Button } from '@material-ui/core'; | ||||
| import produce from 'immer'; | ||||
| import { styles as commonStyles } from 'component/common'; | ||||
| import { trim } from 'component/common/util'; | ||||
| import { IAddon, IAddonProvider } from 'interfaces/addons'; | ||||
| import { AddonParameters } from './AddonParameters/AddonParameters'; | ||||
| import { AddonEvents } from './AddonEvents/AddonEvents'; | ||||
| import cloneDeep from 'lodash.clonedeep'; | ||||
| @ -11,22 +20,48 @@ import { useHistory } from 'react-router-dom'; | ||||
| import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { makeStyles } from '@material-ui/core/styles'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| 
 | ||||
| const useStyles = makeStyles(theme => ({ | ||||
|     nameInput: { | ||||
|         marginRight: '1.5rem', | ||||
|     }, | ||||
|     formSection: { padding: '10px 28px' }, | ||||
|     buttonsSection: { | ||||
|         padding: '10px 28px', | ||||
|         '& > *': { | ||||
|             marginRight: theme.spacing(1), | ||||
|         }, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
| interface IAddonFormProps { | ||||
|     provider?: IAddonProvider; | ||||
|     addon: IAddon; | ||||
|     fetch: () => void; | ||||
|     editMode: boolean; | ||||
| } | ||||
| 
 | ||||
| export const AddonForm: VFC<IAddonFormProps> = ({ | ||||
|     editMode, | ||||
|     provider, | ||||
|     addon: initialValues, | ||||
|     fetch, | ||||
| }) => { | ||||
|     const { createAddon, updateAddon } = useAddonsApi(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const history = useHistory(); | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     const [config, setConfig] = useState(addon); | ||||
|     const [errors, setErrors] = useState({ | ||||
|     const [formValues, setFormValues] = useState(initialValues); | ||||
|     const [errors, setErrors] = useState<{ | ||||
|         containsErrors: boolean; | ||||
|         parameters: Record<string, string>; | ||||
|         events?: string; | ||||
|         general?: string; | ||||
|         description?: string; | ||||
|     }>({ | ||||
|         containsErrors: false, | ||||
|         parameters: {}, | ||||
|     }); | ||||
|     const submitText = editMode ? 'Update' : 'Create'; | ||||
| @ -38,68 +73,73 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|     }, [fetch, provider]); // empty array => fetch only first time
 | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setConfig({ ...addon }); | ||||
|         setFormValues({ ...initialValues }); | ||||
|         /* eslint-disable-next-line */ | ||||
|     }, [addon.description, addon.provider]); | ||||
|     }, [initialValues.description, initialValues.provider]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (provider && !config.provider) { | ||||
|             setConfig({ ...addon, provider: provider.name }); | ||||
|         if (provider && !formValues.provider) { | ||||
|             setFormValues({ ...initialValues, provider: provider.name }); | ||||
|         } | ||||
|     }, [provider, addon, config.provider]); | ||||
|     }, [provider, initialValues, formValues.provider]); | ||||
| 
 | ||||
|     const setFieldValue = field => evt => { | ||||
|         evt.preventDefault(); | ||||
|         const newConfig = { ...config }; | ||||
|         newConfig[field] = evt.target.value; | ||||
|         setConfig(newConfig); | ||||
|     const setFieldValue = | ||||
|         (field: string): ChangeEventHandler<HTMLInputElement> => | ||||
|         event => { | ||||
|             event.preventDefault(); | ||||
|             setFormValues({ ...formValues, [field]: event.target.value }); | ||||
|         }; | ||||
| 
 | ||||
|     const onEnabled: MouseEventHandler = event => { | ||||
|         event.preventDefault(); | ||||
|         setFormValues(({ enabled }) => ({ ...formValues, enabled: !enabled })); | ||||
|     }; | ||||
| 
 | ||||
|     const onEnabled = evt => { | ||||
|         evt.preventDefault(); | ||||
|         const enabled = !config.enabled; | ||||
|         setConfig({ ...config, enabled }); | ||||
|     }; | ||||
|     const setParameterValue = | ||||
|         (param: string): ChangeEventHandler<HTMLInputElement> => | ||||
|         event => { | ||||
|             event.preventDefault(); | ||||
|             setFormValues( | ||||
|                 produce(draft => { | ||||
|                     draft.parameters[param] = event.target.value; | ||||
|                 }) | ||||
|             ); | ||||
|         }; | ||||
| 
 | ||||
|     const setParameterValue = param => evt => { | ||||
|         evt.preventDefault(); | ||||
|         const newConfig = { ...config }; | ||||
|         newConfig.parameters[param] = evt.target.value; | ||||
|         setConfig(newConfig); | ||||
|     }; | ||||
| 
 | ||||
|     const setEventValue = name => evt => { | ||||
|         const newConfig = { ...config }; | ||||
|         if (evt.target.checked) { | ||||
|             newConfig.events.push(name); | ||||
|         } else { | ||||
|             newConfig.events = newConfig.events.filter(e => e !== name); | ||||
|         } | ||||
|         setConfig(newConfig); | ||||
|         setErrors({ ...errors, events: undefined }); | ||||
|     }; | ||||
|     const setEventValue = | ||||
|         (name: string) => (event: ChangeEvent<HTMLInputElement>) => { | ||||
|             const newConfig = { ...formValues }; | ||||
|             if (event.target.checked) { | ||||
|                 newConfig.events.push(name); | ||||
|             } else { | ||||
|                 newConfig.events = newConfig.events.filter(e => e !== name); | ||||
|             } | ||||
|             setFormValues(newConfig); | ||||
|             setErrors({ ...errors, events: undefined }); | ||||
|         }; | ||||
| 
 | ||||
|     const onCancel = () => { | ||||
|         history.goBack(); | ||||
|     }; | ||||
| 
 | ||||
|     const onSubmit = async evt => { | ||||
|         evt.preventDefault(); | ||||
|     const onSubmit: FormEventHandler<HTMLFormElement> = async event => { | ||||
|         event.preventDefault(); | ||||
|         if (!provider) return; | ||||
| 
 | ||||
|         const updatedErrors = cloneDeep(errors); | ||||
|         updatedErrors.containsErrors = false; | ||||
| 
 | ||||
|         // Validations
 | ||||
|         if (config.events.length === 0) { | ||||
|         if (formValues.events.length === 0) { | ||||
|             updatedErrors.events = 'You must listen to at least one event'; | ||||
|             updatedErrors.containsErrors = true; | ||||
|         } | ||||
| 
 | ||||
|         provider.parameters.forEach(p => { | ||||
|             const value = trim(config.parameters[p.name]); | ||||
|             if (p.required && !value) { | ||||
|                 updatedErrors.parameters[p.name] = 'This field is required'; | ||||
|         provider.parameters.forEach(parameterConfig => { | ||||
|             const value = trim(formValues.parameters[parameterConfig.name]); | ||||
|             if (parameterConfig.required && !value) { | ||||
|                 updatedErrors.parameters[parameterConfig.name] = | ||||
|                     'This field is required'; | ||||
|                 updatedErrors.containsErrors = true; | ||||
|             } | ||||
|         }); | ||||
| @ -111,14 +151,14 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
| 
 | ||||
|         try { | ||||
|             if (editMode) { | ||||
|                 await updateAddon(config); | ||||
|                 await updateAddon(formValues); | ||||
|                 history.push('/addons'); | ||||
|                 setToastData({ | ||||
|                     type: 'success', | ||||
|                     title: 'Addon updated successfully', | ||||
|                 }); | ||||
|             } else { | ||||
|                 await createAddon(config); | ||||
|                 await createAddon(formValues); | ||||
|                 history.push('/addons'); | ||||
|                 setToastData({ | ||||
|                     type: 'success', | ||||
| @ -126,11 +166,14 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|                     title: 'Addon created successfully', | ||||
|                 }); | ||||
|             } | ||||
|         } catch (e) { | ||||
|             setToastApiError({ | ||||
|                 text: e.toString(), | ||||
|         } catch (error) { | ||||
|             const message = formatUnknownError(error); | ||||
|             setToastApiError(message); | ||||
|             setErrors({ | ||||
|                 parameters: {}, | ||||
|                 general: message, | ||||
|                 containsErrors: true, | ||||
|             }); | ||||
|             setErrors({ parameters: {}, general: e.message }); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| @ -138,7 +181,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|         name, | ||||
|         description, | ||||
|         documentationUrl = 'https://unleash.github.io/docs/addons', | ||||
|     } = provider ? provider : {}; | ||||
|     } = provider ? provider : ({} as Partial<IAddonProvider>); | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent headerContent={`Configure ${name} addon`}> | ||||
| @ -155,7 +198,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|                         size="small" | ||||
|                         label="Provider" | ||||
|                         name="provider" | ||||
|                         value={config.provider} | ||||
|                         value={formValues.provider} | ||||
|                         disabled | ||||
|                         variant="outlined" | ||||
|                         className={styles.nameInput} | ||||
| @ -163,24 +206,25 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|                     <FormControlLabel | ||||
|                         control={ | ||||
|                             <Switch | ||||
|                                 checked={config.enabled} | ||||
|                                 onChange={onEnabled} | ||||
|                                 checked={formValues.enabled} | ||||
|                                 onClick={onEnabled} | ||||
|                             /> | ||||
|                         } | ||||
|                         label={config.enabled ? 'Enabled' : 'Disabled'} | ||||
|                         label={formValues.enabled ? 'Enabled' : 'Disabled'} | ||||
|                     /> | ||||
|                 </section> | ||||
|                 <section className={styles.formSection}> | ||||
|                     <TextField | ||||
|                         size="small" | ||||
|                         style={{ width: '80%' }} | ||||
|                         rows={4} | ||||
|                         minRows={4} | ||||
|                         multiline | ||||
|                         label="Description" | ||||
|                         name="description" | ||||
|                         placeholder="" | ||||
|                         value={config.description} | ||||
|                         error={errors.description} | ||||
|                         value={formValues.description} | ||||
|                         error={Boolean(errors.description)} | ||||
|                         helperText={errors.description} | ||||
|                         onChange={setFieldValue('description')} | ||||
|                         variant="outlined" | ||||
|                     /> | ||||
| @ -188,7 +232,7 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|                 <section className={styles.formSection}> | ||||
|                     <AddonEvents | ||||
|                         provider={provider} | ||||
|                         checkedEvents={config.events} | ||||
|                         checkedEvents={formValues.events} | ||||
|                         setEventValue={setEventValue} | ||||
|                         error={errors.events} | ||||
|                     /> | ||||
| @ -196,13 +240,13 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|                 <section className={styles.formSection}> | ||||
|                     <AddonParameters | ||||
|                         provider={provider} | ||||
|                         config={config} | ||||
|                         errors={errors} | ||||
|                         config={formValues} | ||||
|                         parametersErrors={errors.parameters} | ||||
|                         editMode={editMode} | ||||
|                         setParameterValue={setParameterValue} | ||||
|                     /> | ||||
|                 </section> | ||||
|                 <section className={styles.formSection}> | ||||
|                 <section className={styles.buttonsSection}> | ||||
|                     <Button type="submit" color="primary" variant="contained"> | ||||
|                         {submitText} | ||||
|                     </Button> | ||||
| @ -214,12 +258,3 @@ export const AddonForm = ({ editMode, provider, addon, fetch }) => { | ||||
|         </PageContent> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| AddonForm.propTypes = { | ||||
|     provider: PropTypes.object, | ||||
|     addon: PropTypes.object.isRequired, | ||||
|     fetch: PropTypes.func.isRequired, | ||||
|     submit: PropTypes.func.isRequired, | ||||
|     cancel: PropTypes.func.isRequired, | ||||
|     editMode: PropTypes.bool.isRequired, | ||||
| }; | ||||
| @ -1,9 +1,6 @@ | ||||
| import { TextField } from '@material-ui/core'; | ||||
| import { | ||||
|     IAddonConfig, | ||||
|     IAddonProvider, | ||||
|     IAddonProviderParams, | ||||
| } from 'interfaces/addons'; | ||||
| import { IAddonConfig, IAddonProviderParams } from 'interfaces/addons'; | ||||
| import { ChangeEventHandler } from 'react'; | ||||
| 
 | ||||
| const resolveType = ({ type = 'text', sensitive = false }, value: string) => { | ||||
|     if (sensitive && value === MASKED_VALUE) { | ||||
| @ -17,31 +14,29 @@ const resolveType = ({ type = 'text', sensitive = false }, value: string) => { | ||||
| 
 | ||||
| const MASKED_VALUE = '*****'; | ||||
| 
 | ||||
| interface IAddonParameterProps { | ||||
|     provider: IAddonProvider; | ||||
|     errors: Record<string, string>; | ||||
| export interface IAddonParameterProps { | ||||
|     parametersErrors: Record<string, string>; | ||||
|     definition: IAddonProviderParams; | ||||
|     setParameterValue: (param: string) => void; | ||||
|     setParameterValue: (param: string) => ChangeEventHandler<HTMLInputElement>; | ||||
|     config: IAddonConfig; | ||||
| } | ||||
| 
 | ||||
| export const AddonParameter = ({ | ||||
|     definition, | ||||
|     config, | ||||
|     errors, | ||||
|     parametersErrors, | ||||
|     setParameterValue, | ||||
| }: IAddonParameterProps) => { | ||||
|     const value = config.parameters[definition.name] || ''; | ||||
|     const type = resolveType(definition, value); | ||||
|     // @ts-expect-error
 | ||||
|     const error = errors.parameters[definition.name]; | ||||
|     const error = parametersErrors[definition.name]; | ||||
| 
 | ||||
|     return ( | ||||
|         <div style={{ width: '80%', marginTop: '25px' }}> | ||||
|             <TextField | ||||
|                 size="small" | ||||
|                 style={{ width: '100%' }} | ||||
|                 rows={definition.type === 'textfield' ? 9 : 0} | ||||
|                 minRows={definition.type === 'textfield' ? 9 : 0} | ||||
|                 multiline={definition.type === 'textfield'} | ||||
|                 type={type} | ||||
|                 label={definition.displayName} | ||||
| @ -51,8 +46,7 @@ export const AddonParameter = ({ | ||||
|                     shrink: true, | ||||
|                 }} | ||||
|                 value={value} | ||||
|                 error={error} | ||||
|                 // @ts-expect-error
 | ||||
|                 error={Boolean(error)} | ||||
|                 onChange={setParameterValue(definition.name)} | ||||
|                 variant="outlined" | ||||
|                 helperText={definition.description} | ||||
|  | ||||
| @ -1,19 +1,22 @@ | ||||
| import React from 'react'; | ||||
| import { IAddonConfig, IAddonProvider } from 'interfaces/addons'; | ||||
| import { AddonParameter } from './AddonParameter/AddonParameter'; | ||||
| import { IAddonProvider } from 'interfaces/addons'; | ||||
| import { | ||||
|     AddonParameter, | ||||
|     IAddonParameterProps, | ||||
| } from './AddonParameter/AddonParameter'; | ||||
| 
 | ||||
| interface IAddonParametersProps { | ||||
|     provider: IAddonProvider; | ||||
|     errors: Record<string, string>; | ||||
|     provider?: IAddonProvider; | ||||
|     parametersErrors: IAddonParameterProps['parametersErrors']; | ||||
|     editMode: boolean; | ||||
|     setParameterValue: (param: string) => void; | ||||
|     config: IAddonConfig; | ||||
|     setParameterValue: IAddonParameterProps['setParameterValue']; | ||||
|     config: IAddonParameterProps['config']; | ||||
| } | ||||
| 
 | ||||
| export const AddonParameters = ({ | ||||
|     provider, | ||||
|     config, | ||||
|     errors, | ||||
|     parametersErrors, | ||||
|     setParameterValue, | ||||
|     editMode, | ||||
| }: IAddonParametersProps) => { | ||||
| @ -30,11 +33,10 @@ export const AddonParameters = ({ | ||||
|                 </p> | ||||
|             ) : null} | ||||
|             {provider.parameters.map(parameter => ( | ||||
|                 // @ts-expect-error
 | ||||
|                 <AddonParameter | ||||
|                     key={parameter.name} | ||||
|                     definition={parameter} | ||||
|                     errors={errors} | ||||
|                     parametersErrors={parametersErrors} | ||||
|                     config={config} | ||||
|                     setParameterValue={setParameterValue} | ||||
|                 /> | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons'; | ||||
| import { AvailableAddons } from './AvailableAddons/AvailableAddons'; | ||||
| import { Avatar } from '@material-ui/core'; | ||||
| import { DeviceHub } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import slackIcon from 'assets/icons/slack.svg'; | ||||
| import jiraIcon from 'assets/icons/jira.svg'; | ||||
| import webhooksIcon from 'assets/icons/webhooks.svg'; | ||||
|  | ||||
| @ -6,7 +6,7 @@ import { | ||||
|     ListItemText, | ||||
| } from '@material-ui/core'; | ||||
| import { Delete, Edit, Visibility, VisibilityOff } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     DELETE_ADDON, | ||||
|     UPDATE_ADDON, | ||||
| @ -20,7 +20,7 @@ import { ReactElement, useContext, useState } from 'react'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { IAddon } from 'interfaces/addons'; | ||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| 
 | ||||
| interface IConfigureAddonsProps { | ||||
|  | ||||
| @ -2,18 +2,19 @@ import { useParams } from 'react-router-dom'; | ||||
| import useAddons from 'hooks/api/getters/useAddons/useAddons'; | ||||
| import { AddonForm } from '../AddonForm/AddonForm'; | ||||
| import cloneDeep from 'lodash.clonedeep'; | ||||
| import { IAddon } from 'interfaces/addons'; | ||||
| 
 | ||||
| interface IAddonCreateParams { | ||||
|     providerId: string; | ||||
| } | ||||
| 
 | ||||
| const DEFAULT_DATA = { | ||||
| export const DEFAULT_DATA = { | ||||
|     provider: '', | ||||
|     description: '', | ||||
|     enabled: true, | ||||
|     parameters: {}, | ||||
|     events: [], | ||||
| }; | ||||
| } as unknown as IAddon; // TODO: improve type
 | ||||
| 
 | ||||
| export const CreateAddon = () => { | ||||
|     const { providerId } = useParams<IAddonCreateParams>(); | ||||
| @ -31,7 +32,6 @@ export const CreateAddon = () => { | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         // @ts-expect-error
 | ||||
|         <AddonForm | ||||
|             editMode={editMode} | ||||
|             provider={provider} | ||||
|  | ||||
| @ -3,19 +3,12 @@ import useAddons from 'hooks/api/getters/useAddons/useAddons'; | ||||
| import { AddonForm } from '../AddonForm/AddonForm'; | ||||
| import cloneDeep from 'lodash.clonedeep'; | ||||
| import { IAddon } from 'interfaces/addons'; | ||||
| import { DEFAULT_DATA } from '../CreateAddon/CreateAddon'; | ||||
| 
 | ||||
| interface IAddonEditParams { | ||||
|     addonId: string; | ||||
| } | ||||
| 
 | ||||
| const DEFAULT_DATA = { | ||||
|     provider: '', | ||||
|     description: '', | ||||
|     enabled: true, | ||||
|     parameters: {}, | ||||
|     events: [], | ||||
| }; | ||||
| 
 | ||||
| export const EditAddon = () => { | ||||
|     const { addonId } = useParams<IAddonEditParams>(); | ||||
| 
 | ||||
| @ -26,12 +19,10 @@ export const EditAddon = () => { | ||||
|         (addon: IAddon) => addon.id === Number(addonId) | ||||
|     ) || { ...cloneDeep(DEFAULT_DATA) }; | ||||
|     const provider = addon | ||||
|         ? // @ts-expect-error
 | ||||
|           providers.find(provider => provider.name === addon.provider) | ||||
|         ? providers.find(provider => provider.name === addon.provider) | ||||
|         : undefined; | ||||
| 
 | ||||
|     return ( | ||||
|         // @ts-expect-error
 | ||||
|         <AddonForm | ||||
|             editMode={editMode} | ||||
|             provider={provider} | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage'; | ||||
| import { useLocation } from 'react-router-dom'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage'; | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| 
 | ||||
| const ApiPage = () => { | ||||
|     const { pathname } = useLocation(); | ||||
|  | ||||
| @ -17,7 +17,7 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox'; | ||||
| import { IAutocompleteBoxOption } from 'component/common/AutocompleteBox/AutocompleteBox'; | ||||
| import { useStyles } from '../ApiTokenForm.styles'; | ||||
| import { SelectAllButton } from './SelectAllButton/SelectAllButton'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| const ALL_PROJECTS = '*'; | ||||
| 
 | ||||
| @ -107,7 +107,7 @@ export const SelectProjectInput: VFC<ISelectProjectInputProps> = ({ | ||||
|     const renderInput = (params: AutocompleteRenderInputParams) => ( | ||||
|         <TextField | ||||
|             {...params} | ||||
|             error={!!error} | ||||
|             error={Boolean(error)} | ||||
|             helperText={error} | ||||
|             variant="outlined" | ||||
|             label="Projects" | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { useContext, useState } from 'react'; | ||||
| import { | ||||
|     Box, | ||||
|     IconButton, | ||||
|     Table, | ||||
|     TableBody, | ||||
| @ -15,16 +16,15 @@ import useApiTokens from 'hooks/api/getters/useApiTokens/useApiTokens'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi'; | ||||
| import ApiError from 'component/common/ApiError/ApiError'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { DELETE_API_TOKEN } from 'component/providers/AccessProvider/permissions'; | ||||
| import { useStyles } from './ApiTokenList.styles'; | ||||
| import Secret from './secret'; | ||||
| import { Delete, FileCopy } from '@material-ui/icons'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import copy from 'copy-to-clipboard'; | ||||
| import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||
| import { formatDateYMD } from 'utils/formatDate'; | ||||
| import { ProjectsList } from './ProjectsList/ProjectsList'; | ||||
| import { useStyles } from './ApiTokenList.styles'; | ||||
| 
 | ||||
| interface IApiToken { | ||||
|     createdAt: Date; | ||||
| @ -182,7 +182,13 @@ export const ApiTokenList = () => { | ||||
|                                     } | ||||
|                                 /> | ||||
|                                 <TableCell className={styles.hideMD}> | ||||
|                                     <Secret value={item.secret} /> | ||||
|                                     <Box | ||||
|                                         component="span" | ||||
|                                         display="inline-block" | ||||
|                                         width="250px" | ||||
|                                     > | ||||
|                                         ************************************ | ||||
|                                     </Box> | ||||
|                                 </TableCell> | ||||
|                                 <TableCell className={styles.actionsContainer}> | ||||
|                                     <Tooltip title="Copy token"> | ||||
|  | ||||
| @ -1,16 +0,0 @@ | ||||
| import PropTypes from 'prop-types'; | ||||
| function Secret({ value }) { | ||||
|     return ( | ||||
|         <div> | ||||
|             <span style={{ width: '250px', display: 'inline-block' }}> | ||||
|                 ************************************ | ||||
|             </span> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| Secret.propTypes = { | ||||
|     value: PropTypes.string, | ||||
| }; | ||||
| 
 | ||||
| export default Secret; | ||||
| @ -4,8 +4,8 @@ import { Button } from '@material-ui/core'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     CREATE_API_TOKEN, | ||||
|     READ_API_TOKEN, | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { Typography } from '@material-ui/core'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { UserToken } from './UserToken/UserToken'; | ||||
| 
 | ||||
| interface IConfirmUserLink { | ||||
|  | ||||
| @ -2,7 +2,7 @@ import React from 'react'; | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { OidcAuth } from './OidcAuth/OidcAuth'; | ||||
| import { SamlAuth } from './SamlAuth/SamlAuth'; | ||||
|  | ||||
| @ -104,7 +104,7 @@ export const AutoCreateForm = ({ | ||||
|                         label="Email domains" | ||||
|                         name="emailDomains" | ||||
|                         disabled={!data.autoCreate || !data.enabled} | ||||
|                         required={!!data.autoCreate} | ||||
|                         required={Boolean(data.autoCreate)} | ||||
|                         value={data.emailDomains || ''} | ||||
|                         placeholder="@company.com, @anotherCompany.com" | ||||
|                         style={{ width: '400px' }} | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { useContext } from 'react'; | ||||
| import InvoiceList from './InvoiceList'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| 
 | ||||
| const InvoiceAdminPage = () => { | ||||
|  | ||||
| @ -9,8 +9,8 @@ import { | ||||
| } from '@material-ui/core'; | ||||
| import OpenInNew from '@material-ui/icons/OpenInNew'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { formatApiPath } from 'utils/formatPath'; | ||||
| import useInvoices from 'hooks/api/getters/useInvoices/useInvoices'; | ||||
| import { IInvoice } from 'interfaces/invoice'; | ||||
|  | ||||
| @ -9,7 +9,7 @@ import { | ||||
| import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; | ||||
| 
 | ||||
| import { useStyles } from './ProjectRoleForm.styles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import React, { ReactNode } from 'react'; | ||||
| import { IPermission } from 'interfaces/project'; | ||||
| import { | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import React from 'react'; | ||||
| import { IProjectRole } from 'interfaces/role'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import Input from 'component/common/Input/Input'; | ||||
| import { useStyles } from './ProjectRoleDeleteConfirm.styles'; | ||||
| 
 | ||||
|  | ||||
| @ -2,8 +2,8 @@ import { Button } from '@material-ui/core'; | ||||
| import { useContext } from 'react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import AdminMenu from 'component/admin/menu/AdminMenu'; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { Typography } from '@material-ui/core'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| 
 | ||||
| import { ReactComponent as EmailIcon } from 'assets/icons/email.svg'; | ||||
| import { useStyles } from './ConfirmUserEmail.styles'; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { Typography } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import UserInviteLink from './UserInviteLink/UserInviteLink'; | ||||
| 
 | ||||
| interface IConfirmUserLink { | ||||
|  | ||||
| @ -11,7 +11,7 @@ import { | ||||
| import { useStyles } from './UserForm.styles'; | ||||
| import React from 'react'; | ||||
| import useUsers from 'hooks/api/getters/useUsers/useUsers'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { EDIT } from 'constants/misc'; | ||||
| import useUiBootstrap from 'hooks/api/getters/useUiBootstrap/useUiBootstrap'; | ||||
| 
 | ||||
|  | ||||
| @ -3,10 +3,10 @@ import UsersList from './UsersList/UsersList'; | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import { TableActions } from 'component/common/Table/TableActions/TableActions'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { useStyles } from './UserAdmin.styles'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
|  | ||||
| @ -3,11 +3,11 @@ import classnames from 'classnames'; | ||||
| import { Avatar, TextField, Typography } from '@material-ui/core'; | ||||
| import { trim } from 'component/common/util'; | ||||
| import { modalStyles } from 'component/admin/users/util'; | ||||
| import Dialogue from 'component/common/Dialogue/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import PasswordChecker from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { IUser } from 'interfaces/user'; | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import React from 'react'; | ||||
| import Dialogue from 'component/common/Dialogue/Dialogue'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { REMOVE_USER_ERROR } from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import useLoading from 'hooks/useLoading'; | ||||
|  | ||||
| @ -10,7 +10,7 @@ import classnames from 'classnames'; | ||||
| import { Delete, Edit, Lock } from '@material-ui/icons'; | ||||
| import { SyntheticEvent, useContext } from 'react'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { IUser } from 'interfaces/user'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
|  | ||||
| @ -10,7 +10,7 @@ import { | ||||
| import classnames from 'classnames'; | ||||
| import ChangePassword from './ChangePassword/ChangePassword'; | ||||
| import DeleteUser from './DeleteUser/DeleteUser'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import ConfirmUserAdded from '../ConfirmUserAdded/ConfirmUserAdded'; | ||||
|  | ||||
| @ -9,13 +9,14 @@ import { | ||||
|     Typography, | ||||
| } from '@material-ui/core'; | ||||
| import { Link as LinkIcon } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { UPDATE_APPLICATION } from 'component/providers/AccessProvider/permissions'; | ||||
| import { ApplicationView } from '../ApplicationView/ApplicationView'; | ||||
| import { ApplicationUpdate } from '../ApplicationUpdate/ApplicationUpdate'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { TabNav } from 'component/common/TabNav/TabNav/TabNav'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import useApplicationsApi from 'hooks/api/actions/useApplicationsApi/useApplicationsApi'; | ||||
| import useApplication from 'hooks/api/getters/useApplication/useApplication'; | ||||
| @ -25,7 +26,6 @@ import useToast from 'hooks/useToast'; | ||||
| import PermissionButton from 'component/common/PermissionButton/PermissionButton'; | ||||
| import { formatDateYMD } from 'utils/formatDate'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import { TabNav } from 'component/common/TabNav/TabNav/TabNav'; | ||||
| 
 | ||||
| export const ApplicationEdit = () => { | ||||
|     const history = useHistory(); | ||||
| @ -93,7 +93,7 @@ export const ApplicationEdit = () => { | ||||
|         <PageContent | ||||
|             headerContent={ | ||||
|                 <HeaderTitle | ||||
|                     title={ | ||||
|                     titleElement={ | ||||
|                         <span | ||||
|                             style={{ | ||||
|                                 display: 'flex', | ||||
| @ -106,6 +106,7 @@ export const ApplicationEdit = () => { | ||||
|                             {appName} | ||||
|                         </span> | ||||
|                     } | ||||
|                     title={appName} | ||||
|                     actions={ | ||||
|                         <> | ||||
|                             <ConditionallyRender | ||||
|  | ||||
| @ -4,9 +4,9 @@ import { Warning } from '@material-ui/icons'; | ||||
| import { AppsLinkList, styles as commonStyles } from 'component/common'; | ||||
| import { SearchField } from 'component/common/SearchField/SearchField'; | ||||
| import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import useApplications from 'hooks/api/getters/useApplications/useApplications'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| export const ApplicationList = () => { | ||||
|     const { applications, loading } = useApplications(); | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { | ||||
|     CREATE_FEATURE, | ||||
|     CREATE_STRATEGY, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { getTogglePath } from 'utils/routePathHelpers'; | ||||
| import useApplication from 'hooks/api/getters/useApplication/useApplication'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive'; | ||||
| import FeatureToggleList from '../feature/FeatureToggleList/FeatureToggleList'; | ||||
| import { FeatureToggleList } from '../feature/FeatureToggleList/FeatureToggleList'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { useFeaturesFilter } from 'hooks/useFeaturesFilter'; | ||||
| import { useFeatureArchiveApi } from 'hooks/api/actions/useFeatureArchiveApi/useReviveFeatureApi'; | ||||
| @ -20,7 +20,7 @@ export const ArchiveListContainer = () => { | ||||
|     const { filtered, filter, setFilter } = useFeaturesFilter(archivedFeatures); | ||||
|     const { sorted, sort, setSort } = useFeaturesSort(filtered); | ||||
| 
 | ||||
|     const revive = (feature: string) => { | ||||
|     const onRevive = (feature: string) => { | ||||
|         reviveFeature(feature) | ||||
|             .then(refetchArchived) | ||||
|             .then(() => | ||||
| @ -38,13 +38,13 @@ export const ArchiveListContainer = () => { | ||||
|         <FeatureToggleList | ||||
|             features={sorted} | ||||
|             loading={loading} | ||||
|             revive={revive} | ||||
|             onRevive={onRevive} | ||||
|             flags={uiConfig.flags} | ||||
|             filter={filter} | ||||
|             setFilter={setFilter} | ||||
|             sort={sort} | ||||
|             setSort={setSort} | ||||
|             archive | ||||
|             isArchive | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import React, { useEffect, useState, useRef, FC } from 'react'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IAnimateOnMountProps { | ||||
|     mounted: boolean; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import Breadcrumbs from '@material-ui/core/Breadcrumbs'; | ||||
| import { Link, useLocation } from 'react-router-dom'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from './BreadcrumbNav.styles'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import { useContext } from 'react'; | ||||
|  | ||||
| @ -15,7 +15,7 @@ type TargetElement = | ||||
| 
 | ||||
| type RenderFunc = () => JSX.Element; | ||||
| 
 | ||||
| const ConditionallyRender = ({ | ||||
| export const ConditionallyRender = ({ | ||||
|     condition, | ||||
|     show, | ||||
|     elseShow, | ||||
| @ -51,5 +51,3 @@ const ConditionallyRender = ({ | ||||
|     } | ||||
|     return null; | ||||
| }; | ||||
| 
 | ||||
| export default ConditionallyRender; | ||||
|  | ||||
| @ -1,3 +0,0 @@ | ||||
| import ConditionallyRender from './ConditionallyRender'; | ||||
| 
 | ||||
| export default ConditionallyRender; | ||||
| @ -1,5 +1,5 @@ | ||||
| import { IConstraint } from 'interfaces/strategy'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| import { ConstraintAccordionEdit } from './ConstraintAccordionEdit/ConstraintAccordionEdit'; | ||||
| import { ConstraintAccordionView } from './ConstraintAccordionView/ConstraintAccordionView'; | ||||
|  | ||||
| @ -6,7 +6,7 @@ import { ConstraintFormHeader } from './ConstraintFormHeader/ConstraintFormHeade | ||||
| import { useStyles } from './ConstraintAccordionEditBody.styles'; | ||||
| import React from 'react'; | ||||
| import { newOperators } from 'constants/operators'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { oneOf } from 'utils/oneOf'; | ||||
| import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert'; | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Checkbox } from '@material-ui/core'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; | ||||
| import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
|  | ||||
| @ -2,7 +2,7 @@ import React, { useState } from 'react'; | ||||
| import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader'; | ||||
| import { FormControl, RadioGroup, Radio } from '@material-ui/core'; | ||||
| import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useCommonStyles } from 'themes/commonStyles'; | ||||
| import { ILegalValue } from 'interfaces/context'; | ||||
| import { | ||||
|  | ||||
| @ -5,7 +5,7 @@ import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashCon | ||||
| import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; | ||||
| import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon'; | ||||
| import { Help } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { dateOperators, DATE_AFTER, IN } from 'constants/operators'; | ||||
| import { SAVE } from '../ConstraintAccordionEdit'; | ||||
| import { resolveText } from './helpers'; | ||||
|  | ||||
| @ -7,8 +7,8 @@ import { useWeakMap } from 'hooks/useWeakMap'; | ||||
| import { objectId } from 'utils/objectId'; | ||||
| import { useStyles } from './ConstraintAccordionList.styles'; | ||||
| import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { Button } from '@material-ui/core'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IConstraintAccordionListProps { | ||||
|     constraints: IConstraint[]; | ||||
|  | ||||
| @ -5,7 +5,7 @@ import { useState } from 'react'; | ||||
| import { stringOperators } from 'constants/operators'; | ||||
| import { IConstraint } from 'interfaces/strategy'; | ||||
| import { oneOf } from 'utils/oneOf'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles'; | ||||
| import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch'; | ||||
| import { formatConstraintValue } from 'utils/formatConstraintValue'; | ||||
|  | ||||
| @ -5,7 +5,7 @@ import { Delete, Edit } from '@material-ui/icons'; | ||||
| import { IConstraint } from 'interfaces/strategy'; | ||||
| 
 | ||||
| import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import React from 'react'; | ||||
| import { formatConstraintValue } from 'utils/formatConstraintValue'; | ||||
| import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { TextField, InputAdornment, Chip } from '@material-ui/core'; | ||||
| import { Search } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IConstraintValueSearchProps { | ||||
|     filter: string; | ||||
|  | ||||
| @ -7,7 +7,7 @@ import { | ||||
|     DialogTitle, | ||||
| } from '@material-ui/core'; | ||||
| 
 | ||||
| import ConditionallyRender from '../ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from './Dialogue.styles'; | ||||
| import { DIALOGUE_CONFIRM_ID } from 'utils/testIds'; | ||||
| 
 | ||||
| @ -15,7 +15,7 @@ interface IDialogue { | ||||
|     primaryButtonText?: string; | ||||
|     secondaryButtonText?: string; | ||||
|     open: boolean; | ||||
|     onClick: (e: React.SyntheticEvent) => void; | ||||
|     onClick?: (e: React.SyntheticEvent) => void; | ||||
|     onClose?: (e: React.SyntheticEvent) => void; | ||||
|     style?: object; | ||||
|     title: string; | ||||
| @ -26,7 +26,7 @@ interface IDialogue { | ||||
|     permissionButton?: JSX.Element; | ||||
| } | ||||
| 
 | ||||
| const Dialogue: React.FC<IDialogue> = ({ | ||||
| export const Dialogue: React.FC<IDialogue> = ({ | ||||
|     children, | ||||
|     open, | ||||
|     onClick, | ||||
| @ -44,7 +44,9 @@ const Dialogue: React.FC<IDialogue> = ({ | ||||
|     const handleClick = formId | ||||
|         ? (e: React.SyntheticEvent) => { | ||||
|               e.preventDefault(); | ||||
|               onClick(e); | ||||
|               if (onClick) { | ||||
|                   onClick(e); | ||||
|               } | ||||
|           } | ||||
|         : onClick; | ||||
|     return ( | ||||
| @ -103,5 +105,3 @@ const Dialogue: React.FC<IDialogue> = ({ | ||||
|         </Dialog> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default Dialogue; | ||||
|  | ||||
| @ -1,3 +0,0 @@ | ||||
| import Dialogue from './Dialogue'; | ||||
| 
 | ||||
| export default Dialogue; | ||||
| @ -0,0 +1,23 @@ | ||||
| import { ReactNode, VFC } from 'react'; | ||||
| import { Button, ButtonProps, Icon } from '@material-ui/core'; | ||||
| 
 | ||||
| interface IDropdownButtonProps { | ||||
|     label: string; | ||||
|     id?: string; | ||||
|     title?: ButtonProps['title']; | ||||
|     className?: string; | ||||
|     icon?: ReactNode; | ||||
|     startIcon?: ButtonProps['startIcon']; | ||||
|     style?: ButtonProps['style']; | ||||
|     onClick: ButtonProps['onClick']; | ||||
| } | ||||
| 
 | ||||
| export const DropdownButton: VFC<IDropdownButtonProps> = ({ | ||||
|     label, | ||||
|     icon, | ||||
|     ...rest | ||||
| }) => ( | ||||
|     <Button {...rest} endIcon={<Icon>{icon}</Icon>}> | ||||
|         {label} | ||||
|     </Button> | ||||
| ); | ||||
| @ -1,13 +1,26 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { | ||||
|     CSSProperties, | ||||
|     MouseEventHandler, | ||||
|     ReactNode, | ||||
|     useState, | ||||
|     VFC, | ||||
| } from 'react'; | ||||
| import { Menu } from '@material-ui/core'; | ||||
| import { ArrowDropDown } from '@material-ui/icons'; | ||||
| import { DropdownButton } from './DropdownButton/DropdownButton'; | ||||
| 
 | ||||
| import { DropdownButton } from '..'; | ||||
| export interface IDropdownMenuProps { | ||||
|     renderOptions: () => ReactNode; | ||||
|     id: string; | ||||
|     title?: string; | ||||
|     callback?: MouseEventHandler; | ||||
|     icon?: ReactNode; | ||||
|     label: string; | ||||
|     startIcon?: ReactNode; | ||||
|     style?: CSSProperties; | ||||
| } | ||||
| 
 | ||||
| import styles from '../common.module.scss'; | ||||
| 
 | ||||
| const DropdownMenu = ({ | ||||
| const DropdownMenu: VFC<IDropdownMenuProps> = ({ | ||||
|     renderOptions, | ||||
|     id, | ||||
|     title, | ||||
| @ -18,11 +31,13 @@ const DropdownMenu = ({ | ||||
|     startIcon, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const [anchor, setAnchor] = React.useState(null); | ||||
|     const [anchor, setAnchor] = useState<Element | null>(null); | ||||
| 
 | ||||
|     const handleOpen = e => setAnchor(e.currentTarget); | ||||
|     const handleOpen: MouseEventHandler<HTMLButtonElement> = e => { | ||||
|         setAnchor(e.currentTarget); | ||||
|     }; | ||||
| 
 | ||||
|     const handleClose = e => { | ||||
|     const handleClose: MouseEventHandler<HTMLDivElement> = e => { | ||||
|         if (callback && typeof callback === 'function') { | ||||
|             callback(e); | ||||
|         } | ||||
| @ -46,7 +61,6 @@ const DropdownMenu = ({ | ||||
|             /> | ||||
|             <Menu | ||||
|                 id={id} | ||||
|                 className={styles.dropdownMenu} | ||||
|                 onClick={handleClose} | ||||
|                 anchorEl={anchor} | ||||
|                 open={Boolean(anchor)} | ||||
| @ -57,14 +71,4 @@ const DropdownMenu = ({ | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| DropdownMenu.propTypes = { | ||||
|     renderOptions: PropTypes.func, | ||||
|     id: PropTypes.string, | ||||
|     title: PropTypes.string, | ||||
|     callback: PropTypes.func, | ||||
|     icon: PropTypes.object, | ||||
|     label: PropTypes.string, | ||||
|     startIcon: PropTypes.object, | ||||
| }; | ||||
| 
 | ||||
| export default DropdownMenu; | ||||
| @ -1,6 +1,6 @@ | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; | ||||
| import Dialogue from '../Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import PermissionButton from '../PermissionButton/PermissionButton'; | ||||
| import { useStyles } from './EnvironmentStrategyDialog.styles'; | ||||
| import { formatCreateStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate'; | ||||
| @ -35,7 +35,6 @@ const EnvironmentStrategyDialog = ({ | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         // @ts-expect-error
 | ||||
|         <Dialogue | ||||
|             open={open} | ||||
|             maxWidth="sm" | ||||
|  | ||||
| @ -8,7 +8,7 @@ import { | ||||
|     Tooltip, | ||||
| } from '@material-ui/core'; | ||||
| import { FileCopy, Info } from '@material-ui/icons'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import Loader from '../Loader/Loader'; | ||||
| import copy from 'copy-to-clipboard'; | ||||
| import useToast from 'hooks/useToast'; | ||||
|  | ||||
| @ -1,15 +1,26 @@ | ||||
| import React from 'react'; | ||||
| import { ReactNode, VFC } from 'react'; | ||||
| import classnames from 'classnames'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import { Typography } from '@material-ui/core'; | ||||
| import ConditionallyRender from '../ConditionallyRender/ConditionallyRender'; | ||||
| import { Variant } from '@material-ui/core/styles/createTypography'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| import { useStyles } from './styles'; | ||||
| import { usePageTitle } from 'hooks/usePageTitle'; | ||||
| 
 | ||||
| const HeaderTitle = ({ | ||||
| interface IHeaderTitleProps { | ||||
|     title: string; | ||||
|     titleElement?: ReactNode; | ||||
|     subtitle?: string; | ||||
|     variant?: 'inherit' | Variant; | ||||
|     loading?: boolean; | ||||
|     actions?: ReactNode; | ||||
|     className?: string; | ||||
| } | ||||
| 
 | ||||
| export const HeaderTitle: VFC<IHeaderTitleProps> = ({ | ||||
|     title, | ||||
|     titleElement, | ||||
|     actions, | ||||
|     subtitle, | ||||
|     variant, | ||||
| @ -28,25 +39,15 @@ const HeaderTitle = ({ | ||||
|                     variant={variant || 'h1'} | ||||
|                     className={classnames(styles.headerTitle, className)} | ||||
|                 > | ||||
|                     {title} | ||||
|                     {titleElement || title} | ||||
|                 </Typography> | ||||
|                 {subtitle && <small>{subtitle}</small>} | ||||
|             </div> | ||||
| 
 | ||||
|             <ConditionallyRender | ||||
|                 condition={actions} | ||||
|                 condition={Boolean(actions)} | ||||
|                 show={<div className={styles.headerActions}>{actions}</div>} | ||||
|             /> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default HeaderTitle; | ||||
| 
 | ||||
| HeaderTitle.propTypes = { | ||||
|     title: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), | ||||
|     subtitle: PropTypes.string, | ||||
|     variant: PropTypes.string, | ||||
|     loading: PropTypes.bool, | ||||
|     actions: PropTypes.element, | ||||
| }; | ||||
| @ -1,3 +0,0 @@ | ||||
| import HeaderTitle from './HeaderTitle'; | ||||
| 
 | ||||
| export default HeaderTitle; | ||||
| @ -1,62 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { TextField } from '@material-ui/core'; | ||||
| 
 | ||||
| function InputListField({ | ||||
|     label, | ||||
|     values = [], | ||||
|     error, | ||||
|     errorText, | ||||
|     name, | ||||
|     updateValues, | ||||
|     placeholder = '', | ||||
|     onBlur = () => {}, | ||||
|     FormHelperTextProps, | ||||
| }) { | ||||
|     const handleChange = evt => { | ||||
|         const values = evt.target.value.split(/,\s?/); | ||||
|         const trimmedValues = values.map(v => v.trim()); | ||||
|         updateValues(trimmedValues); | ||||
|     }; | ||||
| 
 | ||||
|     const handleKeyDown = evt => { | ||||
|         if (evt.key === 'Backspace') { | ||||
|             const currentValue = evt.target.value; | ||||
|             if (currentValue.endsWith(', ')) { | ||||
|                 evt.preventDefault(); | ||||
|                 const value = currentValue.slice(0, -2); | ||||
|                 updateValues(value.split(/,\s*/)); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <TextField | ||||
|             name={name} | ||||
|             error={error} | ||||
|             helperText={errorText} | ||||
|             placeholder={placeholder} | ||||
|             value={values ? values.join(', ') : ''} | ||||
|             onKeyDown={handleKeyDown} | ||||
|             onChange={handleChange} | ||||
|             onBlur={onBlur} | ||||
|             label={label} | ||||
|             style={{ width: '100%' }} | ||||
|             variant="outlined" | ||||
|             size="small" | ||||
|             FormHelperTextProps={FormHelperTextProps} | ||||
|         /> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| InputListField.propTypes = { | ||||
|     label: PropTypes.string.isRequired, | ||||
|     values: PropTypes.array, | ||||
|     error: PropTypes.string, | ||||
|     placeholder: PropTypes.string, | ||||
|     name: PropTypes.string.isRequired, | ||||
|     updateValues: PropTypes.func.isRequired, | ||||
|     onBlur: PropTypes.func, | ||||
| }; | ||||
| 
 | ||||
| export default InputListField; | ||||
| @ -0,0 +1,53 @@ | ||||
| import { VFC } from 'react'; | ||||
| import { TextField, TextFieldProps } from '@material-ui/core'; | ||||
| 
 | ||||
| interface IInputListFieldProps { | ||||
|     label: string; | ||||
|     values?: any[]; | ||||
|     error?: boolean; | ||||
|     placeholder?: string; | ||||
|     name: string; | ||||
|     updateValues: (values: string[]) => void; | ||||
|     onBlur?: TextFieldProps['onBlur']; | ||||
|     helperText?: TextFieldProps['helperText']; | ||||
|     FormHelperTextProps?: TextFieldProps['FormHelperTextProps']; | ||||
| } | ||||
| 
 | ||||
| export const InputListField: VFC<IInputListFieldProps> = ({ | ||||
|     values = [], | ||||
|     updateValues, | ||||
|     placeholder = '', | ||||
|     error, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const handleChange: TextFieldProps['onChange'] = event => { | ||||
|         const values = event.target.value.split(/,\s?/); | ||||
|         const trimmedValues = values.map(v => v.trim()); | ||||
|         updateValues(trimmedValues); | ||||
|     }; | ||||
| 
 | ||||
|     const handleKeyDown: TextFieldProps['onKeyDown'] = event => { | ||||
|         if (event.key === 'Backspace') { | ||||
|             const currentValue = (event.target as HTMLInputElement).value; | ||||
|             if (currentValue.endsWith(', ')) { | ||||
|                 event.preventDefault(); | ||||
|                 const value = currentValue.slice(0, -2); | ||||
|                 updateValues(value.split(/,\s*/)); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <TextField | ||||
|             {...rest} | ||||
|             error={error} | ||||
|             placeholder={placeholder} | ||||
|             value={values ? values.join(', ') : ''} | ||||
|             onKeyDown={handleKeyDown} | ||||
|             onChange={handleChange} | ||||
|             style={{ width: '100%' }} | ||||
|             variant="outlined" | ||||
|             size="small" | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| @ -1,6 +1,6 @@ | ||||
| import { ListItem } from '@material-ui/core'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from 'component/common/ListPlaceholder/ListPlaceholder.styles'; | ||||
| 
 | ||||
| interface IListPlaceholderProps { | ||||
|  | ||||
| @ -2,7 +2,7 @@ import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import classnames from 'classnames'; | ||||
| import HeaderTitle from '../HeaderTitle'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { Paper } from '@material-ui/core'; | ||||
| import { useStyles } from './styles'; | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import classnames from 'classnames'; | ||||
| import { useStyles } from './PaginationUI.styles'; | ||||
| 
 | ||||
|  | ||||
| @ -1,8 +1,13 @@ | ||||
| import { IconButton, InputAdornment, TextField } from '@material-ui/core'; | ||||
| import { | ||||
|     IconButton, | ||||
|     InputAdornment, | ||||
|     TextField, | ||||
|     TextFieldProps, | ||||
| } from '@material-ui/core'; | ||||
| import { Visibility, VisibilityOff } from '@material-ui/icons'; | ||||
| import React, { useState } from 'react'; | ||||
| import React, { useState, VFC } from 'react'; | ||||
| 
 | ||||
| const PasswordField = ({ ...rest }) => { | ||||
| const PasswordField: VFC<TextFieldProps> = ({ ...rest }) => { | ||||
|     const [showPassword, setShowPassword] = useState(false); | ||||
| 
 | ||||
|     const handleClickShowPassword = () => { | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { Button, ButtonProps } from '@material-ui/core'; | ||||
| import { Lock } from '@material-ui/icons'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import React, { useContext } from 'react'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver'; | ||||
| import { formatAccessText } from 'utils/formatAccessText'; | ||||
| import { useId } from 'hooks/useId'; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { useState, useEffect } from 'react'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Typography } from '@material-ui/core'; | ||||
| import { useStyles } from './Proclamation.styles'; | ||||
| import { IProclamationToast } from 'interfaces/uiConfig'; | ||||
|  | ||||
| @ -1,79 +0,0 @@ | ||||
| import React from 'react'; | ||||
| import { MenuItem } from '@material-ui/core'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import DropdownMenu from '../DropdownMenu/DropdownMenu'; | ||||
| import useProjects from 'hooks/api/getters/useProjects/useProjects'; | ||||
| 
 | ||||
| const ALL_PROJECTS = { id: '*', name: '> All projects' }; | ||||
| 
 | ||||
| const ProjectSelect = ({ currentProjectId, updateCurrentProject, ...rest }) => { | ||||
|     const { projects } = useProjects(); | ||||
| 
 | ||||
|     const setProject = v => { | ||||
|         const id = v && typeof v === 'string' ? v.trim() : '*'; | ||||
|         updateCurrentProject(id); | ||||
|     }; | ||||
| 
 | ||||
|     // TODO fixme | ||||
|     let curentProject = projects.find(i => i.id === currentProjectId); | ||||
|     if (!curentProject) { | ||||
|         curentProject = ALL_PROJECTS; | ||||
|     } | ||||
| 
 | ||||
|     const handleChangeProject = e => { | ||||
|         const target = e.target.getAttribute('data-target'); | ||||
|         setProject(target); | ||||
|     }; | ||||
| 
 | ||||
|     const renderProjectItem = (selectedId, item) => ( | ||||
|         <MenuItem | ||||
|             disabled={selectedId === item.id} | ||||
|             data-target={item.id} | ||||
|             key={item.id} | ||||
|             style={{ fontSize: '14px' }} | ||||
|         > | ||||
|             {item.name} | ||||
|         </MenuItem> | ||||
|     ); | ||||
| 
 | ||||
|     const renderProjectOptions = () => { | ||||
|         const start = [ | ||||
|             <MenuItem | ||||
|                 disabled={curentProject === ALL_PROJECTS} | ||||
|                 data-target={ALL_PROJECTS.id} | ||||
|                 key={ALL_PROJECTS.id} | ||||
|                 style={{ fontSize: '14px' }} | ||||
|             > | ||||
|                 {ALL_PROJECTS.name} | ||||
|             </MenuItem>, | ||||
|         ]; | ||||
| 
 | ||||
|         return [ | ||||
|             ...start, | ||||
|             ...projects.map(p => renderProjectItem(currentProjectId, p)), | ||||
|         ]; | ||||
|     }; | ||||
| 
 | ||||
|     const { updateSetting, ...passDown } = rest; | ||||
| 
 | ||||
|     return ( | ||||
|         <React.Fragment> | ||||
|             <DropdownMenu | ||||
|                 id={'project'} | ||||
|                 title="Select project" | ||||
|                 label={`${curentProject.name}`} | ||||
|                 callback={handleChangeProject} | ||||
|                 renderOptions={renderProjectOptions} | ||||
|                 className="" | ||||
|                 {...passDown} | ||||
|             /> | ||||
|         </React.Fragment> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ProjectSelect.propTypes = { | ||||
|     currentProjectId: PropTypes.string.isRequired, | ||||
|     updateCurrentProject: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default ProjectSelect; | ||||
| @ -0,0 +1,74 @@ | ||||
| import React, { MouseEventHandler, useMemo, VFC } from 'react'; | ||||
| import { MenuItem, Typography } from '@material-ui/core'; | ||||
| import DropdownMenu, { IDropdownMenuProps } from '../DropdownMenu/DropdownMenu'; | ||||
| import useProjects from 'hooks/api/getters/useProjects/useProjects'; | ||||
| import { IProjectCard } from 'interfaces/project'; | ||||
| 
 | ||||
| const ALL_PROJECTS = { id: '*', name: '> All projects' }; | ||||
| 
 | ||||
| interface IProjectSelectProps { | ||||
|     currentProjectId: string; | ||||
|     updateCurrentProject: (id: string) => void; | ||||
| } | ||||
| 
 | ||||
| const ProjectSelect: VFC<IProjectSelectProps & Partial<IDropdownMenuProps>> = ({ | ||||
|     currentProjectId, | ||||
|     updateCurrentProject, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const { projects } = useProjects(); | ||||
| 
 | ||||
|     const setProject = (value?: string | null) => { | ||||
|         const id = value && typeof value === 'string' ? value.trim() : '*'; | ||||
|         updateCurrentProject(id); | ||||
|     }; | ||||
| 
 | ||||
|     const currentProject = useMemo(() => { | ||||
|         const project = projects.find(i => i.id === currentProjectId); | ||||
|         return project || ALL_PROJECTS; | ||||
|     }, [currentProjectId, projects]); | ||||
| 
 | ||||
|     const handleChangeProject: MouseEventHandler = event => { | ||||
|         const target = (event.target as Element).getAttribute('data-target'); | ||||
|         setProject(target); | ||||
|     }; | ||||
| 
 | ||||
|     const renderProjectItem = (selectedId: string, item: IProjectCard) => ( | ||||
|         <MenuItem | ||||
|             disabled={selectedId === item.id} | ||||
|             data-target={item.id} | ||||
|             key={item.id} | ||||
|             style={{ fontSize: '14px' }} | ||||
|         > | ||||
|             {item.name} | ||||
|         </MenuItem> | ||||
|     ); | ||||
| 
 | ||||
|     const renderProjectOptions = () => [ | ||||
|         <MenuItem | ||||
|             disabled={currentProject === ALL_PROJECTS} | ||||
|             data-target={ALL_PROJECTS.id} | ||||
|             key={ALL_PROJECTS.id} | ||||
|         > | ||||
|             <Typography variant="body2">{ALL_PROJECTS.name}</Typography> | ||||
|         </MenuItem>, | ||||
|         ...projects.map(project => | ||||
|             renderProjectItem(currentProjectId, project) | ||||
|         ), | ||||
|     ]; | ||||
| 
 | ||||
|     return ( | ||||
|         <React.Fragment> | ||||
|             <DropdownMenu | ||||
|                 id={'project'} | ||||
|                 title="Select project" | ||||
|                 label={`${currentProject.name}`} | ||||
|                 callback={handleChangeProject} | ||||
|                 renderOptions={renderProjectOptions} | ||||
|                 {...rest} | ||||
|             /> | ||||
|         </React.Fragment> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default ProjectSelect; | ||||
| @ -1,27 +0,0 @@ | ||||
| import { Route, useLocation, Redirect } from 'react-router-dom'; | ||||
| 
 | ||||
| const ProtectedRoute = ({ | ||||
|     component: Component, | ||||
|     unauthorized, | ||||
|     renderProps = {}, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const { pathname, search } = useLocation(); | ||||
|     const redirect = encodeURIComponent(pathname + search); | ||||
|     const loginLink = `/login?redirect=${redirect}`; | ||||
| 
 | ||||
|     return ( | ||||
|         <Route | ||||
|             {...rest} | ||||
|             render={props => { | ||||
|                 if (unauthorized) { | ||||
|                     return <Redirect to={loginLink} />; | ||||
|                 } else { | ||||
|                     return <Component {...props} {...renderProps} />; | ||||
|                 } | ||||
|             }} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default ProtectedRoute; | ||||
| @ -0,0 +1,24 @@ | ||||
| import { VFC } from 'react'; | ||||
| import { Route, useLocation, Redirect, RouteProps } from 'react-router-dom'; | ||||
| 
 | ||||
| interface IProtectedRouteProps { | ||||
|     unauthorized?: boolean; | ||||
| } | ||||
| 
 | ||||
| const ProtectedRoute: VFC<IProtectedRouteProps & RouteProps> = ({ | ||||
|     component: Component, | ||||
|     unauthorized, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const { pathname, search } = useLocation(); | ||||
|     const redirect = encodeURIComponent(pathname + search); | ||||
|     const loginLink = `/login?redirect=${redirect}`; | ||||
| 
 | ||||
|     return unauthorized ? ( | ||||
|         <Route {...rest} render={() => <Redirect to={loginLink} />} /> | ||||
|     ) : ( | ||||
|         <Route {...rest} component={Component} /> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default ProtectedRoute; | ||||
| @ -1,5 +1,5 @@ | ||||
| import { useMediaQuery } from '@material-ui/core'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import PermissionButton from '../PermissionButton/PermissionButton'; | ||||
| import PermissionIconButton from '../PermissionIconButton/PermissionIconButton'; | ||||
| import React from 'react'; | ||||
|  | ||||
| @ -1,24 +1,24 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import React, { useState, VFC } from 'react'; | ||||
| import classnames from 'classnames'; | ||||
| import { debounce } from 'debounce'; | ||||
| import { InputBase, Chip } from '@material-ui/core'; | ||||
| import SearchIcon from '@material-ui/icons/Search'; | ||||
| import { useStyles } from 'component/common/SearchField/styles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface ISearchFieldProps { | ||||
|     updateValue: React.Dispatch<React.SetStateAction<string>>; | ||||
|     updateValue: (value: string) => void; | ||||
|     initialValue?: string; | ||||
|     className?: string; | ||||
|     showValueChip?: boolean; | ||||
| } | ||||
| 
 | ||||
| export const SearchField = ({ | ||||
| export const SearchField: VFC<ISearchFieldProps> = ({ | ||||
|     updateValue, | ||||
|     initialValue = '', | ||||
|     className = '', | ||||
|     showValueChip, | ||||
| }: ISearchFieldProps) => { | ||||
| }) => { | ||||
|     const styles = useStyles(); | ||||
|     const [localValue, setLocalValue] = useState(initialValue); | ||||
|     const debounceUpdateValue = debounce(updateValue, 500); | ||||
|  | ||||
| @ -1,3 +0,0 @@ | ||||
| import SearchField from './SearchField'; | ||||
| 
 | ||||
| export default SearchField; | ||||
| @ -3,7 +3,7 @@ import { useStyles } from './StatusChip.styles'; | ||||
| 
 | ||||
| interface IStatusChip { | ||||
|     stale: boolean; | ||||
|     showActive?: true; | ||||
|     showActive?: boolean; | ||||
| } | ||||
| 
 | ||||
| const StatusChip = ({ stale, showActive = true }: IStatusChip) => { | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { Tooltip } from '@material-ui/core'; | ||||
| import ConditionallyRender from '../ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IStringTruncatorProps { | ||||
|     text: string; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { useState } from 'react'; | ||||
| import { IconButton, Tooltip } from '@material-ui/core'; | ||||
| import { Search } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import AnimateOnMount from 'component/common/AnimateOnMount/AnimateOnMount'; | ||||
| import { TableSearchField } from 'component/common/Table/TableActions/TableSearchField/TableSearchField'; | ||||
| import { useStyles } from 'component/common/Table/TableActions/TableActions.styles'; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { IconButton, InputBase, Tooltip } from '@material-ui/core'; | ||||
| import { Search, Close } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from 'component/common/Table/TableActions/TableSearchField/TableSearchField.styles'; | ||||
| import classnames from 'classnames'; | ||||
| 
 | ||||
|  | ||||
| @ -7,7 +7,7 @@ import { | ||||
|     KeyboardArrowUp, | ||||
| } from '@material-ui/icons'; | ||||
| import { IUsersSort, UsersSortType } from 'hooks/useUsersSort'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useStyles } from 'component/common/Table/TableCellSortable/TableCellSortable.styles'; | ||||
| import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext'; | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ import { useContext } from 'react'; | ||||
| import { IconButton, Tooltip } from '@material-ui/core'; | ||||
| import CheckMarkBadge from 'component/common/CheckmarkBadge/CheckMarkBadge'; | ||||
| import UIContext from 'contexts/UIContext'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import Close from '@material-ui/icons/Close'; | ||||
| import { IToast } from 'interfaces/toast'; | ||||
| 
 | ||||
|  | ||||
| @ -83,10 +83,6 @@ | ||||
|     min-height: 200px; | ||||
| } | ||||
| 
 | ||||
| .dropdownButton { | ||||
|     font-weight: normal; | ||||
| } | ||||
| 
 | ||||
| .toggleName { | ||||
|     color: #37474f !important; | ||||
|     font-weight: 700; | ||||
|  | ||||
| @ -15,7 +15,7 @@ import { | ||||
| import { Apps } from '@material-ui/icons'; | ||||
| 
 | ||||
| import styles from './common.module.scss'; | ||||
| import ConditionallyRender from './ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from './ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| export { styles }; | ||||
| 
 | ||||
| @ -114,38 +114,6 @@ IconLink.propTypes = { | ||||
|     icon: PropTypes.object, | ||||
| }; | ||||
| 
 | ||||
| export const DropdownButton = ({ | ||||
|     label, | ||||
|     id, | ||||
|     className = styles.dropdownButton, | ||||
|     title, | ||||
|     icon, | ||||
|     startIcon, | ||||
|     style, | ||||
|     ...rest | ||||
| }) => ( | ||||
|     <Button | ||||
|         id={id} | ||||
|         className={className} | ||||
|         title={title} | ||||
|         style={style} | ||||
|         {...rest} | ||||
|         startIcon={startIcon} | ||||
|         endIcon={<Icon>{icon}</Icon>} | ||||
|     > | ||||
|         {label} | ||||
|     </Button> | ||||
| ); | ||||
| 
 | ||||
| DropdownButton.propTypes = { | ||||
|     label: PropTypes.string, | ||||
|     style: PropTypes.object, | ||||
|     id: PropTypes.string, | ||||
|     title: PropTypes.string, | ||||
|     icon: PropTypes.object, | ||||
|     startIcon: PropTypes.object, | ||||
| }; | ||||
| 
 | ||||
| export const MenuItemWithIcon = React.forwardRef( | ||||
|     ({ icon: IconComponent, label, disabled, ...menuItemProps }, ref) => ( | ||||
|         <MenuItem | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { useStyles } from 'component/context/ContectFormChip/ContextFormChip.styles'; | ||||
| import { Cancel } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IContextFormChipProps { | ||||
|     label: string; | ||||
|  | ||||
| @ -1,11 +1,6 @@ | ||||
| import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     CREATE_CONTEXT_FIELD, | ||||
|     DELETE_CONTEXT_FIELD, | ||||
|     UPDATE_CONTEXT_FIELD, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import { useContext, useState, VFC } from 'react'; | ||||
| import { Add, Album, Delete, Edit } from '@material-ui/icons'; | ||||
| import { Link, useHistory } from 'react-router-dom'; | ||||
| import { | ||||
|     Button, | ||||
|     IconButton, | ||||
| @ -16,30 +11,38 @@ import { | ||||
|     Tooltip, | ||||
|     useMediaQuery, | ||||
| } from '@material-ui/core'; | ||||
| import { Add, Album, Delete, Edit } from '@material-ui/icons'; | ||||
| import { useContext, useState } from 'react'; | ||||
| import { Link, useHistory } from 'react-router-dom'; | ||||
| import { useStyles } from './styles'; | ||||
| import ConfirmDialogue from 'component/common/Dialogue'; | ||||
| import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     CREATE_CONTEXT_FIELD, | ||||
|     DELETE_CONTEXT_FIELD, | ||||
|     UPDATE_CONTEXT_FIELD, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; | ||||
| import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import { useStyles } from './styles'; | ||||
| 
 | ||||
| const ContextList = () => { | ||||
| const ContextList: VFC = () => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
|     const [showDelDialogue, setShowDelDialogue] = useState(false); | ||||
|     const smallScreen = useMediaQuery('(max-width:700px)'); | ||||
|     const [name, setName] = useState(); | ||||
|     const [name, setName] = useState<string>(); | ||||
|     const { context, refetchUnleashContext } = useUnleashContext(); | ||||
|     const { removeContext } = useContextsApi(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const history = useHistory(); | ||||
|     const styles = useStyles(); | ||||
| 
 | ||||
|     const onDeleteContext = async name => { | ||||
|     const onDeleteContext = async () => { | ||||
|         try { | ||||
|             if (name === undefined) { | ||||
|                 throw new Error(); | ||||
|             } | ||||
|             await removeContext(name); | ||||
|             refetchUnleashContext(); | ||||
|             setToastData({ | ||||
| @ -93,6 +96,7 @@ const ContextList = () => { | ||||
|                     show={ | ||||
|                         <Tooltip title="Delete context field"> | ||||
|                             <IconButton | ||||
|                                 aria-label="delete" | ||||
|                                 onClick={() => { | ||||
|                                     setName(field.name); | ||||
|                                     setShowDelDialogue(true); | ||||
| @ -133,6 +137,7 @@ const ContextList = () => { | ||||
|             } | ||||
|         /> | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent | ||||
|             headerContent={ | ||||
| @ -151,7 +156,7 @@ const ContextList = () => { | ||||
|             </List> | ||||
|             <ConfirmDialogue | ||||
|                 open={showDelDialogue} | ||||
|                 onClick={() => onDeleteContext(name)} | ||||
|                 onClick={onDeleteContext} | ||||
|                 onClose={() => { | ||||
|                     setName(undefined); | ||||
|                     setShowDelDialogue(false); | ||||
| @ -10,10 +10,10 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; | ||||
| import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import PageContent from 'component/common/PageContent/PageContent'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import HeaderTitle from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| 
 | ||||
| const CreateEnvironment = () => { | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import React from 'react'; | ||||
| import { IEnvironment } from 'interfaces/environments'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import Input from 'component/common/Input/Input'; | ||||
| import EnvironmentCard from '../EnvironmentCard/EnvironmentCard'; | ||||
| import { useStyles } from './EnvironmentDeleteConfirm.styles'; | ||||
|  | ||||
| @ -1,21 +1,21 @@ | ||||
| import HeaderTitle from 'component/common/HeaderTitle'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import { Add } from '@material-ui/icons'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import { List } from '@material-ui/core'; | ||||
| import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; | ||||
| import { IEnvironment, ISortOrderPayload } from 'interfaces/environments'; | ||||
| import { useState } from 'react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import EnvironmentDeleteConfirm from './EnvironmentDeleteConfirm/EnvironmentDeleteConfirm'; | ||||
| import { List } from '@material-ui/core'; | ||||
| import { Add } from '@material-ui/icons'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { IEnvironment, ISortOrderPayload } from 'interfaces/environments'; | ||||
| import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import PageContent from 'component/common/PageContent'; | ||||
| import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; | ||||
| import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi'; | ||||
| import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem'; | ||||
| import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm'; | ||||
| import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem'; | ||||
| import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm'; | ||||
| import EnvironmentDeleteConfirm from './EnvironmentDeleteConfirm/EnvironmentDeleteConfirm'; | ||||
| 
 | ||||
| const EnvironmentList = () => { | ||||
|     const defaultEnv = { | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { | ||||
|     Edit, | ||||
|     OfflineBolt, | ||||
| } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| import { IEnvironment } from 'interfaces/environments'; | ||||
| import React, { useContext, useRef } from 'react'; | ||||
|  | ||||
| @ -2,8 +2,8 @@ import { capitalize } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import React from 'react'; | ||||
| import { IEnvironment } from 'interfaces/environments'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import EnvironmentCard from '../EnvironmentCard/EnvironmentCard'; | ||||
| 
 | ||||
| interface IEnvironmentToggleConfirmProps { | ||||
|  | ||||
| @ -1,4 +1,10 @@ | ||||
| import { useState, useRef, useEffect } from 'react'; | ||||
| import { | ||||
|     useState, | ||||
|     useRef, | ||||
|     useEffect, | ||||
|     FormEventHandler, | ||||
|     ChangeEventHandler, | ||||
| } from 'react'; | ||||
| import { Link, useHistory, useParams } from 'react-router-dom'; | ||||
| import { | ||||
|     Button, | ||||
| @ -9,33 +15,35 @@ import { | ||||
| } from '@material-ui/core'; | ||||
| import { FileCopy } from '@material-ui/icons'; | ||||
| import { styles as commonStyles } from 'component/common'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import styles from './CopyFeature.module.scss'; | ||||
| import { trim } from 'component/common/util'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { getTogglePath } from 'utils/routePathHelpers'; | ||||
| import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| 
 | ||||
| export const CopyFeatureToggle = () => { | ||||
|     const [replaceGroupId, setReplaceGroupId] = useState(true); | ||||
|     const [apiError, setApiError] = useState(''); | ||||
|     const [nameError, setNameError] = useState(undefined); | ||||
|     const [newToggleName, setNewToggleName] = useState(); | ||||
|     const [nameError, setNameError] = useState<string | undefined>(); | ||||
|     const [newToggleName, setNewToggleName] = useState<string>(); | ||||
|     const { cloneFeatureToggle, validateFeatureToggleName } = useFeatureApi(); | ||||
|     const inputRef = useRef(); | ||||
|     const { name: copyToggleName, id: projectId } = useParams(); | ||||
|     const inputRef = useRef<HTMLInputElement>(); | ||||
|     const { name: copyToggleName, id: projectId } = useParams<{ | ||||
|         name: string; | ||||
|         id: string; | ||||
|     }>(); | ||||
|     const { feature } = useFeature(projectId, copyToggleName); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const history = useHistory(); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         inputRef.current?.focus(); | ||||
|     }, []); | ||||
| 
 | ||||
|     const setValue = evt => { | ||||
|         const value = trim(evt.target.value); | ||||
|     const setValue: ChangeEventHandler<HTMLInputElement> = event => { | ||||
|         const value = trim(event.target.value); | ||||
|         setNewToggleName(value); | ||||
|     }; | ||||
| 
 | ||||
| @ -48,13 +56,13 @@ export const CopyFeatureToggle = () => { | ||||
|             await validateFeatureToggleName(newToggleName); | ||||
| 
 | ||||
|             setNameError(undefined); | ||||
|         } catch (err) { | ||||
|             setNameError(err.message); | ||||
|         } catch (error) { | ||||
|             setNameError(formatUnknownError(error)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const onSubmit = async evt => { | ||||
|         evt.preventDefault(); | ||||
|     const onSubmit: FormEventHandler = async event => { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         if (nameError) { | ||||
|             return; | ||||
| @ -62,14 +70,12 @@ export const CopyFeatureToggle = () => { | ||||
| 
 | ||||
|         try { | ||||
|             await cloneFeatureToggle(projectId, copyToggleName, { | ||||
|                 name: newToggleName, | ||||
|                 name: newToggleName as string, | ||||
|                 replaceGroupId, | ||||
|             }); | ||||
|             history.push( | ||||
|                 getTogglePath(projectId, newToggleName, uiConfig.flags.E) | ||||
|             ); | ||||
|         } catch (e) { | ||||
|             setApiError(e.toString()); | ||||
|             history.push(getTogglePath(projectId, newToggleName as string)); | ||||
|         } catch (error) { | ||||
|             setApiError(formatUnknownError(error)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| @ -84,7 +90,7 @@ export const CopyFeatureToggle = () => { | ||||
|                 <h1>Copy {copyToggleName}</h1> | ||||
|             </div> | ||||
|             <ConditionallyRender | ||||
|                 condition={apiError} | ||||
|                 condition={Boolean(apiError)} | ||||
|                 show={<Alert severity="error">{apiError}</Alert>} | ||||
|             /> | ||||
|             <section className={styles.content}> | ||||
| @ -116,7 +122,6 @@ export const CopyFeatureToggle = () => { | ||||
|                             <Switch | ||||
|                                 value={replaceGroupId} | ||||
|                                 checked={replaceGroupId} | ||||
|                                 label="Replace groupId" | ||||
|                                 onChange={toggleReplaceGroupId} | ||||
|                             /> | ||||
|                         } | ||||
| @ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; | ||||
| import { Button, IconButton, Tooltip } from '@material-ui/core'; | ||||
| import useMediaQuery from '@material-ui/core/useMediaQuery'; | ||||
| import { Add } from '@material-ui/icons'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { NAVIGATE_TO_CREATE_FEATURE } from 'utils/testIds'; | ||||
| import { IFeaturesFilter } from 'hooks/useFeaturesFilter'; | ||||
| import { useCreateFeaturePath } from 'component/feature/CreateFeatureButton/useCreateFeaturePath'; | ||||
|  | ||||
| @ -12,7 +12,7 @@ import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes'; | ||||
| import { KeyboardArrowDownOutlined } from '@material-ui/icons'; | ||||
| import { projectFilterGenerator } from 'utils/projectFilterGenerator'; | ||||
| import FeatureProjectSelect from '../FeatureView/FeatureSettings/FeatureSettingsProject/FeatureProjectSelect/FeatureProjectSelect'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { trim } from 'component/common/util'; | ||||
| import Input from 'component/common/Input/Input'; | ||||
| import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import React from 'react'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { IFeatureToggle } from 'interfaces/featureToggle'; | ||||
| import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit'; | ||||
|  | ||||
| @ -13,7 +13,7 @@ import { useStyles } from './FeatureStrategyForm.styles'; | ||||
| import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds'; | ||||
| import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import { Checkbox, FormControlLabel } from '@material-ui/core'; | ||||
| import { PRODUCTION } from 'constants/environmentTypes'; | ||||
|  | ||||
| @ -4,13 +4,13 @@ import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit'; | ||||
| import Dialogue from 'component/common/Dialogue'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import PermissionButton from 'component/common/PermissionButton/PermissionButton'; | ||||
| import { DELETE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; | ||||
| import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | ||||
| import { STRATEGY_FORM_REMOVE_ID } from 'utils/testIds'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||
| import { Delete } from '@material-ui/icons'; | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; | ||||
| import { ISegment } from 'interfaces/segment'; | ||||
| import { Clear, VisibilityOff, Visibility } from '@material-ui/icons'; | ||||
| import { useStyles } from './FeatureStrategySegmentChip.styles'; | ||||
| import ConditionallyRender from 'component/common/ConditionallyRender'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { constraintAccordionListId } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; | ||||
| import { Tooltip } from '@material-ui/core'; | ||||
| 
 | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user