diff --git a/src/assets/images/onboarding/actions-logging.svg b/src/assets/images/onboarding/actions-logging.svg
deleted file mode 100644
index e060d6f35..000000000
--- a/src/assets/images/onboarding/actions-logging.svg
+++ /dev/null
@@ -1,41 +0,0 @@
-
diff --git a/src/assets/images/onboarding/care-team-sharing.svg b/src/assets/images/onboarding/care-team-sharing.svg
deleted file mode 100644
index 368fcb486..000000000
--- a/src/assets/images/onboarding/care-team-sharing.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/src/assets/images/onboarding/data-deletion.svg b/src/assets/images/onboarding/data-deletion.svg
new file mode 100644
index 000000000..7a4553b06
--- /dev/null
+++ b/src/assets/images/onboarding/data-deletion.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/assets/images/onboarding/medical-secrecy.svg b/src/assets/images/onboarding/medical-secrecy.svg
new file mode 100644
index 000000000..cd2caabd4
--- /dev/null
+++ b/src/assets/images/onboarding/medical-secrecy.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/assets/images/onboarding/minimal-data-use.svg b/src/assets/images/onboarding/perimeter-scope.svg
similarity index 100%
rename from src/assets/images/onboarding/minimal-data-use.svg
rename to src/assets/images/onboarding/perimeter-scope.svg
diff --git a/src/views/Onboarding/InfoCallout.tsx b/src/views/Onboarding/InfoCallout.tsx
new file mode 100644
index 000000000..d0360d57e
--- /dev/null
+++ b/src/views/Onboarding/InfoCallout.tsx
@@ -0,0 +1,29 @@
+import { Box, Divider, Typography } from '@mui/material'
+import InfoBadge from 'components/ui/InfoBadge'
+import type { CircleBadgeVariant } from 'components/ui/CircleBadge'
+import React from 'react'
+import { eds } from 'styles/palette'
+
+import useStyles from './styles'
+
+type Props = {
+ children: React.ReactNode
+ variant?: CircleBadgeVariant
+}
+
+/** Encart d'information séparé du contenu par un filet, utilisé pour les exemples (RG3429.03). */
+const InfoCallout = ({ children, variant = 'filled' }: Props) => {
+ const { classes } = useStyles()
+
+ return (
+ <>
+
+
+
+ {children}
+
+ >
+ )
+}
+
+export default InfoCallout
diff --git a/src/views/Onboarding/Onboarding.tsx b/src/views/Onboarding/Onboarding.tsx
index 760a3bc2e..cfc7e2b41 100644
--- a/src/views/Onboarding/Onboarding.tsx
+++ b/src/views/Onboarding/Onboarding.tsx
@@ -8,6 +8,7 @@ import React from 'react'
import { Navigate } from 'react-router-dom'
import { OnboardingProvider, useOnboarding } from './OnboardingContext'
+import ScreenTag from './ScreenTag'
import { ONBOARDING_STEPS } from './steps'
import useStyles from './styles'
import UserMenu from './UserMenu'
@@ -65,7 +66,16 @@ const OnboardingLayout = () => {
layout={screenConfig?.layout}
footer={footer}
>
- {screen === 'welcome' ? : ActiveScreen && }
+ {screen === 'welcome' ? (
+
+ ) : (
+ ActiveScreen && (
+ <>
+ {screenConfig?.tag && }
+
+ >
+ )
+ )}
{error && (
{screenConfig?.primaryAction?.errorMessage ?? PROGRESS_ERROR_MESSAGE}
diff --git a/src/views/Onboarding/ScreenTag.tsx b/src/views/Onboarding/ScreenTag.tsx
new file mode 100644
index 000000000..2b2abccc6
--- /dev/null
+++ b/src/views/Onboarding/ScreenTag.tsx
@@ -0,0 +1,20 @@
+import { Typography } from '@mui/material'
+import React from 'react'
+
+import useStyles from './styles'
+
+type Props = {
+ label: string
+}
+
+const ScreenTag = ({ label }: Props) => {
+ const { classes } = useStyles()
+
+ return (
+
+ {label}
+
+ )
+}
+
+export default ScreenTag
diff --git a/src/views/Onboarding/__tests__/Onboarding.test.tsx b/src/views/Onboarding/__tests__/Onboarding.test.tsx
index aa959354b..5ee6d9733 100644
--- a/src/views/Onboarding/__tests__/Onboarding.test.tsx
+++ b/src/views/Onboarding/__tests__/Onboarding.test.tsx
@@ -85,28 +85,30 @@ describe('Onboarding page', () => {
expect(screen.getByTestId('onboarding-warning')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: /Continuer/ }))
- expect(screen.getByText("Les finalités d'usage")).toBeInTheDocument()
+ expect(screen.getByText('Vos accès sont personnels')).toBeInTheDocument()
expect(screen.queryByTestId('onboarding-warning')).not.toBeInTheDocument()
})
- it('holds the charter signature until the consent is ticked', async () => {
+ it('holds the commitments validation until the certification is ticked', async () => {
signCharter.mockResolvedValue({ charter_signed_at: '2026-01-01T00:00:00Z' })
const user = userEvent.setup()
renderAt({ ...baseStatus, onboarding_step: 1 })
- // Walk the commitments screens down to the charter.
- for (let i = 0; i < 7; i++) {
- await user.click(screen.getByRole('button', { name: /Continuer/ }))
+ // Walk the intro then the ten commitments, down to the summary.
+ await user.click(screen.getByRole('button', { name: /Continuer/ }))
+ for (let i = 0; i < 10; i++) {
+ // eslint-disable-next-line no-await-in-loop
+ await user.click(screen.getByRole('button', { name: /Je m’y engage/ }))
}
- expect(screen.getByRole('heading', { name: "Signer la charte d'engagement" })).toBeInTheDocument()
- const sign = screen.getByRole('button', { name: /Signer/ })
- expect(sign).toBeDisabled()
+ expect(screen.getByRole('heading', { name: 'Synthèse de vos engagements' })).toBeInTheDocument()
+ const validate = screen.getByRole('button', { name: /Valider/ })
+ expect(validate).toBeDisabled()
await user.click(screen.getByRole('checkbox', { name: /Je certifie avoir pris connaissance/ }))
- expect(sign).toBeEnabled()
+ expect(validate).toBeEnabled()
- await user.click(sign)
+ await user.click(validate)
await waitFor(() => expect(signCharter).toHaveBeenCalledTimes(1))
})
diff --git a/src/views/Onboarding/__tests__/OnboardingContext.test.tsx b/src/views/Onboarding/__tests__/OnboardingContext.test.tsx
index cc654c664..ce06b9602 100644
--- a/src/views/Onboarding/__tests__/OnboardingContext.test.tsx
+++ b/src/views/Onboarding/__tests__/OnboardingContext.test.tsx
@@ -13,7 +13,7 @@ vi.mock('services/aphp/serviceOnboarding', () => ({
import { ONBOARDING_STATUS_QUERY_KEY } from 'hooks/onboarding/useOnboardingStatus'
import { OnboardingProvider, useOnboarding } from '../OnboardingContext'
-const CHARTER_SUBSTEP = 7
+const SUMMARY_SUBSTEP = 11
const renderOnboarding = (initialStep: number) => {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
@@ -30,9 +30,9 @@ const renderOnboarding = (initialStep: number) => {
return renderHook(() => useOnboarding(), { wrapper })
}
-/** Walks the commitments step from its first screen up to the charter. */
-const goToCharter = async (result: { current: ReturnType }) => {
- for (let i = 0; i < CHARTER_SUBSTEP; i++) {
+/** Walks the commitments step from its first screen up to the summary. */
+const goToSummary = async (result: { current: ReturnType }) => {
+ for (let i = 0; i < SUMMARY_SUBSTEP; i++) {
// eslint-disable-next-line no-await-in-loop
await act(async () => {
result.current.goNext()
@@ -163,16 +163,16 @@ describe('OnboardingContext', () => {
expect(result.current.isLastStep).toBe(true)
})
- it('labels the primary button `Signer` on the charter screen (US-3309)', async () => {
+ it('labels the primary button `Valider` on the summary screen (RG3429.07)', async () => {
const { result } = renderOnboarding(1)
- await goToCharter(result)
- expect(result.current.subStep).toBe(CHARTER_SUBSTEP)
- expect(result.current.primaryLabel).toBe('Signer')
+ await goToSummary(result)
+ expect(result.current.subStep).toBe(SUMMARY_SUBSTEP)
+ expect(result.current.primaryLabel).toBe('Valider')
})
- it('signs the charter then moves to the confirmation screen', async () => {
+ it('records the commitments then moves on to the guided tour', async () => {
const { result } = renderOnboarding(1)
- await goToCharter(result)
+ await goToSummary(result)
act(() => result.current.setAcknowledged(true))
await act(async () => {
@@ -180,14 +180,15 @@ describe('OnboardingContext', () => {
})
expect(signCharterCall).toHaveBeenCalledTimes(1)
- expect(result.current.subStep).toBe(CHARTER_SUBSTEP + 1)
- expect(result.current.primaryLabel).toBe('Continuer')
+ expect(result.current.currentStep).toBe(2)
+ expect(result.current.subStep).toBe(0)
+ expect(result.current.primaryLabel).toBe('Accéder à Cohort360')
})
- it('stays on the charter screen and raises an error when the signature fails', async () => {
+ it('stays on the summary screen and raises an error when the validation fails', async () => {
signCharterCall.mockRejectedValue(new Error('boom'))
const { result } = renderOnboarding(1)
- await goToCharter(result)
+ await goToSummary(result)
act(() => result.current.setAcknowledged(true))
await act(async () => {
@@ -195,7 +196,7 @@ describe('OnboardingContext', () => {
})
await waitFor(() => expect(result.current.error).toBe(true))
- expect(result.current.subStep).toBe(CHARTER_SUBSTEP)
+ expect(result.current.subStep).toBe(SUMMARY_SUBSTEP)
expect(updateStep).not.toHaveBeenCalled()
})
@@ -229,22 +230,23 @@ describe('OnboardingContext', () => {
expect(result.current.error).toBe(false)
})
- it('does not sign twice when stepping back from the confirmation screen', async () => {
+ it('does not record twice when stepping back to the summary', async () => {
const { result } = renderOnboarding(1)
- await goToCharter(result)
+ await goToSummary(result)
act(() => result.current.setAcknowledged(true))
await act(async () => {
result.current.goNext()
})
act(() => result.current.goBack())
- expect(result.current.subStep).toBe(CHARTER_SUBSTEP)
+ expect(result.current.currentStep).toBe(1)
+ expect(result.current.subStep).toBe(SUMMARY_SUBSTEP)
act(() => result.current.setAcknowledged(true))
await act(async () => {
result.current.goNext()
})
expect(signCharterCall).toHaveBeenCalledTimes(1)
- expect(result.current.subStep).toBe(CHARTER_SUBSTEP + 1)
+ expect(result.current.currentStep).toBe(2)
})
})
diff --git a/src/views/Onboarding/__tests__/ScreenTag.test.tsx b/src/views/Onboarding/__tests__/ScreenTag.test.tsx
new file mode 100644
index 000000000..cabb3a2e4
--- /dev/null
+++ b/src/views/Onboarding/__tests__/ScreenTag.test.tsx
@@ -0,0 +1,18 @@
+import { render, screen } from '@testing-library/react'
+import React from 'react'
+import { describe, expect, it } from 'vitest'
+
+import { getCommitmentTag } from '../commitments'
+import ScreenTag from '../ScreenTag'
+
+describe('ScreenTag (RG3429.02)', () => {
+ it('renders the label it is given', () => {
+ render()
+ expect(screen.getByText('Engagement 3')).toBeInTheDocument()
+ })
+
+ it('numbers the commitments from one', () => {
+ expect(getCommitmentTag(0)).toBe('Engagement 1')
+ expect(getCommitmentTag(9)).toBe('Engagement 10')
+ })
+})
diff --git a/src/views/Onboarding/__tests__/steps.test.ts b/src/views/Onboarding/__tests__/steps.test.ts
index e77aa52b5..61019c847 100644
--- a/src/views/Onboarding/__tests__/steps.test.ts
+++ b/src/views/Onboarding/__tests__/steps.test.ts
@@ -4,37 +4,62 @@ vi.mock('services/aphp/serviceOnboarding', () => ({
default: { updateStep: vi.fn(), signCharter: vi.fn() }
}))
-import { getScreenConfig, getStepScreenCount, ONBOARDING_STEPS } from '../steps'
+import { COMMITMENTS } from '../commitments'
+import { COMMITMENT_PRIMARY_LABEL, getScreenConfig, getStepScreenCount, ONBOARDING_STEPS } from '../steps'
const COMMITMENTS_STEP = 1
const HANDSON_STEP = 2
+const SUMMARY_SUBSTEP = 11
const commitmentScreens = () => ONBOARDING_STEPS[COMMITMENTS_STEP].screens
describe('onboarding step 2 configuration', () => {
- it('exposes the seven informative screens then the charter and its confirmation', () => {
+ it('opens on the rules then walks the ten commitments before the summary', () => {
expect(commitmentScreens().map((screen) => screen.key)).toEqual([
'usage-rules',
+ 'personal-access',
+ 'perimeter-scope',
'usage-purposes',
- 'minimal-data-use',
'data-crossing',
+ 'medical-secrecy',
+ 'data-protection',
+ 'incident-reporting',
+ 'habilitation-lifecycle',
'data-deletion',
- 'care-team-sharing',
'actions-logging',
- 'charter-signature',
- 'charter-confirmation'
+ 'commitments-summary'
])
})
- it('only the charter screen replaces the default primary button', () => {
- const withAction = commitmentScreens().filter((screen) => screen.primaryAction)
- expect(withAction).toHaveLength(1)
- expect(withAction[0].key).toBe('charter-signature')
- expect(withAction[0].primaryAction?.label).toBe('Signer')
+ it('tags each commitment screen in order (RG3429.02)', () => {
+ const tags = commitmentScreens()
+ .map((screen) => screen.tag)
+ .filter(Boolean)
+ expect(tags).toEqual(COMMITMENTS.map((_, index) => `Engagement ${index + 1}`))
})
- it('renders the charter inside the default card wrapper', () => {
- expect(getScreenConfig(COMMITMENTS_STEP, 7)?.layout).toBeUndefined()
+ it('labels the ten commitment buttons `Je m’y engage` (RG3429.04)', () => {
+ const commitments = commitmentScreens().filter((screen) => screen.tag)
+ expect(commitments).toHaveLength(10)
+ for (const screen of commitments) {
+ expect(screen.primaryAction?.label).toBe(COMMITMENT_PRIMARY_LABEL)
+ expect(screen.primaryAction?.run).toBeUndefined()
+ }
+ })
+
+ it('only the summary screen records the validation (RG3429.07)', () => {
+ const summary = getScreenConfig(COMMITMENTS_STEP, SUMMARY_SUBSTEP)
+ expect(summary?.key).toBe('commitments-summary')
+ expect(summary?.primaryAction?.label).toBe('Valider')
+ expect(summary?.primaryAction?.run).toBeDefined()
+ })
+
+ it('holds the summary button until the certification is ticked (RG3429.08)', () => {
+ expect(getScreenConfig(COMMITMENTS_STEP, SUMMARY_SUBSTEP)?.requiresAcknowledgement).toBe(true)
+ })
+
+ it('renders the summary inside the default card wrapper', () => {
+ expect(getScreenConfig(COMMITMENTS_STEP, SUMMARY_SUBSTEP)?.layout).toBeUndefined()
})
it('counts a screenless step as a single slot so the journey never stalls', () => {
diff --git a/src/views/Onboarding/commitments.ts b/src/views/Onboarding/commitments.ts
new file mode 100644
index 000000000..f7907b36f
--- /dev/null
+++ b/src/views/Onboarding/commitments.ts
@@ -0,0 +1,15 @@
+/** Libellés des 10 engagements, dans l'ordre du parcours (RG3429.02). */
+export const COMMITMENTS: string[] = [
+ 'Vos accès sont personnels',
+ "Vous n'accédez qu'aux données de votre périmètre",
+ "Vous n'utilisez les données que pour les finalités prévues",
+ 'Vous ne croisez pas les données',
+ 'Vous respectez le secret médical',
+ 'Vous protégez les données que vous manipulez',
+ "Vous alertez en cas d'incident",
+ 'Vous anticipez la modification ou la clôture de vos habilitations',
+ "Vous supprimez les données à l'issue de votre mission",
+ 'Vous acceptez que vos actions soient enregistrées'
+]
+
+export const getCommitmentTag = (index: number): string => `Engagement ${index + 1}`
diff --git a/src/views/Onboarding/screens/commitments/ActionsLogging.tsx b/src/views/Onboarding/screens/commitments/ActionsLogging.tsx
index 150814a7b..06348cfe7 100644
--- a/src/views/Onboarding/screens/commitments/ActionsLogging.tsx
+++ b/src/views/Onboarding/screens/commitments/ActionsLogging.tsx
@@ -1,9 +1,7 @@
-import ActionsLoggingArtwork from 'assets/images/onboarding/actions-logging.svg?react'
import { Box, Link, Typography } from '@mui/material'
import { getConfig } from 'config'
import React from 'react'
-import Illustration from '../../Illustration'
import useStyles from '../../styles'
const ActionsLogging = () => {
@@ -13,15 +11,13 @@ const ActionsLogging = () => {
return (
- L'enregistrement de vos actions
+ Vous acceptez que vos actions soient enregistrées
-
- À chaque connexion à Cohort360, vos actions sont enregistrées par la DSI et conservées pendant 3 ans.
-
+ À chaque connexion à Cohort360, vos actions sont enregistrées par la DSN et conservées pendant 3 ans.
- Ces données sont utilisées pour assurer la sécurité du système, réaliser des statistiques d'usage et répondre
+ Vos données d'usage sont utilisées pour assurer la sécurité du système, réaliser des statistiques et répondre
aux demandes d'audit de la CNIL.
@@ -29,9 +25,7 @@ const ActionsLogging = () => {
{mailDataProtection}
- .
-
)
}
diff --git a/src/views/Onboarding/screens/commitments/CareTeamSharing.tsx b/src/views/Onboarding/screens/commitments/CareTeamSharing.tsx
deleted file mode 100644
index 2cca52b25..000000000
--- a/src/views/Onboarding/screens/commitments/CareTeamSharing.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import CareTeamSharingArtwork from 'assets/images/onboarding/care-team-sharing.svg?react'
-import { Box, Typography } from '@mui/material'
-import React from 'react'
-
-import Illustration from '../../Illustration'
-import useStyles from '../../styles'
-
-const CareTeamSharing = () => {
- const { classes } = useStyles()
-
- return (
-
-
- Un partage limité à « l'équipe de soin »
-
-
- Votre compte est défini par l'accès à un périmètre précis. Vous pouvez partager les données
- associées à un patient ou groupe de patients{' '}
- seulement avec les personnes ayant participé à leur prise en charge.
-
-
-
- )
-}
-
-export default CareTeamSharing
diff --git a/src/views/Onboarding/screens/commitments/CharterConfirmation.tsx b/src/views/Onboarding/screens/commitments/CharterConfirmation.tsx
deleted file mode 100644
index 0ac959f17..000000000
--- a/src/views/Onboarding/screens/commitments/CharterConfirmation.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import CheckIcon from '@mui/icons-material/Check'
-import { Box, Divider, Typography } from '@mui/material'
-import InfoBadge from 'components/ui/InfoBadge'
-import React from 'react'
-
-import useStyles from '../../styles'
-
-const CharterConfirmation = () => {
- const { classes } = useStyles()
-
- return (
-
-
-
- Votre charte d'engagement a bien été signée.
-
-
- Vous pouvez maintenant accéder à une présentation guidée de 3 fonctionnalités clés de Cohort360 ou bien accéder
- directement à l'outil.
-
-
-
-
-
-
- Vous pourrez retrouver la prise en main guidée dans le centre d'aide de Cohort360, accessible à tout moment
- dans l'outil.
-
-
-
- )
-}
-
-export default CharterConfirmation
diff --git a/src/views/Onboarding/screens/commitments/CharterSignature.tsx b/src/views/Onboarding/screens/commitments/CharterSignature.tsx
deleted file mode 100644
index 7db51fd57..000000000
--- a/src/views/Onboarding/screens/commitments/CharterSignature.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Box, Checkbox, Divider, FormControlLabel, Link, Typography } from '@mui/material'
-import React from 'react'
-
-import { useOnboarding } from '../../OnboardingContext'
-import useStyles from '../../styles'
-
-const CHARTER_PDF_URL = '/documents/charte-engagement-cohort360.pdf'
-const CHARTER_PDF_EMBED_URL = `${CHARTER_PDF_URL}#navpanes=0&toolbar=0&statusbar=0&view=FitH`
-
-export const CHARTER_CONSENT_TEXT =
- 'Je certifie avoir pris connaissance de mes responsabilités vis-à-vis de l’utilisation des données mises à disposition dans Cohort360 et je m’engage à les respecter.'
-
-const CharterSignature = () => {
- const { classes } = useStyles()
- const { acknowledged, setAcknowledged } = useOnboarding()
-
- return (
-
-
- Signer la charte d'engagement
-
-
-
-
-
-
- Télécharger une copie de la charte d'engagement
-
-
-
- setAcknowledged(event.target.checked)}
- />
- }
- label={{CHARTER_CONSENT_TEXT}}
- />
-
- )
-}
-
-export default CharterSignature
diff --git a/src/views/Onboarding/screens/commitments/CommitmentsSummary.tsx b/src/views/Onboarding/screens/commitments/CommitmentsSummary.tsx
new file mode 100644
index 000000000..16d5c55e4
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/CommitmentsSummary.tsx
@@ -0,0 +1,43 @@
+import CheckCircleIcon from '@mui/icons-material/CheckCircle'
+import { Box, Checkbox, Divider, FormControlLabel, Typography } from '@mui/material'
+import React from 'react'
+
+import { COMMITMENTS } from '../../commitments'
+import { useOnboarding } from '../../OnboardingContext'
+import useStyles from '../../styles'
+
+export const CHARTER_CONSENT_TEXT =
+ 'Je certifie avoir pris connaissance de mes responsabilités vis-à-vis de l’utilisation des données mises à disposition dans Cohort360 et je m’engage à les respecter.'
+
+const CommitmentsSummary = () => {
+ const { classes } = useStyles()
+ const { acknowledged, setAcknowledged } = useOnboarding()
+
+ return (
+
+
+ Synthèse de vos engagements
+
+ {COMMITMENTS.map((commitment) => (
+
+
+ {commitment}
+
+ ))}
+
+ setAcknowledged(event.target.checked)}
+ />
+ }
+ label={{CHARTER_CONSENT_TEXT}}
+ />
+
+ )
+}
+
+export default CommitmentsSummary
diff --git a/src/views/Onboarding/screens/commitments/DataCrossing.tsx b/src/views/Onboarding/screens/commitments/DataCrossing.tsx
index 527154519..bd1e467a5 100644
--- a/src/views/Onboarding/screens/commitments/DataCrossing.tsx
+++ b/src/views/Onboarding/screens/commitments/DataCrossing.tsx
@@ -3,6 +3,7 @@ import { Box, Typography } from '@mui/material'
import React from 'react'
import Illustration from '../../Illustration'
+import InfoCallout from '../../InfoCallout'
import useStyles from '../../styles'
const DataCrossing = () => {
@@ -11,13 +12,16 @@ const DataCrossing = () => {
return (
- Le croisement des données
+ Vous ne croisez pas les données
- Le croisement des données obtenues grâce à Cohort360 avec des données issues d'autres bases de données est{' '}
- formellement interdit.
+ Vous vous engagez à ne pas croiser des données exportées via Cohort360 avec d'autres sources de données.
+
+ Que ce soit des données externes, comme par exemple des données du SNDS ou de l'INSEE ou des données internes à
+ l'AP-HP issues d'un autre service pour lequel vous n'êtes pas habilité.
+
)
}
diff --git a/src/views/Onboarding/screens/commitments/DataDeletion.tsx b/src/views/Onboarding/screens/commitments/DataDeletion.tsx
index dcc1cad01..92f7b7ee6 100644
--- a/src/views/Onboarding/screens/commitments/DataDeletion.tsx
+++ b/src/views/Onboarding/screens/commitments/DataDeletion.tsx
@@ -1,7 +1,9 @@
-import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'
-import { Avatar, Box, Typography } from '@mui/material'
+import DataDeletionArtwork from 'assets/images/onboarding/data-deletion.svg?react'
+import { Box, Typography } from '@mui/material'
import React from 'react'
+import Illustration from '../../Illustration'
+import InfoCallout from '../../InfoCallout'
import useStyles from '../../styles'
const DataDeletion = () => {
@@ -10,18 +12,17 @@ const DataDeletion = () => {
return (
- La suppression de vos données
+ Vous supprimez les données à l'issue de votre mission
- Vous devez détruire toutes données sur supports mobiles dès la fin de leur utilisation. Par
- ailleurs, vous vous engagez à demander la clôture des accès, si nécessaire de manière
- anticipée, dès que ces derniers ne sont plus requis.
+ En cas de cessation de vos fonctions, ou à la fin de votre projet, vous vous engagez à : supprimer intégralement
+ les données, fichiers et supports d'information relatifs à ces données, ainsi que les documents, codes ou moyens
+ d'accès que vous détenez, sans en conserver de copie.
-
-
-
-
-
+
+
+ À l'issue de votre mission, tout accès aux données, en ligne ou par un export conservé, devient illégitime.
+
)
}
diff --git a/src/views/Onboarding/screens/commitments/DataProtection.tsx b/src/views/Onboarding/screens/commitments/DataProtection.tsx
new file mode 100644
index 000000000..94cc7d10e
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/DataProtection.tsx
@@ -0,0 +1,29 @@
+import { Box, Typography } from '@mui/material'
+import React from 'react'
+
+import InfoCallout from '../../InfoCallout'
+import useStyles from '../../styles'
+
+const DataProtection = () => {
+ const { classes } = useStyles()
+
+ return (
+
+
+ Vous protégez les données que vous manipulez
+
+
+ Vous vous engagez à prendre toutes les mesures et précautions conformes aux usages et à l'état de l'art dans le
+ cadre de vos attributions, afin d'éviter l'utilisation détournée ou frauduleuse de ces données, et d'en
+ préserver la sécurité matérielle.
+
+
+ Par exemple : déposer un export sur un service en ligne externe à l'AP-HP, le stocker sur un ordinateur partagé
+ ou le transmettre à un prestataire, constitue un usage que vous ne maîtrisez plus et vous expose à un risque de
+ poursuite.
+
+
+ )
+}
+
+export default DataProtection
diff --git a/src/views/Onboarding/screens/commitments/HabilitationLifecycle.tsx b/src/views/Onboarding/screens/commitments/HabilitationLifecycle.tsx
new file mode 100644
index 000000000..63fae7c17
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/HabilitationLifecycle.tsx
@@ -0,0 +1,27 @@
+import { Box, Typography } from '@mui/material'
+import React from 'react'
+
+import InfoCallout from '../../InfoCallout'
+import useStyles from '../../styles'
+
+const HabilitationLifecycle = () => {
+ const { classes } = useStyles()
+
+ return (
+
+
+ Vous anticipez la modification ou la clôture de vos habilitations
+
+
+ Vous vous engagez à demander la clôture ou la modification des accès - si nécessaire de manière anticipée - en
+ cas de changement de situation, ou dès qu'ils ne sont plus requis à votre point de contact EDS ou directement au
+ support.
+
+
+ Par exemple : un changement de service, la fin d'un projet ou un départ modifient votre habilitation.
+
+
+ )
+}
+
+export default HabilitationLifecycle
diff --git a/src/views/Onboarding/screens/commitments/IncidentReporting.tsx b/src/views/Onboarding/screens/commitments/IncidentReporting.tsx
new file mode 100644
index 000000000..39b157df2
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/IncidentReporting.tsx
@@ -0,0 +1,27 @@
+import { Box, Typography } from '@mui/material'
+import React from 'react'
+
+import InfoCallout from '../../InfoCallout'
+import useStyles from '../../styles'
+
+const IncidentReporting = () => {
+ const { classes } = useStyles()
+
+ return (
+
+
+ Vous alertez en cas d'incident
+
+
+ Vous vous engagez à faire remonter tout dysfonctionnement, problème ou incident observé à votre point de contact
+ EDS ou directement au support.
+
+
+ Par exemple : voir apparaître des données qui ne correspondent pas à votre périmètre, ou constater qu'un accès
+ fonctionne encore alors qu'il aurait dû être fermé.
+
+
+ )
+}
+
+export default IncidentReporting
diff --git a/src/views/Onboarding/screens/commitments/MedicalSecrecy.tsx b/src/views/Onboarding/screens/commitments/MedicalSecrecy.tsx
new file mode 100644
index 000000000..bb6fcfe3d
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/MedicalSecrecy.tsx
@@ -0,0 +1,33 @@
+import MedicalSecrecyArtwork from 'assets/images/onboarding/medical-secrecy.svg?react'
+import { Box, Typography } from '@mui/material'
+import React from 'react'
+
+import Illustration from '../../Illustration'
+import InfoCallout from '../../InfoCallout'
+import useStyles from '../../styles'
+
+const MedicalSecrecy = () => {
+ const { classes } = useStyles()
+
+ return (
+
+
+ Vous respectez le secret médical
+
+
+ Vous vous engagez à ne divulguer les informations obtenues qu'aux personnes dûment autorisées, en adéquation
+ avec leur fonction.
+
+
+
+ Par exemple : il est interdit de partager les données associées à un patient ou groupe de patient avec des
+ personnes n'ayant pas participé à leur prise en charge.
+
+
+ )
+}
+
+export default MedicalSecrecy
diff --git a/src/views/Onboarding/screens/commitments/MinimalDataUse.tsx b/src/views/Onboarding/screens/commitments/MinimalDataUse.tsx
deleted file mode 100644
index e0ee6c4e1..000000000
--- a/src/views/Onboarding/screens/commitments/MinimalDataUse.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import MinimalDataUseArtwork from 'assets/images/onboarding/minimal-data-use.svg?react'
-import { Box, Typography } from '@mui/material'
-import React from 'react'
-
-import Illustration from '../../Illustration'
-import useStyles from '../../styles'
-
-const MinimalDataUse = () => {
- const { classes } = useStyles()
-
- return (
-
-
- L'utilisation minimale des données
-
-
- En utilisant Cohort360, vous vous engagez à utiliser uniquement les données strictement nécessaires à votre
- recherche.
-
-
-
- )
-}
-
-export default MinimalDataUse
diff --git a/src/views/Onboarding/screens/commitments/PerimeterScope.tsx b/src/views/Onboarding/screens/commitments/PerimeterScope.tsx
new file mode 100644
index 000000000..573318873
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/PerimeterScope.tsx
@@ -0,0 +1,34 @@
+import PerimeterScopeArtwork from 'assets/images/onboarding/perimeter-scope.svg?react'
+import { Box, Typography } from '@mui/material'
+import React from 'react'
+
+import Illustration from '../../Illustration'
+import InfoCallout from '../../InfoCallout'
+import useStyles from '../../styles'
+
+const PerimeterScope = () => {
+ const { classes } = useStyles()
+
+ return (
+
+
+ Vous n'accédez qu'aux données de votre périmètre
+
+
+ Vous vous engagez à n'accéder qu'aux seuls dossiers et données auxquels l'AP-HP vous donne accès et qui sont
+ strictement nécessaires à l'exercice de vos missions, selon les instructions du service concerné.
+
+
+ Vous vous engagez à ne pas accéder aux autres dossiers ou données, même si cet accès est techniquement possible,
+ et vous reconnaissez qu'un tel accès sera qualifié d'illégitime et sanctionnable.
+
+
+
+ Si votre périmètre ne correspond pas à votre besoin réel, une demande de modification est possible, si vous êtes
+ déjà habilité à Cohort360.
+
+
+ )
+}
+
+export default PerimeterScope
diff --git a/src/views/Onboarding/screens/commitments/PersonalAccess.tsx b/src/views/Onboarding/screens/commitments/PersonalAccess.tsx
new file mode 100644
index 000000000..1d8f5035e
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/PersonalAccess.tsx
@@ -0,0 +1,27 @@
+import { Box, Typography } from '@mui/material'
+import React from 'react'
+
+import InfoCallout from '../../InfoCallout'
+import useStyles from '../../styles'
+
+const PersonalAccess = () => {
+ const { classes } = useStyles()
+
+ return (
+
+
+ Vos accès sont personnels
+
+
+ Vous vous engagez à garder pour vous seul les accès qui vous ont été accordés et à ne les
+ employer qu'en accord avec les informations renseignées au formulaire de demande d'accès.
+
+
+ Par exemple : un collègue qui travaille sur le même projet ou dans le même service ne peut pas utiliser votre
+ compte. Il doit faire sa propre demande d'habilitation.
+
+
+ )
+}
+
+export default PersonalAccess
diff --git a/src/views/Onboarding/screens/commitments/UsagePurposes.tsx b/src/views/Onboarding/screens/commitments/UsagePurposes.tsx
index 40a7fc694..9a8abef8f 100644
--- a/src/views/Onboarding/screens/commitments/UsagePurposes.tsx
+++ b/src/views/Onboarding/screens/commitments/UsagePurposes.tsx
@@ -1,46 +1,25 @@
-import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined'
-import QueryStatsIcon from '@mui/icons-material/QueryStats'
-import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined'
import { Box, Typography } from '@mui/material'
-import type { SvgIconProps } from '@mui/material'
-import type { ComponentType } from 'react'
import React from 'react'
+import InfoCallout from '../../InfoCallout'
import useStyles from '../../styles'
-type Purpose = {
- label: string
- icon: ComponentType
-}
-
-const PURPOSES: Purpose[] = [
- { label: 'Innovation', icon: LightbulbOutlinedIcon },
- { label: 'Recherche', icon: QueryStatsIcon },
- { label: "Pilotage de l'activité hospitalière", icon: ShareOutlinedIcon }
-]
-
const UsagePurposes = () => {
const { classes } = useStyles()
return (
- Les finalités d'usage
+ Vous n'utilisez les données que pour les finalités prévues
- L'Entrepôt de Données de Santé (EDS) de l'AP-HP contient des données à caractère personnel sensibles.
-
-
- Seules certaines finalités d'utilisation des données sont autorisées :
+ Vous vous engagez à ne pas utiliser les informations, données ou renseignements auxquels vous accédez pour
+ d'autres fins que celles strictement prévues par vos attributions dans le cadre de vos missions.
- {PURPOSES.map(({ label, icon: Icon }) => (
-
-
-
-
- {label}
-
- ))}
+
+ Par exemple : la réutilisation de données exportées dans un cadre autorisé pour d'autres usages est interdit.
+ Consulter le dossier médical d'un proche, même dans une intention de soin, est également interdit.
+
)
}
diff --git a/src/views/Onboarding/screens/commitments/__tests__/ActionsLogging.test.tsx b/src/views/Onboarding/screens/commitments/__tests__/ActionsLogging.test.tsx
index 3d5408f56..29ec29670 100644
--- a/src/views/Onboarding/screens/commitments/__tests__/ActionsLogging.test.tsx
+++ b/src/views/Onboarding/screens/commitments/__tests__/ActionsLogging.test.tsx
@@ -19,11 +19,6 @@ describe('ActionsLogging (RG3308.03)', () => {
expect(screen.getByText(/accéder à vos données et les rectifier/)).toBeInTheDocument()
})
- it('describes its illustration to assistive technologies', () => {
- render()
- expect(screen.getByRole('img', { name: 'Vos actions sont enregistrées et conservées' })).toBeInTheDocument()
- })
-
it('reads the address from the runtime configuration', () => {
updateConfig({ system: { mailDataProtection: 'dpo@example.org' } })
render()
diff --git a/src/views/Onboarding/screens/commitments/__tests__/CharterSignature.test.tsx b/src/views/Onboarding/screens/commitments/__tests__/CharterSignature.test.tsx
deleted file mode 100644
index 3f74d863e..000000000
--- a/src/views/Onboarding/screens/commitments/__tests__/CharterSignature.test.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { render, screen } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-import React from 'react'
-import { describe, expect, it } from 'vitest'
-
-import { OnboardingProvider } from '../../../OnboardingContext'
-import CharterSignature from '../CharterSignature'
-
-const CHARTER_PDF_URL = '/documents/charte-engagement-cohort360.pdf'
-
-const renderCharter = () => {
- const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
- return render(
-
-
-
-
-
- )
-}
-
-describe('CharterSignature (RG3309.01)', () => {
- it('embeds the charter as a scrollable PDF document', () => {
- renderCharter()
- const document = screen.getByLabelText("Charte d'engagement Cohort360")
- expect(document).toHaveAttribute('data', `${CHARTER_PDF_URL}#navpanes=0&toolbar=0&statusbar=0&view=FitH`)
- expect(document).toHaveAttribute('type', 'application/pdf')
- })
-
- it('offers a fallback link for browsers that cannot render the PDF inline', () => {
- renderCharter()
- expect(screen.getByRole('link', { name: "Ouvrir la charte d'engagement" })).toHaveAttribute('href', CHARTER_PDF_URL)
- })
-
- it('exposes the mandatory consent, unchecked at first', async () => {
- const user = userEvent.setup()
- renderCharter()
-
- const consent = screen.getByRole('checkbox', { name: /Je certifie avoir pris connaissance/ })
- expect(consent).not.toBeChecked()
-
- await user.click(consent)
- expect(consent).toBeChecked()
- })
-})
diff --git a/src/views/Onboarding/screens/commitments/__tests__/CommitmentsSummary.test.tsx b/src/views/Onboarding/screens/commitments/__tests__/CommitmentsSummary.test.tsx
new file mode 100644
index 000000000..5e91948ee
--- /dev/null
+++ b/src/views/Onboarding/screens/commitments/__tests__/CommitmentsSummary.test.tsx
@@ -0,0 +1,41 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import React from 'react'
+import { describe, expect, it } from 'vitest'
+
+import { COMMITMENTS } from '../../../commitments'
+import { OnboardingProvider } from '../../../OnboardingContext'
+import CommitmentsSummary from '../CommitmentsSummary'
+
+const renderSummary = () => {
+ const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
+ return render(
+
+
+
+
+
+ )
+}
+
+describe('CommitmentsSummary (RG3429.05)', () => {
+ it('lists the ten commitments', () => {
+ renderSummary()
+ for (const commitment of COMMITMENTS) {
+ expect(screen.getByText(commitment)).toBeInTheDocument()
+ }
+ expect(COMMITMENTS).toHaveLength(10)
+ })
+
+ it('exposes the mandatory certification, unchecked at first (RG3429.07)', async () => {
+ const user = userEvent.setup()
+ renderSummary()
+
+ const consent = screen.getByRole('checkbox', { name: /Je certifie avoir pris connaissance/ })
+ expect(consent).not.toBeChecked()
+
+ await user.click(consent)
+ expect(consent).toBeChecked()
+ })
+})
diff --git a/src/views/Onboarding/screens/commitments/__tests__/screens.test.tsx b/src/views/Onboarding/screens/commitments/__tests__/screens.test.tsx
index f1f568b6f..69ec52ad7 100644
--- a/src/views/Onboarding/screens/commitments/__tests__/screens.test.tsx
+++ b/src/views/Onboarding/screens/commitments/__tests__/screens.test.tsx
@@ -2,9 +2,11 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import { describe, expect, it } from 'vitest'
-import CharterConfirmation from '../CharterConfirmation'
import DataCrossing from '../DataCrossing'
-import MinimalDataUse from '../MinimalDataUse'
+import DataDeletion from '../DataDeletion'
+import MedicalSecrecy from '../MedicalSecrecy'
+import PerimeterScope from '../PerimeterScope'
+import PersonalAccess from '../PersonalAccess'
import UsagePurposes from '../UsagePurposes'
import UsageRules from '../UsageRules'
@@ -22,39 +24,54 @@ describe('UsageRules', () => {
})
})
-describe('DataCrossing', () => {
- it('states that cross-referencing with other databases is forbidden', () => {
- render()
- expect(screen.getByRole('heading')).toHaveTextContent('Le croisement des données')
- expect(screen.getByText(/formellement interdit/)).toBeInTheDocument()
- expect(screen.getByRole('img')).toHaveAccessibleName(/croisement entre deux jeux de données est interdit/)
+describe('PersonalAccess', () => {
+ it('states that credentials are never shared, with an example (RG3429.03)', () => {
+ render()
+ expect(screen.getByRole('heading')).toHaveTextContent('Vos accès sont personnels')
+ expect(screen.getByText(/pour vous seul/)).toBeInTheDocument()
+ expect(screen.getByRole('note')).toHaveTextContent(/ne peut pas utiliser votre compte/)
})
})
-describe('MinimalDataUse', () => {
- it('states that only strictly necessary data may be used', () => {
- render()
- expect(screen.getByRole('heading')).toHaveTextContent("L'utilisation minimale des données")
- expect(screen.getByText(/strictement nécessaires à votre recherche/)).toBeInTheDocument()
+describe('PerimeterScope', () => {
+ it('limits access to the granted perimeter', () => {
+ render()
+ expect(screen.getByRole('heading')).toHaveTextContent("Vous n'accédez qu'aux données de votre périmètre")
+ expect(screen.getByText(/illégitime et sanctionnable/)).toBeInTheDocument()
expect(screen.getByRole('img')).toBeInTheDocument()
})
})
describe('UsagePurposes', () => {
- it('lists the three authorised purposes and nothing else', () => {
+ it('restricts the data to the purposes of the mission', () => {
render()
- expect(screen.getByRole('heading')).toHaveTextContent("Les finalités d'usage")
- expect(screen.getByText('Innovation')).toBeInTheDocument()
- expect(screen.getByText('Recherche')).toBeInTheDocument()
- expect(screen.getByText("Pilotage de l'activité hospitalière")).toBeInTheDocument()
- expect(screen.getByText(/Seules certaines finalités/)).toBeInTheDocument()
+ expect(screen.getByRole('heading')).toHaveTextContent("Vous n'utilisez les données que pour les finalités prévues")
+ expect(screen.getByRole('note')).toHaveTextContent(/dossier médical d'un proche/)
+ })
+})
+
+describe('DataCrossing', () => {
+ it('states that cross-referencing with other sources is forbidden', () => {
+ render()
+ expect(screen.getByRole('heading')).toHaveTextContent('Vous ne croisez pas les données')
+ expect(screen.getByRole('img')).toHaveAccessibleName(/croisement entre deux jeux de données est interdit/)
+ expect(screen.getByRole('note')).toHaveTextContent(/SNDS/)
})
})
-describe('CharterConfirmation', () => {
- it('confirms the signature and points to the help centre', () => {
- render()
- expect(screen.getByRole('heading')).toHaveTextContent("Votre charte d'engagement a bien été signée.")
- expect(screen.getByText(/centre d'aide de Cohort360/)).toBeInTheDocument()
+describe('MedicalSecrecy', () => {
+ it('restricts disclosure to duly authorised people', () => {
+ render()
+ expect(screen.getByRole('heading')).toHaveTextContent('Vous respectez le secret médical')
+ expect(screen.getByRole('img')).toBeInTheDocument()
+ })
+})
+
+describe('DataDeletion', () => {
+ it('requires the data to be deleted without any copy left', () => {
+ render()
+ expect(screen.getByRole('heading')).toHaveTextContent("Vous supprimez les données à l'issue de votre mission")
+ expect(screen.getByText(/sans en conserver de copie/)).toBeInTheDocument()
+ expect(screen.getByRole('img')).toBeInTheDocument()
})
})
diff --git a/src/views/Onboarding/screens/environment/UserRights.tsx b/src/views/Onboarding/screens/environment/UserRights.tsx
index e6821fedd..d8350bf96 100644
--- a/src/views/Onboarding/screens/environment/UserRights.tsx
+++ b/src/views/Onboarding/screens/environment/UserRights.tsx
@@ -1,9 +1,11 @@
-import CheckCircleIcon from '@mui/icons-material/CheckCircle'
+import CheckIcon from '@mui/icons-material/Check'
import { Box, CircularProgress, Link, Typography } from '@mui/material'
+import CircleBadge from 'components/ui/CircleBadge'
import moment from 'moment'
import React from 'react'
import { useAppSelector } from 'state'
+import { aphp } from 'styles/palette'
import useStyles from '../../styles'
import { useUserAccesses } from './useUserAccesses'
@@ -71,7 +73,9 @@ const UserRights = () => {
Vos droits :
{access.rights.map((right) => (
-
+
+
+
{right}
))}
diff --git a/src/views/Onboarding/screens/environment/WhatIsEds.tsx b/src/views/Onboarding/screens/environment/WhatIsEds.tsx
index 9fc9e62e4..f3cf0add3 100644
--- a/src/views/Onboarding/screens/environment/WhatIsEds.tsx
+++ b/src/views/Onboarding/screens/environment/WhatIsEds.tsx
@@ -3,13 +3,12 @@ import BiotechOutlinedIcon from '@mui/icons-material/BiotechOutlined'
import ContactPageOutlinedIcon from '@mui/icons-material/ContactPageOutlined'
import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'
import MedicationOutlinedIcon from '@mui/icons-material/MedicationOutlined'
-import { Box, Divider, Link, Typography } from '@mui/material'
+import { Box, Link, Typography } from '@mui/material'
import type { SvgIconProps } from '@mui/material'
-import InfoBadge from 'components/ui/InfoBadge'
import type { ComponentType } from 'react'
import React from 'react'
-import { eds } from 'styles/palette'
+import InfoCallout from '../../InfoCallout'
import useStyles from '../../styles'
const EDS_URL = 'https://panorama.eds.aphp.fr/explorer-les-donnees'
@@ -78,14 +77,10 @@ const WhatIsEds = () => {
))}
-
-
-
-
- Selon votre profil d'habilitation, vous accédez à des données nominatives (identité des patients visible) ou
- pseudonymisées (identité masquée).
-
-
+
+ Selon votre profil d'habilitation, vous accédez à des données nominatives (identité des patients visible) ou
+ pseudonymisées (identité masquée).
+
)
}
diff --git a/src/views/Onboarding/steps.ts b/src/views/Onboarding/steps.ts
index 7b080648d..4ad81fccc 100644
--- a/src/views/Onboarding/steps.ts
+++ b/src/views/Onboarding/steps.ts
@@ -4,13 +4,17 @@ import MenuBookIcon from '@mui/icons-material/MenuBook'
import type { SvgIconProps } from '@mui/material'
import type { ComponentType } from 'react'
+import { getCommitmentTag } from './commitments'
import ActionsLogging from './screens/commitments/ActionsLogging'
-import CareTeamSharing from './screens/commitments/CareTeamSharing'
-import CharterConfirmation from './screens/commitments/CharterConfirmation'
-import CharterSignature from './screens/commitments/CharterSignature'
+import CommitmentsSummary from './screens/commitments/CommitmentsSummary'
import DataCrossing from './screens/commitments/DataCrossing'
import DataDeletion from './screens/commitments/DataDeletion'
-import MinimalDataUse from './screens/commitments/MinimalDataUse'
+import DataProtection from './screens/commitments/DataProtection'
+import HabilitationLifecycle from './screens/commitments/HabilitationLifecycle'
+import IncidentReporting from './screens/commitments/IncidentReporting'
+import MedicalSecrecy from './screens/commitments/MedicalSecrecy'
+import PerimeterScope from './screens/commitments/PerimeterScope'
+import PersonalAccess from './screens/commitments/PersonalAccess'
import UsagePurposes from './screens/commitments/UsagePurposes'
import UsageRules from './screens/commitments/UsageRules'
import DataAccess from './screens/environment/DataAccess'
@@ -22,6 +26,8 @@ import KeyFeatures from './screens/handson/KeyFeatures'
// Mirrors User.ONBOARDING_TOTAL_STEPS server-side.
export const ONBOARDING_TOTAL_STEPS = 3
+export const COMMITMENT_PRIMARY_LABEL = 'Je m’y engage'
+
/** Actions a screen's primary button can trigger, injected by the wizard. */
export type OnboardingActions = {
signCharter: () => Promise
@@ -41,6 +47,8 @@ export type OnboardingPrimaryAction = {
export type OnboardingScreenConfig = {
key: string
component: ComponentType
+ /** Étiquette affichée au-dessus du titre, par exemple « Engagement 3 » (RG3429.02). */
+ tag?: string
/** `bare` drops the white card, for screens bringing their own container. */
layout?: 'card' | 'bare'
primaryAction?: OnboardingPrimaryAction
@@ -56,6 +64,27 @@ export type OnboardingStepConfig = {
screens: OnboardingScreenConfig[]
}
+/**
+ * Les dix engagements, dans l'ordre de `COMMITMENTS` : chacun porte son étiquette et remplace le bouton
+ * de navigation par « Je m’y engage » (RG3429.02, RG3429.04).
+ */
+const COMMITMENT_SCREENS: OnboardingScreenConfig[] = [
+ { key: 'personal-access', component: PersonalAccess },
+ { key: 'perimeter-scope', component: PerimeterScope },
+ { key: 'usage-purposes', component: UsagePurposes },
+ { key: 'data-crossing', component: DataCrossing },
+ { key: 'medical-secrecy', component: MedicalSecrecy },
+ { key: 'data-protection', component: DataProtection },
+ { key: 'incident-reporting', component: IncidentReporting },
+ { key: 'habilitation-lifecycle', component: HabilitationLifecycle },
+ { key: 'data-deletion', component: DataDeletion },
+ { key: 'actions-logging', component: ActionsLogging }
+].map((screen, index) => ({
+ ...screen,
+ tag: getCommitmentTag(index),
+ primaryAction: { label: COMMITMENT_PRIMARY_LABEL }
+}))
+
export const ONBOARDING_STEPS: OnboardingStepConfig[] = [
{
key: 'environment',
@@ -76,23 +105,17 @@ export const ONBOARDING_STEPS: OnboardingStepConfig[] = [
icon: HistoryEduIcon,
screens: [
{ key: 'usage-rules', component: UsageRules },
- { key: 'usage-purposes', component: UsagePurposes },
- { key: 'minimal-data-use', component: MinimalDataUse },
- { key: 'data-crossing', component: DataCrossing },
- { key: 'data-deletion', component: DataDeletion },
- { key: 'care-team-sharing', component: CareTeamSharing },
- { key: 'actions-logging', component: ActionsLogging },
+ ...COMMITMENT_SCREENS,
{
- key: 'charter-signature',
- component: CharterSignature,
+ key: 'commitments-summary',
+ component: CommitmentsSummary,
requiresAcknowledgement: true,
primaryAction: {
- label: 'Signer',
+ label: 'Valider',
run: ({ signCharter }) => signCharter(),
- errorMessage: 'Une erreur est survenue lors de la signature de la charte. Veuillez réessayer.'
+ errorMessage: 'Une erreur est survenue lors de la validation de vos engagements. Veuillez réessayer.'
}
- },
- { key: 'charter-confirmation', component: CharterConfirmation }
+ }
]
},
{
diff --git a/src/views/Onboarding/styles.ts b/src/views/Onboarding/styles.ts
index 46db016b4..e8ddb8f8b 100644
--- a/src/views/Onboarding/styles.ts
+++ b/src/views/Onboarding/styles.ts
@@ -1,5 +1,5 @@
import type { Theme } from '@mui/material/styles'
-import { eds } from 'styles/palette'
+import { aphp, eds } from 'styles/palette'
import { makeStyles } from 'tss-react/mui'
import { onboardingTokens as T } from './tokens'
@@ -176,6 +176,16 @@ const useStyles = makeStyles()((theme: Theme) => ({
backgroundColor: eds.blue[600]
}
},
+ screenTag: {
+ display: 'inline-block',
+ marginBottom: theme.spacing(2),
+ padding: theme.spacing(0.5, 1.5),
+ borderRadius: 4,
+ backgroundColor: eds.blue[50],
+ color: eds.blue[800],
+ fontSize: 14,
+ lineHeight: '18px'
+ },
title: {
color: T.ink,
fontWeight: 700
@@ -320,6 +330,12 @@ const useStyles = makeStyles()((theme: Theme) => ({
flexShrink: 0,
color: theme.palette.success.main
},
+ rightBadge: {
+ flexShrink: 0
+ },
+ rightCheck: {
+ color: aphp.vert[600]
+ },
rightLabel: {
color: T.ink,
fontSize: 16,
@@ -336,7 +352,7 @@ const useStyles = makeStyles()((theme: Theme) => ({
border: `1px solid ${T.cardBorder}`,
borderRadius: 6,
padding: theme.spacing(1, 3, 3),
- backgroundColor: T.tileBg
+ backgroundColor: aphp.vert[100]
},
consentRow: {
display: 'flex',
@@ -383,42 +399,6 @@ const useStyles = makeStyles()((theme: Theme) => ({
height: 'auto',
maxWidth: 552
},
- deletionIcon: {
- width: 56,
- height: 56,
- backgroundColor: T.deepBlue
- },
- documentViewer: {
- padding: theme.spacing(3),
- borderRadius: 6,
- backgroundColor: T.documentBg
- },
- documentFrame: {
- display: 'block',
- width: '100%',
- height: 420,
- border: 'none',
- backgroundColor: T.surface
- },
- documentFallback: {
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- gap: theme.spacing(2),
- padding: theme.spacing(6),
- backgroundColor: T.surface
- },
- confirmationIcon: {
- display: 'block',
- margin: '0 auto',
- fontSize: 72,
- color: theme.palette.success.main
- },
- confirmationTitle: {
- marginTop: theme.spacing(3),
- color: T.ink,
- fontWeight: 700
- },
video: {
display: 'block',
width: '100%',