From 1a63d91f95205f51016081754b4f1046e9576836 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Fri, 16 Jul 2021 15:41:54 +0200 Subject: [PATCH] Feat/new navigation (#314) * feat: change color scheme * feat: add navigation menu * fix: add bg image * fix: add archive and strategies to navigation * fix: round corners * feat: mobile view project details * feat: mobile view navigation * fix: only show menu if user is admin * fix: rename navigation * fix: only render relevant routes for oss context * feat: add project actions * feat: add icons * feat: add breadcrumbs * fix: place breadcrumbs absolutely * fix: adjust breadcrumbs * fix: toast * fix: cleanup * fix login * fix: breadcrumbs * fix: add billing link * fix: links * fix: feature view * fix: path to go back * fix: remove default value * fix: remove unused imports * refactor: delete outdated test * fix: add item to filter in breadcrumb * fix: remove console log --- frontend/src/app.css | 20 ++ frontend/src/assets/fonts/Sen-Bold.ttf | Bin 0 -> 47992 bytes frontend/src/assets/fonts/Sen-ExtraBold.ttf | Bin 0 -> 47472 bytes frontend/src/assets/fonts/Sen-Regular.ttf | Bin 0 -> 45556 bytes .../src/assets/img/logo-dark-with-text.svg | 1 + frontend/src/assets/img/logo-dark.svg | 1 + frontend/src/assets/img/logo-with-name.svg | 9 + frontend/src/assets/img/texture.svg | 14 ++ frontend/src/common.styles.js | 6 + .../AccessProvider/AccessProvider.tsx | 21 ++- .../component/AccessProvider/permissions.ts | 1 + .../component/addons/form-addon-component.jsx | 1 - .../application-edit-component-test.js.snap | 12 ++ .../BreadcrumbNav/BreadcrumbNav.styles.ts | 13 ++ .../common/BreadcrumbNav/BreadcrumbNav.tsx | 54 ++++++ .../common/PageContent/PageContent.jsx | 6 +- .../common/PaginateUI/PaginateUI.tsx | 11 +- .../common/Proclamation/Proclamation.tsx | 1 - .../ResponsiveButton/ResponsiveButton.tsx | 16 +- .../component/common/SearchField/styles.js | 2 +- frontend/src/component/common/Toast/Toast.tsx | 3 +- frontend/src/component/common/flags.js | 1 - frontend/src/component/common/util.js | 7 + .../FeatureToggleListItemChip/styles.js | 5 +- .../list-component-test.jsx.snap | 12 ++ .../FeatureToggleListNew.styles.ts | 8 + .../FeatureToggleListNew.tsx | 3 +- .../FeatureToggleListNewItem.tsx | 58 +++--- .../feature/FeatureView/FeatureView.jsx | 6 +- .../create/CreateFeature/CreateFeature.jsx | 18 +- .../StrategyCard/StrategyCard.styles.js | 7 +- .../StrategyCardHeader.styles.js | 2 +- .../strategy/strategies-list-component.jsx | 21 +-- .../update-variant-component-test.jsx.snap | 4 +- .../view-component-test.jsx.snap | 14 +- .../layout/MainLayout/MainLayout.jsx | 14 +- frontend/src/component/menu/Header/Header.jsx | 84 --------- .../component/menu/Header/Header.styles.ts | 88 +++++++++ frontend/src/component/menu/Header/Header.tsx | 172 ++++++++++++++++++ .../NavigationLink/NavigationLink.styles.ts | 28 +++ .../Header/NavigationLink/NavigationLink.tsx | 35 ++++ .../Header/NavigationMenu/NavigationMenu.tsx | 42 +++++ frontend/src/component/menu/Header/index.jsx | 7 - frontend/src/component/menu/Header/styles.js | 40 ---- .../__snapshots__/drawer-test.jsx.snap | 5 - .../__snapshots__/routes-test.jsx.snap | 96 ---------- .../component/menu/__tests__/drawer-test.jsx | 27 --- frontend/src/component/menu/drawer.jsx | 165 ++++++++--------- .../src/component/menu/drawer.module.scss | 12 +- frontend/src/component/menu/routes.js | 69 +++---- .../project/Project/Project.styles.ts | 11 ++ .../src/component/project/Project/Project.tsx | 33 +++- .../ProjectFeatureToggles.styles.ts | 10 + .../ProjectFeatureToggles.tsx | 51 ++++-- .../Project/ProjectInfo/ProjectInfo.styles.ts | 50 ++++- .../Project/ProjectInfo/ProjectInfo.tsx | 69 ++++--- .../project/ProjectCard/ProjectCard.styles.ts | 10 + .../project/ProjectCard/ProjectCard.tsx | 101 +++++++++- .../project/ProjectList/ProjectList.jsx | 146 --------------- .../project/ProjectList/ProjectList.styles.ts | 24 +++ .../ProjectList.tsx} | 19 +- .../component/project/ProjectList/index.jsx | 22 --- .../project/ProjectList/loadingData.ts | 40 ++++ .../component/project/ProjectList/styles.js | 11 -- .../ProjectListNew/ProjectListNew.styles.ts | 13 -- .../project/ProjectListNew/loadingData.ts | 32 ---- .../project/form-project-component.jsx | 55 +++--- .../list-component-test.jsx.snap | 12 ++ .../strategy-details-component-test.jsx.snap | 6 + frontend/src/component/styles.module.scss | 4 + .../tag-type-create-component-test.js.snap | 18 ++ .../tag-type-list-component-test.js.snap | 12 ++ .../user/UserProfile/UserProfile.jsx | 1 - .../user/UserProfile/UserProfile.styles.js | 2 +- .../UserProfileContent/UserProfileContent.jsx | 24 +++ .../UserProfileContent.styles.js | 6 + .../SecondaryLoginActions.styles.ts | 2 +- .../src/hooks/api/actions/useApi/useApi.ts | 14 ++ .../actions/useProjectApi/useProjectApi.ts | 24 +++ .../api/getters/useUiConfig/defaultValue.ts | 2 +- .../api/getters/useUiConfig/useUiConfig.ts | 5 +- .../src/hooks/api/getters/useUser/useUser.ts | 36 ++++ frontend/src/hooks/usePermissions.ts | 8 + frontend/src/hooks/useToast.tsx | 26 +++ frontend/src/interfaces/uiConfig.ts | 35 ++++ frontend/src/themes/main-theme.js | 18 +- 86 files changed, 1422 insertions(+), 772 deletions(-) create mode 100644 frontend/src/assets/fonts/Sen-Bold.ttf create mode 100644 frontend/src/assets/fonts/Sen-ExtraBold.ttf create mode 100644 frontend/src/assets/fonts/Sen-Regular.ttf create mode 100644 frontend/src/assets/img/logo-dark-with-text.svg create mode 100644 frontend/src/assets/img/logo-dark.svg create mode 100644 frontend/src/assets/img/logo-with-name.svg create mode 100644 frontend/src/assets/img/texture.svg create mode 100644 frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts create mode 100644 frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.tsx delete mode 100644 frontend/src/component/menu/Header/Header.jsx create mode 100644 frontend/src/component/menu/Header/Header.styles.ts create mode 100644 frontend/src/component/menu/Header/Header.tsx create mode 100644 frontend/src/component/menu/Header/NavigationLink/NavigationLink.styles.ts create mode 100644 frontend/src/component/menu/Header/NavigationLink/NavigationLink.tsx create mode 100644 frontend/src/component/menu/Header/NavigationMenu/NavigationMenu.tsx delete mode 100644 frontend/src/component/menu/Header/index.jsx delete mode 100644 frontend/src/component/menu/Header/styles.js delete mode 100644 frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap delete mode 100644 frontend/src/component/menu/__tests__/drawer-test.jsx create mode 100644 frontend/src/component/project/Project/Project.styles.ts delete mode 100644 frontend/src/component/project/ProjectList/ProjectList.jsx create mode 100644 frontend/src/component/project/ProjectList/ProjectList.styles.ts rename frontend/src/component/project/{ProjectListNew/ProjectListNew.tsx => ProjectList/ProjectList.tsx} (88%) delete mode 100644 frontend/src/component/project/ProjectList/index.jsx create mode 100644 frontend/src/component/project/ProjectList/loadingData.ts delete mode 100644 frontend/src/component/project/ProjectList/styles.js delete mode 100644 frontend/src/component/project/ProjectListNew/ProjectListNew.styles.ts delete mode 100644 frontend/src/component/project/ProjectListNew/loadingData.ts create mode 100644 frontend/src/hooks/api/actions/useProjectApi/useProjectApi.ts create mode 100644 frontend/src/hooks/api/getters/useUser/useUser.ts create mode 100644 frontend/src/hooks/usePermissions.ts create mode 100644 frontend/src/hooks/useToast.tsx create mode 100644 frontend/src/interfaces/uiConfig.ts 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 0000000000000000000000000000000000000000..bd184b83cb4ad9254db91a381f1eef66aa6bbd94 GIT binary patch literal 47992 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 literal 0 HcmV?d00001 diff --git a/frontend/src/assets/fonts/Sen-ExtraBold.ttf b/frontend/src/assets/fonts/Sen-ExtraBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..13b3be361802d07a2be5f2cdd944e0930df26f78 GIT binary patch literal 47472 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 literal 0 HcmV?d00001 diff --git a/frontend/src/assets/fonts/Sen-Regular.ttf b/frontend/src/assets/fonts/Sen-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8b08770a026b57c8bfd9de1232d825dbd05826bc GIT binary patch literal 45556 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 literal 0 HcmV?d00001 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 + +
+