diff --git a/frontend/src/app.css b/frontend/src/app.css index ab615b4b49..0ab5cdcfd6 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -22,6 +22,24 @@ src: url('./assets/fonts/Roboto-700.ttf'); } +@font-face { + font-family: 'Sen'; + font-weight: 400; + src: url('./assets/fonts/Sen-Regular.ttf'); +} + +@font-face { + font-family: 'Sen'; + font-weight: 500; + src: url('./assets/fonts/Sen-Bold.ttf'); +} + +@font-face { + font-family: 'Sen'; + font-weight: 700; + src: url('./assets/fonts/Sen-ExtraBold.ttf'); +} + * { box-sizing: border-box; } @@ -34,10 +52,12 @@ html { body { height: 100%; + /* font-family: 'Sen'; */ } .MuiButton-root { border-radius: 3px; + text-transform: none; } .skeleton { diff --git a/frontend/src/assets/fonts/Sen-Bold.ttf b/frontend/src/assets/fonts/Sen-Bold.ttf new file mode 100644 index 0000000000..bd184b83cb Binary files /dev/null and b/frontend/src/assets/fonts/Sen-Bold.ttf differ diff --git a/frontend/src/assets/fonts/Sen-ExtraBold.ttf b/frontend/src/assets/fonts/Sen-ExtraBold.ttf new file mode 100644 index 0000000000..13b3be3618 Binary files /dev/null and b/frontend/src/assets/fonts/Sen-ExtraBold.ttf differ diff --git a/frontend/src/assets/fonts/Sen-Regular.ttf b/frontend/src/assets/fonts/Sen-Regular.ttf new file mode 100644 index 0000000000..8b08770a02 Binary files /dev/null and b/frontend/src/assets/fonts/Sen-Regular.ttf differ diff --git a/frontend/src/assets/img/logo-dark-with-text.svg b/frontend/src/assets/img/logo-dark-with-text.svg new file mode 100644 index 0000000000..51862398e8 --- /dev/null +++ b/frontend/src/assets/img/logo-dark-with-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/img/logo-dark.svg b/frontend/src/assets/img/logo-dark.svg new file mode 100644 index 0000000000..b79f917031 --- /dev/null +++ b/frontend/src/assets/img/logo-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/img/logo-with-name.svg b/frontend/src/assets/img/logo-with-name.svg new file mode 100644 index 0000000000..ed650f953d --- /dev/null +++ b/frontend/src/assets/img/logo-with-name.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/img/texture.svg b/frontend/src/assets/img/texture.svg new file mode 100644 index 0000000000..648099ac5d --- /dev/null +++ b/frontend/src/assets/img/texture.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/src/common.styles.js b/frontend/src/common.styles.js index f962781817..318abaaa33 100644 --- a/frontend/src/common.styles.js +++ b/frontend/src/common.styles.js @@ -69,6 +69,12 @@ export const useCommonStyles = makeStyles(theme => ({ bottom: '40px', transform: 'translateY(400px)', }, + fadeInBottomStartWithoutFixed: { + opacity: '0', + right: '40px', + bottom: '40px', + transform: 'translateY(400px)', + }, fadeInBottomEnter: { transform: 'translateY(0)', opacity: '1', diff --git a/frontend/src/component/AccessProvider/AccessProvider.tsx b/frontend/src/component/AccessProvider/AccessProvider.tsx index 53fedb6d23..31d2c9f3c3 100644 --- a/frontend/src/component/AccessProvider/AccessProvider.tsx +++ b/frontend/src/component/AccessProvider/AccessProvider.tsx @@ -14,6 +14,25 @@ interface IPermission { } const AccessProvider: FC = ({ store, children }) => { + const isAdminHigherOrder = () => { + let called = false; + let result = false; + + return () => { + if (called) return result; + const permissions = store.getState().user.get('permissions') || []; + result = permissions.some( + (p: IPermission) => p.permission === ADMIN + ); + + if (permissions.length > 0) { + called = true; + } + }; + }; + + const isAdmin = isAdminHigherOrder(); + const hasAccess = (permission: string, project: string) => { const permissions = store.getState().user.get('permissions') || []; @@ -36,7 +55,7 @@ const AccessProvider: FC = ({ store, children }) => { return result; }; - const context = { hasAccess }; + const context = { hasAccess, isAdmin }; return ( diff --git a/frontend/src/component/AccessProvider/permissions.ts b/frontend/src/component/AccessProvider/permissions.ts index b9bd814315..34b597774d 100644 --- a/frontend/src/component/AccessProvider/permissions.ts +++ b/frontend/src/component/AccessProvider/permissions.ts @@ -1,4 +1,5 @@ export const ADMIN = 'ADMIN'; +export const EDITOR = 'EDITOR'; export const CREATE_FEATURE = 'CREATE_FEATURE'; export const UPDATE_FEATURE = 'UPDATE_FEATURE'; export const DELETE_FEATURE = 'DELETE_FEATURE'; diff --git a/frontend/src/component/addons/form-addon-component.jsx b/frontend/src/component/addons/form-addon-component.jsx index 47a8dcc434..ab8fa4260f 100644 --- a/frontend/src/component/addons/form-addon-component.jsx +++ b/frontend/src/component/addons/form-addon-component.jsx @@ -32,7 +32,6 @@ const AddonFormComponent = ({ }, [fetch, provider]); // empty array => fetch only first time useEffect(() => { - console.log(addon); setConfig({ ...addon }); /* eslint-disable-next-line */ }, [addon.description, addon.provider]); diff --git a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap index 46e78fbfd2..bebdb4496c 100644 --- a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap +++ b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap @@ -24,6 +24,12 @@ exports[`renders correctly if no application 1`] = ` exports[`renders correctly with permissions 1`] = `
({ + breadcrumbNav: { + position: 'absolute', + top: '4px', + }, + breadcrumbNavParagraph: { textTransform: 'capitalize', color: 'inherit' }, + breadcrumbLink: { + textTransform: 'capitalize', + textDecoration: 'none', + }, +})); diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx new file mode 100644 index 0000000000..a3617ee0f7 --- /dev/null +++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx @@ -0,0 +1,54 @@ +import Breadcrumbs from '@material-ui/core/Breadcrumbs'; +import { Link, useLocation } from 'react-router-dom'; +import ConditionallyRender from '../ConditionallyRender'; +import { useStyles } from './BreadcrumbNav.styles'; + +const BreadcrumbNav = () => { + const styles = useStyles(); + const location = useLocation(); + + const paths = location.pathname + .split('/') + .filter(item => item) + .filter( + item => + item !== 'create' && + item !== 'edit' && + item !== 'access' && + item !== 'view' && + item !== 'variants' && + item !== 'logs' && + item !== 'metrics' && + item !== 'copy' + ); + + return ( + 1} + show={ + + {paths.map((path, index) => { + const lastItem = index === paths.length - 1; + if (lastItem) { + return ( +

+ {path} +

+ ); + } + return ( + + {path} + + ); + })} +
+ } + /> + ); +}; + +export default BreadcrumbNav; diff --git a/frontend/src/component/common/PageContent/PageContent.jsx b/frontend/src/component/common/PageContent/PageContent.jsx index 06e1c0ab70..026c0aa648 100644 --- a/frontend/src/component/common/PageContent/PageContent.jsx +++ b/frontend/src/component/common/PageContent/PageContent.jsx @@ -41,7 +41,11 @@ const PageContent = ({ const paperProps = disableBorder ? { elevation: 0 } : {}; return ( - + {header}
{children}
diff --git a/frontend/src/component/common/PaginateUI/PaginateUI.tsx b/frontend/src/component/common/PaginateUI/PaginateUI.tsx index 8ed87b627e..6d4ea54126 100644 --- a/frontend/src/component/common/PaginateUI/PaginateUI.tsx +++ b/frontend/src/component/common/PaginateUI/PaginateUI.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import ConditionallyRender from '../ConditionallyRender'; import classnames from 'classnames'; import { useStyles } from './PaginationUI.styles'; @@ -7,6 +7,7 @@ import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; import DoubleArrowIcon from '@material-ui/icons/DoubleArrow'; +import { useMediaQuery, useTheme } from '@material-ui/core'; interface IPaginateUIProps { pages: any[]; @@ -24,9 +25,17 @@ const PaginateUI = ({ nextPage, }: IPaginateUIProps) => { const STARTLIMIT = 6; + const theme = useTheme(); const styles = useStyles(); const [limit, setLimit] = useState(STARTLIMIT); const [start, setStart] = useState(0); + const matches = useMediaQuery(theme.breakpoints.down('sm')); + + useEffect(() => { + if (matches) { + setLimit(4); + } + }, [matches]); return ( { return false; } } - console.log('RETURNING TRUE'); return true; }; diff --git a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx index e4155bbd7d..86ca6f1a57 100644 --- a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx +++ b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx @@ -8,12 +8,13 @@ interface IResponsiveButtonProps { maxWidth: string; } -const ResponsiveButton = ({ +const ResponsiveButton: React.FC = ({ Icon, onClick, maxWidth, tooltip, -}: IResponsiveButtonProps) => { + children, +}) => { const smallScreen = useMediaQuery(`(max-width:${maxWidth})`); return ( @@ -21,14 +22,19 @@ const ResponsiveButton = ({ condition={smallScreen} show={ - + } elseShow={ - } /> diff --git a/frontend/src/component/common/SearchField/styles.js b/frontend/src/component/common/SearchField/styles.js index 2b05321c1c..09a4e41d1f 100644 --- a/frontend/src/component/common/SearchField/styles.js +++ b/frontend/src/component/common/SearchField/styles.js @@ -5,7 +5,7 @@ export const useStyles = makeStyles(theme => ({ display: 'flex', alignItems: 'center', backgroundColor: theme.palette.searchField.main, - borderRadius: theme.borders.radius.main, + borderRadius: '25px', padding: '0.25rem 0.5rem', maxWidth: '450px', [theme.breakpoints.down('sm')]: { diff --git a/frontend/src/component/common/Toast/Toast.tsx b/frontend/src/component/common/Toast/Toast.tsx index c00e5c0afe..c626edf1d0 100644 --- a/frontend/src/component/common/Toast/Toast.tsx +++ b/frontend/src/component/common/Toast/Toast.tsx @@ -24,7 +24,7 @@ const Toast = ({ {text} diff --git a/frontend/src/component/common/flags.js b/frontend/src/component/common/flags.js index 8fb12224b4..247c687fce 100644 --- a/frontend/src/component/common/flags.js +++ b/frontend/src/component/common/flags.js @@ -3,5 +3,4 @@ export const C = 'C'; export const RBAC = 'RBAC'; export const OIDC = 'OIDC'; -export const PROJECTCARDACTIONS = false; export const PROJECTFILTERING = false; diff --git a/frontend/src/component/common/util.js b/frontend/src/component/common/util.js index 00ec7e6afc..8d6eb63959 100644 --- a/frontend/src/component/common/util.js +++ b/frontend/src/component/common/util.js @@ -16,6 +16,13 @@ const dateOptions = { year: 'numeric', }; +export const filterByFlags = flags => r => { + if (r.flag && !flags[r.flag]) { + return false; + } + return true; +}; + export const scrollToTop = () => { window.scrollTo(0, 0); }; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItemChip/styles.js b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItemChip/styles.js index 7873aeece8..1ca8703e8e 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItemChip/styles.js +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListItem/FeatureToggleListItemChip/styles.js @@ -3,7 +3,8 @@ import { makeStyles } from '@material-ui/styles'; export const useStyles = makeStyles(theme => ({ typeChip: { margin: '0 8px', - boxShadow: theme.boxShadows.chip.main, - backgroundColor: theme.palette.chips.main, + background: 'transparent', + border: `1px solid ${theme.palette.primary.main}`, + color: theme.palette.primary.main, }, })); diff --git a/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap index a3e78ac35d..bc5137f9ec 100644 --- a/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap +++ b/frontend/src/component/feature/FeatureToggleList/__tests__/__snapshots__/list-component-test.jsx.snap @@ -41,6 +41,12 @@ exports[`renders correctly with one feature 1`] = `
({ tableCellHeader: { paddingBottom: '0.5rem', }, + typeHeader: { + [theme.breakpoints.down('sm')]: { + display: 'none', + }, + }, tableCellName: { width: '250px', }, @@ -20,6 +25,9 @@ export const useStyles = makeStyles(theme => ({ tableCellType: { display: 'flex', alignItems: 'center', + [theme.breakpoints.down('sm')]: { + display: 'none', + }, }, icon: { marginRight: '0.3rem', diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx index 6efb8962c1..02f539b3ac 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx @@ -89,7 +89,8 @@ const FeatureToggleListNew = ({ diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx index 79ff08cfb8..0fb5ba54dd 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx @@ -1,11 +1,18 @@ -import { useRef, useState } from 'react'; -import { Switch, TableCell, TableRow } from '@material-ui/core'; +import { useRef } from 'react'; +import { + Switch, + TableCell, + TableRow, + useMediaQuery, + useTheme, +} from '@material-ui/core'; import { useHistory } from 'react-router'; import { getFeatureTypeIcons } from '../../../../utils/get-feature-type-icons'; import { useStyles } from '../FeatureToggleListNew.styles'; import useToggleFeatureByEnv from '../../../../hooks/api/actions/useToggleFeatureByEnv/useToggleFeatureByEnv'; import { IEnvironments } from '../../../../interfaces/featureToggle'; -import Toast from '../../../common/Toast/Toast'; +import ConditionallyRender from '../../../common/ConditionallyRender'; +import useToast from '../../../../hooks/useToast'; interface IFeatureToggleListNewItemProps { name: string; @@ -20,15 +27,14 @@ const FeatureToggleListNewItem = ({ environments, projectId, }: IFeatureToggleListNewItemProps) => { + const theme = useTheme(); + const { toast, setToastData } = useToast(); + const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); const { toggleFeatureByEnvironment } = useToggleFeatureByEnv( projectId, name ); - const [snackbarData, setSnackbardata] = useState({ - show: false, - type: 'success', - text: '', - }); + const styles = useStyles(); const history = useHistory(); const ref = useRef(null); @@ -42,14 +48,14 @@ const FeatureToggleListNewItem = ({ const handleToggle = (env: IEnvironments) => { toggleFeatureByEnvironment(env.name, env.enabled) .then(() => { - setSnackbardata({ + setToastData({ show: true, type: 'success', text: 'Successfully updated toggle status.', }); }) .catch(e => { - setSnackbardata({ + setToastData({ show: true, type: 'error', text: e.toString(), @@ -57,10 +63,6 @@ const FeatureToggleListNewItem = ({ }); }; - const hideSnackbar = () => { - setSnackbardata(prev => ({ ...prev, show: false })); - }; - const IconComponent = getFeatureTypeIcons(type); return ( @@ -69,12 +71,21 @@ const FeatureToggleListNewItem = ({ {name} - -
- {' '} - {type} -
-
+ +
+ {' '} + {type} +
+
+ } + /> + {environments.map((env: IEnvironments) => { return ( - + {toast} ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureView.jsx b/frontend/src/component/feature/FeatureView/FeatureView.jsx index 03fc8c74d7..4d60c4e12e 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.jsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.jsx @@ -222,7 +222,11 @@ const FeatureView = ({ return (
diff --git a/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx b/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx index c424e219be..73677d77a2 100644 --- a/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx +++ b/frontend/src/component/feature/create/CreateFeature/CreateFeature.jsx @@ -18,6 +18,8 @@ import { } from '../../../../testIds'; import { CREATE_FEATURE } from '../../../AccessProvider/permissions'; import { projectFilterGenerator } from '../../../../utils/project-filter-generator'; +import { useHistory } from 'react-router-dom'; +import useQueryParams from '../../../../hooks/useQueryParams'; const CreateFeature = ({ input, @@ -25,9 +27,19 @@ const CreateFeature = ({ setValue, validateName, onSubmit, - onCancel, user, }) => { + const params = useQueryParams(); + const project = params.get('project'); + const history = useHistory(); + + useEffect(() => { + if (project) { + setValue('project', project); + } + /* eslint-disable-next-line */ + }, []); + useEffect(() => { window.onbeforeunload = () => 'Data will be lost if you leave the page, are you sure?'; @@ -37,6 +49,8 @@ const CreateFeature = ({ }; }, []); + const onCancel = () => history.goBack(); + return (
@@ -73,7 +87,7 @@ const CreateFeature = ({
setValue('project', v.target.value)} filter={projectFilterGenerator(user, CREATE_FEATURE)} /> diff --git a/frontend/src/component/feature/strategy/StrategyCard/StrategyCard.styles.js b/frontend/src/component/feature/strategy/StrategyCard/StrategyCard.styles.js index 7b0a825692..85c859be05 100644 --- a/frontend/src/component/feature/strategy/StrategyCard/StrategyCard.styles.js +++ b/frontend/src/component/feature/strategy/StrategyCard/StrategyCard.styles.js @@ -4,9 +4,7 @@ export const useStyles = makeStyles(theme => ({ strategyCard: { width: '337px', height: '100%', - [theme.breakpoints.down('xs')]: { - width: '100%', - }, + [theme.breakpoints.down('1250')]: { width: '300px', }, @@ -16,5 +14,8 @@ export const useStyles = makeStyles(theme => ({ [theme.breakpoints.down('860')]: { width: '380px', }, + [theme.breakpoints.down('xs')]: { + width: '100%', + }, }, })); diff --git a/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.styles.js b/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.styles.js index dabafe7bed..f980091f8d 100644 --- a/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.styles.js +++ b/frontend/src/component/feature/strategy/StrategyCard/StrategyCardHeader/StrategyCardHeader.styles.js @@ -9,7 +9,7 @@ export const useStyles = makeStyles(theme => ({ }, strategyCardHeader: { display: 'flex', - background: `linear-gradient(${theme.palette.cards.gradient.top}, ${theme.palette.cards.gradient.bottom})`, + background: theme.palette.primary.dark, color: '#fff', textAlign: 'left', }, diff --git a/frontend/src/component/feature/strategy/strategies-list-component.jsx b/frontend/src/component/feature/strategy/strategies-list-component.jsx index ae87d0b2cd..ce72e7f568 100644 --- a/frontend/src/component/feature/strategy/strategies-list-component.jsx +++ b/frontend/src/component/feature/strategy/strategies-list-component.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import cloneDeep from 'lodash.clonedeep'; import arrayMove from 'array-move'; -import { Button } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import HeaderTitle from '../../common/HeaderTitle'; @@ -14,6 +13,8 @@ import EditStrategyModal from './EditStrategyModal/EditStrategyModal'; import ConditionallyRender from '../../common/ConditionallyRender'; import CreateStrategy from './AddStrategy/AddStrategy'; import Dialogue from '../../common/Dialogue/Dialogue'; +import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton'; +import { Add } from '@material-ui/icons'; const cleanStrategy = strategy => ({ name: strategy.name, @@ -110,12 +111,8 @@ const StrategiesList = props => { setEditStrategyIndex(undefined); }; - const { - strategies, - configuredStrategies, - featureToggleName, - editable, - } = props; + const { strategies, configuredStrategies, featureToggleName, editable } = + props; const resolveStrategyDefinition = strategyName => { if (!strategies || strategies.length === 0) { @@ -170,14 +167,14 @@ const StrategiesList = props => { title="Activation strategies" actions={ <> - + } /> diff --git a/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap b/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap index b2e8ed4823..9e86b3c067 100644 --- a/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap +++ b/frontend/src/component/feature/variant/__tests__/__snapshots__/update-variant-component-test.jsx.snap @@ -497,10 +497,10 @@ exports[`renders correctly with with variants 1`] = `
Stickiness diff --git a/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap b/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap index 2c1ff5afc5..04e47b5ae0 100644 --- a/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap +++ b/frontend/src/component/feature/view/__tests__/__snapshots__/view-component-test.jsx.snap @@ -5,6 +5,8 @@ exports[`renders correctly with one feature 1`] = ` className="MuiPaper-root MuiPaper-elevation1 MuiPaper-rounded" style={ Object { + "borderRadius": "10px", + "boxShadow": "none", "overflow": "visible", } } @@ -140,10 +142,10 @@ exports[`renders correctly with one feature 1`] = `
Project @@ -175,7 +177,7 @@ exports[`renders correctly with one feature 1`] = ` >
diff --git a/frontend/src/component/layout/MainLayout/MainLayout.jsx b/frontend/src/component/layout/MainLayout/MainLayout.jsx index a81d082715..056b204b77 100644 --- a/frontend/src/component/layout/MainLayout/MainLayout.jsx +++ b/frontend/src/component/layout/MainLayout/MainLayout.jsx @@ -6,15 +6,24 @@ import { Grid } from '@material-ui/core'; import styles from '../../styles.module.scss'; import ErrorContainer from '../../error/error-container'; -import Header from '../../menu/Header'; +import Header from '../../menu/Header/Header'; import Footer from '../../menu/Footer/Footer'; import Proclamation from '../../common/Proclamation/Proclamation'; +import BreadcrumbNav from '../../common/BreadcrumbNav/BreadcrumbNav'; const useStyles = makeStyles(theme => ({ container: { height: '100%', justifyContent: 'space-between', }, + contentContainer: { + height: '100%', + padding: '3.25rem 0', + position: 'relative', + [theme.breakpoints.down('sm')]: { + padding: '3.25rem 0.75rem', + }, + }, })); const MainLayout = ({ children, location, uiConfig }) => { @@ -26,7 +35,8 @@ const MainLayout = ({ children, location, uiConfig }) => {
-
+
+ {children}
diff --git a/frontend/src/component/menu/Header/Header.jsx b/frontend/src/component/menu/Header/Header.jsx deleted file mode 100644 index aa3d326308..0000000000 --- a/frontend/src/component/menu/Header/Header.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; -import { useTheme } from '@material-ui/core/styles'; -import { Route } from 'react-router-dom'; -import { - AppBar, - Container, - Typography, - IconButton, - Tooltip, -} from '@material-ui/core'; -import { DrawerMenu } from '../drawer'; -import MenuIcon from '@material-ui/icons/Menu'; -import Breadcrumb from '../breadcrumb'; -import UserProfile from '../../user/UserProfile'; -import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; -import MenuBookIcon from '@material-ui/icons/MenuBook'; -import { useStyles } from './styles'; - -const Header = ({ uiConfig }) => { - const theme = useTheme(); - const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); - const styles = useStyles(); - const [openDrawer, setOpenDrawer] = useState(false); - - const toggleDrawer = () => setOpenDrawer(prev => !prev); - - const { links, name, flags } = uiConfig; - - return ( - - - - - - - - - - } - /> - -
- - - - - - -
- -
-
-
- ); -}; - -Header.propTypes = { - uiConfig: PropTypes.object.isRequired, - location: PropTypes.object.isRequired, -}; - -export default Header; diff --git a/frontend/src/component/menu/Header/Header.styles.ts b/frontend/src/component/menu/Header/Header.styles.ts new file mode 100644 index 0000000000..1a7c9db2eb --- /dev/null +++ b/frontend/src/component/menu/Header/Header.styles.ts @@ -0,0 +1,88 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + header: { + backgroundColor: '#fff', + color: '#000', + padding: '0.5rem', + boxShadow: 'none', + }, + links: { + display: 'flex', + justifyContent: 'center', + marginLeft: '1.5rem', + ['& a']: { + textDecoration: 'none', + color: 'inherit', + marginRight: '1.5rem', + display: 'flex', + alignItems: 'center', + }, + }, + container: { + display: 'flex', + alignItems: 'center', + [theme.breakpoints.down('sm')]: { + padding: '0', + }, + }, + drawerButton: { + color: '#000', + }, + advancedNavButton: { + border: 'none', + background: 'transparent', + height: '100%', + display: 'flex', + fontSize: '1rem', + fontFamily: theme.typography.fontFamily, + alignItems: 'center', + color: 'inherit', + }, + headerTitle: { + fontSize: '1.4rem', + }, + userContainer: { + marginLeft: 'auto', + display: 'flex', + alignItems: 'center', + }, + logoOnly: { + width: '60px', + }, + logo: { + width: '150px', + }, + popover: { + top: '25px', + }, + menuItem: { + minWidth: '150px', + }, + menuItemBox: { + width: '12.5px', + height: '12.5px', + display: 'block', + backgroundColor: theme.palette.primary.main, + marginRight: '1rem', + borderRadius: '2px', + }, + navMenuLink: { + textDecoration: 'none', + alignItems: 'center', + display: 'flex', + color: '#000', + }, + docsLink: { + color: '#000', + textDecoration: 'none', + padding: '0.25rem 0.8rem', + display: 'flex', + alignItems: 'center', + }, + docsIcon: { + color: '#6C6C6C', + height: '25px', + width: '25px', + }, +})); diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx new file mode 100644 index 0000000000..559d77dfa1 --- /dev/null +++ b/frontend/src/component/menu/Header/Header.tsx @@ -0,0 +1,172 @@ +import { useEffect, useState } from 'react'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import { useTheme } from '@material-ui/core/styles'; +import { Link } from 'react-router-dom'; +import { AppBar, Container, IconButton, Tooltip } from '@material-ui/core'; +import { DrawerMenu } from '../drawer'; +import MenuIcon from '@material-ui/icons/Menu'; +import SettingsIcon from '@material-ui/icons/Settings'; +import UserProfile from '../../user/UserProfile'; +import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; +import MenuBookIcon from '@material-ui/icons/MenuBook'; +import { ReactComponent as UnleashLogo } from '../../../assets/img/logo-dark-with-text.svg'; + +import { useStyles } from './Header.styles'; +import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; +import { useCommonStyles } from '../../../common.styles'; +import { ADMIN } from '../../AccessProvider/permissions'; +import useUser from '../../../hooks/api/getters/useUser/useUser'; +import { IPermission } from '../../../interfaces/user'; +import NavigationMenu from './NavigationMenu/NavigationMenu'; +import { getRoutes } from '../routes'; +import { KeyboardArrowDown } from '@material-ui/icons'; +import { filterByFlags } from '../../common/util'; + +const Header = () => { + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(); + const [anchorElAdvanced, setAnchorElAdvanced] = useState(); + const [admin, setAdmin] = useState(false); + const { permissions } = useUser(); + const commonStyles = useCommonStyles(); + const { uiConfig } = useUiConfig(); + const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const styles = useStyles(); + const [openDrawer, setOpenDrawer] = useState(false); + + const toggleDrawer = () => setOpenDrawer(prev => !prev); + const handleClose = () => setAnchorEl(null); + const handleCloseAdvanced = () => setAnchorElAdvanced(null); + + useEffect(() => { + const admin = permissions.find( + (element: IPermission) => element.permission === ADMIN + ); + + if (admin) { + setAdmin(true); + } + }, [permissions]); + + const { links, name, flags } = uiConfig; + const routes = getRoutes(); + + const filteredMainRoutes = { + mainNavRoutes: routes.mainNavRoutes.filter(filterByFlags(flags)), + adminRoutes: routes.adminRoutes, + }; + + return ( + <> + + + + + + } + elseShow={ + + {' '} + + } + /> + + + + Projects} + /> + + + +
+ } + /> +
+ + + + + + + + setAnchorEl(e.currentTarget) + } + onMouseEnter={e => + setAnchorEl(e.currentTarget) + } + > + + + } + /> + + + + } + /> + + +
+ + + + ); +}; + +export default Header; diff --git a/frontend/src/component/menu/Header/NavigationLink/NavigationLink.styles.ts b/frontend/src/component/menu/Header/NavigationLink/NavigationLink.styles.ts new file mode 100644 index 0000000000..06021ecf6b --- /dev/null +++ b/frontend/src/component/menu/Header/NavigationLink/NavigationLink.styles.ts @@ -0,0 +1,28 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + menuItem: { + minWidth: '150px', + height: '100%', + width: '100%', + margin: '0', + padding: '0', + }, + menuItemBox: { + width: '12.5px', + height: '12.5px', + display: 'block', + backgroundColor: theme.palette.primary.main, + marginRight: '1rem', + borderRadius: '2px', + }, + navMenuLink: { + textDecoration: 'none', + alignItems: 'center', + display: 'flex', + color: '#000', + height: '100%', + width: '100%', + padding: '0.5rem 1rem', + }, +})); diff --git a/frontend/src/component/menu/Header/NavigationLink/NavigationLink.tsx b/frontend/src/component/menu/Header/NavigationLink/NavigationLink.tsx new file mode 100644 index 0000000000..52435c475b --- /dev/null +++ b/frontend/src/component/menu/Header/NavigationLink/NavigationLink.tsx @@ -0,0 +1,35 @@ +import { ListItem, Link } from '@material-ui/core'; +import { Link as RouterLink } from 'react-router-dom'; + +import { useStyles } from './NavigationLink.styles'; + +interface INavigationLinkProps { + path: string; + text: string; + handleClose: () => void; +} + +const NavigationLink = ({ path, text, handleClose }: INavigationLinkProps) => { + const styles = useStyles(); + + return ( + { + handleClose(); + }} + > + + + {text} + + + ); +}; + +export default NavigationLink; diff --git a/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx b/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx new file mode 100644 index 0000000000..c562d9aa3e --- /dev/null +++ b/frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx @@ -0,0 +1,42 @@ +import { Popover, List } from '@material-ui/core'; +import NavigationLink from '../NavigationLink/NavigationLink'; + +interface INavigationMenuProps { + options: any[]; + id: string; + anchorEl: any; + handleClose: () => void; +} + +const NavigationMenu = ({ + options, + id, + handleClose, + anchorEl, +}: INavigationMenuProps) => { + return ( + + + {options.map(option => { + return ( + + ); + })} + + + ); +}; + +export default NavigationMenu; diff --git a/frontend/src/component/menu/Header/index.jsx b/frontend/src/component/menu/Header/index.jsx deleted file mode 100644 index ab7c566f66..0000000000 --- a/frontend/src/component/menu/Header/index.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { connect } from 'react-redux'; - -import Header from './Header'; - -const mapStateToProps = state => ({ uiConfig: state.uiConfig.toJS() }); - -export default connect(mapStateToProps)(Header); diff --git a/frontend/src/component/menu/Header/styles.js b/frontend/src/component/menu/Header/styles.js deleted file mode 100644 index 175349da12..0000000000 --- a/frontend/src/component/menu/Header/styles.js +++ /dev/null @@ -1,40 +0,0 @@ -import { makeStyles } from '@material-ui/styles'; - -export const useStyles = makeStyles(theme => ({ - header: { - padding: '1rem', - boxShadow: 'none', - [theme.breakpoints.down('sm')]: { - padding: '1rem 0.5rem', - }, - }, - container: { - display: 'flex', - alignItems: 'center', - [theme.breakpoints.down('sm')]: { - padding: '0', - }, - }, - drawerButton: { - color: theme.palette.iconButtons.main, - }, - headerTitle: { - fontSize: '1.4rem', - }, - userContainer: { - marginLeft: 'auto', - display: 'flex', - alignItems: 'center', - }, - docsLink: { - color: '#fff', - textDecoration: 'none', - padding: '0.25rem 0.8rem', - display: 'flex', - alignItems: 'center', - }, - docsIcon: { - height: '25px', - width: '25px', - }, -})); diff --git a/frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap deleted file mode 100644 index e854c255b9..0000000000 --- a/frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render DrawerMenu 1`] = `null`; - -exports[`should render DrawerMenu with "features" selected 1`] = `null`; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap index f26d38664e..92d83c76f6 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap @@ -4,14 +4,6 @@ exports[`returns all baseRoutes 1`] = ` Array [ Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/features", "title": "Feature Toggles", @@ -19,14 +11,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/strategies", "title": "Strategies", @@ -34,14 +18,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/history", "title": "Event History", @@ -49,14 +25,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/archive", "title": "Archived Toggles", @@ -64,14 +32,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/applications", "title": "Applications", @@ -80,14 +40,6 @@ Array [ Object { "component": [Function], "flag": "C", - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/context", "title": "Context Fields", @@ -96,14 +48,6 @@ Array [ Object { "component": [Function], "flag": "P", - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/projects", "title": "Projects", @@ -111,14 +55,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/tag-types", "title": "Tag types", @@ -127,14 +63,6 @@ Array [ Object { "component": [Function], "hidden": false, - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/addons", "title": "Addons", @@ -142,14 +70,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/reporting", "title": "Reporting", @@ -158,14 +78,6 @@ Array [ Object { "component": [Function], "hidden": false, - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/admin", "title": "Admin", @@ -173,14 +85,6 @@ Array [ }, Object { "component": [Function], - "icon": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": Object { - "$$typeof": Symbol(react.forward_ref), - "render": [Function], - }, - }, "layout": "main", "path": "/logout", "title": "Sign out", diff --git a/frontend/src/component/menu/__tests__/drawer-test.jsx b/frontend/src/component/menu/__tests__/drawer-test.jsx deleted file mode 100644 index caaae2eff4..0000000000 --- a/frontend/src/component/menu/__tests__/drawer-test.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { MemoryRouter } from 'react-router-dom'; - -import { DrawerMenu } from '../drawer'; - -jest.mock('@material-ui/core'); - -test('should render DrawerMenu', () => { - const tree = renderer.create( - - - - ); - - expect(tree).toMatchSnapshot(); -}); - -test('should render DrawerMenu with "features" selected', () => { - const tree = renderer.create( - - - - ); - - expect(tree).toMatchSnapshot(); -}); diff --git a/frontend/src/component/menu/drawer.jsx b/frontend/src/component/menu/drawer.jsx index 1ec21b2720..ba8814442e 100644 --- a/frontend/src/component/menu/drawer.jsx +++ b/frontend/src/component/menu/drawer.jsx @@ -1,61 +1,13 @@ import { Divider, Drawer, List } from '@material-ui/core'; -import { NavLink } from 'react-router-dom'; -import classnames from 'classnames'; import PropTypes from 'prop-types'; import GitHubIcon from '@material-ui/icons/GitHub'; import LibraryBooksIcon from '@material-ui/icons/LibraryBooks'; import styles from './drawer.module.scss'; -import { baseRoutes as routes } from './routes'; - import { ReactComponent as LogoIcon } from '../../assets/icons/logo_wbg.svg'; - -const filterByFlags = flags => r => { - if (r.flag && !flags[r.flag]) { - return false; - } - return true; -}; - -function getIcon(IconComponent) { - if (IconComponent === 'c_github') { - return ; - } else if (IconComponent === 'library_books') { - return ; - } else { - return ; - } -} - -function renderLink(link, toggleDrawer) { - if (link.path) { - return ( - toggleDrawer()} - key={link.path} - to={link.path} - className={classnames(styles.navigationLink)} - activeClassName={classnames(styles.navigationLinkActive)} - > - {getIcon(link.icon)} {link.value} - - ); - } else { - return ( - - {getIcon(link.icon)} {link.value} - - ); - } -} +import NavigationLink from './Header/NavigationLink/NavigationLink'; +import ConditionallyRender from '../common/ConditionallyRender'; export const DrawerMenu = ({ links = [], @@ -63,45 +15,84 @@ export const DrawerMenu = ({ flags = {}, open = false, toggleDrawer, -}) => ( - toggleDrawer()} - > -
-
- - + admin, + routes, +}) => { + const renderLinks = () => { + return links.map(link => { + let icon = null; + if (link.value === 'GitHub') { + icon = ; + } else if (link.value === 'Documentation') { + icon = ; + } - {title} - + return ( + + {icon} + {link.value} + + ); + }); + }; + + return ( + toggleDrawer()} + > +
+
+ + + + {title} + +
+ + + {routes.mainNavRoutes.map(item => ( + toggleDrawer()} + path={item.path} + text={item.title} + key={item.path} + /> + ))} + + + + + + {routes.adminRoutes.map(item => ( + toggleDrawer()} + path={item.path} + text={item.title} + key={item.path} + /> + ))} + + + } + /> + +
{renderLinks()}
- - - {routes.filter(filterByFlags(flags)).map(item => ( - toggleDrawer()} - key={item.path} - to={item.path} - className={classnames(styles.navigationLink)} - activeClassName={classnames( - styles.navigationLinkActive - )} - > - {getIcon(item.icon)} - {item.title} - - ))} - - - - {links.map(l => renderLink(l, toggleDrawer))} - -
- -); + + ); +}; DrawerMenu.propTypes = { links: PropTypes.array, diff --git a/frontend/src/component/menu/drawer.module.scss b/frontend/src/component/menu/drawer.module.scss index 33c52efd4a..730d8c3497 100644 --- a/frontend/src/component/menu/drawer.module.scss +++ b/frontend/src/component/menu/drawer.module.scss @@ -22,10 +22,19 @@ margin-left: 0.25rem; } -.drawerList { +.drawerList, +.iconLinkList { display: flex; flex-direction: column; align-items: centre; + padding: 0.5rem; +} + +.iconLink { + display: flex; + padding: 0.8rem; + text-decoration: none; + color: inherit; } .navigationLink { @@ -44,6 +53,7 @@ .navigationIcon { margin-right: 16px; + fill: #635dc5; } .iconGitHub { diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index e7c23af929..d9b8d09fac 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -15,7 +15,6 @@ import ContextFields from '../../page/context'; import CreateContextField from '../../page/context/create'; import EditContextField from '../../page/context/edit'; import LogoutFeatures from '../../page/user/logout'; -import ListProjects from '../../page/project'; import CreateProject from '../../page/project/create'; import EditProject from '../../page/project/edit'; import ViewProject from '../../page/project/view'; @@ -39,25 +38,9 @@ import { P, C } from '../common/flags'; import NewUser from '../user/NewUser'; import ResetPassword from '../user/ResetPassword/ResetPassword'; import ForgottenPassword from '../user/ForgottenPassword/ForgottenPassword'; -import ProjectListNew from '../project/ProjectListNew/ProjectListNew'; +import ProjectListNew from '../project/ProjectList/ProjectList'; import Project from '../project/Project/Project'; -import { - List, - Extension, - History, - Archive as ArchiveIcon, - Apps, - Label, - DeviceHub, - Album, - ExitToApp, - FolderOpen, - Report, - Money, - Person, -} from '@material-ui/icons'; - export const routes = [ // Features { @@ -87,7 +70,6 @@ export const routes = [ { path: '/features', title: 'Feature Toggles', - icon: List, component: Features, type: 'protected', layout: 'main', @@ -113,7 +95,6 @@ export const routes = [ { path: '/strategies', title: 'Strategies', - icon: Extension, component: Strategies, type: 'protected', layout: 'main', @@ -131,7 +112,6 @@ export const routes = [ { path: '/history', title: 'Event History', - icon: History, component: HistoryPage, type: 'protected', layout: 'main', @@ -149,7 +129,6 @@ export const routes = [ { path: '/archive', title: 'Archived Toggles', - icon: ArchiveIcon, component: Archive, type: 'protected', layout: 'main', @@ -167,7 +146,6 @@ export const routes = [ { path: '/applications', title: 'Applications', - icon: Apps, component: Applications, type: 'protected', layout: 'main', @@ -193,7 +171,6 @@ export const routes = [ { path: '/context', title: 'Context Fields', - icon: Album, component: ContextFields, type: 'protected', flag: C, @@ -245,20 +222,9 @@ export const routes = [ { path: '/projects', title: 'Projects', - icon: FolderOpen, - component: ListProjects, - flag: P, - type: 'protected', - layout: 'main', - }, - { - path: '/projects-new', - title: 'Projects new', - icon: 'folder_open', component: ProjectListNew, flag: P, type: 'protected', - hidden: true, layout: 'main', }, @@ -281,7 +247,6 @@ export const routes = [ { path: '/tag-types', title: 'Tag types', - icon: Label, component: ListTagTypes, type: 'protected', layout: 'main', @@ -298,7 +263,6 @@ export const routes = [ { path: '/tags', title: 'Tags', - icon: Label, component: ListTags, hidden: true, type: 'protected', @@ -325,7 +289,6 @@ export const routes = [ { path: '/addons', title: 'Addons', - icon: DeviceHub, component: Addons, hidden: false, type: 'protected', @@ -334,7 +297,6 @@ export const routes = [ { path: '/reporting', title: 'Reporting', - icon: Report, component: Reporting, type: 'protected', layout: 'main', @@ -367,7 +329,6 @@ export const routes = [ { path: '/admin-invoices', title: 'Invoices', - icon: Money, component: AdminInvoice, hidden: true, type: 'protected', @@ -376,7 +337,6 @@ export const routes = [ { path: '/admin', title: 'Admin', - icon: Album, component: Admin, hidden: false, type: 'protected', @@ -385,7 +345,6 @@ export const routes = [ { path: '/logout', title: 'Sign out', - icon: ExitToApp, component: LogoutFeatures, type: 'unprotected', layout: 'main', @@ -393,7 +352,6 @@ export const routes = [ { path: '/login', title: 'Log in', - icon: Person, component: Login, type: 'unprotected', hidden: true, @@ -430,3 +388,28 @@ export const getRoute = path => routes.find(route => route.path === path); export const baseRoutes = routes .filter(route => !route.hidden) .filter(route => !route.parent); + +const computeRoutes = () => { + const computedRoutes = { + mainNavRoutes: + baseRoutes.filter( + route => + route.path !== '/admin' && + route.path !== '/logout' && + route.path !== '/history' + ) || [], + adminRoutes: + routes.filter( + route => + (route.path.startsWith('/admin') && + route.path !== '/admin-invoices' && + route.path !== '/admin') || + route.path === '/history' + ) || [], + }; + return () => { + return computedRoutes; + }; +}; + +export const getRoutes = computeRoutes(); diff --git a/frontend/src/component/project/Project/Project.styles.ts b/frontend/src/component/project/Project/Project.styles.ts new file mode 100644 index 0000000000..cdc33a7f16 --- /dev/null +++ b/frontend/src/component/project/Project/Project.styles.ts @@ -0,0 +1,11 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + containerStyles: { + marginTop: '1.5rem', + display: 'flex', + [theme.breakpoints.down('sm')]: { + flexDirection: 'column', + }, + }, +})); diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index 3aeed3a95f..23f196d5c2 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -6,20 +6,46 @@ import ApiError from '../../common/ApiError/ApiError'; import ConditionallyRender from '../../common/ConditionallyRender'; import ProjectFeatureToggles from './ProjectFeatureToggles/ProjectFeatureToggles'; import ProjectInfo from './ProjectInfo/ProjectInfo'; +import { useStyles } from './Project.styles'; +import { IconButton } from '@material-ui/core'; +import { Edit } from '@material-ui/icons'; +import { Link } from 'react-router-dom'; +import useToast from '../../../hooks/useToast'; +import useQueryParams from '../../../hooks/useQueryParams'; +import { useEffect } from 'react'; const Project = () => { const { id } = useParams<{ id: string }>(); + const params = useQueryParams(); const { project, error, loading, refetch } = useProject(id); const ref = useLoading(loading); + const { toast, setToastData } = useToast(); const { members, features, health } = project; const commonStyles = useCommonStyles(); + const styles = useStyles(); - const containerStyles = { marginTop: '1.5rem', display: 'flex' }; + useEffect(() => { + const created = params.get('created'); + const edited = params.get('edited'); + + if (created || edited) { + const text = created ? 'Project created' : 'Project updated'; + setToastData({ + show: true, + type: 'success', + text, + }); + } + /* eslint-disable-next-line */ + }, []); return (

- {project?.name} + {project?.name}{' '} + + +

{ /> } /> -
+
{ />
+ {toast}
); }; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts index 37a5bce823..4a7839d6bd 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts @@ -6,6 +6,10 @@ export const useStyles = makeStyles(theme => ({ marginLeft: '2rem', width: '100%', position: 'relative', + [theme.breakpoints.down('sm')]: { + marginLeft: '0', + paddingBottom: '4rem', + }, }, header: { padding: '1rem', @@ -22,4 +26,10 @@ export const useStyles = makeStyles(theme => ({ height: '30px', width: '30px', }, + noTogglesFound: { + marginBottom: '0.5rem', + }, + link: { + textDecoration: 'none', + }, })); diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx index a9ca984c9f..d479d8542e 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -1,12 +1,14 @@ -import { Button, IconButton } from '@material-ui/core'; +import { IconButton } from '@material-ui/core'; +import { Add } from '@material-ui/icons'; import FilterListIcon from '@material-ui/icons/FilterList'; import { useParams } from 'react-router'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import { IFeatureToggleListItem } from '../../../../interfaces/featureToggle'; import ConditionallyRender from '../../../common/ConditionallyRender'; import { PROJECTFILTERING } from '../../../common/flags'; import HeaderTitle from '../../../common/HeaderTitle'; import PageContent from '../../../common/PageContent'; +import ResponsiveButton from '../../../common/ResponsiveButton/ResponsiveButton'; import FeatureToggleListNew from '../../../feature/FeatureToggleListNew/FeatureToggleListNew'; import { useStyles } from './ProjectFeatureToggles.styles'; @@ -21,6 +23,7 @@ const ProjectFeatureToggles = ({ }: IProjectFeatureToggles) => { const styles = useStyles(); const { id } = useParams(); + const history = useHistory(); return ( } /> - + } /> } > - 0} + show={ + + } + elseShow={ + <> +

+ No feature toggles added yet. +

+ + Add your first toggle + + + } />
); diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts index fdfde78e46..4090f7bb73 100644 --- a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts +++ b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.styles.ts @@ -3,11 +3,23 @@ import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ projectInfo: { width: '275px', - padding: '1rem', display: 'flex', flexDirection: 'column', alignItems: 'center', boxShadow: 'none', + [theme.breakpoints.down('sm')]: { + flexDirection: 'row', + alignItems: 'stretch', + width: '100%', + marginBottom: '1rem', + }, + }, + projectIcon: { + margin: '2rem 0', + [theme.breakpoints.down('sm')]: { + margin: '0 0 0.25rem 0', + width: '53px', + }, }, subtitle: { marginBottom: '1.25rem', @@ -15,10 +27,35 @@ export const useStyles = makeStyles(theme => ({ emphazisedText: { fontSize: '1.5rem', marginBottom: '1rem', + [theme.breakpoints.down('sm')]: { + fontSize: '1rem', + marginBottom: '2rem', + }, }, infoSection: { - margin: '1.8rem 0', + margin: '0', textAlign: 'center', + marginBottom: '1.5rem', + backgroundColor: '#fff', + borderRadius: '10px', + width: '100%', + padding: '1.5rem 1rem 1.5rem 1rem', + [theme.breakpoints.down('sm')]: { + margin: '0 0.25rem', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + fontSize: '0.8rem', + position: 'relative', + padding: '0.8rem', + ['&:first-child']: { + marginLeft: '0', + }, + ['&:last-child']: { + marginRight: '0', + }, + }, }, arrowIcon: { color: '#635dc5', @@ -27,5 +64,14 @@ export const useStyles = makeStyles(theme => ({ infoLink: { textDecoration: 'none', color: '#635dc5', + [theme.breakpoints.down('sm')]: { + position: 'absolute', + bottom: '5px', + }, + }, + linkText: { + [theme.breakpoints.down('sm')]: { + display: 'none', + }, }, })); diff --git a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx index d4924f095d..dc0fa4e04e 100644 --- a/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx +++ b/frontend/src/component/project/Project/ProjectInfo/ProjectInfo.tsx @@ -1,4 +1,3 @@ -import { Paper } from '@material-ui/core'; import { useStyles } from './ProjectInfo.styles'; import { Link } from 'react-router-dom'; import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; @@ -33,15 +32,19 @@ const ProjectInfo = ({ return ( ); }; diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts b/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts index 076623f75f..d4b523a582 100644 --- a/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts +++ b/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts @@ -11,6 +11,9 @@ export const useStyles = makeStyles(theme => ({ margin: '0.5rem', boxShadow: 'none', border: '1px solid #efefef', + [theme.breakpoints.down('xs')]: { + justifyContent: 'center', + }, }, header: { display: 'flex', @@ -38,4 +41,11 @@ export const useStyles = makeStyles(theme => ({ color: '#4A4599', fontWeight: 'bold', }, + actionsBtn: { + transform: 'translateX(15px)', + }, + icon: { + color: theme.palette.grey[700], + marginRight: '0.5rem', + }, })); diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.tsx b/frontend/src/component/project/ProjectCard/ProjectCard.tsx index ded5ccaae2..114abb9ee0 100644 --- a/frontend/src/component/project/ProjectCard/ProjectCard.tsx +++ b/frontend/src/component/project/ProjectCard/ProjectCard.tsx @@ -1,17 +1,30 @@ -import { Card, IconButton } from '@material-ui/core'; +import { Card, IconButton, Menu, MenuItem } from '@material-ui/core'; +import { Dispatch, SetStateAction } from 'react'; import { useStyles } from './ProjectCard.styles'; import MoreVertIcon from '@material-ui/icons/MoreVert'; import { ReactComponent as ProjectIcon } from '../../../assets/icons/projectIcon.svg'; import ConditionallyRender from '../../common/ConditionallyRender'; -import { PROJECTCARDACTIONS } from '../../common/flags'; - +import { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import Dialogue from '../../common/Dialogue'; +import useProjectApi from '../../../hooks/api/actions/useProjectApi/useProjectApi'; +import useProjects from '../../../hooks/api/getters/useProjects/useProjects'; +import { Delete, Edit } from '@material-ui/icons'; interface IProjectCardProps { name: string; featureCount: number; health: number; memberCount: number; + id: string; onHover: () => void; + setToastData: Dispatch< + SetStateAction<{ + show: boolean; + type: string; + text: string; + }> + >; } const ProjectCard = ({ @@ -20,20 +33,66 @@ const ProjectCard = ({ health, memberCount, onHover, + id, + setToastData, }: IProjectCardProps) => { const styles = useStyles(); + const { refetch: refetchProjectOverview } = useProjects(); + const [anchorEl, setAnchorEl] = useState(null); + const [showDelDialog, setShowDelDialog] = useState(false); + const { deleteProject } = useProjectApi(); + const history = useHistory(); + + const handleClick = e => { + e.preventDefault(); + setAnchorEl(e.currentTarget); + }; + return (

{name}

+ } /> + { + e.preventDefault(); + setAnchorEl(null); + }} + > + { + e.preventDefault(); + history.push(`/projects/edit/${id}`); + }} + > + + Edit project + + { + e.preventDefault(); + setShowDelDialog(true); + }} + > + + Delete project + +
@@ -59,6 +118,38 @@ const ProjectCard = ({

members

+ { + e.preventDefault(); + deleteProject(id) + .then(() => { + setToastData({ + show: true, + type: 'success', + text: 'Successfully deleted project', + }); + refetchProjectOverview(); + }) + .catch(e => { + setToastData({ + show: true, + type: 'error', + text: e.toString(), + }); + }) + .finally(() => { + setShowDelDialog(false); + setAnchorEl(null); + }); + }} + onClose={e => { + e.preventDefault(); + setAnchorEl(null); + setShowDelDialog(false); + }} + title="Really delete project" + /> ); }; diff --git a/frontend/src/component/project/ProjectList/ProjectList.jsx b/frontend/src/component/project/ProjectList/ProjectList.jsx deleted file mode 100644 index c5025dfb37..0000000000 --- a/frontend/src/component/project/ProjectList/ProjectList.jsx +++ /dev/null @@ -1,146 +0,0 @@ -import PropTypes from 'prop-types'; -import { useContext, useEffect, useState } from 'react'; -import HeaderTitle from '../../common/HeaderTitle'; -import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; -import { - CREATE_PROJECT, - DELETE_PROJECT, - UPDATE_PROJECT, -} from '../../AccessProvider/permissions'; -import { - IconButton, - List, - ListItem, - ListItemAvatar, - ListItemText, - Tooltip, -} from '@material-ui/core'; -import { - Add, - SupervisedUserCircle, - Delete, - FolderOpen, -} from '@material-ui/icons'; - -import { Link } from 'react-router-dom'; -import ConfirmDialogue from '../../common/Dialogue'; -import PageContent from '../../common/PageContent/PageContent'; -import { useStyles } from './styles'; -import AccessContext from '../../../contexts/AccessContext'; -import ResponsiveButton from '../../common/ResponsiveButton/ResponsiveButton'; - -const ProjectList = ({ projects, fetchProjects, removeProject, history }) => { - const { hasAccess } = useContext(AccessContext); - const [showDelDialogue, setShowDelDialogue] = useState(false); - const [project, setProject] = useState(undefined); - const styles = useStyles(); - useEffect(() => { - fetchProjects(); - }, [fetchProjects]); - - const addProjectButton = () => ( - history.push('/projects/create')} - maxWidth="700px" - tooltip="Add new project" - /> - } - /> - ); - - const projectLink = ({ id, name }) => ( - - {name} - - ); - - const mgmAccessButton = project => ( - - - - - - - - ); - - const deleteProjectButton = project => ( - - { - setProject(project); - setShowDelDialogue(true); - }} - > - - - - ); - - const renderProjectList = () => - projects.map(project => ( - - - - - - - - - )); - - return ( - - } - > - - 0} - show={renderProjectList()} - elseShow={No projects defined} - /> - - { - removeProject(project); - setProject(undefined); - setShowDelDialogue(false); - }} - onClose={() => { - setProject(undefined); - setShowDelDialogue(false); - }} - title="Really delete project" - /> - - ); -}; - -ProjectList.propTypes = { - projects: PropTypes.array.isRequired, - fetchProjects: PropTypes.func.isRequired, - removeProject: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, -}; - -export default ProjectList; diff --git a/frontend/src/component/project/ProjectList/ProjectList.styles.ts b/frontend/src/component/project/ProjectList/ProjectList.styles.ts new file mode 100644 index 0000000000..2b3bfb963e --- /dev/null +++ b/frontend/src/component/project/ProjectList/ProjectList.styles.ts @@ -0,0 +1,24 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + container: { + display: 'flex', + flexWrap: 'wrap', + [theme.breakpoints.down('xs')]: { + justifyContent: 'center', + }, + }, + apiError: { + maxWidth: '400px', + marginBottom: '1rem', + }, + cardLink: { + color: 'inherit', + textDecoration: 'none', + border: 'none', + padding: '0', + background: 'transparent', + fontFamily: theme.typography.fontFamily, + pointer: 'cursor', + }, +})); diff --git a/frontend/src/component/project/ProjectListNew/ProjectListNew.tsx b/frontend/src/component/project/ProjectList/ProjectList.tsx similarity index 88% rename from frontend/src/component/project/ProjectListNew/ProjectListNew.tsx rename to frontend/src/component/project/ProjectList/ProjectList.tsx index f5e7305160..7b091fdb12 100644 --- a/frontend/src/component/project/ProjectListNew/ProjectListNew.tsx +++ b/frontend/src/component/project/ProjectList/ProjectList.tsx @@ -5,7 +5,7 @@ import { getProjectFetcher } from '../../../hooks/api/getters/useProject/getProj import useProjects from '../../../hooks/api/getters/useProjects/useProjects'; import ConditionallyRender from '../../common/ConditionallyRender'; import ProjectCard from '../ProjectCard/ProjectCard'; -import { useStyles } from './ProjectListNew.styles'; +import { useStyles } from './ProjectList.styles'; import { IProjectCard } from '../../../interfaces/project'; import loadingData from './loadingData'; @@ -18,6 +18,7 @@ import { CREATE_PROJECT } from '../../AccessProvider/permissions'; import { Add } from '@material-ui/icons'; import ApiError from '../../common/ApiError/ApiError'; +import useToast from '../../../hooks/useToast'; type projectMap = { [index: string]: boolean; @@ -26,6 +27,7 @@ type projectMap = { const ProjectListNew = () => { const { hasAccess } = useContext(AccessContext); const history = useHistory(); + const { toast, setToastData } = useToast(); const styles = useStyles(); const { projects, loading, error, refetch } = useProjects(); @@ -74,7 +76,9 @@ const ProjectListNew = () => { name={project?.name} memberCount={project?.memberCount} health={project?.health} + id={project?.id} featureCount={project?.featureCount} + setToastData={setToastData} /> ); @@ -88,10 +92,12 @@ const ProjectListNew = () => { data-loading onHover={() => {}} key={project.id} - projectName={project.name} - members={2} + name={project.name} + id={project.id} + memberCount={2} health={95} - toggles={4} + featureCount={4} + setToastData={setToastData} /> ); }); @@ -114,7 +120,9 @@ const ProjectListNew = () => { } maxWidth="700px" tooltip="Add new project" - /> + > + Add new project + } /> } @@ -129,6 +137,7 @@ const ProjectListNew = () => { elseShow={renderProjects()} />
+ {toast}
); diff --git a/frontend/src/component/project/ProjectList/index.jsx b/frontend/src/component/project/ProjectList/index.jsx deleted file mode 100644 index 73dacbfef7..0000000000 --- a/frontend/src/component/project/ProjectList/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import { connect } from 'react-redux'; -import { fetchProjects, removeProject } from '../../../store/project/actions'; -import ProjectList from './ProjectList'; - -const mapStateToProps = state => { - const projects = state.projects.toJS(); - - return { - projects, - }; -}; - -const mapDispatchToProps = dispatch => ({ - removeProject: project => { - removeProject(project)(dispatch); - }, - fetchProjects: () => fetchProjects()(dispatch), -}); - -const ProjectListContainer = connect(mapStateToProps, mapDispatchToProps)(ProjectList); - -export default ProjectListContainer; diff --git a/frontend/src/component/project/ProjectList/loadingData.ts b/frontend/src/component/project/ProjectList/loadingData.ts new file mode 100644 index 0000000000..04de3f2973 --- /dev/null +++ b/frontend/src/component/project/ProjectList/loadingData.ts @@ -0,0 +1,40 @@ +const loadingData = [ + { + id: 'loading1', + name: 'loading1', + memberCount: 1, + health: 95, + featureCount: 4, + createdAt: '', + description: '', + }, + { + id: 'loading2', + name: 'loading2', + memberCount: 1, + health: 95, + featureCount: 4, + createdAt: '', + description: '', + }, + { + id: 'loading3', + name: 'loading3', + memberCount: 1, + health: 95, + featureCount: 4, + createdAt: '', + description: '', + }, + { + id: 'loading4', + name: 'loading4', + memberCount: 1, + health: 95, + featureCount: 4, + createdAt: '', + description: '', + }, +]; + +export default loadingData; diff --git a/frontend/src/component/project/ProjectList/styles.js b/frontend/src/component/project/ProjectList/styles.js deleted file mode 100644 index fb054b3439..0000000000 --- a/frontend/src/component/project/ProjectList/styles.js +++ /dev/null @@ -1,11 +0,0 @@ -import { makeStyles } from '@material-ui/styles'; - -export const useStyles = makeStyles({ - listItem: { - padding: 0, - ['& a']: { - textDecoration: 'none', - color: 'inherit', - }, - }, -}); diff --git a/frontend/src/component/project/ProjectListNew/ProjectListNew.styles.ts b/frontend/src/component/project/ProjectListNew/ProjectListNew.styles.ts deleted file mode 100644 index 958b81cb0a..0000000000 --- a/frontend/src/component/project/ProjectListNew/ProjectListNew.styles.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles'; - -export const useStyles = makeStyles(theme => ({ - container: { - display: 'flex', - flexWrap: 'wrap', - }, - apiError: { - maxWidth: '400px', - marginBottom: '1rem', - }, - cardLink: { color: 'inherit', textDecoration: 'none' }, -})); diff --git a/frontend/src/component/project/ProjectListNew/loadingData.ts b/frontend/src/component/project/ProjectListNew/loadingData.ts deleted file mode 100644 index 2747f907b3..0000000000 --- a/frontend/src/component/project/ProjectListNew/loadingData.ts +++ /dev/null @@ -1,32 +0,0 @@ -const loadingData = [ - { - id: 'loading1', - name: 'loading1', - members: 1, - health: 95, - toggles: 4, - }, - { - id: 'loading2', - name: 'loading2', - members: 1, - health: 95, - toggles: 4, - }, - { - id: 'loading3', - name: 'loading3', - members: 1, - health: 95, - toggles: 4, - }, - { - id: 'loading4', - name: 'loading4', - members: 1, - health: 95, - toggles: 4, - }, -]; - -export default loadingData; diff --git a/frontend/src/component/project/form-project-component.jsx b/frontend/src/component/project/form-project-component.jsx index 3518fb7856..5f8349bc54 100644 --- a/frontend/src/component/project/form-project-component.jsx +++ b/frontend/src/component/project/form-project-component.jsx @@ -1,6 +1,6 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import PropTypes from 'prop-types'; -import { TextField, Typography } from '@material-ui/core'; +import { TextField, Typography, Button } from '@material-ui/core'; import styles from './Project.module.scss'; import classnames from 'classnames'; @@ -10,7 +10,7 @@ import PageContent from '../common/PageContent/PageContent'; import AccessContext from '../../contexts/AccessContext'; import ConditionallyRender from '../common/ConditionallyRender'; import { CREATE_PROJECT } from '../AccessProvider/permissions'; -import { Link } from 'react-router-dom'; +import HeaderTitle from '../common/HeaderTitle'; class ProjectFormComponent extends Component { static contextType = AccessContext; @@ -75,15 +75,10 @@ class ProjectFormComponent extends Component { }; onCancel = evt => { - const { editMode } = this.props; const { project } = this.state; - evt.preventDefault(); - if (editMode) { - this.props.history.push(`/projects/view/${project.id}`); - } else { - this.props.history.push('/projects'); - } + + this.props.history.push(`/projects/${project.id}`); }; onSubmit = async evt => { @@ -93,8 +88,10 @@ class ProjectFormComponent extends Component { const valid = await this.validate(project.id); if (valid) { + const { editMode } = this.props; + const query = editMode ? 'edited=true' : 'created=true'; await this.props.submit(project); - this.props.history.push(`/projects/view/${project.id}`); + this.props.history.push(`/projects/${project.id}?${query}`); } }; @@ -107,20 +104,28 @@ class ProjectFormComponent extends Component { return ( - {submitText} Project - - Manage access - - } - /> -
+ + this.props.history.push( + `/projects/${project.id}/access` + ) + } + > + Manage access + + } + /> + } + /> } >
setShowProfile(prev => !prev)} - tabIndex="1" role="button" disableRipple > diff --git a/frontend/src/component/user/UserProfile/UserProfile.styles.js b/frontend/src/component/user/UserProfile/UserProfile.styles.js index c19edc5b97..304de4bee3 100644 --- a/frontend/src/component/user/UserProfile/UserProfile.styles.js +++ b/frontend/src/component/user/UserProfile/UserProfile.styles.js @@ -8,7 +8,7 @@ export const useStyles = makeStyles({ position: 'relative', }, button: { - color: '#fff', + color: 'inherit', padding: '0.2rem 1rem', }, }); diff --git a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx index 6cda828e03..6c501b5c9d 100644 --- a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx +++ b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx @@ -9,12 +9,14 @@ import { Select, InputLabel, } from '@material-ui/core'; +import { Link } from 'react-router-dom'; import classnames from 'classnames'; import { useStyles } from './UserProfileContent.styles'; import { useCommonStyles } from '../../../../common.styles'; import { Alert } from '@material-ui/lab'; import EditProfile from '../EditProfile/EditProfile'; import legacyStyles from '../../user.module.scss'; +import usePermissions from '../../../../hooks/usePermissions'; const UserProfileContent = ({ showProfile, @@ -30,6 +32,7 @@ const UserProfileContent = ({ const [updatedPassword, setUpdatedPassword] = useState(false); const [edititingProfile, setEditingProfile] = useState(false); const styles = useStyles(); + const { isAdmin } = usePermissions(); const setLocale = value => { updateSettingLocation('locale', value); @@ -128,6 +131,27 @@ const UserProfileContent = ({
+ + Account and billing + + } + /> + + Privacy policy + +
+