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 - - - - - - Votre navigateur ne peut pas afficher la charte directement. - - - Ouvrir 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%',