diff --git a/src/components/Orchestrator/CustomPages/ValidationModal.tsx b/src/components/Orchestrator/CustomPages/ValidationModal.tsx index 24cb824f..9a65ccce 100644 --- a/src/components/Orchestrator/CustomPages/ValidationModal.tsx +++ b/src/components/Orchestrator/CustomPages/ValidationModal.tsx @@ -1,14 +1,13 @@ import { createModal } from '@codegouvfr/react-dsfr/Modal' -import { useState, useEffect, useId } from 'react' +import { useState, useEffect, useId, type MutableRefObject } from 'react' import { assert } from 'tsafe/assert' export type Props = { - actions: { + actionsRef: MutableRefObject<{ open?: () => Promise - } + }> } -export function ValidationModal(props: Props) { - const { actions } = props +export function ValidationModal({actionsRef}: Props) { const id = useId() @@ -27,7 +26,7 @@ export function ValidationModal(props: Props) { >(undefined) useEffect(() => { - actions.open = () => + actionsRef.current.open = () => new Promise((resolve) => { setOpenState({ resolve }) modal.open() diff --git a/src/components/Orchestrator/Orchestrator.tsx b/src/components/Orchestrator/Orchestrator.tsx index 963f8e03..f8341cb0 100644 --- a/src/components/Orchestrator/Orchestrator.tsx +++ b/src/components/Orchestrator/Orchestrator.tsx @@ -5,6 +5,7 @@ import { type LunaticError, type LunaticData, } from '@inseefr/lunatic' +import { useRef } from 'react' import { fr } from '@codegouvfr/react-dsfr' import { downloadAsJson } from 'utils/downloadAsJson' import { useNavigate } from '@tanstack/react-router' @@ -15,14 +16,14 @@ import { Validation } from './CustomPages/Validation' import { useStromaeNavigation } from './useStromaeNavigation' import { EndPage } from './CustomPages/EndPage' import { ValidationModal } from './CustomPages/ValidationModal' -import { assert } from 'tsafe/assert' import type { SurveyUnitData } from 'model/SurveyUnitData' import type { StateData } from 'model/StateData' import { isBlockingError, isSameErrors } from './utils/controls' import { slotComponents } from './slotComponents' import type { LunaticGetReferentiel } from './utils/lunaticType' import { isObjectEmpty } from 'utils/isObjectEmpty' -import { useUpdateEffect } from 'utils/useUpdateEffect' +import { useUpdateEffect } from 'hooks/useUpdateEffect' +import { useRefSync } from 'hooks/useRefSync' export type OrchestratorProps = OrchestratorProps.Common & (OrchestratorProps.Visualize | OrchestratorProps.Collect) @@ -79,21 +80,22 @@ export function Orchestrator(props: OrchestratorProps) { Record | undefined >(undefined) - const [validationModalActions] = useState<{ - open?: () => Promise - }>({}) + const validationModalActionsRef = useRef({ + open: () => Promise.resolve(), + }) - const goNextHandlingControls = () => { + // Decorates goNext function with controls behavior + const goNextWithControls = () => { const { currentErrors } = compileControls() - //No errors, we goNext + // No errors, continue if (!currentErrors) { setActiveErrors(undefined) goNextLunatic() return } - //An error is blocking, we stay on the page + // An error is blocking, we stay on the page if (isBlockingError(currentErrors)) { //compileControls returns isCritical but I prefer define my own rules of blocking error in the orchestrator setActiveErrors(currentErrors) @@ -111,19 +113,16 @@ export function Orchestrator(props: OrchestratorProps) { } const { currentPage, goNext, goToPage, goPrevious } = useStromaeNavigation({ - goNextLunatic: goNextHandlingControls, + goNextLunatic: goNextWithControls, goPrevLunatic, isFirstPage, isLastPage, goToLunaticPage, initialCurrentPage, - openValidationModal: () => { - assert(validationModalActions.open !== undefined) - return validationModalActions.open() - }, + openValidationModal: () => validationModalActionsRef.current.open(), }) - function getCurrentStateData(): StateData { + const getCurrentStateData = (): StateData => { switch (currentPage) { case 'endPage': return { date: Date.now(), currentPage, state: 'VALIDATED' } @@ -139,7 +138,7 @@ export function Orchestrator(props: OrchestratorProps) { } } - function handleDownloadData() { + const downloadAsJsonRef = useRefSync(() => { downloadAsJson({ dataToDownload: { data: getData(false), @@ -149,19 +148,19 @@ export function Orchestrator(props: OrchestratorProps) { //The label of source is not dynamic filename: `${source.label.value}-${new Date().toLocaleDateString()}`, }) - } + }) const isDownloadPage = currentPage === 'downloadPage' + // When reaching the download page, start downloading the page useEffect(() => { if (!isDownloadPage || mode !== 'visualize') return - handleDownloadData() + downloadAsJsonRef.current() navigate({ to: '/visualize' }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDownloadPage]) + }, [isDownloadPage, downloadAsJsonRef, navigate, mode]) + // Persist data when page change in "collect" mode useUpdateEffect(() => { - // Persist data when the currentPage or pageTag changes and mode is 'collect'. if (mode !== 'collect') return const { updateCollectedData, updateStateData } = props @@ -181,7 +180,7 @@ export function Orchestrator(props: OrchestratorProps) { @@ -206,7 +205,7 @@ export function Orchestrator(props: OrchestratorProps) { {currentPage === 'endPage' && ( )} - + diff --git a/src/hooks/useDocumentTitle.ts b/src/hooks/useDocumentTitle.ts index 3adf081b..136598ed 100644 --- a/src/hooks/useDocumentTitle.ts +++ b/src/hooks/useDocumentTitle.ts @@ -10,7 +10,7 @@ export function useDocumentTitle(title: string) { return () => { document.title = prevTitle } - }) + }, [title]) } export function useSequenceTitle(sequenceLabel: ReactNode) { diff --git a/src/hooks/useLogoutUrl.tsx b/src/hooks/useLogoutUrl.tsx new file mode 100644 index 00000000..1f906619 --- /dev/null +++ b/src/hooks/useLogoutUrl.tsx @@ -0,0 +1,40 @@ +import { + useState, + createContext, + useContext, + useEffect, + type PropsWithChildren, +} from 'react' + +/** + * We need to know which questionnaire is currently used to redirect the user when he logs out + * + * ## Example + * + * - For "LOG2021X11" we will redirect the user to domain.ltd/log after logout + * - For "rece2021X11" we will redirect the user to domain.ltd/rece + */ +const SetLogoutPathContext = createContext((() => {}) as (s: string) => void) +const LogoutPathContext = createContext('') + +export const LogoutPathProvider = ({ children }: PropsWithChildren) => { + const [logoutPath, setLogoutPath] = useState('') + return ( + + + {children} + + + ) +} + +export function useLogoutUrl(): string { + return `${import.meta.env.VITE_PORTAIL_URL}/${useContext(LogoutPathContext)}` +} + +export function useSetLogoutQuestionnaire(questionnaireId: string): void { + const setLogoutPath = useContext(SetLogoutPathContext) + useEffect(() => { + setLogoutPath((questionnaireId.match(/^[^2]+/) ?? '')[0].toLowerCase()) + }, [questionnaireId, setLogoutPath]) +} diff --git a/src/hooks/useRefSync.ts b/src/hooks/useRefSync.ts new file mode 100644 index 00000000..ce50a2c8 --- /dev/null +++ b/src/hooks/useRefSync.ts @@ -0,0 +1,10 @@ +import { useRef } from 'react' + +/** + * useRef, but keep the value in sync on every render + */ +export function useRefSync(value: T) { + const ref = useRef(value) + ref.current = value + return ref +} diff --git a/src/utils/useUpdateEffect.ts b/src/hooks/useUpdateEffect.ts similarity index 100% rename from src/utils/useUpdateEffect.ts rename to src/hooks/useUpdateEffect.ts diff --git a/src/utils/useWhyRender.ts b/src/hooks/useWhyRender.ts similarity index 100% rename from src/utils/useWhyRender.ts rename to src/hooks/useWhyRender.ts diff --git a/src/pages/Collect/CollectPage.tsx b/src/pages/Collect/CollectPage.tsx index 33fc31fe..7cc4f8ea 100644 --- a/src/pages/Collect/CollectPage.tsx +++ b/src/pages/Collect/CollectPage.tsx @@ -9,13 +9,19 @@ import type { import { useSetStateData, useUpdateCollectedData } from 'api/06-survey-units' import type { LunaticData } from '@inseefr/lunatic' import type { StateData } from 'model/StateData' +import { useDocumentTitle } from 'hooks/useDocumentTitle' +import { useSetLogoutQuestionnaire } from 'hooks/useLogoutUrl' export function CollectPage() { - const { surveyUnitId } = collectRoute.useParams() + const { surveyUnitId, questionnaireId } = collectRoute.useParams() const queryClient = useQueryClient() + useSetLogoutQuestionnaire(questionnaireId) const loaderResults = collectRoute.useLoaderData() + //TODO -> use Metadata + useDocumentTitle("Questionnaire | Filière d'Enquête") + const { source, surveyUnitData } = loaderResults const getReferentiel: LunaticGetReferentiel = (name: string) => diff --git a/src/pages/Collect/route.tsx b/src/pages/Collect/route.tsx index 51565711..0367aec9 100644 --- a/src/pages/Collect/route.tsx +++ b/src/pages/Collect/route.tsx @@ -18,7 +18,7 @@ export const collectRoute = createRoute({ params: { questionnaireId, surveyUnitId }, context: { queryClient }, }) => { - document.title = "Questionnaire | Filière d'Enquête" + const sourcePr = queryClient .ensureQueryData(getGetQuestionnaireDataQueryOptions(questionnaireId)) diff --git a/src/router/Layout/Header.tsx b/src/router/Layout/Header.tsx index fb07b971..028d574c 100644 --- a/src/router/Layout/Header.tsx +++ b/src/router/Layout/Header.tsx @@ -3,10 +3,12 @@ import { Header as DsfrHeader } from '@codegouvfr/react-dsfr/Header' import logoInsee from 'assets/logo-insee.png' import { headerFooterDisplayItem } from '@codegouvfr/react-dsfr/Display' import { Badge } from '@codegouvfr/react-dsfr/Badge' +import { useLogoutUrl } from 'hooks/useLogoutUrl' export function Header() { const { isUserLoggedIn, logout } = useOidc() + const logoutUrl = useLogoutUrl() return ( logout({ redirectTo: 'specific url', - url: import.meta.env.VITE_PORTAIL_URL, + url: logoutUrl, }), }, text: 'Se déconnecter', diff --git a/src/router/Layout/Layout.tsx b/src/router/Layout/Layout.tsx index f4bce9f6..46ecc69f 100644 --- a/src/router/Layout/Layout.tsx +++ b/src/router/Layout/Layout.tsx @@ -1,6 +1,7 @@ import { Header } from './Header' import { Footer } from './Footer' import type { PropsWithChildren } from 'react' +import { LogoutPathProvider } from 'hooks/useLogoutUrl' export function Layout(props: PropsWithChildren) { const { children } = props @@ -8,11 +9,13 @@ export function Layout(props: PropsWithChildren) {
-
-
- {children} -
-
) }