diff --git a/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx b/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx
index b4f6055685..5b11604b26 100644
--- a/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx
+++ b/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx
@@ -12,6 +12,7 @@ import LeaveProjectButton from '../table/cells/action-buttons/leave-project-butt
import DeleteProjectButton from '../table/cells/action-buttons/delete-project-button'
import { Project } from '../../../../../../types/project/dashboard/api'
import CompileAndDownloadProjectPDFButton from '../table/cells/action-buttons/compile-and-download-project-pdf-button'
+import RenameProjectButton from '../table/cells/action-buttons/rename-project-button'
type ActionButtonProps = {
project: Project
@@ -194,6 +195,26 @@ function DeleteProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
)
}
+function RenameProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
+ const handleClick = (handleOpenModal: () => void) => {
+ handleOpenModal()
+ onClick()
+ }
+ return (
+
+ {(text, handleOpenModal) => (
+ handleClick(handleOpenModal)}
+ className="projects-action-menu-item"
+ >
+ {' '}
+ {text}
+
+ )}
+
+ )
+}
+
type ActionDropdownProps = {
project: Project
}
@@ -216,6 +237,7 @@ function ActionsDropdown({ project }: ActionDropdownProps) {
+
void) => React.ReactElement
+}
+
+function RenameProjectButton({ project, children }: RenameProjectButtonProps) {
+ const { t } = useTranslation()
+ const text = t('rename')
+ const [showModal, setShowModal] = useState(false)
+ const isMounted = useIsMounted()
+
+ const handleOpenModal = useCallback(() => {
+ setShowModal(true)
+ }, [])
+
+ const handleCloseModal = useCallback(() => {
+ if (isMounted.current) {
+ setShowModal(false)
+ }
+ }, [isMounted])
+
+ if (project.accessLevel !== 'owner') {
+ return null
+ }
+ return (
+ <>
+ {children(text, handleOpenModal)}
+
+ >
+ )
+}
+
+export default memo(RenameProjectButton)
diff --git a/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx b/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx
index d0da91f076..2c059f437f 100644
--- a/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx
+++ b/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx
@@ -12,7 +12,10 @@ function ProjectToolsMoreDropdownButton() {
{t('more')}
-
+
diff --git a/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/rename-project-button.test.tsx b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/rename-project-button.test.tsx
new file mode 100644
index 0000000000..bf7773b56c
--- /dev/null
+++ b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/rename-project-button.test.tsx
@@ -0,0 +1,68 @@
+import RenameProjectButton from '@/features/project-list/components/table/cells/action-buttons/rename-project-button'
+import {
+ renderWithProjectListContext,
+ resetProjectListContextFetch,
+} from '../../../../helpers/render-with-context'
+import { ownedProject, sharedProject } from '../../../../fixtures/projects-data'
+import { Project } from '../../../../../../../../types/project/dashboard/api'
+import { fireEvent, screen, waitFor } from '@testing-library/react'
+import fetchMock from 'fetch-mock'
+import { expect } from 'chai'
+
+// Little test jig for rendering the button
+function renderWithProject(project: Project) {
+ renderWithProjectListContext(
+
+ {(text, onClick) => {
+ return
+ }}
+
+ )
+}
+
+describe('', function () {
+ afterEach(function () {
+ resetProjectListContextFetch()
+ })
+
+ it('opens the modal when clicked', function () {
+ renderWithProject(ownedProject)
+ const btn = screen.getByRole('button')
+ fireEvent.click(btn)
+ screen.getByText('Rename Project')
+ screen.getByDisplayValue(ownedProject.name)
+ })
+
+ it('does not render the button when already archived', function () {
+ renderWithProject(sharedProject)
+ expect(screen.queryByRole('button')).to.be.null
+ })
+
+ it('should rename the project', async function () {
+ const project = Object.assign({}, ownedProject)
+ const renameProjectMock = fetchMock.post(
+ `express:/project/:projectId/rename`,
+ {
+ status: 200,
+ },
+ { delay: 0 }
+ )
+ renderWithProject(ownedProject)
+ const btn = screen.getByRole('button')
+ fireEvent.click(btn)
+ screen.getByText('Rename Project')
+ const confirmBtn = screen.getByText('Rename') as HTMLButtonElement
+ expect(confirmBtn.disabled).to.be.true
+ const nameInput = screen.getByDisplayValue(ownedProject.name)
+ fireEvent.change(nameInput, { target: { value: 'new name' } })
+ expect(confirmBtn.disabled).to.be.false
+ fireEvent.click(confirmBtn)
+ expect(confirmBtn.disabled).to.be.true
+
+ await waitFor(
+ () =>
+ expect(renameProjectMock.called(`/project/${project.id}/rename`)).to.be
+ .true
+ )
+ })
+})
diff --git a/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx b/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx
index 420cd10f8b..276c98fdee 100644
--- a/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx
+++ b/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx
@@ -64,14 +64,20 @@ describe('', function () {
render()
screen.getByLabelText('Select Starfleet Report (readAndWrite)').click()
screen.getByRole('button', { name: 'More' }).click()
- expect(screen.queryByRole('menuitem', { name: 'Rename' })).to.be.null
+ expect(
+ within(
+ screen.getByTestId('project-tools-more-dropdown-menu')
+ ).queryByRole('menuitem', { name: 'Rename' })
+ ).to.be.null
})
it('displays the Rename option for a project owned by the current user', function () {
render()
screen.getByLabelText('Select Starfleet Report (owner)').click()
screen.getByRole('button', { name: 'More' }).click()
- screen.getByRole('menuitem', { name: 'Rename' }).click()
+ within(screen.getByTestId('project-tools-more-dropdown-menu'))
+ .getByRole('menuitem', { name: 'Rename' })
+ .click()
within(screen.getByRole('dialog')).getByText('Rename Project')
})
})
diff --git a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts
index cafc192d63..dd294c8a58 100644
--- a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts
+++ b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts
@@ -97,6 +97,8 @@ export const trashedAndNotOwnedProject = {
export const sharedProject = archiveableProject
+export const ownedProject = copyableProject
+
export const projectsData: Array = [
copyableProject,
archiveableProject,