Skip to content

Commit

Permalink
Ajout du lien au back-end sur le formulaire coordinateur (#220)
Browse files Browse the repository at this point in the history
* Ajout du lien au back-end sur le formulaire coordinateur

* Ajout de tests

* Correction de l'appel à la GeoAPI

---------

Co-authored-by: Ornella <[email protected]>
  • Loading branch information
Alezco and Ornella452 authored Sep 23, 2024
1 parent 9467c9e commit c247614
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 40 deletions.
6 changes: 5 additions & 1 deletion src/views/candidature-conseiller/CandidatureConseiller.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
width: 100%;
}

.cc-section legend + * {
.cc-section legend+* {
clear: left;
}

Expand Down Expand Up @@ -37,3 +37,7 @@
display: flex;
justify-content: center;
}

html {
scroll-behavior: smooth;
}
33 changes: 31 additions & 2 deletions src/views/candidature-conseiller/useApiAdmin.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ export const useApiAdmin = () => {
}
};

const creerCandidatureCoordinateur = async structureData => {
const baseUrl = import.meta.env.VITE_APP_API_URL;
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: structureData
};

try {
return await fetch(`${baseUrl}/candidature-structure-coordinateur`, requestOptions);
} catch (error) {
return error;
}
};

const convertValueToBoolean = (conseillerData, key) => {
conseillerData[key] = conseillerData[key] === 'on' || conseillerData[key] === 'oui';
};
Expand Down Expand Up @@ -62,7 +77,7 @@ export const useApiAdmin = () => {
convertValueToBoolean(conseillerData, 'estEnFormation');
convertValueToBoolean(conseillerData, 'estDiplomeMedNum');
convertValueToBoolean(conseillerData, 'aUneExperienceMedNum');
const codePostal = conseillerData.lieuHabitation.split(' ')?.[0];
const codePostal = conseillerData.lieuHabitation.match(/\d{5}/)?.[0];
await handleInformationsVille(conseillerData, codePostal);
delete conseillerData.lieuHabitation;
delete conseillerData['g-recaptcha-response'];
Expand Down Expand Up @@ -108,9 +123,23 @@ export const useApiAdmin = () => {
return JSON.stringify(structureData);
};

const buildCoordinateurData = async formData => {
const coordinateurData = Object.fromEntries(formData);
handleContact(coordinateurData);
handleInformationsStructure(coordinateurData);
await handleAdresse(coordinateurData);
convertValueToBoolean(coordinateurData, 'aIdentifieCoordinateur');
convertValueToBoolean(coordinateurData, 'confirmationEngagement');
delete coordinateurData['g-recaptcha-response'];
return JSON.stringify(coordinateurData);
};

return {
buildConseillerData,
buildStructureData,
buildCoordinateurData,
creerCandidatureConseiller,
creerCandidatureStructure };
creerCandidatureStructure,
creerCandidatureCoordinateur,
};
};
2 changes: 1 addition & 1 deletion src/views/candidature-conseiller/useGeoApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const useGeoApi = () => {
};

