From 966e514c5a92a4f67d2b8228f1f5695c1531df5d Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 15 Jul 2025 12:42:09 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20redirect=20t?= =?UTF-8?q?o=20doc=20after=20duplicate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we duplicate a document from a document page, we now redirect the user to the newly created document. --- CHANGELOG.md | 1 + .../__tests__/app-impress/doc-header.spec.ts | 12 ++++++---- .../doc-header/__tests__/DocToolBox.spec.tsx | 7 ++++++ .../docs/doc-header/components/DocToolBox.tsx | 21 ++++------------- .../doc-management/api/useDuplicateDoc.tsx | 23 ++++++++++++++----- .../docs-grid/components/DocsGridActions.tsx | 20 ++-------------- 6 files changed, 40 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3594108d2e..ba9d3d6f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Changed +- ♻️(frontend) redirect to doc after duplicate #1175 - 🔧(project) change env.d system by using local files #1200 ### Fixed diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index d0997ec850..9539dff0ab 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -442,9 +442,10 @@ test.describe('Doc Header', () => { page.getByText('Document duplicated successfully!'), ).toBeVisible(); - await page.goto('/'); - const duplicateTitle = 'Copy of ' + docTitle; + await verifyDocName(page, duplicateTitle); + + await page.goto('/'); const row = await getGridRow(page, duplicateTitle); @@ -477,9 +478,12 @@ test.describe('Doc Header', () => { page.getByText('Document duplicated successfully!'), ).toBeVisible(); - const duplicateDuplicateTitle = 'Copy of ' + childTitle; + const duplicateTitle = 'Copy of ' + childTitle; + + await verifyDocName(page, duplicateTitle); + await expect( - page.getByTestId('doc-tree').getByText(duplicateDuplicateTitle), + page.getByTestId('doc-tree').getByText(duplicateTitle), ).toBeVisible(); }); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx index 35faa9a837..11856db563 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx @@ -32,6 +32,13 @@ jest.mock('@/features/docs/doc-export/', () => ({ ModalExport: () => ModalExport, })); +jest.mock('next/router', () => ({ + ...jest.requireActual('next/router'), + useRouter: () => ({ + push: jest.fn(), + }), +})); + const doc = { nb_accesses: 1, abilities: { diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx index cc41286389..b84321c17d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx @@ -1,11 +1,7 @@ import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; -import { - Button, - VariantType, - useModal, - useToastProvider, -} from '@openfun/cunningham-react'; +import { Button, useModal } from '@openfun/cunningham-react'; import { useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -62,7 +58,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { }, [doc, treeContext?.root]); const queryClient = useQueryClient(); - const { toast } = useToastProvider(); + const router = useRouter(); const { spacingsTokens, colorsTokens } = useCunninghamTheme(); @@ -74,15 +70,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { const { isSmallMobile, isDesktop } = useResponsiveStore(); const copyDocLink = useCopyDocLink(doc.id); const { mutate: duplicateDoc } = useDuplicateDoc({ - onSuccess: () => { - toast(t('Document duplicated successfully!'), VariantType.SUCCESS, { - duration: 3000, - }); - }, - onError: () => { - toast(t('Failed to duplicate the document...'), VariantType.ERROR, { - duration: 3000, - }); + onSuccess: (data) => { + void router.push(`/docs/${data.id}`); }, }); const { isFeatureFlagActivated } = useAnalytics(); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx index 0c6c9193b1..f097401d7f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDuplicateDoc.tsx @@ -1,13 +1,14 @@ +import { VariantType, useToastProvider } from '@openfun/cunningham-react'; import { UseMutationOptions, useMutation, useQueryClient, } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; import * as Y from 'yjs'; import { APIError, errorCauses, fetchAPI } from '@/api'; import { toBase64 } from '@/docs/doc-editor'; -import { KEY_DOC_TREE } from '@/docs/doc-tree'; import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning'; import { useProviderStore } from '../stores'; @@ -52,9 +53,10 @@ type DuplicateDocOptions = UseMutationOptions< DuplicateDocParams >; -export function useDuplicateDoc(options: DuplicateDocOptions) { +export function useDuplicateDoc(options?: DuplicateDocOptions) { const queryClient = useQueryClient(); - + const { toast } = useToastProvider(); + const { t } = useTranslation(); const { provider } = useProviderStore(); const { mutateAsync: updateDoc } = useUpdateDoc({ @@ -86,10 +88,19 @@ export function useDuplicateDoc(options: DuplicateDocOptions) { void queryClient.resetQueries({ queryKey: [KEY_LIST_DOC], }); - void queryClient.resetQueries({ - queryKey: [KEY_DOC_TREE], + + toast(t('Document duplicated successfully!'), VariantType.SUCCESS, { + duration: 3000, }); - void options.onSuccess?.(data, variables, context); + + void options?.onSuccess?.(data, variables, context); + }, + onError: (error, variables, context) => { + toast(t('Failed to duplicate the document...'), VariantType.ERROR, { + duration: 3000, + }); + + void options?.onError?.(error, variables, context); }, }); } diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx index 8928a3ffc6..f2fbc04c0a 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx @@ -1,8 +1,4 @@ -import { - VariantType, - useModal, - useToastProvider, -} from '@openfun/cunningham-react'; +import { useModal } from '@openfun/cunningham-react'; import { useTranslation } from 'react-i18next'; import { DropdownMenu, DropdownMenuOption, Icon } from '@/components'; @@ -25,21 +21,9 @@ export const DocsGridActions = ({ openShareModal, }: DocsGridActionsProps) => { const { t } = useTranslation(); - const { toast } = useToastProvider(); const deleteModal = useModal(); - const { mutate: duplicateDoc } = useDuplicateDoc({ - onSuccess: () => { - toast(t('Document duplicated successfully!'), VariantType.SUCCESS, { - duration: 3000, - }); - }, - onError: () => { - toast(t('Failed to duplicate the document...'), VariantType.ERROR, { - duration: 3000, - }); - }, - }); + const { mutate: duplicateDoc } = useDuplicateDoc(); const removeFavoriteDoc = useDeleteFavoriteDoc({ listInvalideQueries: [KEY_LIST_DOC], From 83f2b3886ef204639cbf98bf4b2f035b9eb6f18e Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 15 Jul 2025 13:14:41 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20duplicate=20act?= =?UTF-8?q?ion=20to=20doc=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We added a duplicate action to the document tree. --- CHANGELOG.md | 4 ++++ .../__tests__/app-impress/doc-header.spec.ts | 17 +++++++++----- .../components/DocTreeItemActions.tsx | 22 +++++++++++++++++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba9d3d6f2d..8d16acc8a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Added + +- ✨(frontend) add duplicate action to doc tree #1175 + ### Changed - ♻️(frontend) redirect to doc after duplicate #1175 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index 9539dff0ab..ed7486cde1 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -471,14 +471,19 @@ test.describe('Doc Header', () => { await editor.click(); await editor.fill('Hello Duplicated World'); - await page.getByLabel('Open the document options').click(); + const duplicateTitle = 'Copy of ' + childTitle; + const docTree = page.getByTestId('doc-tree'); - await page.getByRole('menuitem', { name: 'Duplicate' }).click(); - await expect( - page.getByText('Document duplicated successfully!'), - ).toBeVisible(); + const child = docTree + .getByRole('treeitem') + .locator('.--docs-sub-page-item') + .filter({ + hasText: childTitle, + }); + await child.hover(); + await child.getByText(`more_horiz`).click(); - const duplicateTitle = 'Copy of ' + childTitle; + await page.getByRole('menuitem', { name: 'Duplicate' }).click(); await verifyDocName(page, duplicateTitle); diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx index 37fe819942..1d8d8a9248 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx @@ -10,13 +10,14 @@ import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { Box, BoxButton, Icon } from '@/components'; - import { Doc, ModalRemoveDoc, Role, useCopyDocLink, -} from '../../doc-management'; + useDuplicateDoc, +} from '@/docs/doc-management'; + import { useCreateChildrenDoc } from '../api/useCreateChildren'; import { useDetachDoc } from '../api/useDetach'; import MoveDocIcon from '../assets/doc-extract-bold.svg'; @@ -45,6 +46,11 @@ export const DocTreeItemActions = ({ const { isCurrentParent } = useTreeUtils(doc); const { mutate: detachDoc } = useDetachDoc(); const treeContext = useTreeContext(); + const { mutate: duplicateDoc } = useDuplicateDoc({ + onSuccess: (data) => { + void router.push(`/docs/${data.id}`); + }, + }); const handleDetachDoc = () => { if (!treeContext?.root) { @@ -89,6 +95,18 @@ export const DocTreeItemActions = ({ }, ] : []), + { + label: t('Duplicate'), + icon: , + isDisabled: !doc.abilities.duplicate, + callback: () => { + duplicateDoc({ + docId: doc.id, + with_accesses: false, + canSave: doc.abilities.partial_update, + }); + }, + }, { label: t('Delete'), isDisabled: !doc.abilities.destroy,