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

MEP #257

Merged
merged 9 commits into from
Nov 28, 2024
Merged

MEP #257

Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@
})();
</script>
<script type="module" src="/src/index.js"></script>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</body>
</html>
6 changes: 3 additions & 3 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ function App() {
<Router>
<Routes>
<Route path="/accueil" element={<GestionHash />}/>
<Route path="/nouveau-formulaire-conseiller" element={<PageCandidatureConseiller />}/>
<Route path="/nouveau-formulaire-structure" element={<PageCandidatureStructure />}/>
<Route path="/nouveau-formulaire-coordinateur" element={<PageCandidatureCoordinateur />}/>
<Route path="/candidature-conseiller" element={<PageCandidatureConseiller />}/>
<Route path="/candidature-poste-conseiller" element={<PageCandidatureStructure />}/>
<Route path="/candidature-poste-coordinateur" element={<PageCandidatureCoordinateur />}/>
<Route path="/candidature-validee-conseiller" element={<PageCandidatureValideeConseiller />}/>
<Route path="/candidature-validee-structure" element={<PageCandidatureValideeStructure />}/>
<Route path="/candidature-confirmer-conseiller/:token" element={<PageConfirmationEmailCandidatureConseiller />}/>
Expand Down
18 changes: 15 additions & 3 deletions src/components/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,12 @@ function Menu() {
aria-expanded={activeMenu === 'cnfs'}
aria-controls="menu-cnfs"
onClick={onClickMenu}
{...(location.pathname.startsWith('/aide-candidat') || location.pathname.startsWith('/aide-structure') ? { 'aria-current': true } : {})}>
Recrutement
{
...(location.pathname.startsWith('/aide-candidat') ||
location.pathname.startsWith('/aide-structure') ||
location.pathname.startsWith('/candidature-poste-coordinateur') ? { 'aria-current': true } : {})
}>
Candidature
</button>
<div className={`fr-collapse fr-menu ${activeMenu === 'cnfs' ? 'fr-collapse--expanded' : ''}`} id="menu-cnfs">
<ul className="fr-menu__list">
Expand All @@ -115,7 +119,15 @@ function Menu() {
to="/aide-structure"
className="fr-nav__link"
{...(location.pathname.startsWith('/aide-structure') ? { 'aria-current': 'page' } : {})}>
Recruter un conseiller num&eacute;rique
Obtenir un poste de conseiller num&eacute;rique
</Link>
</li>
<li>
<Link
to="/candidature-poste-coordinateur"
className="fr-nav__link"
{...(location.pathname.startsWith('/candidature-poste-coordinateur') ? { 'aria-current': 'page' } : {})}>
Obtenir un poste de coordinateur de conseiller num&eacute;rique
</Link>
</li>
</ul>
Expand Down
7 changes: 4 additions & 3 deletions src/components/commun/BoutonRadio.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';

export default function BoutonRadio({ children, id, nomGroupe }) {
export default function BoutonRadio({ children, id, nomGroupe, onChange, checked }) {
return (
<div className="fr-fieldset__element">
<div className="fr-radio-group">
<input type="radio" id={id} name={nomGroupe} value={id} required />
<input type="radio" id={id} name={nomGroupe} value={id} required onChange={onChange} checked={checked} />
<label className="fr-label" htmlFor={id}>
{children}
</label>
Expand All @@ -18,5 +18,6 @@ BoutonRadio.propTypes = {
children: PropTypes.node,
id: PropTypes.string,
nomGroupe: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
checked: PropTypes.bool,
};
15 changes: 11 additions & 4 deletions src/components/commun/Captcha.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';

const SITE_KEY = '84e24b30-44ca-488c-9260-ec80c290c166';
const SITE_KEY = '0x4AAAAAAA0pjEgohPDZsyqu';

export default function Captcha() {
export default function Captcha({ setWidgetId }) {
const captchaRef = useRef(null);

useEffect(() => {
if (window.hcaptcha) {
window.hcaptcha.render(captchaRef.current, {
if (window.turnstile) {
window.turnstile.remove();
const widgetId = window.turnstile.render(captchaRef.current, {
sitekey: SITE_KEY,
});
setWidgetId(widgetId);
}
}, []);

return (
<div ref={captchaRef}></div>
);
}

Captcha.propTypes = {
setWidgetId: PropTypes.func,
};
10 changes: 8 additions & 2 deletions src/components/commun/Datepicker.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';

export default function Datepicker({ children, id, isRequired = true, onChange, min }) {
export default function Datepicker({ children, id, isRequired = true, onChange, min, error }) {
return (
<div className="fr-input-group">
<div className={`fr-input-group${error ? ' fr-input-group--error' : ''}`}>
<label className="fr-label" htmlFor={id}>
{children}
</label>
<input className="fr-input cc-datepicker" id={id} type="date" required={isRequired} onChange={onChange} min={min} name={id} />
{error && (
<p id="text-input-error-desc-error" className="fr-error-text">
{error}
</p>
)}
</div>
);
}
Expand All @@ -19,4 +24,5 @@ Datepicker.propTypes = {
onChange: PropTypes.func,
min: PropTypes.string,
name: PropTypes.string,
error: PropTypes.string
};
32 changes: 28 additions & 4 deletions src/components/commun/Input.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';

export default function Input({ children, id, isRequired = true, autoComplete = 'on', testId = '', type = 'text',
pattern, onChange, list, min, disabled, isLoading, ariaBusy, value }) {
export default function Input({
children,
id,
isRequired = true,
autoComplete = 'on',
testId = '',
type = 'text',
pattern,
onChange,
list,
min,
disabled,
isLoading,
ariaBusy,
value,
maxlength,
error,
}) {
return (
<div className="fr-fieldset__element">
<div className="fr-input-group">
<div className={`fr-input-group${error ? ' fr-input-group--error' : ''}`}>
<label className="fr-label" htmlFor={id}>{children}</label>
<input
className="fr-input"
Expand All @@ -14,12 +30,13 @@ export default function Input({ children, id, isRequired = true, autoComplete =
required={isRequired}
autoComplete={autoComplete}
pattern={pattern}
maxLength={maxlength}
onChange={onChange}
list={list}
min={min}
disabled={disabled}
name={id}
value={value?.trim()}
value={value}
aria-busy={ariaBusy}
data-testid={testId}
/>
Expand All @@ -28,6 +45,11 @@ export default function Input({ children, id, isRequired = true, autoComplete =
<div className="fr-spinner fr-spinner--sm" aria-label="Chargement..."></div>
</div>
)}
{error && (
<p id="text-input-error-desc-error" className="fr-error-text">
{error}
</p>
)}
</div>
</div>
);
Expand All @@ -48,4 +70,6 @@ Input.propTypes = {
ariaBusy: PropTypes.bool,
value: PropTypes.string,
testId: PropTypes.string,
maxlength: PropTypes.string,
error: PropTypes.string
};
46 changes: 46 additions & 0 deletions src/components/commun/RadioGroup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import BoutonRadio from './BoutonRadio';

export default function RadioGroup({ nomGroupe, options, tailleColonne = options.length }) {
const [checkedValue, setCheckedValue] = useState(options[0].id);

return (
<div role="radiogroup">
<div className="fr-grid-row">
<div className={`${tailleColonne === options.length ? 'fr-col-12' : 'fr-col-6'}`}>
{options.slice(0, tailleColonne).map(({ id, label }) => (
<BoutonRadio
id={id}
key={id}
nomGroupe={nomGroupe}
onChange={() => setCheckedValue(id)}
checked={checkedValue === id}
>
{label}
</BoutonRadio>
))}
</div>
{options.length > tailleColonne && <div className="fr-col-6">
{options.slice(tailleColonne).map(({ id, label }) => (
<BoutonRadio
id={id}
key={id}
nomGroupe={nomGroupe}
onChange={() => setCheckedValue(id)}
checked={checkedValue === id}
>
{label}
</BoutonRadio>
))}
</div>}
</div>
</div>
);
}

RadioGroup.propTypes = {
nomGroupe: PropTypes.string,
options: PropTypes.array,
tailleColonne: PropTypes.number
};
13 changes: 10 additions & 3 deletions src/components/commun/ZoneDeTexte.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';

export default function ZoneDeTexte({ children, id, isRequired = true }) {
export default function ZoneDeTexte({ children, id, isRequired = true, maxlength = '2500', error }) {
return (
<div className="fr-input-group">
<div className={`fr-input-group${error ? ' fr-input-group--error' : ''}`}>
<label className="fr-label" htmlFor={id}>
{children}
</label>
<textarea className="fr-input" id={id} name={id} required={isRequired}></textarea>
<textarea className="fr-input" id={id} name={id} required={isRequired} maxLength={maxlength}></textarea>
{error && (
<p id="text-input-error-desc-error" className="fr-error-text">
{error}
</p>
)}
</div>
);
}
Expand All @@ -16,4 +21,6 @@ ZoneDeTexte.propTypes = {
children: PropTypes.node,
id: PropTypes.string,
isRequired: PropTypes.bool,
maxlength: PropTypes.string,
error: PropTypes.string,
};
9 changes: 9 additions & 0 deletions src/shared/checkValidity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const checkValidity = (ref, setErrors) => {
const formData = new FormData(ref.current);
const keys = Array.from(formData.keys());
const formElements = keys.map(key => document.getElementById(key)).filter(key => key !== null);
const errors = formElements.map(formElement => ({
[formElement.id]: formElement.validationMessage,
}));
setErrors(Object.assign({}, ...errors));
};
4 changes: 2 additions & 2 deletions src/views/aide-candidat/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React from 'react';

function Actions() {

const urlFormConseiller = import.meta.env.VITE_APP_FORMS_URL + '/conseiller/new';
const urlFormConseiller = '/candidature-conseiller';

return (
<div className="fr-container fr-col-lg-5 fr-col-xs-12 fr-mb-10w" style={{ textAlign: 'center' }}>
<a
href={urlFormConseiller}
target="_blank" rel="noopener noreferrer"
target="_self" rel="noopener noreferrer"
className="buttonCustom blueButtonCustom fr-text--xx-bold"
title="Devenir conseiller num&eacute;rique">Devenir conseiller num&eacute;rique</a>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/views/aide-structure/Questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';

function Questions() {

const urlFormStructure = import.meta.env.VITE_APP_FORMS_URL + '/structure/new';
const urlFormStructure = '/candidature-poste-conseiller';

return (
<div className="fr-container fr-mb-5w">
Expand Down Expand Up @@ -49,7 +49,7 @@ function Questions() {
</em>
</p>
<div className="fr-container fr-col-lg-5 fr-col-xs-12 fr-mb-10w" style={{ textAlign: 'center' }}>
<a href={urlFormStructure} target="_blank" rel="noopener noreferrer"
<a href={urlFormStructure} target="_self" rel="noopener noreferrer"
className="buttonCustom redButtonCustom fr-text--xx-bold"
title="Recruter des conseillers num&eacute;riques">
Recruter des conseillers num&eacute;riques
Expand Down
20 changes: 14 additions & 6 deletions src/views/candidature-conseiller/AddressChooser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,34 @@ import React, { useState } from 'react';
import Input from '../../components/commun/Input';
import { useGeoApi } from './useGeoApi';
import { debounce } from './debounce';
import PropTypes from 'prop-types';

export default function AddressChooser() {
export default function AddressChooser({ error }) {
const { searchByName, villes } = useGeoApi();
const [codeCommune, setCodeCommune] = useState('');

return (
<>
<Input
autoComplete= "off"
autoComplete="off"
id="lieuHabitation"
list="resultatsRecherche"
error={error}
onChange={debounce(async event => {
searchByName(event.target.value);
const codeCommune = await villes.find(({ codesPostaux, nom }) =>
if (event.target.value.length >= 3) {
searchByName(event.target.value);
}
const codeCommune = await villes?.find(({ codesPostaux, nom }) =>
(`${codesPostaux[0]} ${nom}`).toUpperCase() === (event.target.value).toUpperCase())?.code;
setCodeCommune(codeCommune);
})}
>
Votre lieu d’habitation <span className="cc-obligatoire">*</span>{' '}
<span className="fr-hint-text">Saississez le nom ou le code postal de votre commune.</span>
</Input>
<Input type="hidden" id="lieuHabitationCodeCommune" value={codeCommune} testId="lieuHabitationCodeCommune-hidden"/>
<Input type="hidden" id="lieuHabitationCodeCommune" value={codeCommune} testId="lieuHabitationCodeCommune-hidden" />
<datalist id="resultatsRecherche">
{villes.map(({ codesPostaux, nom }, key) => (
{villes?.map(({ codesPostaux, nom }, key) => (
<option value={`${codesPostaux[0]} ${nom}`} key={key}>
{codesPostaux[0]} {nom}
</option>
Expand All @@ -34,3 +38,7 @@ export default function AddressChooser() {
</>
);
}

AddressChooser.propTypes = {
error: PropTypes.string
};
Loading
Loading