const getVilleParCode = async codePostal => {
const url = `${baseUrl.toString()}&code=${codePostal}`;
const url = `${baseUrl.toString()}&codePostal=${codePostal}`;
const ville = await fetch(url);
return await ville.json();
};
Expand Down
10 changes: 5 additions & 5 deletions src/views/candidature-coordinateur/BesoinEnCoordinateur.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ export default function BesoinEnCoordinateur() {
Avez-vous déjà identifié un candidat pour le poste de coordinateur de conseiller numérique ?<span className="cc-obligatoire">*</span>
</p>
<p className="fr-text--sm fr-hint-text">Si oui, merci d’inviter ce candidat à s’inscrire sur la plateforme Conseiller numérique</p>
<BoutonRadio id="oui" nomGroupe="aIdentifieCandidat">
<BoutonRadio id="oui" nomGroupe="aIdentifieCoordinateur">
Oui
</BoutonRadio>
<BoutonRadio id="non" nomGroupe="aIdentifieCandidat">
<BoutonRadio id="non" nomGroupe="aIdentifieCoordinateur">
Non
</BoutonRadio>
<p className="fr-mt-4w fr-mb-3w cc-bold">Le coordinateur<span className="cc-obligatoire">*</span></p>
<BoutonRadio id="coordination" nomGroupe="coordinateur">
<BoutonRadio id="FT" nomGroupe="coordinateurTypeContrat">
Effectuera uniquement des missions de coordination
</BoutonRadio>
<BoutonRadio id="publics" nomGroupe="coordinateur">
<BoutonRadio id="PT" nomGroupe="coordinateurTypeContrat">
Accompagnera également des publics
</BoutonRadio>
<hr />
<p className="fr-mb-3w cc-bold">À partir de quand êtes vous prêt à accueillir votre coordinateur ?<span className="cc-obligatoire">*</span></p>
<Datepicker id="choisir-date" min={dateDuJour}>
<Datepicker id="dateDebutMission" min={dateDuJour}>
Choisir une date
</Datepicker>
</fieldset >
Expand Down
33 changes: 31 additions & 2 deletions src/views/candidature-coordinateur/CandidatureCoordinateur.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import InformationsDeStructure from '../candidature-structure/InformationsDeStru
import BesoinEnCoordinateur from './BesoinEnCoordinateur';
import Motivation from './Motivation';
import Engagement from './Engagement';
import { useScrollToSection } from '../../hooks/useScrollToSection';
import Alert from '../../components/commun/Alert';
import Captcha from '../../components/commun/Captcha';
import { useScrollToSection } from '../../hooks/useScrollToSection';
import { useNavigate } from 'react-router-dom';
import { useApiAdmin } from '../candidature-conseiller/useApiAdmin';

import '@gouvfr/dsfr/dist/component/form/form.min.css';
import '@gouvfr/dsfr/dist/component/input/input.min.css';
Expand All @@ -15,16 +18,35 @@ 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 '../candidature-conseiller/CandidatureConseiller.css';

export default function CandidatureCoordinateur() {
const [geoLocation, setGeoLocation] = useState(null);
const [validationError, setValidationError] = useState('');
const navigate = useNavigate();
const { buildCoordinateurData, creerCandidatureCoordinateur } = useApiAdmin();
useScrollToSection();

useEffect(() => {
document.title = 'Conseiller numérique - Devenir coordinateur de conseillers numériques';
}, []);

const validerLaCandidature = async event => {
event.preventDefault();

const formData = new FormData(event.currentTarget);
const coordinateurData = await buildCoordinateurData(formData);
const resultatCreation = await creerCandidatureCoordinateur(coordinateurData);
if (resultatCreation.status >= 400) {
const error = await resultatCreation.json();
setValidationError(error.message);
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
navigate('/candidature-validee');
}
};

return (
<div className="fr-container fr-mt-5w fr-mb-5w">
<div className="fr-grid-row">
Expand All @@ -34,7 +56,14 @@ export default function CandidatureCoordinateur() {
<div className="fr-col-12 fr-col-md-8 fr-py-12v">
<h1 className="cc-titre fr-mb-5w">Je souhaite engager un coordinateur pour mes conseillers numériques</h1>
<p className="fr-text--sm fr-hint-text">Les champs avec <span className="cc-obligatoire">*</span> sont obligatoires.</p>
<form aria-label="Candidature coordinateur" >
{validationError &&
<div className="fr-pb-2w">
<Alert titre="Erreur de validation">
{validationError}
</Alert>
</div>
}
<form aria-label="Candidature coordinateur" onSubmit={validerLaCandidature}>
<InformationsDeStructure setGeoLocation={setGeoLocation} geoLocation={geoLocation} />
<InformationsDeContact />
<BesoinEnCoordinateur />
Expand Down
124 changes: 118 additions & 6 deletions src/views/candidature-coordinateur/CandidatureCoordinateur.test.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { render, screen, within } from '@testing-library/react';
import { render, screen, within, fireEvent, act } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import CandidatureCoordinateur from './CandidatureCoordinateur';
import { textMatcher } from '../../../test/test-utils';
import { textMatcher, dateDujour } from '../../../test/test-utils';
import * as ReactRouterDom from 'react-router-dom';

vi.mock('react-router-dom', () => ({
useLocation: () => ({ hash: '' }),
Expand Down Expand Up @@ -142,22 +143,22 @@ describe('candidature coordinateur', () => {

const oui = screen.getByRole('radio', { name: 'Oui' });
expect(oui).toBeRequired();
expect(oui).toHaveAttribute('name', 'aIdentifieCandidat');
expect(oui).toHaveAttribute('name', 'aIdentifieCoordinateur');

const non = screen.getByRole('radio', { name: 'Non' });
expect(non).toBeRequired();
expect(non).toHaveAttribute('name', 'aIdentifieCandidat');
expect(non).toHaveAttribute('name', 'aIdentifieCoordinateur');

const leCoordinateur = within(etapeBesoinCoordinateur).getByText(textMatcher('Le coordinateur*'), { selector: 'p' });
expect(leCoordinateur).toBeInTheDocument();

const coordination = screen.getByRole('radio', { name: 'Effectuera uniquement des missions de coordination' });
expect(coordination).toBeRequired();
expect(coordination).toHaveAttribute('name', 'coordinateur');
expect(coordination).toHaveAttribute('name', 'coordinateurTypeContrat');

const publics = screen.getByRole('radio', { name: 'Accompagnera également des publics' });
expect(publics).toBeRequired();
expect(publics).toHaveAttribute('name', 'coordinateur');
expect(publics).toHaveAttribute('name', 'coordinateurTypeContrat');

const dateAccueilCoordinateur = within(etapeBesoinCoordinateur).getByText(
textMatcher('À partir de quand êtes vous prêt à accueillir votre coordinateur ?*'),
Expand Down Expand Up @@ -243,5 +244,116 @@ describe('candidature coordinateur', () => {
const formulaire = screen.getByRole('form', { name: 'Candidature coordinateur' });
within(formulaire).getByRole('button', { name: 'Envoyer votre candidature' });
});

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(<CandidatureCoordinateur />);
const siret = screen.getByLabelText('SIRET / RIDET *');
fireEvent.change(siret, { target: { value: '1234567890123' } });
const denomination = screen.getByLabelText('Dénomination *');
fireEvent.change(denomination, { target: { value: 'Entreprise' } });
const adresse = screen.getByLabelText('Adresse *');
fireEvent.change(adresse, { target: { value: '75056 Paris' } });
const typeStructure = screen.getByRole('radio', { name: 'Une commune' });
fireEvent.click(typeStructure);
const prenom = screen.getByLabelText('Prénom *');
fireEvent.change(prenom, { target: { value: 'Jean' } });
const nom = screen.getByLabelText('Nom *');
fireEvent.change(nom, { target: { value: 'Dupont' } });
const fonction = screen.getByLabelText('Fonction *');
fireEvent.change(fonction, { target: { value: 'Test' } });
const email = screen.getByLabelText('Adresse e-mail *');
fireEvent.change(email, { target: { value: '[email protected]' } });
const telephone = screen.getByLabelText('Téléphone *');
fireEvent.change(telephone, { target: { value: '+33123456789' } });
const identificationCandidat = screen.getByRole('radio', { name: 'Oui' });
fireEvent.click(identificationCandidat);
const typeMission = screen.getByRole('radio', { name: 'Accompagnera également des publics' });
fireEvent.click(typeMission);
const date = screen.getByLabelText('Choisir une date');
fireEvent.change(date, { target: { value: dateDujour() } });
const descriptionMotivation = screen.getByLabelText('Votre message *');
fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } });
const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' });
fireEvent.click(confirmation);

// 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(<CandidatureCoordinateur />);
const siret = screen.getByLabelText('SIRET / RIDET *');
fireEvent.change(siret, { target: { value: '1234567890123' } });
const denomination = screen.getByLabelText('Dénomination *');
fireEvent.change(denomination, { target: { value: 'Entreprise' } });
const adresse = screen.getByLabelText('Adresse *');
fireEvent.change(adresse, { target: { value: '75056 Paris' } });
const typeStructure = screen.getByRole('radio', { name: 'Une commune' });
fireEvent.click(typeStructure);
const prenom = screen.getByLabelText('Prénom *');
fireEvent.change(prenom, { target: { value: 'Jean' } });
const nom = screen.getByLabelText('Nom *');
fireEvent.change(nom, { target: { value: 'Dupont' } });
const fonction = screen.getByLabelText('Fonction *');
fireEvent.change(fonction, { target: { value: 'Test' } });
const email = screen.getByLabelText('Adresse e-mail *');
fireEvent.change(email, { target: { value: '[email protected]' } });
const telephone = screen.getByLabelText('Téléphone *');
fireEvent.change(telephone, { target: { value: '+33123456789' } });
const identificationCandidat = screen.getByRole('radio', { name: 'Oui' });
fireEvent.click(identificationCandidat);
const typeMission = screen.getByRole('radio', { name: 'Accompagnera également des publics' });
fireEvent.click(typeMission);
const date = screen.getByLabelText('Choisir une date');
fireEvent.change(date, { target: { value: dateDujour() } });
const descriptionMotivation = screen.getByLabelText('Votre message *');
fireEvent.change(descriptionMotivation, { target: { value: 'je suis motivé !' } });
const confirmation = screen.getByRole('checkbox', { name: 'Je confirme avoir lu et pris connaissance des conditions d’engagement. *' });
fireEvent.click(confirmation);

// 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();
});
});

4 changes: 4 additions & 0 deletions src/views/candidature-structure/CandidatureStructure.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@
transform: rotate(360deg);
}
}

html {
scroll-behavior: smooth;
}
Loading

0 comments on commit c247614

Please sign in to comment.