{
+ it('triggers telemetry contact support event', async () => {
+ const user = userEvent.setup()
+ const pushEvent = vi.fn()
+
+ vi.mocked(useMode).mockReturnValueOnce(MODE_TYPE.COLLECT)
+
+ const { getAllByText } = renderWithRouter(
+
+ {},
+ }}
+ >
+
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+
+ const e = getAllByText('Contact support')[0]
+ await user.click(e)
+
+ await waitFor(() => expect(pushEvent).toHaveBeenCalledOnce())
+ await waitFor(() =>
+ expect(pushEvent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: TELEMETRY_EVENT_TYPE.CONTACT_SUPPORT,
+ })
+ )
+ )
+ })
+
+ it('does not trigger telemetry contact support event when not in collect mode', async () => {
+ const user = userEvent.setup()
+ const pushEvent = vi.fn()
+
+ vi.mocked(useMode).mockReturnValueOnce(MODE_TYPE.VISUALIZE)
+
+ const { getAllByText } = renderWithRouter(
+
+ {},
+ }}
+ >
+
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+
+ const e = getAllByText('Contact support')[0]
+ await user.click(e)
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ })
+
+ it('does not trigger telemetry contact support event when telemetry is disabled', async () => {
+ const user = userEvent.setup()
+ const pushEvent = vi.fn()
+
+ vi.mocked(useMode).mockReturnValueOnce(MODE_TYPE.COLLECT)
+
+ const { getAllByText } = renderWithRouter(
+
+ {},
+ }}
+ >
+
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+
+ const e = getAllByText('Contact support')[0]
+ await user.click(e)
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ })
+})
diff --git a/src/shared/components/Layout/Header.tsx b/src/shared/components/Layout/Header.tsx
index f01795fb..153ceed3 100644
--- a/src/shared/components/Layout/Header.tsx
+++ b/src/shared/components/Layout/Header.tsx
@@ -1,3 +1,6 @@
+import { MODE_TYPE } from '@/constants/mode'
+import { TELEMETRY_EVENT_EXIT_SOURCE } from '@/constants/telemetry'
+import { useTelemetry } from '@/contexts/TelemetryContext'
import {
declareComponentKeys,
useResolveLocalizedString,
@@ -6,10 +9,12 @@ import {
import { useOidc } from '@/oidc'
import { collectPath } from '@/pages/Collect/route'
import { executePreLogoutActions } from '@/shared/hooks/prelogout'
+import { useMode } from '@/shared/hooks/useMode'
import { useMetadataStore } from '@/shared/metadataStore/useMetadataStore'
+import { computeContactSupportEvent, computeExitEvent } from '@/utils/telemetry'
import { headerFooterDisplayItem } from '@codegouvfr/react-dsfr/Display'
import { Header as DsfrHeader } from '@codegouvfr/react-dsfr/Header'
-import { useMatchRoute, useSearch } from '@tanstack/react-router'
+import { useSearch } from '@tanstack/react-router'
export function Header() {
const { t } = useTranslation({ Header })
@@ -18,18 +23,20 @@ export function Header() {
useResolveLocalizedString({
labelWhenMismatchingLanguage: true,
})
+ const mode = useMode()
const {
label: serviceTitle,
mainLogo,
surveyUnitIdentifier,
} = useMetadataStore()
+ const { isTelemetryDisabled, pushEvent, triggerBatchTelemetryCallback } =
+ useTelemetry()
/**
* There is an issue with this part of the code: the search type is not well narrowed with isCollectRoute. I'm waiting for a better solution.
*/
- const matchRoute = useMatchRoute()
- const isCollectRoute = !!matchRoute({ to: collectPath })
+ const isCollectRoute = mode === MODE_TYPE.COLLECT
const search = useSearch({ strict: false })
return (
@@ -54,6 +61,12 @@ export function Header() {
? `${import.meta.env.VITE_PORTAIL_URL}${search?.['pathAssistance'] ?? ''}`
: '',
disabled: isCollectRoute,
+ onClick:
+ isCollectRoute && !isTelemetryDisabled
+ ? () => {
+ pushEvent(computeContactSupportEvent())
+ }
+ : undefined,
},
text: t('quick access support'),
},
@@ -65,6 +78,16 @@ export function Header() {
buttonProps: {
onClick: async () => {
await executePreLogoutActions()
+ if (!isTelemetryDisabled) {
+ pushEvent(
+ computeExitEvent({
+ source: TELEMETRY_EVENT_EXIT_SOURCE.LOGOUT,
+ })
+ )
+ if (triggerBatchTelemetryCallback) {
+ await triggerBatchTelemetryCallback()
+ }
+ }
logout({
redirectTo: 'specific url',
url: `${import.meta.env.VITE_PORTAIL_URL}${search?.['pathLogout'] ?? ''}`,
diff --git a/src/shared/components/Orchestrator/CustomPages/EndPage.tsx b/src/shared/components/Orchestrator/CustomPages/EndPage.tsx
index b4e1f51e..52b77615 100644
--- a/src/shared/components/Orchestrator/CustomPages/EndPage.tsx
+++ b/src/shared/components/Orchestrator/CustomPages/EndPage.tsx
@@ -1,4 +1,5 @@
import { declareComponentKeys, useTranslation } from '@/i18n'
+import type { StateData } from '@/model/StateData'
import { useDocumentTitle } from '@/shared/hooks/useDocumentTitle'
import { fr } from '@codegouvfr/react-dsfr'
@@ -12,7 +13,7 @@ export function EndPage({
state,
}: Readonly<{
date?: number
- state?: 'INIT' | 'COMPLETED' | 'VALIDATED' | 'TOEXTRACT' | 'EXTRACTED'
+ state?: StateData['state']
}>) {
const { t } = useTranslation({ EndPage })
const formattedDate = date ? new Date(date).toLocaleString() : undefined
diff --git a/src/shared/components/Orchestrator/CustomPages/WelcomeModal.tsx b/src/shared/components/Orchestrator/CustomPages/WelcomeModal.tsx
index 70dd278e..ab755c31 100644
--- a/src/shared/components/Orchestrator/CustomPages/WelcomeModal.tsx
+++ b/src/shared/components/Orchestrator/CustomPages/WelcomeModal.tsx
@@ -28,7 +28,7 @@ export function WelcomeModal({ goBack, open }: Props) {
modal.open()
wasDisplayed.current = true
}
- }, 10)
+ }, 50)
}, [open])
return (
diff --git a/src/shared/components/Orchestrator/Orchestrator.test.tsx b/src/shared/components/Orchestrator/Orchestrator.test.tsx
new file mode 100644
index 00000000..7f5b5e4c
--- /dev/null
+++ b/src/shared/components/Orchestrator/Orchestrator.test.tsx
@@ -0,0 +1,232 @@
+import { MODE_TYPE } from '@/constants/mode'
+import { TELEMETRY_EVENT_TYPE } from '@/constants/telemetry'
+import { TelemetryContext } from '@/contexts/TelemetryContext'
+import { renderWithRouter } from '@/utils/tests'
+import { act, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { expect } from 'vitest'
+import { Orchestrator } from './Orchestrator'
+
+describe('Orchestrator', () => {
+ const surveyUnitData = {
+ stateData: undefined,
+ data: undefined,
+ personalization: undefined,
+ id: 'my-service-unit-id',
+ }
+ const metadata = {
+ label: 'my label',
+ objectives: 'my objectives',
+ mainLogo: { label: 'logo label', url: '' },
+ surveyUnitIdentifier: 'my survey id',
+ }
+ const source = {
+ components: [
+ {
+ componentType: 'Sequence',
+ page: '1',
+ id: 's1',
+ },
+ {
+ componentType: 'Question',
+ page: '1',
+ components: [
+ {
+ componentType: 'Input',
+ page: '1',
+ label: { value: 'my-question', type: 'TXT' },
+ id: 'i1',
+ response: { name: 'my-question-input' },
+ },
+ ],
+ },
+ ],
+ variables: [],
+ }
+ const OrchestratorTestWrapper = ({
+ mode,
+ }: {
+ mode: MODE_TYPE.COLLECT | MODE_TYPE.REVIEW | MODE_TYPE.VISUALIZE
+ }) => (
+
{
+ return new Promise(() => [])
+ }}
+ updateDataAndStateData={() => {
+ return new Promise(() => {})
+ }}
+ getDepositProof={() => {
+ return new Promise(() => {})
+ }}
+ />
+ )
+
+ it('sets idSU as default value', async () => {
+ const setDefaultValues = vi.fn()
+
+ renderWithRouter(
+ {},
+ setDefaultValues,
+ }}
+ >
+
+
+ )
+
+ await waitFor(() => expect(setDefaultValues).toHaveBeenCalledOnce())
+ await waitFor(() =>
+ expect(setDefaultValues).toHaveBeenCalledWith({
+ idSU: 'my-service-unit-id',
+ })
+ )
+ })
+
+ it('triggers telemetry init event', async () => {
+ const pushEvent = vi.fn()
+
+ renderWithRouter(
+ {},
+ }}
+ >
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).toHaveBeenCalledOnce())
+ await waitFor(() =>
+ expect(pushEvent).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: TELEMETRY_EVENT_TYPE.INIT,
+ })
+ )
+ )
+ })
+
+ it('does not trigger telemetry event in visualize mode', async () => {
+ const pushEvent = vi.fn()
+
+ const { getByText } = renderWithRouter(
+ {},
+ }}
+ >
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ act(() => getByText('Start').click())
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ })
+
+ it('does not trigger telemetry event in review mode', async () => {
+ const pushEvent = vi.fn()
+
+ const { getByText } = renderWithRouter(
+ {},
+ }}
+ >
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ act(() => getByText('Start').click())
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ })
+
+ it('does not trigger telemetry event if disabled', async () => {
+ const pushEvent = vi.fn()
+
+ const { getByText } = renderWithRouter(
+ {},
+ }}
+ >
+
+
+ )
+
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ act(() => getByText('Start').click())
+ await waitFor(() => expect(pushEvent).not.toHaveBeenCalled())
+ })
+
+ it('triggers telemetry next page event', async () => {
+ const pushEvent = vi.fn()
+
+ const { getByText } = renderWithRouter(
+ {},
+ }}
+ >
+
+
+ )
+
+ act(() => getByText('Start').click())
+
+ await waitFor(() => expect(pushEvent).toHaveBeenCalledTimes(2))
+ expect(pushEvent).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ type: TELEMETRY_EVENT_TYPE.NEW_PAGE,
+ })
+ )
+ })
+
+ it('triggers telemetry input event', async () => {
+ const pushEvent = vi.fn()
+ const user = userEvent.setup()
+
+ const { getByText } = renderWithRouter(
+ {},
+ }}
+ >
+
+
+ )
+
+ act(() => getByText('Start').click())
+
+ const e = getByText('my-question')
+ await user.click(e)
+ await user.keyboard('f')
+
+ await new Promise((r) => setTimeout(r, 1000))
+ await waitFor(() => expect(pushEvent).toHaveBeenCalledTimes(3))
+ expect(pushEvent).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ type: TELEMETRY_EVENT_TYPE.INPUT,
+ })
+ )
+ })
+})
diff --git a/src/shared/components/Orchestrator/Orchestrator.tsx b/src/shared/components/Orchestrator/Orchestrator.tsx
index d5381ca8..e8dd9a82 100644
--- a/src/shared/components/Orchestrator/Orchestrator.tsx
+++ b/src/shared/components/Orchestrator/Orchestrator.tsx
@@ -1,23 +1,35 @@
+import { MODE_TYPE } from '@/constants/mode'
+import { PAGE_TYPE } from '@/constants/page'
+import { useTelemetry } from '@/contexts/TelemetryContext'
import type { Metadata } from '@/model/Metadata'
import type { StateData } from '@/model/StateData'
import type { SurveyUnitData } from '@/model/SurveyUnitData'
+import { usePushEventAfterInactivity } from '@/shared/components/Orchestrator/usePushEventAfterInactivity'
import { useAddPreLogoutAction } from '@/shared/hooks/prelogout'
import { usePrevious } from '@/shared/hooks/usePrevious'
import { downloadAsJson } from '@/utils/downloadAsJson'
import { isObjectEmpty } from '@/utils/isObjectEmpty'
import { hasBeenSent, shouldDisplayWelcomeModal } from '@/utils/orchestrator'
+import {
+ computeControlEvent,
+ computeControlSkipEvent,
+ computeInitEvent,
+ computeInputEvent,
+ computeNewPageEvent,
+} from '@/utils/telemetry'
import { useRefSync } from '@/utils/useRefSync'
import { useUpdateEffect } from '@/utils/useUpdateEffect'
import { fr } from '@codegouvfr/react-dsfr'
import {
LunaticComponents,
useLunatic,
+ type LunaticChangesHandler,
type LunaticData,
type LunaticError,
type LunaticSource,
} from '@inseefr/lunatic'
import { useNavigate } from '@tanstack/react-router'
-import { useEffect, useMemo, useRef, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { EndPage } from './CustomPages/EndPage'
import { ValidationModal } from './CustomPages/ValidationModal'
import { ValidationPage } from './CustomPages/ValidationPage'
@@ -28,13 +40,10 @@ import { VTLDevTools } from './VTLDevTools/VTLDevtools'
import { createLunaticLogger } from './VTLDevTools/VTLErrorStore'
import { slotComponents } from './slotComponents'
import { useStromaeNavigation } from './useStromaeNavigation'
+import { computeLunaticComponents } from './utils/components'
import { isBlockingError, isSameErrors } from './utils/controls'
import { trimCollectedData } from './utils/data'
-import type {
- LunaticComponentsProps,
- LunaticGetReferentiel,
- LunaticPageTag,
-} from './utils/lunaticType'
+import type { LunaticGetReferentiel, LunaticPageTag } from './utils/lunaticType'
import { scrollAndFocusToFirstError } from './utils/scrollAndFocusToFirstError'
import { isSequencePage } from './utils/sequence'
@@ -56,27 +65,33 @@ export type OrchestratorProps = OrchestratorProps.Common &
export namespace OrchestratorProps {
export type Common = {
+ /** Questionnaire data consumed by Lunatic to make its components */
source: LunaticSource
+ /** Initial survey unit data when we initialize the orchestrator */
surveyUnitData: SurveyUnitData | undefined
+ /** Allows to fetch nomenclature by id */
getReferentiel: LunaticGetReferentiel
+ /** Survey unit metadata */
metadata: Metadata
}
export type Visualize = {
- mode: 'visualize'
+ mode: MODE_TYPE.VISUALIZE
}
export type Review = {
- mode: 'review'
+ mode: MODE_TYPE.REVIEW
}
export type Collect = {
- mode: 'collect'
+ mode: MODE_TYPE.COLLECT
+ /** Updates data with the modified data and survey state */
updateDataAndStateData: (params: {
stateData: StateData
data: LunaticData['COLLECTED']
onSuccess?: () => void
}) => Promise
+ /** Allows user to download a deposit proof PDF */
getDepositProof: () => Promise
}
}
@@ -84,22 +99,75 @@ export namespace OrchestratorProps {
export function Orchestrator(props: OrchestratorProps) {
const { source, surveyUnitData, getReferentiel, mode, metadata } = props
- const initialCurrentPage = surveyUnitData?.stateData?.currentPage
- const initialState = surveyUnitData?.stateData?.state
- const pagination = source.pagination ?? 'question'
+ // Allow to send telemetry events once survey unit id has been set
+ const [isTelemetryActivated, setIsTelemetryActivated] =
+ useState(false)
+
+ const navigate = useNavigate()
+ const {
+ isTelemetryDisabled,
+ pushEvent,
+ setDefaultValues,
+ triggerBatchTelemetryCallback,
+ } = useTelemetry()
+ const { setEventToPushAfterInactivity, triggerInactivityTimeoutEvent } =
+ usePushEventAfterInactivity(pushEvent)
const containerRef = useRef(null)
const contentRef = useRef(null)
const pageTagRef = useRef('1')
+ const validationModalActionsRef = useRef({
+ open: () => Promise.resolve(),
+ })
+
+ const initialCurrentPage = surveyUnitData?.stateData?.currentPage
+ const initialState = surveyUnitData?.stateData?.state
+ const pagination = source.pagination ?? 'question'
+
+ /** Displays the welcome modal which allows to come back to current page */
+ const shouldWelcome = shouldDisplayWelcomeModal(
+ initialState,
+ initialCurrentPage
+ )
const lunaticLogger = useMemo(
() =>
- mode === 'visualize'
+ mode === MODE_TYPE.VISUALIZE
? createLunaticLogger({ pageTag: pageTagRef })
: undefined,
[mode]
)
+ /** Triggers telemetry input event on Lunatic change */
+ const handleLunaticChange: LunaticChangesHandler = useCallback(
+ (changes) => {
+ if (changes.length === 1) {
+ // could be a text input, we only send the event once user has stopped
+ // actively typing since Lunatic triggers its onChange on every input
+ const { name, value, iteration } = changes[0]
+ setEventToPushAfterInactivity(
+ computeInputEvent({
+ value: value,
+ name: name,
+ iteration: iteration,
+ })
+ )
+ } else {
+ for (const { name, value, iteration } of changes) {
+ // weird inputs, probably not text input, push everything
+ pushEvent(
+ computeInputEvent({
+ value: value,
+ name: name,
+ iteration: iteration,
+ })
+ )
+ }
+ }
+ },
+ [pushEvent, setEventToPushAfterInactivity]
+ )
+
const {
getComponents,
Provider: LunaticProvider,
@@ -119,8 +187,9 @@ export function Orchestrator(props: OrchestratorProps) {
activeControls: true,
getReferentiel,
autoSuggesterLoading: true,
- trackChanges: mode === 'collect',
+ trackChanges: mode === MODE_TYPE.COLLECT,
withOverview: true,
+ onChange: isTelemetryActivated ? handleLunaticChange : undefined,
})
pageTagRef.current = pageTag
@@ -133,16 +202,6 @@ export function Orchestrator(props: OrchestratorProps) {
Record | undefined
>(undefined)
- useEffect(() => {
- if (activeErrors) {
- scrollAndFocusToFirstError()
- }
- }, [activeErrors])
-
- const validationModalActionsRef = useRef({
- open: () => Promise.resolve(),
- })
-
// Decorates goNext function with controls behavior
const goNextWithControls = (goNext: () => void) => {
const { currentErrors } = compileControls()
@@ -154,19 +213,31 @@ export function Orchestrator(props: OrchestratorProps) {
return
}
- // 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)
- return
- }
-
// activeErrors and currentErrors are the same and no blocking error, we go next
- if (isSameErrors(currentErrors, activeErrors)) {
+ if (
+ !isBlockingError(currentErrors) &&
+ isSameErrors(currentErrors, activeErrors)
+ ) {
+ if (isTelemetryActivated) {
+ pushEvent(
+ computeControlSkipEvent({
+ controlIds: Object.keys(currentErrors),
+ })
+ )
+ }
setActiveErrors(undefined)
goNext()
return
}
+
+ // display the errors to the user
+ if (isTelemetryActivated) {
+ pushEvent(
+ computeControlEvent({
+ controlIds: Object.keys(currentErrors),
+ })
+ )
+ }
setActiveErrors(currentErrors)
}
@@ -185,17 +256,18 @@ export function Orchestrator(props: OrchestratorProps) {
const getCurrentStateData = useRefSync((): StateData => {
switch (currentPage) {
- case 'endPage':
+ case PAGE_TYPE.END:
return { date: Date.now(), currentPage, state: 'VALIDATED' }
- case 'lunaticPage':
+ case PAGE_TYPE.LUNATIC:
return { date: Date.now(), currentPage: pageTag, state: 'INIT' }
- case 'validationPage':
- case 'welcomePage':
+ case PAGE_TYPE.VALIDATION:
+ case PAGE_TYPE.WELCOME:
default:
return { date: Date.now(), currentPage, state: 'INIT' }
}
})
+ /** Allows to download data for visualize */
const downloadAsJsonRef = useRefSync(() => {
downloadAsJson({
dataToDownload: {
@@ -209,7 +281,7 @@ export function Orchestrator(props: OrchestratorProps) {
})
const triggerDataAndStateUpdate = () => {
- if (mode === 'collect' && !hasBeenSent(initialState)) {
+ if (mode === MODE_TYPE.COLLECT && !hasBeenSent(initialState)) {
const stateData = getCurrentStateData.current()
const data = getChangedData()
@@ -217,8 +289,8 @@ export function Orchestrator(props: OrchestratorProps) {
const isCollectedDataEmpty = isObjectEmpty(data.COLLECTED ?? {})
if (
isCollectedDataEmpty &&
- (currentPage === 'lunaticPage'
- ? previousPage === 'lunaticPage' && previousPageTag === pageTag
+ (currentPage === PAGE_TYPE.LUNATIC
+ ? previousPage === PAGE_TYPE.LUNATIC && previousPageTag === pageTag
: stateData.currentPage === previousPage)
) {
// no change, no need to push anything
@@ -239,12 +311,46 @@ export function Orchestrator(props: OrchestratorProps) {
}
}
+ // Telemetry initialization
+ useEffect(() => {
+ if (!isTelemetryDisabled && mode === MODE_TYPE.COLLECT) {
+ setDefaultValues({ idSU: surveyUnitData?.id })
+ setIsTelemetryActivated(true)
+ }
+ }, [isTelemetryDisabled, mode, setDefaultValues, surveyUnitData?.id])
+
+ // Initialization
+ useEffect(() => {
+ if (isTelemetryActivated) {
+ pushEvent(computeInitEvent())
+ }
+ }, [isTelemetryActivated, pushEvent])
+
+ useEffect(() => {
+ if (activeErrors) {
+ scrollAndFocusToFirstError()
+ }
+ }, [activeErrors])
+
useAddPreLogoutAction(async () => {
+ if (isTelemetryActivated) {
+ triggerInactivityTimeoutEvent()
+ }
triggerDataAndStateUpdate()
})
- //When page change
+ // On page change
useUpdateEffect(() => {
+ if (isTelemetryActivated) {
+ triggerInactivityTimeoutEvent()
+ pushEvent(
+ computeNewPageEvent({
+ page: currentPage,
+ pageTag,
+ })
+ )
+ }
+
//Reset scroll to the container when the top is not visible
if (
containerRef.current &&
@@ -267,68 +373,47 @@ export function Orchestrator(props: OrchestratorProps) {
// Persist data when component unmount (ie when navigate etc...)
useEffect(() => {
return () => {
+ if (isTelemetryActivated) {
+ triggerInactivityTimeoutEvent()
+ if (triggerBatchTelemetryCallback) {
+ ;(async () => {
+ await triggerBatchTelemetryCallback()
+ })()
+ }
+ }
triggerDataAndStateUpdate()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
- const navigate = useNavigate()
+ const { components, bottomComponents } = computeLunaticComponents(
+ getComponents(),
+ pagination
+ )
const handleDepositProofClick = async () => {
switch (mode) {
- case 'visualize': {
+ case MODE_TYPE.VISUALIZE: {
downloadAsJsonRef.current()
navigate({ to: '/visualize', params: {} })
break
}
- case 'collect': {
+ case MODE_TYPE.COLLECT: {
return props.getDepositProof()
}
- case 'review':
+ case MODE_TYPE.REVIEW:
default:
break
}
}
- const { components, bottomComponents } = getComponents().reduce<{
- components: LunaticComponentsProps
- bottomComponents: LunaticComponentsProps
- }>(
- (acc, c) => {
- // In sequence pagination we do not want to display Sequence components
- if (pagination === 'sequence' && c.componentType === 'Sequence') {
- return acc // Skip this component
- }
-
- // We want to be able to display at the bottom components with position "bottom"
- if (c.position === 'bottom') {
- return {
- components: acc.components,
- bottomComponents: [...acc.bottomComponents, c],
- }
- }
-
- return {
- components: [...acc.components, c],
- bottomComponents: acc.bottomComponents,
- }
- },
- { components: [], bottomComponents: [] }
- )
-
- // Displays the welcome modal which allows to come back to current page
- const shouldWelcome = shouldDisplayWelcomeModal(
- initialState,
- initialCurrentPage
- )
-
return (
0 && (
- {currentPage === 'lunaticPage' && (
+ {currentPage === PAGE_TYPE.LUNATIC && (
- {currentPage === 'welcomePage' && (
+ {currentPage === PAGE_TYPE.WELCOME && (
)}
- {currentPage === 'lunaticPage' && (
+ {currentPage === PAGE_TYPE.LUNATIC && (
)}
- {currentPage === 'validationPage' && }
- {currentPage === 'endPage' ? (
+ {currentPage === PAGE_TYPE.VALIDATION && }
+ {currentPage === PAGE_TYPE.END && (
- ) : null}
+ )}
initialCurrentPage
@@ -377,7 +462,7 @@ export function Orchestrator(props: OrchestratorProps) {
open={shouldWelcome}
/>
- {mode === 'visualize' && }
+ {mode === MODE_TYPE.VISUALIZE && }
diff --git a/src/shared/components/Orchestrator/SurveyContainer.tsx b/src/shared/components/Orchestrator/SurveyContainer.tsx
index 8ecac1ac..452394d8 100644
--- a/src/shared/components/Orchestrator/SurveyContainer.tsx
+++ b/src/shared/components/Orchestrator/SurveyContainer.tsx
@@ -1,3 +1,5 @@
+import { MODE_TYPE } from '@/constants/mode'
+import { PAGE_TYPE } from '@/constants/page'
import { declareComponentKeys, useTranslation } from '@/i18n'
import type { InternalPageType } from '@/model/Page'
import { fr } from '@codegouvfr/react-dsfr'
@@ -37,68 +39,63 @@ export function SurveyContainer(
const { t } = useTranslation({ SurveyContainer })
- const isPreviousButtonDisplayed = ['welcomePage', 'endPage'].includes(
+ const isPreviousButtonDisplayed = [PAGE_TYPE.WELCOME, PAGE_TYPE.END].includes(
currentPage
)
const [isLayoutExpanded, setIsLayoutExpanded] = useState(false)
- const displaySequenceHeader = !isSequencePage && currentPage === 'lunaticPage'
+ const displaySequenceHeader =
+ !isSequencePage && currentPage === PAGE_TYPE.LUNATIC
return (
<>
{!isPreviousButtonDisplayed && (
- <>
-
- {displaySequenceHeader && (
-
- )}
-
-
+
+ {displaySequenceHeader && (
+
+ )}
+
+
+
+
+
+ {pagination === 'sequence' && currentPage === PAGE_TYPE.LUNATIC && (
+
+ iconId={
+ isLayoutExpanded
+ ? 'ri-collapse-diagonal-line'
+ : 'ri-expand-diagonal-line'
+ }
+ priority="tertiary"
+ onClick={() => setIsLayoutExpanded((expanded) => !expanded)}
+ title={t('button expand')}
+ />
-
- {pagination === 'sequence' && currentPage === 'lunaticPage' && (
- <>
-
-
- >
- )}
-
+ )}
- >
+
)}
@@ -107,7 +104,7 @@ export function SurveyContainer(
className={fr.cx(
'fr-col-12',
'fr-mb-10v',
- ...(!(isLayoutExpanded && currentPage === 'lunaticPage')
+ ...(!(isLayoutExpanded && currentPage === PAGE_TYPE.LUNATIC)
? (['fr-col-md-9', 'fr-col-lg-8'] as const)
: [])
)}
@@ -118,7 +115,7 @@ export function SurveyContainer(
title={t('button continue title', { currentPage })}
id="continue-button"
onClick={
- currentPage === 'endPage'
+ currentPage === PAGE_TYPE.END
? handleDepositProofClick
: handleNextClick
}
@@ -126,7 +123,7 @@ export function SurveyContainer(
{t('button continue label', { currentPage })}
{bottomContent}
- {mode === 'visualize' && (
+ {mode === MODE_TYPE.VISUALIZE && (