Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appel du back-end depuis le formulaire #207

Merged
merged 9 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="App">
Expand All @@ -49,6 +50,7 @@ function App() {
<Route path="/nouveau-formulaire-conseiller" element={<PageCandidatureConseiller />}/>
<Route path="/nouveau-formulaire-structure" element={<PageCandidatureStructure />}/>
<Route path="/nouveau-formulaire-coordinateur" element={<PageCandidatureCoordinateur />}/>
<Route path="/candidature-validee" element={<PageCandidatureValidee />}/>
<Route path="/kit-communication" element={<KitCommunication />}/>
<Route path="/donnees-personnelles" element={<DonneesPersonnelles />}/>
<Route path="/mentions-legales" element={<MentionsLegales />}/>
Expand Down
17 changes: 17 additions & 0 deletions src/components/commun/Alert.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';

export default function Alert({ children, titre }) {
return (
<div className="fr-alert fr-alert--error">
<h3 className="fr-alert__title">{titre}</h3>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<h3 className="fr-alert__title">{titre}</h3>
<div className="fr-alert__title">{titre}</div>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est bien un titre, et c'est ce qui est proposé par le DSFR

<p>{children}</p>
</div>
);
}

Alert.propTypes = {
children: PropTypes.node,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
children: PropTypes.node,
children: string,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dans notre exemple, c'est une string, mais on veut pouvoir mettre du JSX

titre: PropTypes.string,
};

2 changes: 1 addition & 1 deletion src/components/commun/BoutonRadio.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function BoutonRadio({ children, id, nomGroupe }) {
return (
<div className="fr-fieldset__element">
<div className="fr-radio-group">
<input type="radio" id={id} name={nomGroupe} required />
<input type="radio" id={id} name={nomGroupe} value={id} required />
Alezco marked this conversation as resolved.
Show resolved Hide resolved
<label className="fr-label" htmlFor={id}>
{children}
</label>
Expand Down
2 changes: 1 addition & 1 deletion src/components/commun/Datepicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function Datepicker({ children, id, isRequired = true, onChange,
<label className="fr-label" htmlFor={id}>
{children}
</label>
<input className="fr-input cc-datepicker" id={id} type="date" required={isRequired} onChange={onChange} min={min} />
<input className="fr-input cc-datepicker" id={id} type="date" required={isRequired} onChange={onChange} min={min} name={id} />
Alezco marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
}
Expand Down
12 changes: 11 additions & 1 deletion src/components/commun/Input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ export default function Input({ children, id, isRequired = true, type = 'text',
<div className="fr-fieldset__element">
<div className="fr-input-group">
<label className="fr-label" htmlFor={id}>{children}</label>
<input className="fr-input" type={type} id={id} required={isRequired} pattern={pattern} onChange={onChange} list={list} min={min} />
<input
className="fr-input"
type={type}
id={id}
required={isRequired}
pattern={pattern}
onChange={onChange}
list={list}
min={min}
name={id}
/>
</div>
</div>
);
Expand Down
8 changes: 5 additions & 3 deletions src/views/candidature-conseiller/AddressChooser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import { useGeoApi } from './useGeoApi';
import { debounce } from './debounce';

export default function AddressChooser() {
const { search, villes } = useGeoApi();
const { searchByName, villes } = useGeoApi();

return (
<>
<Input
id="lieuHabitation"
list="resultatsRecherche"
isRequired={false}
onChange={debounce(event => search(event.target.value))}
onChange={debounce(event => searchByName(event.target.value))}
>
Votre lieu d’habitation <span className="fr-hint-text">Saississez le nom ou le code postal de votre commune.</span>
</Input>
<datalist id="resultatsRecherche">
{villes.map(({ code, nom }) => (
<option value={`${code} ${nom}`} key={code}>{code} {nom}</option>
<option value={`${code} ${nom}`} key={code}>
{code} {nom}
</option>
))}
</datalist>
</>
Expand Down
44 changes: 38 additions & 6 deletions src/views/candidature-conseiller/CandidatureConseiller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Disponibilite from './Disponibilite';
import Motivation from './Motivation';
import EnResume from './EnResume';
import { useScrollToSection } from '../../hooks/useScrollToSection';
import { useNavigate } from 'react-router-dom';

import '@gouvfr/dsfr/dist/component/form/form.min.css';
import '@gouvfr/dsfr/dist/component/input/input.min.css';
Expand All @@ -14,25 +15,46 @@ 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';
import { useApiAdmin } from './useApiAdmin';
import Alert from '../../components/commun/Alert';

export default function CandidatureConseiller() {
const [dateDisponibilite, setDateDisponibilite] = useState('');
const [isSituationValid, setIsSituationValid] = useState(true);

const [validationError, setValidationError] = useState();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [validationError, setValidationError] = useState();
const [validationError, setValidationError] = useState('');

const { buildConseillerData, creerCandidatureConseiller } = useApiAdmin();
const navigate = useNavigate();
useScrollToSection();

const validerLaCandidature = event => {
const estSituationRemplie = formData => {
const demandeurEmploi = formData.get('estDemandeurEmploi');
const enEmploi = formData.get('estEnEmploi');
const enFormation = formData.get('estEnFormation');
const diplome = formData.get('estDiplomeMedNum');

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' });
Alezco marked this conversation as resolved.
Show resolved Hide resolved
} else {
navigate('/candidature-validee');
Alezco marked this conversation as resolved.
Show resolved Hide resolved
}
}
};

Expand All @@ -45,7 +67,17 @@ export default function CandidatureConseiller() {
<div className="fr-col-12 fr-col-md-8 fr-py-12v">
<h1 className="cc-titre fr-mb-5w">Je veux devenir conseiller numérique</h1>
<p className="fr-text--sm fr-hint-text">Les champs avec <span className="cc-obligatoire">*</span> sont obligatoires.</p>
<form aria-label="Candidature conseiller" onSubmit={validerLaCandidature}>
{validationError &&
<div className="fr-pb-2w">
<Alert titre="Erreur de validation">
{validationError}
</Alert>
Alezco marked this conversation as resolved.
Show resolved Hide resolved
</div>
}
<form
aria-label="Candidature conseiller"
onSubmit={validerLaCandidature}
>
<InformationsDeContact />
<SituationEtExperience isSituationValid={isSituationValid} />
<Disponibilite setDateDisponibilite={setDateDisponibilite} />
Expand Down
25 changes: 13 additions & 12 deletions src/views/candidature-conseiller/CandidatureConseiller.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { textMatcher, dateDujour } from '../../../test/test-utils';

vi.mock('react-router-dom', () => ({
useLocation: () => ({ hash: '' }),
useNavigate: () => { }
}));

describe('candidature conseiller', () => {
Expand Down Expand Up @@ -57,9 +58,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');
Expand Down Expand Up @@ -104,11 +105,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', () => {
Expand Down Expand Up @@ -166,31 +167,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é', () => {
Expand All @@ -210,7 +211,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();
});

Expand Down
16 changes: 8 additions & 8 deletions src/views/candidature-conseiller/Disponibilite.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function Disponibilite({ setDateDisponibilite }) {
<p className="fr-text--sm fr-hint-text">
Accompagnement de personnes vers l’autonomie dans leurs usages de technologies, services et médias numériques.
</p>
<Datepicker id="choisir-date" onChange={event => setDateDisponibilite(event.target.value)} min={dateDuJour}>
<Datepicker id="dateDisponibilite" onChange={event => setDateDisponibilite(event.target.value)} min={dateDuJour}>
Choisir une date
</Datepicker>
<hr />
Expand All @@ -26,27 +26,27 @@ export default function Disponibilite({ setDateDisponibilite }) {
<p className="fr-text--sm fr-hint-text">Distance à partir de votre lieu d’habitation</p>
<div className="fr-grid-row">
<div className="fr-col-6">
<BoutonRadio id="5km" nomGroupe="distanceDomicile">
<BoutonRadio id="5" nomGroupe="distanceMax">
5 km
</BoutonRadio>
<BoutonRadio id="10km" nomGroupe="distanceDomicile">
<BoutonRadio id="10" nomGroupe="distanceMax">
10 km
</BoutonRadio>
<BoutonRadio id="15km" nomGroupe="distanceDomicile">
<BoutonRadio id="15" nomGroupe="distanceMax">
15 km
</BoutonRadio>
<BoutonRadio id="20km" nomGroupe="distanceDomicile">
<BoutonRadio id="20" nomGroupe="distanceMax">
20 km
</BoutonRadio>
</div>
<div className="fr-col-6">
<BoutonRadio id="40km" nomGroupe="distanceDomicile">
<BoutonRadio id="40" nomGroupe="distanceMax">
40 km
</BoutonRadio>
<BoutonRadio id="100km" nomGroupe="distanceDomicile">
<BoutonRadio id="100" nomGroupe="distanceMax">
100 km
</BoutonRadio>
<BoutonRadio id="France entière" nomGroupe="distanceDomicile">
<BoutonRadio id="2000" nomGroupe="distanceMax">
France entière
</BoutonRadio>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/views/candidature-conseiller/InformationsDeContact.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export default function InformationsDeContact() {
<Input
id="telephone"
type="tel"
pattern="0[1-9]{9}"
pattern="[+](33|590|596|594|262|269|687)[1-9]{9}"
isRequired={false}
>
Téléphone <span className="fr-hint-text">Format attendu : 0122334455</span>
Téléphone <span className="fr-hint-text">Format attendu : +33122334455</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est une demande métier ?!?
Parce que dans la suite gestionnaire, on enregistre la version d'avant...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour l'instant c'est pour matcher avec le back, mais on pourra modifier ça dans une prochaine PR si besoin

</Input>
<AddressChooser />
</fieldset>
Expand Down
2 changes: 1 addition & 1 deletion src/views/candidature-conseiller/Motivation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</p>
<ZoneDeTexte id="descriptionMotivation">
<ZoneDeTexte id="motivation">
Votre message <span className="cc-obligatoire">*</span>
</ZoneDeTexte>
</fieldset>
Expand Down
10 changes: 5 additions & 5 deletions src/views/candidature-conseiller/SituationEtExperience.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -20,7 +20,7 @@ export default function SituationEtExperience({ isSituationValid }) {
Êtes-vous actuellement dans l’une des situations suivantes ? <span className="cc-obligatoire">*</span>
</p>
{situations.map(({ id, libelle }) =>
<Checkbox id={id} key={id} name="situations" onCheck={handleCheck} required={false}>
<Checkbox id={id} key={id} name={id} onCheck={handleCheck} required={false}>
{libelle}
</Checkbox>
)}
Expand All @@ -30,7 +30,7 @@ export default function SituationEtExperience({ isSituationValid }) {
{
isDiplomeSelected &&
<Input
id="detailDiplome"
id="nomDiplomeMedNum"
Alezco marked this conversation as resolved.
Show resolved Hide resolved
isRequired={false}
>
Précisez le nom de votre diplôme, formation certifiante, modules de formation de médiation, numérique /accompagnement au numérique des publics.
Expand All @@ -41,10 +41,10 @@ export default function SituationEtExperience({ isSituationValid }) {
Avez-vous une expérience professionnelle de médiation numérique ? <span className="cc-obligatoire">*</span>
</p>
<p className="fr-text--sm fr-hint-text">Accompagnement de personnes vers l’autonomie dans leurs usages de technologies, services et médias numériques.</p>
<BoutonRadio id="oui" nomGroupe="experienceProfessionnelle">
<BoutonRadio id="oui" nomGroupe="aUneExperienceMedNum">
Oui
</BoutonRadio>
<BoutonRadio id="non" nomGroupe="experienceProfessionnelle">
<BoutonRadio id="non" nomGroupe="aUneExperienceMedNum">
Non
</BoutonRadio>
</fieldset >
Expand Down
8 changes: 4 additions & 4 deletions src/views/candidature-conseiller/situations.js
Original file line number Diff line number Diff line change
@@ -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)',
}
];
Loading