From 8b6a101f472861ed38a41a2f40a5e01208522bd7 Mon Sep 17 00:00:00 2001 From: Benjamin Morali Date: Mon, 9 Sep 2024 17:13:53 +0200 Subject: [PATCH] Appel du back-end depuis le formulaire (#207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Appel du back-end depuis le formulaire * Ajout de la page de formulaire validé * Correction des problèmes de validation * Ajout de tests pour la candidature validée * Retours de code review * Retours supplémentaires de code review * WIP : test d'erreur à faire fonctionner * Fix test stub * Ajout d'un test de changement de page --- src/App.js | 2 + src/components/commun/Alert.jsx | 17 +++ src/components/commun/BoutonRadio.jsx | 2 +- src/components/commun/Datepicker.jsx | 2 +- src/components/commun/Input.jsx | 12 +- .../candidature-conseiller/AddressChooser.jsx | 8 +- .../CandidatureConseiller.jsx | 50 ++++++- .../CandidatureConseiller.test.jsx | 128 ++++++++++++++++-- .../candidature-conseiller/Disponibilite.jsx | 16 +-- .../InformationsDeContact.jsx | 4 +- .../candidature-conseiller/Motivation.jsx | 2 +- .../SituationEtExperience.jsx | 12 +- .../candidature-conseiller/situations.js | 8 +- .../candidature-conseiller/useApiAdmin.js | 57 ++++++++ src/views/candidature-conseiller/useGeoApi.js | 16 ++- .../CandidatureCoordinateur.jsx | 6 +- .../BesoinEnConseillerNumerique.jsx | 9 +- .../CandidatureStructure.jsx | 8 +- .../CandidatureValidee.css | 7 + .../CandidatureValidee.jsx | 25 ++++ .../CandidatureValidee.test.jsx | 29 ++++ .../PageCandidatureValidee.jsx | 12 ++ 22 files changed, 370 insertions(+), 62 deletions(-) create mode 100644 src/components/commun/Alert.jsx create mode 100644 src/views/candidature-conseiller/useApiAdmin.js create mode 100644 src/views/candidature-validee/CandidatureValidee.css create mode 100644 src/views/candidature-validee/CandidatureValidee.jsx create mode 100644 src/views/candidature-validee/CandidatureValidee.test.jsx create mode 100644 src/views/candidature-validee/PageCandidatureValidee.jsx diff --git a/src/App.js b/src/App.js index 496f951e..edbceb29 100644 --- a/src/App.js +++ b/src/App.js @@ -39,6 +39,7 @@ function App() { const PageCandidatureConseiller = lazy(() => import('./views/candidature-conseiller/PageCandidatureConseiller')); const PageCandidatureStructure = lazy(() => import('./views/candidature-structure/PageCandidatureStructure')); const PageCandidatureCoordinateur = lazy(() => import('./views/candidature-coordinateur/PageCandidatureCoordinateur')); + const PageCandidatureValidee = lazy(() => import('./views/candidature-validee/PageCandidatureValidee')); return (
@@ -49,6 +50,7 @@ function App() { }/> }/> }/> + }/> }/> }/> }/> diff --git a/src/components/commun/Alert.jsx b/src/components/commun/Alert.jsx new file mode 100644 index 00000000..017e7c18 --- /dev/null +++ b/src/components/commun/Alert.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function Alert({ children, titre }) { + return ( +
+

{titre}

+

{children}

+
+ ); +} + +Alert.propTypes = { + children: PropTypes.node, + titre: PropTypes.string, +}; + diff --git a/src/components/commun/BoutonRadio.jsx b/src/components/commun/BoutonRadio.jsx index e8f0894d..d6d4a105 100644 --- a/src/components/commun/BoutonRadio.jsx +++ b/src/components/commun/BoutonRadio.jsx @@ -5,7 +5,7 @@ export default function BoutonRadio({ children, id, nomGroupe }) { return (
- + diff --git a/src/components/commun/Datepicker.jsx b/src/components/commun/Datepicker.jsx index 38589500..58542bdd 100644 --- a/src/components/commun/Datepicker.jsx +++ b/src/components/commun/Datepicker.jsx @@ -7,7 +7,7 @@ export default function Datepicker({ children, id, isRequired = true, onChange, - +
); } diff --git a/src/components/commun/Input.jsx b/src/components/commun/Input.jsx index 2b3badd6..a69a445e 100644 --- a/src/components/commun/Input.jsx +++ b/src/components/commun/Input.jsx @@ -6,7 +6,17 @@ export default function Input({ children, id, isRequired = true, type = 'text',
- +
); diff --git a/src/views/candidature-conseiller/AddressChooser.jsx b/src/views/candidature-conseiller/AddressChooser.jsx index 32c7e7c7..8eb39f6e 100644 --- a/src/views/candidature-conseiller/AddressChooser.jsx +++ b/src/views/candidature-conseiller/AddressChooser.jsx @@ -4,7 +4,7 @@ import { useGeoApi } from './useGeoApi'; import { debounce } from './debounce'; export default function AddressChooser() { - const { search, villes } = useGeoApi(); + const { searchByName, villes } = useGeoApi(); return ( <> @@ -12,13 +12,15 @@ export default function AddressChooser() { id="lieuHabitation" list="resultatsRecherche" isRequired={false} - onChange={debounce(event => search(event.target.value))} + onChange={debounce(event => searchByName(event.target.value))} > Votre lieu d’habitation Saississez le nom ou le code postal de votre commune. {villes.map(({ code, nom }) => ( - + ))} diff --git a/src/views/candidature-conseiller/CandidatureConseiller.jsx b/src/views/candidature-conseiller/CandidatureConseiller.jsx index 70a9a723..d1c4bbe9 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.jsx @@ -1,11 +1,14 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import SommaireConseiller from './SommaireConseiller'; import InformationsDeContact from './InformationsDeContact'; import SituationEtExperience from './SituationEtExperience'; import Disponibilite from './Disponibilite'; import Motivation from './Motivation'; import EnResume from './EnResume'; +import Alert from '../../components/commun/Alert'; import { useScrollToSection } from '../../hooks/useScrollToSection'; +import { useNavigate } from 'react-router-dom'; +import { useApiAdmin } from './useApiAdmin'; import '@gouvfr/dsfr/dist/component/form/form.min.css'; import '@gouvfr/dsfr/dist/component/input/input.min.css'; @@ -14,25 +17,48 @@ import '@gouvfr/dsfr/dist/component/radio/radio.min.css'; import '@gouvfr/dsfr/dist/component/badge/badge.min.css'; import '@gouvfr/dsfr/dist/component/notice/notice.min.css'; import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.min.css'; +import '@gouvfr/dsfr/dist/component/alert/alert.min.css'; import './CandidatureConseiller.css'; export default function CandidatureConseiller() { const [dateDisponibilite, setDateDisponibilite] = useState(''); const [isSituationValid, setIsSituationValid] = useState(true); - + const [validationError, setValidationError] = useState(''); + const { buildConseillerData, creerCandidatureConseiller } = useApiAdmin(); + const navigate = useNavigate(); useScrollToSection(); - const validerLaCandidature = event => { + useEffect(() => { + document.title = 'Conseiller numérique - Devenir conseiller numérique'; + }, []); + + const estSituationRemplie = formData => { + const demandeurEmploi = formData.get('estDemandeurEmploi') === 'on'; + const enEmploi = formData.get('estEnEmploi') === 'on'; + const enFormation = formData.get('estEnFormation') === 'on'; + const diplome = formData.get('estDiplomeMedNum') === 'on'; + + return demandeurEmploi || enEmploi || enFormation || diplome; + }; + + const validerLaCandidature = async event => { event.preventDefault(); const formData = new FormData(event.currentTarget); - const situations = formData.get('situations'); - if (situations === null) { + if (!estSituationRemplie(formData)) { setIsSituationValid(false); document.getElementById('situation-et-experience').scrollIntoView(); } else { - event.currentTarget.submit(); + const conseillerData = await buildConseillerData(formData); + const resultatCreation = await creerCandidatureConseiller(conseillerData); + if (resultatCreation.status >= 400) { + const error = await resultatCreation.json(); + setValidationError(error.message); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } else { + navigate('/candidature-validee'); + } } }; @@ -45,7 +71,17 @@ export default function CandidatureConseiller() {

Je veux devenir conseiller numérique

Les champs avec * sont obligatoires.

-
+ {validationError && +
+ + {validationError} + +
+ } + diff --git a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx index 4d13fc16..7e08c916 100644 --- a/src/views/candidature-conseiller/CandidatureConseiller.test.jsx +++ b/src/views/candidature-conseiller/CandidatureConseiller.test.jsx @@ -1,10 +1,12 @@ -import { render, screen, within, fireEvent } from '@testing-library/react'; +import { render, screen, within, fireEvent, act } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import CandidatureConseiller from './CandidatureConseiller'; import { textMatcher, dateDujour } from '../../../test/test-utils'; +import * as ReactRouterDom from 'react-router-dom'; vi.mock('react-router-dom', () => ({ useLocation: () => ({ hash: '' }), + useNavigate: vi.fn() })); describe('candidature conseiller', () => { @@ -57,9 +59,9 @@ describe('candidature conseiller', () => { expect(email).toHaveAttribute('type', 'email'); expect(email).toBeRequired(); - const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone Format attendu : 0122334455'); + const telephone = within(etapeInformationsDeContact).getByLabelText('Téléphone Format attendu : +33122334455'); expect(telephone).toHaveAttribute('type', 'tel'); - expect(telephone).toHaveAttribute('pattern', '0[1-9]{9}'); + expect(telephone).toHaveAttribute('pattern', '[+](33|590|596|594|262|269|687)[1-9]{9}'); const habitation = within(etapeInformationsDeContact).getByLabelText('Votre lieu d’habitation Saississez le nom ou le code postal de votre commune.'); expect(habitation).toHaveAttribute('type', 'text'); @@ -104,11 +106,11 @@ describe('candidature conseiller', () => { const oui = screen.getByRole('radio', { name: 'Oui' }); expect(oui).toBeRequired(); - expect(oui).toHaveAttribute('name', 'experienceProfessionnelle'); + expect(oui).toHaveAttribute('name', 'aUneExperienceMedNum'); const non = screen.getByRole('radio', { name: 'Non' }); expect(non).toBeRequired(); - expect(non).toHaveAttribute('name', 'experienceProfessionnelle'); + expect(non).toHaveAttribute('name', 'aUneExperienceMedNum'); }); it('quand je coche "diplomé" alors un champ pour préciser le diplôme s’affiche', () => { @@ -166,31 +168,31 @@ describe('candidature conseiller', () => { const _5km = screen.getByRole('radio', { name: '5 km' }); expect(_5km).toBeRequired(); - expect(_5km).toHaveAttribute('name', 'distanceDomicile'); + expect(_5km).toHaveAttribute('name', 'distanceMax'); const _10km = screen.getByRole('radio', { name: '10 km' }); expect(_10km).toBeRequired(); - expect(_10km).toHaveAttribute('name', 'distanceDomicile'); + expect(_10km).toHaveAttribute('name', 'distanceMax'); const _15km = screen.getByRole('radio', { name: '15 km' }); expect(_15km).toBeRequired(); - expect(_15km).toHaveAttribute('name', 'distanceDomicile'); + expect(_15km).toHaveAttribute('name', 'distanceMax'); const _20km = screen.getByRole('radio', { name: '20 km' }); expect(_20km).toBeRequired(); - expect(_20km).toHaveAttribute('name', 'distanceDomicile'); + expect(_20km).toHaveAttribute('name', 'distanceMax'); const _40km = screen.getByRole('radio', { name: '40 km' }); expect(_40km).toBeRequired(); - expect(_40km).toHaveAttribute('name', 'distanceDomicile'); + expect(_40km).toHaveAttribute('name', 'distanceMax'); const _100km = screen.getByRole('radio', { name: '100 km' }); expect(_100km).toBeRequired(); - expect(_100km).toHaveAttribute('name', 'distanceDomicile'); + expect(_100km).toHaveAttribute('name', 'distanceMax'); const franceEntiere = screen.getByRole('radio', { name: 'France entière' }); expect(franceEntiere).toBeRequired(); - expect(franceEntiere).toHaveAttribute('name', 'distanceDomicile'); + expect(franceEntiere).toHaveAttribute('name', 'distanceMax'); }); it('quand j’affiche le formulaire alors l’étape "Votre motivation" est affiché', () => { @@ -210,7 +212,7 @@ describe('candidature conseiller', () => { expect(aideMotivation).toBeInTheDocument(); const descriptionMotivation = within(votreMotivation).getByLabelText('Votre message *'); - expect(descriptionMotivation).toHaveAttribute('name', 'descriptionMotivation'); + expect(descriptionMotivation).toHaveAttribute('name', 'motivation'); expect(descriptionMotivation).toBeRequired(); }); @@ -290,6 +292,14 @@ describe('candidature conseiller', () => { // GIVEN vi.useFakeTimers(); vi.setSystemTime(new Date(2023, 11, 12, 13)); + + vi.stubGlobal('fetch', vi.fn( + () => ({ status: 200, json: async () => Promise.resolve({}) })) + ); + + const mockNavigate = vi.fn().mockReturnValue(() => { }); + vi.spyOn(ReactRouterDom, 'useNavigate').mockReturnValue(mockNavigate); + render(); const prenom = screen.getByLabelText('Prénom *'); fireEvent.change(prenom, { target: { value: 'Jean' } }); @@ -322,6 +332,11 @@ describe('candidature conseiller', () => { // GIVEN vi.useFakeTimers(); vi.setSystemTime(new Date(2023, 11, 12, 13)); + + vi.stubGlobal('fetch', vi.fn( + () => ({ status: 200, json: async () => Promise.resolve({}) })) + ); + render(); const prenom = screen.getByLabelText('Prénom *'); fireEvent.change(prenom, { target: { value: 'Jean' } }); @@ -347,4 +362,91 @@ describe('candidature conseiller', () => { expect(erreurCheckboxes).toBeInTheDocument(); vi.useRealTimers(); }); + + it('quand je remplis le formulaire, que je l’envoie et que le serveur me renvoie une erreur, alors elle s’affiche sur la page', async () => { + // GIVEN + vi.useFakeTimers(); + vi.setSystemTime(new Date(2023, 11, 12, 13)); + + vi.stubGlobal('fetch', vi.fn( + () => ({ status: 400, json: async () => Promise.resolve({ message: 'Cette adresse mail est déjà utilisée' }) })) + ); + + render(); + const prenom = screen.getByLabelText('Prénom *'); + fireEvent.change(prenom, { target: { value: 'Jean' } }); + const nom = screen.getByLabelText('Nom *'); + fireEvent.change(nom, { target: { value: 'Dupont' } }); + const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr'); + fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); + const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' }); + fireEvent.click(enEmploi); + const oui = screen.getByRole('radio', { name: 'Oui' }); + fireEvent.click(oui); + const date = screen.getByLabelText('Choisir une date'); + fireEvent.change(date, { target: { value: dateDujour() } }); + const _5km = screen.getByRole('radio', { name: '5 km' }); + fireEvent.click(_5km); + const descriptionMotivation = screen.getByLabelText('Votre message *'); + fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); + + // WHEN + const envoyer = screen.getByRole('button', { name: 'Envoyer votre candidature' }); + + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(() => { + fireEvent.click(envoyer); + }); + + // THEN + const titreErreurValidation = screen.getByRole('heading', { level: 3, name: 'Erreur de validation' }); + expect(titreErreurValidation).toBeInTheDocument(); + const contenuErreurValidation = screen.getByText('Cette adresse mail est déjà utilisée', { selector: 'p' }); + expect(contenuErreurValidation).toBeInTheDocument(); + vi.useRealTimers(); + }); + + it('quand je remplis le formulaire avec toutes les informations valides, alors je suis redirigé vers la page de candidature validée', async () => { + // GIVEN + vi.useFakeTimers(); + vi.setSystemTime(new Date(2023, 11, 12, 13)); + + vi.stubGlobal('fetch', vi.fn( + () => ({ status: 200, json: async () => Promise.resolve({}) })) + ); + + const mockNavigate = vi.fn().mockReturnValue(() => { }); + vi.spyOn(ReactRouterDom, 'useNavigate').mockReturnValue(mockNavigate); + + render(); + const prenom = screen.getByLabelText('Prénom *'); + fireEvent.change(prenom, { target: { value: 'Jean' } }); + const nom = screen.getByLabelText('Nom *'); + fireEvent.change(nom, { target: { value: 'Dupont' } }); + const email = screen.getByLabelText('Adresse e-mail * Format attendu : nom@domaine.fr'); + fireEvent.change(email, { target: { value: 'jean.dupont@example.com' } }); + const enEmploi = screen.getByRole('checkbox', { name: 'En emploi' }); + fireEvent.click(enEmploi); + const oui = screen.getByRole('radio', { name: 'Oui' }); + fireEvent.click(oui); + const date = screen.getByLabelText('Choisir une date'); + fireEvent.change(date, { target: { value: dateDujour() } }); + const _5km = screen.getByRole('radio', { name: '5 km' }); + fireEvent.click(_5km); + const descriptionMotivation = screen.getByLabelText('Votre message *'); + fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } }); + + // WHEN + const envoyer = screen.getByRole('button', { name: 'Envoyer votre candidature' }); + + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(() => { + fireEvent.click(envoyer); + }); + + // THEN + expect(mockNavigate).toHaveBeenCalledWith('/candidature-validee'); + + vi.useRealTimers(); + }); }); diff --git a/src/views/candidature-conseiller/Disponibilite.jsx b/src/views/candidature-conseiller/Disponibilite.jsx index 1ef67d54..f0f268fe 100644 --- a/src/views/candidature-conseiller/Disponibilite.jsx +++ b/src/views/candidature-conseiller/Disponibilite.jsx @@ -16,7 +16,7 @@ export default function Disponibilite({ setDateDisponibilite }) {

Accompagnement de personnes vers l’autonomie dans leurs usages de technologies, services et médias numériques.

- setDateDisponibilite(event.target.value)} min={dateDuJour}> + setDateDisponibilite(event.target.value)} min={dateDuJour}> Choisir une date
@@ -26,27 +26,27 @@ export default function Disponibilite({ setDateDisponibilite }) {

Distance à partir de votre lieu d’habitation

- + 5 km - + 10 km - + 15 km - + 20 km
- + 40 km - + 100 km - + France entière
diff --git a/src/views/candidature-conseiller/InformationsDeContact.jsx b/src/views/candidature-conseiller/InformationsDeContact.jsx index 427b4e0e..f40ba00a 100644 --- a/src/views/candidature-conseiller/InformationsDeContact.jsx +++ b/src/views/candidature-conseiller/InformationsDeContact.jsx @@ -26,10 +26,10 @@ export default function InformationsDeContact() { - Téléphone Format attendu : 0122334455 + Téléphone Format attendu : +33122334455 diff --git a/src/views/candidature-conseiller/Motivation.jsx b/src/views/candidature-conseiller/Motivation.jsx index 5906b742..eec77939 100644 --- a/src/views/candidature-conseiller/Motivation.jsx +++ b/src/views/candidature-conseiller/Motivation.jsx @@ -9,7 +9,7 @@ export default function Motivation() { En quelques lignes, décrivez votre motivation personnelle pour devenir conseiller numérique et{' '} aider les personnes à devenir autonomes dans l’utilisation des outils numériques.

- + Votre message * diff --git a/src/views/candidature-conseiller/SituationEtExperience.jsx b/src/views/candidature-conseiller/SituationEtExperience.jsx index 4b22f1ee..69a72c23 100644 --- a/src/views/candidature-conseiller/SituationEtExperience.jsx +++ b/src/views/candidature-conseiller/SituationEtExperience.jsx @@ -9,7 +9,7 @@ export default function SituationEtExperience({ isSituationValid }) { const [isDiplomeSelected, setIsDiplomeSelected] = useState(false); const handleCheck = event => { - setIsDiplomeSelected(event.target.id === 'diplome' && event.target.checked); + setIsDiplomeSelected(event.target.id === 'estDiplomeMedNum' && event.target.checked); }; return ( @@ -20,7 +20,7 @@ export default function SituationEtExperience({ isSituationValid }) { Êtes-vous actuellement dans l’une des situations suivantes ? *

{situations.map(({ id, libelle }) => - + {libelle} )} @@ -30,8 +30,8 @@ export default function SituationEtExperience({ isSituationValid }) { { isDiplomeSelected && Précisez le nom de votre diplôme, formation certifiante, modules de formation de médiation, numérique /accompagnement au numérique des publics. @@ -41,10 +41,10 @@ export default function SituationEtExperience({ isSituationValid }) { Avez-vous une expérience professionnelle de médiation numérique ? *

Accompagnement de personnes vers l’autonomie dans leurs usages de technologies, services et médias numériques.

- + Oui - + Non diff --git a/src/views/candidature-conseiller/situations.js b/src/views/candidature-conseiller/situations.js index 7e78b17a..324e7fba 100644 --- a/src/views/candidature-conseiller/situations.js +++ b/src/views/candidature-conseiller/situations.js @@ -1,18 +1,18 @@ export const situations = [ { - id: 'demandeurEmploi', + id: 'estDemandeurEmploi', libelle: 'Demandeur d’emploi', }, { - id: 'enEmploi', + id: 'estEnEmploi', libelle: 'En emploi', }, { - id: 'enFormation', + id: 'estEnFormation', libelle: 'En formation', }, { - id: 'diplome', + id: 'estDiplomeMedNum', libelle: 'Diplômé dans le secteur de la médiation numérique (formation certifiante ou non)', } ]; diff --git a/src/views/candidature-conseiller/useApiAdmin.js b/src/views/candidature-conseiller/useApiAdmin.js new file mode 100644 index 00000000..d75a1cd6 --- /dev/null +++ b/src/views/candidature-conseiller/useApiAdmin.js @@ -0,0 +1,57 @@ +import { useGeoApi } from './useGeoApi'; + +export const useApiAdmin = () => { + const { getVilleParCode } = useGeoApi(); + + const creerCandidatureConseiller = async conseillerData => { + const baseUrl = import.meta.env.VITE_APP_API_URL; + const requestOptions = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: conseillerData + }; + + try { + return await fetch(`${baseUrl}/candidature-conseiller`, requestOptions); + } catch (error) { + return error; + } + }; + + const handleSituations = (conseillerData, key) => { + conseillerData[key] = conseillerData[key] === 'on'; + }; + + const getInformationsVille = async lieuHabitation => { + const codePostal = lieuHabitation.split(' ')?.[0]; + if (codePostal) { + return await getVilleParCode(codePostal); + } + }; + + const handleExperienceMedNum = conseillerData => { + conseillerData.aUneExperienceMedNum = conseillerData.aUneExperienceMedNum === 'oui'; + }; + + const buildConseillerData = async formData => { + const conseillerData = Object.fromEntries(formData); + handleSituations(conseillerData, 'estDemandeurEmploi'); + handleSituations(conseillerData, 'estEnEmploi'); + handleSituations(conseillerData, 'estEnFormation'); + handleSituations(conseillerData, 'estDiplomeMedNum'); + handleExperienceMedNum(conseillerData); + const informationsVille = (await getInformationsVille(formData.get('lieuHabitation')))?.[0]; + conseillerData.nomCommune = informationsVille?.nom; + conseillerData.codePostal = informationsVille?.code; + conseillerData.codeCommune = informationsVille?.code; + conseillerData.location = informationsVille?.centre; + conseillerData.codeDepartement = informationsVille?.codeDepartement; + conseillerData.codeRegion = informationsVille?.codeRegion; + conseillerData.codeCom = informationsVille?.code; + delete conseillerData.lieuHabitation; + + return JSON.stringify(conseillerData); + }; + + return { buildConseillerData, creerCandidatureConseiller }; +}; diff --git a/src/views/candidature-conseiller/useGeoApi.js b/src/views/candidature-conseiller/useGeoApi.js index a7b6029b..68a5b320 100644 --- a/src/views/candidature-conseiller/useGeoApi.js +++ b/src/views/candidature-conseiller/useGeoApi.js @@ -1,16 +1,24 @@ import { useState } from 'react'; export const useGeoApi = () => { - const urlBase = 'https://geo.api.gouv.fr/communes?limit=10&fields=nom,code&'; + const baseUrl = new URL('https://geo.api.gouv.fr/communes'); + baseUrl.searchParams.set('limit', '10'); + baseUrl.searchParams.set('fields', 'nom,code,codesPostaux,centre,codeDepartement,codeRegion,codeCom'); const [villes, setVilles] = useState([]); - const search = async rechercheUtilisateur => { - const url = `${urlBase}&nom=${rechercheUtilisateur}`; + const searchByName = async rechercheUtilisateur => { + const url = `${baseUrl.toString()}&nom=${rechercheUtilisateur}`; const villes = await fetch(url); const resultat = await villes.json(); setVilles(resultat); }; - return { search, villes }; + const getVilleParCode = async codePostal => { + const url = `${baseUrl.toString()}&code=${codePostal}`; + const ville = await fetch(url); + return await ville.json(); + }; + + return { searchByName, getVilleParCode, villes }; }; diff --git a/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx b/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx index ea988091..e08a5e5e 100644 --- a/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx +++ b/src/views/candidature-coordinateur/CandidatureCoordinateur.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import SommaireCoordinateur from './SommaireCoordinateur'; import InformationsDeContact from '../candidature-structure/InformationsDeContact'; import InformationsDeStructure from '../candidature-structure/InformationsDeStructure'; @@ -19,6 +19,10 @@ import '../candidature-conseiller/CandidatureConseiller.css'; export default function CandidatureCoordinateur() { useScrollToSection(); + useEffect(() => { + document.title = 'Conseiller numérique - Devenir coordinateur de conseillers numériques'; + }, []); + return (
diff --git a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx index 1bbc8490..39cdc4d6 100644 --- a/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx +++ b/src/views/candidature-structure/BesoinEnConseillerNumerique.jsx @@ -3,9 +3,8 @@ import React from 'react'; import BoutonRadio from '../../components/commun/BoutonRadio'; import Datepicker from '../../components/commun/Datepicker'; import Input from '../../components/commun/Input'; -import PropTypes from 'prop-types'; -export default function BesoinEnConseillerNumerique({ setDateAccueilConseillerNumerique }) { +export default function BesoinEnConseillerNumerique() { return (
Votre besoin en conseiller(s) numérique(s) @@ -24,13 +23,9 @@ export default function BesoinEnConseillerNumerique({ setDateAccueilConseillerNu

À partir de quand êtes vous prêt à accueillir votre conseiller numerique ?*

- setDateAccueilConseillerNumerique(event.target.value)}> + Choisir une date
); } - -BesoinEnConseillerNumerique.propTypes = { - setDateAccueilConseillerNumerique: PropTypes.func, -}; diff --git a/src/views/candidature-structure/CandidatureStructure.jsx b/src/views/candidature-structure/CandidatureStructure.jsx index 3199ddf4..5e976217 100644 --- a/src/views/candidature-structure/CandidatureStructure.jsx +++ b/src/views/candidature-structure/CandidatureStructure.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect } from 'react'; import SommaireStructure from './SommaireStructure'; import InformationsDeContact from './InformationsDeContact'; import InformationsDeStructure from './InformationsDeStructure'; @@ -17,7 +17,9 @@ import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.min.css'; import '../candidature-conseiller/CandidatureConseiller.css'; export default function CandidatureStructure() { - const [dateAccueilConseillerNumerique, setDateAccueilConseillerNumerique] = useState(); + useEffect(() => { + document.title = 'Conseiller numérique - Engager un conseiller numérique'; + }, []); useScrollToSection(); @@ -33,7 +35,7 @@ export default function CandidatureStructure() { - +