From c99470ec4e4211558c67bf746619855e84251e3b Mon Sep 17 00:00:00 2001 From: sjaanus Date: Fri, 5 Aug 2022 16:10:53 +0300 Subject: [PATCH 1/3] Feat/groups refinements (#1197) * Improvements * Double icon for group * Hide columns * Refinements * Refinements * Reduce padding * Add projectId * Fixes * Make useHiddenColumns component --- .../apiToken/ApiTokenTable/ApiTokenTable.tsx | 39 ++++------- .../component/admin/groups/Group/Group.tsx | 64 ++++++++++--------- .../GroupFormUsersTable.tsx | 12 +++- .../admin/groups/GroupsList/GroupsList.tsx | 20 ++++-- .../common/MainHeader/MainHeader.tsx | 2 +- .../ProjectAccessAssign.tsx | 4 +- .../ProjectRoleDescription.tsx | 2 +- .../ProjectAccessTable/ProjectAccessTable.tsx | 54 ++++++++++++---- .../ProjectGroupView/ProjectGroupView.tsx | 17 ++++- frontend/src/hooks/useHiddenColumns.ts | 15 +++++ 10 files changed, 147 insertions(+), 82 deletions(-) create mode 100644 frontend/src/hooks/useHiddenColumns.ts diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx index 273ca43376..013ea29f3a 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx @@ -18,18 +18,19 @@ import { CopyApiTokenButton } from 'component/admin/apiToken/CopyApiTokenButton/ import { RemoveApiTokenButton } from 'component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { sortTypes } from 'utils/sortTypes'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import theme from 'themes/theme'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { Search } from 'component/common/Search/Search'; +import useHiddenColumns from 'hooks/useHiddenColumns'; export const ApiTokenTable = () => { const { tokens, loading } = useApiTokens(); - const hiddenColumns = useHiddenColumns(); const initialState = useMemo(() => ({ sortBy: [{ id: 'createdAt' }] }), []); + const { uiConfig } = useUiConfig(); const { getTableProps, @@ -52,9 +53,16 @@ export const ApiTokenTable = () => { useSortBy ); - useEffect(() => { - setHiddenColumns(hiddenColumns); - }, [setHiddenColumns, hiddenColumns]); + useHiddenColumns( + setHiddenColumns, + ['Icon', 'createdAt'], + useMediaQuery(theme.breakpoints.down('md')) + ); + useHiddenColumns( + setHiddenColumns, + ['projects', 'environment'], + !uiConfig.flags.E + ); return ( { ); }; -const useHiddenColumns = (): string[] => { - const { uiConfig } = useUiConfig(); - const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); - - return useMemo(() => { - const hidden: string[] = []; - - if (!uiConfig.flags.E) { - hidden.push('projects'); - hidden.push('environment'); - } - - if (isMediumScreen) { - hidden.push('Icon'); - hidden.push('createdAt'); - } - - return hidden; - }, [uiConfig, isMediumScreen]); -}; - const COLUMNS = [ { id: 'Icon', diff --git a/frontend/src/component/admin/groups/Group/Group.tsx b/frontend/src/component/admin/groups/Group/Group.tsx index 6a40d812ca..8ded2867ec 100644 --- a/frontend/src/component/admin/groups/Group/Group.tsx +++ b/frontend/src/component/admin/groups/Group/Group.tsx @@ -1,13 +1,12 @@ import { useEffect, useMemo, useState, VFC } from 'react'; import { - Button, IconButton, styled, Tooltip, useMediaQuery, useTheme, } from '@mui/material'; -import { useSearchParams } from 'react-router-dom'; +import { useSearchParams, Link } from 'react-router-dom'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; import { useGroup } from 'hooks/api/getters/useGroup/useGroup'; @@ -26,17 +25,17 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell'; import { GroupUserRoleCell } from 'component/admin/groups/GroupUserRoleCell/GroupUserRoleCell'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { Delete, Edit } from '@mui/icons-material'; +import { Add, Delete, Edit } from '@mui/icons-material'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { MainHeader } from 'component/common/MainHeader/MainHeader'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup'; -import { Link } from 'react-router-dom'; import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; import { AddGroupUser } from './AddGroupUser/AddGroupUser'; import { EditGroupUser } from './EditGroupUser/EditGroupUser'; import { RemoveGroupUser } from './RemoveGroupUser/RemoveGroupUser'; import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import { UG_EDIT_BTN_ID, UG_DELETE_BTN_ID, @@ -140,30 +139,36 @@ export const Group: VFC = () => { Cell: ({ row: { original: rowUser } }: any) => ( - { - setSelectedUser(rowUser); - setEditUserOpen(true); - }} - > - - + + { + setSelectedUser(rowUser); + setEditUserOpen(true); + }} + > + + + - { - setSelectedUser(rowUser); - setRemoveUserOpen(true); - }} - > - - + + { + setSelectedUser(rowUser); + setRemoveUserOpen(true); + }} + > + + + ), @@ -171,7 +176,7 @@ export const Group: VFC = () => { disableSortBy: true, }, ], - [setSelectedUser, setRemoveUserOpen] + [setSelectedUser, setRemoveUserOpen, group?.users.length] ); const [searchParams, setSearchParams] = useSearchParams(); @@ -306,16 +311,17 @@ export const Group: VFC = () => { } /> - + } > @@ -376,13 +382,13 @@ export const Group: VFC = () => { diff --git a/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx b/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx index b25d8cf80c..c9193a329d 100644 --- a/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx +++ b/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx @@ -1,5 +1,5 @@ import { useMemo, VFC } from 'react'; -import { IconButton, Tooltip } from '@mui/material'; +import { IconButton, Tooltip, useMediaQuery } from '@mui/material'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { IGroupUser } from 'interfaces/group'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; @@ -11,6 +11,8 @@ import { VirtualizedTable } from 'component/common/Table'; import { useFlexLayout, useSortBy, useTable } from 'react-table'; import { sortTypes } from 'utils/sortTypes'; import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import theme from 'themes/theme'; +import useHiddenColumns from 'hooks/useHiddenColumns'; interface IGroupFormUsersTableProps { users: IGroupUser[]; @@ -106,7 +108,7 @@ export const GroupFormUsersTable: VFC = ({ [setUsers] ); - const { headerGroups, rows, prepareRow } = useTable( + const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable( { columns: columns as any[], data: users as any[], @@ -119,6 +121,12 @@ export const GroupFormUsersTable: VFC = ({ useFlexLayout ); + useHiddenColumns( + setHiddenColumns, + ['imageUrl', 'name'], + useMediaQuery(theme.breakpoints.down('md')) + ); + return ( 0} diff --git a/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx b/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx index 7cc52ddd9c..8c4b063bff 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState, VFC } from 'react'; import { useGroups } from 'hooks/api/getters/useGroups/useGroups'; -import { Link, useSearchParams } from 'react-router-dom'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { IGroup } from 'interfaces/group'; import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; @@ -12,6 +12,9 @@ import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightC import { TablePlaceholder } from 'component/common/Table'; import { GroupCard } from './GroupCard/GroupCard'; import { GroupEmpty } from './GroupEmpty/GroupEmpty'; +import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; +import { Add } from '@mui/icons-material'; import { NAVIGATE_TO_CREATE_GROUP } from 'utils/testIds'; type PageQueryType = Partial>; @@ -33,6 +36,7 @@ const groupsSearch = (group: IGroup, searchValue: string) => { }; export const GroupsList: VFC = () => { + const navigate = useNavigate(); const { groups = [], loading } = useGroups(); const [searchParams, setSearchParams] = useSearchParams(); const [searchValue, setSearchValue] = useState( @@ -81,15 +85,17 @@ export const GroupsList: VFC = () => { } /> - + } > diff --git a/frontend/src/component/common/MainHeader/MainHeader.tsx b/frontend/src/component/common/MainHeader/MainHeader.tsx index 2b46d0237b..9c5464bfdb 100644 --- a/frontend/src/component/common/MainHeader/MainHeader.tsx +++ b/frontend/src/component/common/MainHeader/MainHeader.tsx @@ -13,7 +13,7 @@ const StyledMainHeader = styled(Paper)(({ theme }) => ({ const StyledTitleHeader = styled('div')(({ theme }) => ({ display: 'flex', - alignItems: 'center', + alignItems: 'flex-start', justifyContent: 'space-between', })); diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx index 3cdb95adf3..ddb8d64ce5 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx @@ -47,8 +47,8 @@ const StyledAutocompleteWrapper = styled('div')(({ theme }) => ({ }, })); -const StyledButtonContainer = styled('div')(() => ({ - marginTop: 'auto', +const StyledButtonContainer = styled('div')(({ theme }) => ({ + marginTop: theme.spacing(6), display: 'flex', justifyContent: 'flex-end', })); diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx index f9d2276693..75c9090270 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx @@ -9,7 +9,7 @@ const StyledDescription = styled('div')(({ theme }) => ({ padding: theme.spacing(3), backgroundColor: theme.palette.neutral.light, color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallerBody, + fontSize: theme.fontSizes.smallBody, borderRadius: theme.shape.borderRadiusMedium, })); diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx index 01ddcb5828..29754c44d8 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState, VFC } from 'react'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { VirtualizedTable, TablePlaceholder } from 'component/common/Table'; -import { Button, useMediaQuery, useTheme } from '@mui/material'; -import { Delete, Edit } from '@mui/icons-material'; +import { styled, useMediaQuery, useTheme } from '@mui/material'; +import { Add, Delete, Edit } from '@mui/icons-material'; import { sortTypes } from 'utils/sortTypes'; import useProjectAccess, { ENTITY_TYPE, @@ -38,9 +38,11 @@ import { IUser } from 'interfaces/user'; import { IGroup } from 'interfaces/group'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import { ProjectAccessCreate } from 'component/project/ProjectAccess/ProjectAccessCreate/ProjectAccessCreate'; import { ProjectAccessEditUser } from 'component/project/ProjectAccess/ProjectAccessEditUser/ProjectAccessEditUser'; import { ProjectAccessEditGroup } from 'component/project/ProjectAccess/ProjectAccessEditGroup/ProjectAccessEditGroup'; +import useHiddenColumns from 'hooks/useHiddenColumns'; export type PageQueryType = Partial< Record<'sort' | 'order' | 'search', string> @@ -53,6 +55,20 @@ const { value: storedParams, setValue: setStoredParams } = createLocalStorage( defaultSort ); +const StyledUserAvatars = styled('div')(({ theme }) => ({ + display: 'inline-flex', + alignItems: 'center', + flexWrap: 'wrap', + marginLeft: theme.spacing(1), +})); + +const StyledEmptyAvatar = styled(UserAvatar)(({ theme }) => ({ + marginRight: theme.spacing(-3.5), +})); +const StyledGroupAvatar = styled(UserAvatar)(({ theme }) => ({ + outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`, +})); + export const ProjectAccessTable: VFC = () => { const projectId = useRequiredPathParam('projectId'); @@ -77,11 +93,15 @@ export const ProjectAccessTable: VFC = () => { Header: 'Avatar', accessor: 'imageUrl', Cell: ({ row: { original: row } }: any) => ( - - + + } + /> + {row.entity.users?.length} - - + + ), maxWidth: 85, disableSortBy: true, @@ -124,6 +144,7 @@ export const ProjectAccessTable: VFC = () => { searchable: true, }, { + id: 'role', Header: 'Role', accessor: (row: IProjectAccess) => access?.roles.find(({ id }) => id === row.entity.roleId) @@ -145,6 +166,7 @@ export const ProjectAccessTable: VFC = () => { maxWidth: 150, }, { + id: 'lastLogin', Header: 'Last login', accessor: (row: IProjectAccess) => { if (row.type === ENTITY_TYPE.USER) { @@ -240,6 +262,7 @@ export const ProjectAccessTable: VFC = () => { headerGroups, rows, prepareRow, + setHiddenColumns, state: { sortBy }, } = useTable( { @@ -258,6 +281,12 @@ export const ProjectAccessTable: VFC = () => { useFlexLayout ); + useHiddenColumns( + setHiddenColumns, + ['imageUrl', 'username', 'role', 'added', 'lastLogin'], + isSmallScreen + ); + useEffect(() => { const tableState: PageQueryType = {}; tableState.sort = sortBy[0].id; @@ -332,14 +361,15 @@ export const ProjectAccessTable: VFC = () => { } /> - + } > diff --git a/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx b/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx index 39a2c954f2..57a54187fc 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx @@ -20,17 +20,22 @@ import { IGroup, IGroupUser } from 'interfaces/group'; import { VFC, useState } from 'react'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { sortTypes } from 'utils/sortTypes'; +import useHiddenColumns from 'hooks/useHiddenColumns'; const StyledPageContent = styled(PageContent)(({ theme }) => ({ height: '100vh', overflow: 'auto', padding: theme.spacing(7.5, 6), + [theme.breakpoints.down('md')]: { + padding: theme.spacing(4, 2), + }, '& .header': { padding: theme.spacing(0, 0, 2, 0), }, '& .body': { padding: theme.spacing(3, 0, 0, 0), }, + borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`, })); const StyledTitle = styled('div')(({ theme }) => ({ @@ -38,7 +43,7 @@ const StyledTitle = styled('div')(({ theme }) => ({ flexDirection: 'column', '& > span': { color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, + fontSize: theme.fontSizes.bodySize, }, })); @@ -80,6 +85,7 @@ const columns = [ filterName: 'type', }, { + id: 'joined', Header: 'Joined', accessor: 'joinedAt', Cell: DateCell, @@ -87,6 +93,7 @@ const columns = [ maxWidth: 150, }, { + id: 'lastLogin', Header: 'Last login', accessor: (row: IGroupUser) => row.seenAt || '', Cell: ({ row: { original: user } }: any) => ( @@ -135,7 +142,7 @@ export const ProjectGroupView: VFC = ({ group?.users ?? [] ); - const { headerGroups, rows, prepareRow } = useTable( + const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable( { columns: columns as any[], data, @@ -149,6 +156,12 @@ export const ProjectGroupView: VFC = ({ useFlexLayout ); + useHiddenColumns( + setHiddenColumns, + ['imageUrl', 'name', 'joined', 'lastLogin'], + useMediaQuery(theme.breakpoints.down('md')) + ); + return ( (param: Array>) => void, + hiddenColumns: string[], + condition: boolean +) => { + useEffect(() => { + const hidden = condition ? hiddenColumns : []; + setHiddenColumns(hidden); + }, [setHiddenColumns, condition]); +}; + +export default useHiddenColumns; From c978ed6c6b934fc8ed3434e57e4487981e2990f5 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Mon, 8 Aug 2022 09:08:21 +0300 Subject: [PATCH 2/3] Update messages in the dialogs for groups (#1208) * Update messages * Refinement --- .../Group/RemoveGroupUser/RemoveGroupUser.tsx | 13 ++++++------- .../admin/groups/RemoveGroup/RemoveGroup.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/frontend/src/component/admin/groups/Group/RemoveGroupUser/RemoveGroupUser.tsx b/frontend/src/component/admin/groups/Group/RemoveGroupUser/RemoveGroupUser.tsx index 5e8faf6ac2..1a1159c9fb 100644 --- a/frontend/src/component/admin/groups/Group/RemoveGroupUser/RemoveGroupUser.tsx +++ b/frontend/src/component/admin/groups/Group/RemoveGroupUser/RemoveGroupUser.tsx @@ -47,23 +47,22 @@ export const RemoveGroupUser: FC = ({ } }; + const userName = user?.name || user?.username || user?.email; return ( { setOpen(false); }} - title="Remove user from group" + title="Remove user from group?" > - Are you sure you wish to remove{' '} - {user?.name || user?.username || user?.email}{' '} - from {group.name}? Removing the user from this - group may also remove their access from projects this group is - assigned to. + Do you really want to remove {userName} from{' '} + {group.name}? {userName} will + lose all access rights granted by this group. ); diff --git a/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx b/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx index 3dfed93853..d6f1544697 100644 --- a/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx +++ b/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx @@ -42,18 +42,18 @@ export const RemoveGroup: FC = ({ return ( { setOpen(false); }} - title="Delete group" + title="Delete group?" > - Are you sure you wish to delete {group.name}? - If this group is currently assigned to one or more projects then - users belonging to this group may lose access to those projects. + Do you really want to delete {group.name}? + Users who are granted access to projects only via this group + will lose access to those projects. ); From 7c00b760f8934694042972e85de2531cc96b4637 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Mon, 8 Aug 2022 12:59:36 +0300 Subject: [PATCH 3/3] New project page header (#1207) * Added new layout * Fixes --- .../DeleteProject/DeleteProjectDialogue.tsx | 50 ++++++++ .../project/Project/Project.styles.ts | 2 +- .../src/component/project/Project/Project.tsx | 110 +++++++++++++++--- .../project/ProjectCard/ProjectCard.styles.ts | 1 + .../project/ProjectCard/ProjectCard.tsx | 35 +----- 5 files changed, 148 insertions(+), 50 deletions(-) create mode 100644 frontend/src/component/project/Project/DeleteProject/DeleteProjectDialogue.tsx diff --git a/frontend/src/component/project/Project/DeleteProject/DeleteProjectDialogue.tsx b/frontend/src/component/project/Project/DeleteProject/DeleteProjectDialogue.tsx new file mode 100644 index 0000000000..aab3c2977b --- /dev/null +++ b/frontend/src/component/project/Project/DeleteProject/DeleteProjectDialogue.tsx @@ -0,0 +1,50 @@ +import { Dialogue } from 'component/common/Dialogue/Dialogue'; +import React from 'react'; +import { formatUnknownError } from 'utils/formatUnknownError'; +import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; +import useProjects from 'hooks/api/getters/useProjects/useProjects'; +import useToast from 'hooks/useToast'; + +interface IDeleteProjectDialogueProps { + project: string; + open: boolean; + onClose?: () => void; + onSuccess?: () => void; +} + +export const DeleteProjectDialogue = ({ + open, + onClose, + project, + onSuccess, +}: IDeleteProjectDialogueProps) => { + const { deleteProject } = useProjectApi(); + const { refetch: refetchProjectOverview } = useProjects(); + const { setToastData, setToastApiError } = useToast(); + + const onClick = async (e: React.SyntheticEvent) => { + e.preventDefault(); + try { + await deleteProject(project); + refetchProjectOverview(); + setToastData({ + title: 'Deleted project', + type: 'success', + text: 'Successfully deleted project', + }); + onSuccess?.(); + } catch (ex: unknown) { + setToastApiError(formatUnknownError(ex)); + } + onClose?.(); + }; + + return ( + + ); +}; diff --git a/frontend/src/component/project/Project/Project.styles.ts b/frontend/src/component/project/Project/Project.styles.ts index f2c1e99e6d..76c69128aa 100644 --- a/frontend/src/component/project/Project/Project.styles.ts +++ b/frontend/src/component/project/Project/Project.styles.ts @@ -18,7 +18,7 @@ export const useStyles = makeStyles()(theme => ({ marginBottom: '1rem', }, innerContainer: { - padding: '1rem 2rem', + padding: '1.25rem 2rem', display: 'flex', alignItems: 'center', }, diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index 0d31656cd1..14fb794a55 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -4,21 +4,44 @@ import useLoading from 'hooks/useLoading'; import ApiError from 'component/common/ApiError/ApiError'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useStyles } from './Project.styles'; -import { Tab, Tabs } from '@mui/material'; -import { Edit } from '@mui/icons-material'; +import { styled, Tab, Tabs } from '@mui/material'; +import { Delete, Edit } from '@mui/icons-material'; import useToast from 'hooks/useToast'; import useQueryParams from 'hooks/useQueryParams'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { ProjectAccess } from '../ProjectAccess/ProjectAccess'; import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment'; import { ProjectFeaturesArchive } from './ProjectFeaturesArchive/ProjectFeaturesArchive'; import ProjectOverview from './ProjectOverview'; import ProjectHealth from './ProjectHealth/ProjectHealth'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions'; +import { + DELETE_PROJECT, + UPDATE_PROJECT, +} from 'component/providers/AccessProvider/permissions'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { Routes, Route, useLocation } from 'react-router-dom'; +import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue'; + +const StyledDiv = styled('div')(() => ({ + display: 'flex', +})); + +const StyledName = styled('div')(({ theme }) => ({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + paddingBottom: theme.spacing(2), +})); + +const StyledTitle = styled('span')(({ theme }) => ({ + fontSize: theme.fontSizes.smallBody, + fontWeight: 'normal', +})); +const StyledText = styled(StyledTitle)(({ theme }) => ({ + color: theme.palette.grey[800], +})); const Project = () => { const projectId = useRequiredPathParam('projectId'); @@ -33,6 +56,8 @@ const Project = () => { const basePath = `/projects/${projectId}`; const projectName = project?.name || projectId; + const [showDelDialog, setShowDelDialog] = useState(false); + const tabs = [ { title: 'Overview', @@ -85,21 +110,60 @@ const Project = () => {

-
- {projectName} +
+ {projectName} + + + Description:  + + + {project.description} + + + } + /> + + + projectId:  + + + {projectId} + +
- - navigate(`/projects/${projectId}/edit`) - } - tooltipProps={{ title: 'Edit project' }} - data-loading - > - - + + + navigate(`/projects/${projectId}/edit`) + } + tooltipProps={{ title: 'Edit project' }} + data-loading + > + + + { + setShowDelDialog(true); + }} + tooltipProps={{ title: 'Delete project' }} + data-loading + > + + +

