From 7cc0e325bca848b2c15f419885bbd77a8c1b79a9 Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Mon, 9 Oct 2023 16:26:11 -0500 Subject: [PATCH] Revert "replace Manage Team modal with link to TAM invitation page (#327)" (#332) This reverts commit e0268e3fa8e7091d693230d5bd6164279d8ad8ea. --- .../users/UserList/AddUserModal.module.css | 32 +++ .../users/UserList/AddUserModal.test.tsx | 27 +++ .../projects/users/UserList/AddUserModal.tsx | 222 ++++++++++++++++++ .../projects/users/UserList/ManageTeam.tsx | 9 +- 4 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 libs/tup-components/src/projects/users/UserList/AddUserModal.module.css create mode 100644 libs/tup-components/src/projects/users/UserList/AddUserModal.test.tsx create mode 100644 libs/tup-components/src/projects/users/UserList/AddUserModal.tsx diff --git a/libs/tup-components/src/projects/users/UserList/AddUserModal.module.css b/libs/tup-components/src/projects/users/UserList/AddUserModal.module.css new file mode 100644 index 000000000..14338521f --- /dev/null +++ b/libs/tup-components/src/projects/users/UserList/AddUserModal.module.css @@ -0,0 +1,32 @@ +/* To prevent dynamic height adjustment */ +.root :global(.modal-content) { + height: 50vh; +} + +/* To activate scrolling from SectionTableWrapper contentShouldScroll */ +/* NOTE: I do not think this should be required in client CSS */ +/* HELP: What other solutions exist? If not, then… + should SectionTableWrapper apply this? */ +.table-wrap { + overflow-y: auto; +} +.body { + display: grid; + grid-template-rows: auto auto 1fr; +} + +.add-remove-column { + text-align: right; + + /* To "shrink-wrap" table cell */ + /* CAVEAT: Requires table `table-layout: auto` (browser default) */ + width: 1%; + white-space: nowrap; +} + +.success-icon { + margin-right: 0.5rem; + vertical-align: text-top; + + color: var(--global-color-success--normal); +} diff --git a/libs/tup-components/src/projects/users/UserList/AddUserModal.test.tsx b/libs/tup-components/src/projects/users/UserList/AddUserModal.test.tsx new file mode 100644 index 000000000..25f0e9c8c --- /dev/null +++ b/libs/tup-components/src/projects/users/UserList/AddUserModal.test.tsx @@ -0,0 +1,27 @@ +import AddUserModal from './AddUserModal'; +import { testRender } from '@tacc/tup-testing'; +import { screen, fireEvent, within } from '@testing-library/react'; + +describe('AddUserModal', () => { + it('should display search results', async () => { + testRender(); + const modalButton = screen.getByRole('button'); + // open the modal + fireEvent.click(modalButton); + + const searchButton = screen.getByText('Search'); + fireEvent.click(searchButton); + const rows = await screen.findAllByRole('row'); + expect(rows.length).toBe(3); + + // A user in the project should display as added already + const existingUserRow = rows[1]; + const rowQuery = await within(existingUserRow).findByText(/Added/); + expect(rowQuery).toBeDefined(); + + // A user who is not in the project should display a prompt. + const newUserRow = rows[2]; + const rowQuery2 = await within(newUserRow).findByText(/Add User/); + expect(rowQuery2).toBeDefined(); + }); +}); diff --git a/libs/tup-components/src/projects/users/UserList/AddUserModal.tsx b/libs/tup-components/src/projects/users/UserList/AddUserModal.tsx new file mode 100644 index 000000000..61271a47b --- /dev/null +++ b/libs/tup-components/src/projects/users/UserList/AddUserModal.tsx @@ -0,0 +1,222 @@ +import { + Button, + LoadingSpinner, + Icon, + SectionMessage, + SectionTableWrapper, +} from '@tacc/core-components'; +import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; +import { Input } from 'reactstrap'; +import React, { useState } from 'react'; +import { + useUserLookup, + UserSearchResult, + useProjectUsers, + useAddProjectUser, + useRemoveProjectUser, +} from '@tacc/tup-hooks'; + +import styles from './AddUserModal.module.css'; +import stylesUserList from './UserList.module.css'; + +type FieldValue = 'email' | 'username' | 'last_name'; + +const AddUserButton: React.FC<{ username: string; projectId: number }> = ({ + username, + projectId, +}) => { + const { mutate, isLoading } = useAddProjectUser(projectId); + if (isLoading) return ; + return ( + + ); +}; + +const RemoveUser: React.FC<{ username: string; projectId: number }> = ({ + username, + projectId, +}) => { + const { mutate, isLoading } = useRemoveProjectUser(projectId, username); + if (isLoading) + return ( +
+ +
+ ); + return ( + <> + {' '} + Added  |   + + + ); +}; + +const UserSearchTable: React.FC<{ + users: UserSearchResult[]; + projectId: number; +}> = ({ users, projectId }) => { + const { data: projectUsers } = useProjectUsers(projectId); + + const userInProject = (username: string) => { + return (projectUsers || []).some((user) => user.username === username); + }; + + if (!users.length) + return ( + + No users matching your query could be found. + + ); + + return ( + + + + + + + + + + + {users.map((user) => ( + + + + + + + ))} + +
NameEmailUsername
{user.name}{user.email}{user.username} + {userInProject(user.username) ? ( + + ) : ( + + )} +
+ ); +}; + +const AddUserModal: React.FC<{ projectId: number }> = ({ projectId }) => { + const [isOpen, setIsOpen] = useState(false); + const toggle = () => { + setIsOpen(!isOpen); + setField('last_name'); + setQuery(''); + }; + + const [field, setField] = useState('last_name'); + const [query, setQuery] = useState(''); + const { data, isFetching, refetch } = useUserLookup(projectId, query, field); + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + + const closeBtn = ( + + ); + + return ( + <> + + + + Add Users + + +

Search for User

+
onSubmit(e)}> + {/* Radio labels for selecting lastname/email/username for search */} +
+ setField(e.target.value as FieldValue)} + checked={field === 'last_name'} + /> + + + setField(e.target.value as FieldValue)} + checked={field === 'email'} + /> + + + setField(e.target.value as FieldValue)} + checked={field === 'username'} + /> + +
+ {/* Search bar input group */} +
+
+ +
+ setQuery(e.target.value)} + /> +
+ +
+ {/* Search result table */} + {data && ( + + + + )} +
+ +
+ + ); +}; + +export default AddUserModal; diff --git a/libs/tup-components/src/projects/users/UserList/ManageTeam.tsx b/libs/tup-components/src/projects/users/UserList/ManageTeam.tsx index 898c2cde2..1d2a50cd8 100644 --- a/libs/tup-components/src/projects/users/UserList/ManageTeam.tsx +++ b/libs/tup-components/src/projects/users/UserList/ManageTeam.tsx @@ -1,6 +1,7 @@ import { Button } from '@tacc/core-components'; import { useProject, useRoleForCurrentUser } from '@tacc/tup-hooks'; import styles from './UserList.module.css'; +import AddUserModal from './AddUserModal'; const ManageTeam: React.FC<{ projectId: number }> = ({ projectId }) => { const currentUserRole = useRoleForCurrentUser(projectId) ?? ''; @@ -25,13 +26,7 @@ const ManageTeam: React.FC<{ projectId: number }> = ({ projectId }) => { return (
- - - +
); };