From 2b2e81f04217196b3df549ee4bc8387fff3089c9 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Mon, 16 Jun 2025 12:54:07 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20Simplify=20A?= =?UTF-8?q?GPL=20export=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We were maintaining two separate components for AGPL and MIT license exports. This commit consolidates the functionality into a single component that handles both licenses, simplifying the codebase and reducing duplication. --- .../doc-export/__tests__/ExportMIT.test.tsx | 33 +++ .../src/features/docs/doc-export/index.ts | 19 +- .../__tests__/DocToolBoxAGPL.spec.tsx | 28 --- .../__tests__/DocToolBoxMIT.spec.tsx | 35 ---- .../docs/doc-header/components/DocToolBox.tsx | 182 +++++++++++++++-- .../components/DocToolBoxLicenceAGPL.tsx | 192 ------------------ .../components/DocToolBoxLicenceMIT.tsx | 165 --------------- 7 files changed, 213 insertions(+), 441 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBoxLicenceAGPL.tsx delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBoxLicenceMIT.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx new file mode 100644 index 0000000000..a4c7e74099 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx @@ -0,0 +1,33 @@ +const originalEnv = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT; + +jest.mock('@/features/docs/doc-export/utils', () => ({ + anything: true, +})); +jest.mock('@/features/docs/doc-export/components/ModalExport', () => ({ + ModalExport: () => ModalExport, +})); + +describe('useModuleExport', () => { + afterAll(() => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = originalEnv; + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + it('should return undefined when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true'; + const Export = await import('@/features/docs/doc-export/'); + + expect(Export.default).toBeUndefined(); + }); + + it('should load modules when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => { + process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; + const Export = await import('@/features/docs/doc-export/'); + + expect(Export.default).toHaveProperty('ModalExport'); + }); +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/index.ts b/src/frontend/apps/impress/src/features/docs/doc-export/index.ts index 527c58f05c..cb1ab54353 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-export/index.ts @@ -1,3 +1,20 @@ +/** + * To import Export modules you must import from the index file. + * This is to ensure that the Export modules are only loaded when + * the application is not published as MIT. + */ export * from './api'; -export * from './components'; export * from './utils'; + +import * as ModalExport from './components/ModalExport'; + +let modulesExport = undefined; +if (process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false') { + modulesExport = { + ...ModalExport, + }; +} + +type ModulesExport = typeof modulesExport; + +export default modulesExport as ModulesExport; diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx deleted file mode 100644 index 2624889bf7..0000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxAGPL.spec.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { AppWrapper } from '@/tests/utils'; - -import { DocToolBox } from '../components/DocToolBox'; - -const doc = { - nb_accesses: 1, - abilities: { - versions_list: true, - destroy: true, - }, -}; - -jest.mock('@/features/docs/doc-export/', () => ({ - ModalExport: () => ModalExport, -})); - -it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => { - process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; - - render(, { - wrapper: AppWrapper, - }); - - expect(await screen.findByText('download')).toBeInTheDocument(); -}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx deleted file mode 100644 index b7901f2def..0000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxMIT.spec.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import React from 'react'; - -import { AppWrapper } from '@/tests/utils'; - -const doc = { - nb_accesses: 1, - abilities: { - versions_list: true, - destroy: true, - }, -}; - -jest.mock('@/features/docs/doc-export/', () => ({ - ModalExport: () => ModalExport, -})); - -it('DocToolBox dynamic import: loads DocToolBox when NEXT_PUBLIC_PUBLISH_AS_MIT is true', async () => { - process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true'; - - const { DocToolBox } = await import('../components/DocToolBox'); - - render(, { - wrapper: AppWrapper, - }); - - await waitFor( - () => { - expect(screen.queryByText('download')).not.toBeInTheDocument(); - }, - { - timeout: 1000, - }, - ); -}); 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 3cdadfabb7..49c257eea9 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,47 +1,146 @@ import { Button, useModal } from '@openfun/cunningham-react'; import { useQueryClient } from '@tanstack/react-query'; -import dynamic from 'next/dynamic'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; -import { Box, Icon } from '@/components'; +import { + Box, + DropdownMenu, + DropdownMenuOption, + Icon, + IconOptions, +} from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc } from '@/docs/doc-management'; -import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning'; +import Export from '@/docs/doc-export/'; +import { + Doc, + KEY_DOC, + KEY_LIST_DOC, + ModalRemoveDoc, + useCopyDocLink, + useCreateFavoriteDoc, + useDeleteFavoriteDoc, +} from '@/docs/doc-management'; +import { DocShareModal } from '@/docs/doc-share'; +import { + KEY_LIST_DOC_VERSIONS, + ModalSelectVersion, +} from '@/docs/doc-versioning'; +import { useAnalytics } from '@/libs'; import { useResponsiveStore } from '@/stores'; +import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard'; + +const ModalExport = Export?.ModalExport; + interface DocToolBoxProps { doc: Doc; } -const DocToolBoxLicence = dynamic(() => - process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false' - ? import('./DocToolBoxLicenceAGPL').then((mod) => mod.DocToolBoxLicenceAGPL) - : import('./DocToolBoxLicenceMIT').then((mod) => mod.DocToolBoxLicenceMIT), -); - export const DocToolBox = ({ doc }: DocToolBoxProps) => { const { t } = useTranslation(); const hasAccesses = doc.nb_accesses_direct > 1 && doc.abilities.accesses_view; const queryClient = useQueryClient(); - const { spacingsTokens } = useCunninghamTheme(); + const { spacingsTokens, colorsTokens } = useCunninghamTheme(); - const modalHistory = useModal(); + const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); + const [isModalExportOpen, setIsModalExportOpen] = useState(false); + const selectHistoryModal = useModal(); const modalShare = useModal(); - const { isSmallMobile } = useResponsiveStore(); + const { isSmallMobile, isDesktop } = useResponsiveStore(); + const copyDocLink = useCopyDocLink(doc.id); + const { isFeatureFlagActivated } = useAnalytics(); + const removeFavoriteDoc = useDeleteFavoriteDoc({ + listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], + }); + const makeFavoriteDoc = useCreateFavoriteDoc({ + listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], + }); useEffect(() => { - if (modalHistory.isOpen) { + if (selectHistoryModal.isOpen) { return; } void queryClient.resetQueries({ queryKey: [KEY_LIST_DOC_VERSIONS], }); - }, [modalHistory.isOpen, queryClient]); + }, [selectHistoryModal.isOpen, queryClient]); + + const options: DropdownMenuOption[] = [ + ...(isSmallMobile + ? [ + { + label: t('Share'), + icon: 'group', + callback: modalShare.open, + }, + { + label: t('Export'), + icon: 'download', + callback: () => { + setIsModalExportOpen(true); + }, + show: !!ModalExport, + }, + { + label: t('Copy link'), + icon: 'add_link', + callback: copyDocLink, + }, + ] + : []), + { + label: doc.is_favorite ? t('Unpin') : t('Pin'), + icon: 'push_pin', + callback: () => { + if (doc.is_favorite) { + removeFavoriteDoc.mutate({ id: doc.id }); + } else { + makeFavoriteDoc.mutate({ id: doc.id }); + } + }, + testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, + }, + { + label: t('Version history'), + icon: 'history', + disabled: !doc.abilities.versions_list, + callback: () => { + selectHistoryModal.open(); + }, + show: isDesktop, + }, + + { + label: t('Copy as {{format}}', { format: 'Markdown' }), + icon: 'content_copy', + callback: () => { + void copyCurrentEditorToClipboard('markdown'); + }, + }, + { + label: t('Copy as {{format}}', { format: 'HTML' }), + icon: 'content_copy', + callback: () => { + void copyCurrentEditorToClipboard('html'); + }, + show: isFeatureFlagActivated('CopyAsHTML'), + }, + { + label: t('Delete document'), + icon: 'delete', + disabled: !doc.abilities.destroy, + callback: () => { + setIsModalRemoveOpen(true); + }, + }, + ]; + + const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard(); return ( { )} - + } + onClick={() => { + setIsModalExportOpen(true); + }} + size={isSmallMobile ? 'small' : 'medium'} + /> + )} + + + + + + {modalShare.isOpen && ( + modalShare.close()} doc={doc} /> + )} + {isModalExportOpen && ModalExport && ( + setIsModalExportOpen(false)} doc={doc} /> + )} + {isModalRemoveOpen && ( + setIsModalRemoveOpen(false)} doc={doc} /> + )} + {selectHistoryModal.isOpen && ( + selectHistoryModal.close()} doc={doc} - modalHistory={modalHistory} - modalShare={modalShare} /> - + )} ); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBoxLicenceAGPL.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBoxLicenceAGPL.tsx deleted file mode 100644 index c8ded02aaa..0000000000 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBoxLicenceAGPL.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { Button, useModal } from '@openfun/cunningham-react'; -import { useQueryClient } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { css } from 'styled-components'; - -import { - DropdownMenu, - DropdownMenuOption, - Icon, - IconOptions, -} from '@/components'; -import { useCunninghamTheme } from '@/cunningham'; -import { ModalExport } from '@/docs/doc-export/'; -import { - Doc, - KEY_DOC, - KEY_LIST_DOC, - ModalRemoveDoc, - useCopyDocLink, - useCreateFavoriteDoc, - useDeleteFavoriteDoc, -} from '@/docs/doc-management'; -import { - KEY_LIST_DOC_VERSIONS, - ModalSelectVersion, -} from '@/docs/doc-versioning'; -import { useAnalytics } from '@/libs'; -import { useResponsiveStore } from '@/stores'; - -import { DocShareModal } from '../../doc-share'; -import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard'; - -type ModalType = ReturnType; - -interface DocToolBoxLicenceProps { - doc: Doc; - modalHistory: ModalType; - modalShare: ModalType; -} - -export const DocToolBoxLicenceAGPL = ({ - doc, - modalHistory, - modalShare, -}: DocToolBoxLicenceProps) => { - const { t } = useTranslation(); - const queryClient = useQueryClient(); - - const { colorsTokens } = useCunninghamTheme(); - - const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); - const [isModalExportOpen, setIsModalExportOpen] = useState(false); - - const { isSmallMobile, isDesktop } = useResponsiveStore(); - const copyDocLink = useCopyDocLink(doc.id); - const { isFeatureFlagActivated } = useAnalytics(); - const removeFavoriteDoc = useDeleteFavoriteDoc({ - listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], - }); - const makeFavoriteDoc = useCreateFavoriteDoc({ - listInvalideQueries: [KEY_LIST_DOC, KEY_DOC], - }); - const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard(); - - const options: DropdownMenuOption[] = [ - ...(isSmallMobile - ? [ - { - label: t('Share'), - icon: 'group', - callback: modalShare.open, - }, - { - label: t('Export'), - icon: 'download', - callback: () => { - setIsModalExportOpen(true); - }, - }, - { - label: t('Copy link'), - icon: 'add_link', - callback: copyDocLink, - }, - ] - : []), - { - label: doc.is_favorite ? t('Unpin') : t('Pin'), - icon: 'push_pin', - callback: () => { - if (doc.is_favorite) { - removeFavoriteDoc.mutate({ id: doc.id }); - } else { - makeFavoriteDoc.mutate({ id: doc.id }); - } - }, - testId: `docs-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`, - }, - { - label: t('Version history'), - icon: 'history', - disabled: !doc.abilities.versions_list, - callback: () => { - modalHistory.open(); - }, - show: isDesktop, - }, - - { - label: t('Copy as {{format}}', { format: 'Markdown' }), - icon: 'content_copy', - callback: () => { - void copyCurrentEditorToClipboard('markdown'); - }, - }, - { - label: t('Copy as {{format}}', { format: 'HTML' }), - icon: 'content_copy', - callback: () => { - void copyCurrentEditorToClipboard('html'); - }, - show: isFeatureFlagActivated('CopyAsHTML'), - }, - { - label: t('Delete document'), - icon: 'delete', - disabled: !doc.abilities.destroy, - callback: () => { - setIsModalRemoveOpen(true); - }, - }, - ]; - - useEffect(() => { - if (modalHistory.isOpen) { - return; - } - - void queryClient.resetQueries({ - queryKey: [KEY_LIST_DOC_VERSIONS], - }); - }, [modalHistory.isOpen, queryClient]); - - return ( - <> - {!isSmallMobile && ( -