{
+ { + setShowDelDialog(false); + }} + onSuccess={() => { + navigate('/projects'); + }} + /> } /> } /> diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts b/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts index 1f20c987bf..849697ecbe 100644 --- a/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts +++ b/frontend/src/component/project/ProjectCard/ProjectCard.styles.ts @@ -32,6 +32,7 @@ export const useStyles = makeStyles()(theme => ({ boxOrient: 'vertical', textOverflow: 'ellipsis', overflow: 'hidden', + alignItems: 'flex-start', }, projectIcon: { diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.tsx b/frontend/src/component/project/ProjectCard/ProjectCard.tsx index c6e44a532b..815432ea0f 100644 --- a/frontend/src/component/project/ProjectCard/ProjectCard.tsx +++ b/frontend/src/component/project/ProjectCard/ProjectCard.tsx @@ -4,21 +4,17 @@ import MoreVertIcon from '@mui/icons-material/MoreVert'; import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg'; import React, { useState, SyntheticEvent, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Dialogue } from 'component/common/Dialogue/Dialogue'; -import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; -import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { Delete, Edit } from '@mui/icons-material'; import { getProjectEditPath } from 'utils/routePathHelpers'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import useToast from 'hooks/useToast'; import { UPDATE_PROJECT, DELETE_PROJECT, } from 'component/providers/AccessProvider/permissions'; -import { formatUnknownError } from 'utils/formatUnknownError'; import AccessContext from 'contexts/AccessContext'; import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { DeleteProjectDialogue } from '../Project/DeleteProject/DeleteProjectDialogue'; interface IProjectCardProps { name: string; @@ -40,36 +36,15 @@ export const ProjectCard = ({ const { classes } = useStyles(); const { hasAccess } = useContext(AccessContext); const { isOss } = useUiConfig(); - const { refetch: refetchProjectOverview } = useProjects(); const [anchorEl, setAnchorEl] = useState(null); const [showDelDialog, setShowDelDialog] = useState(false); - const { deleteProject } = useProjectApi(); const navigate = useNavigate(); - const { setToastData, setToastApiError } = useToast(); - // @ts-expect-error const handleClick = e => { e.preventDefault(); setAnchorEl(e.currentTarget); }; - const onRemoveProject = async (e: React.SyntheticEvent) => { - e.preventDefault(); - try { - await deleteProject(id); - refetchProjectOverview(); - setToastData({ - title: 'Deleted project', - type: 'success', - text: 'Successfully deleted project', - }); - } catch (e: unknown) { - setToastApiError(formatUnknownError(e)); - } - setShowDelDialog(false); - setAnchorEl(null); - }; - const canDeleteProject = hasAccess(DELETE_PROJECT, id) && id !== DEFAULT_PROJECT_ID; @@ -152,15 +127,13 @@ export const ProjectCard = ({

members

- { - event.preventDefault(); + onClose={() => { setAnchorEl(null); setShowDelDialog(false); }} - title="Really delete project" /> );