Files
ffsaf-site/src/main/webapp/src/pages/DemandeAff.jsx
Thibaut Valentin fefc5d651b
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m4s
feat: re-add siret/rna api
2025-11-19 15:26:36 +01:00

383 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {useEffect, useState} from "react";
import {apiAxios, errFormater, getSaison} from "../utils/Tools.js";
import {toast} from "react-toastify";
import {useLocation, useNavigate} from "react-router-dom";
const notUpperCase = ["de", "la", "le", "les", "des", "du", "d'", "l'", "sur", 'lieu', 'dit'];
function formatAdresse(data) {
const words = data.split(" ");
return words.map((word) => {
if (notUpperCase.includes(word.toLowerCase())) {
return word.toLowerCase();
}
return word[0].toUpperCase() + word.substring(1).toLowerCase();
}).join(" ");
}
function reconstruireAdresse2(infos) {
let adresseReconstruite = "";
if (infos?.complement)
adresseReconstruite += formatAdresse(infos.complement) + ', ';
adresseReconstruite += formatAdresse(infos.voie) + ', ';
adresseReconstruite += infos.code_postal + ' ' + infos.commune.nom + ', ';
if (infos?.pays)
adresseReconstruite += formatAdresse(infos.pays) + ', ';
if (adresseReconstruite.endsWith(', ')) {
adresseReconstruite = adresseReconstruite.slice(0, -2);
}
return adresseReconstruite;
}
function getSaisonToAff(currentDate = new Date()) {
if (currentDate.getMonth() >= 7) { //aout et plus
return currentDate.getFullYear()
} else {
return currentDate.getFullYear() - 1
}
}
export function DemandeAff() {
const {hash} = useLocation();
const navigate = useNavigate();
const [initData, setInitData] = useState(null)
const [needFile, setNeedFile] = useState(true)
useEffect(() => {
if (hash.startsWith("#d")) {
const data = JSON.parse(decodeURI(hash.substring(2)));
setInitData(data)
setNeedFile(false)
} else if (hash.startsWith("#e")) {
apiAxios.get(`/affiliation/request/${hash.substring(2)}`).then(data => {
for (let i = 0; i < data.data.members.length; i++) {
if (data.data.members[i].licence === -1)
data.data.members[i].licence = ""
}
setInitData(data.data)
setNeedFile(false)
}).catch(_ => {
setInitData({})
})
} else {
setInitData({})
}
}, []);
const submit = (event) => {
event.preventDefault()
const formData = new FormData(event.target)
formData.append("m1_role", event.target.m1_role?.value)
formData.append("state_id", event.target.state_id?.value)
let error = false;
for (let i = 1; i <= 3; i++) {
if (event.target[`m${i}_role`]?.value === "0") {
toast.error(`Le rôle du membre ${i} est obligatoire`)
error = true;
}
}
if (error) {
return;
}
if (event.nativeEvent.submitter.value === "undo") {
toast.promise(
apiAxios.delete(`/affiliation/request/${initData.id}`),
{
pending: "Annulation de la demande d'affiliation en cours",
success: "Demande d'affiliation annulée avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de l'annulation de la demande d'affiliation")
}
}
}
).then(_ => {
navigate("/club/me")
})
} else if (event.nativeEvent.submitter.value === "edit") {
formData.append("id", initData.id)
toast.promise(
apiAxios.put(`/affiliation/request/edit`, formData),
{
pending: "Enregistrement des modifications en cours",
success: "Modifications enregistrées avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement des modifications")
}
}
}
).then(_ => {
navigate("/club/me")
})
} else {
formData.append("id", -1)
toast.promise(
apiAxios.post(`/affiliation/request`, formData, {headers: {'Accept': '*/*'}}),
{
pending: "Enregistrement de la demande d'affiliation en cours",
success: "Demande d'affiliation enregistrée avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de la demande d'affiliation")
}
}
}
).then(_ => {
navigate("/affiliation/ok")
})
}
}
return <div>
<h1>Demande d'affiliation {getSaisonToAff() + "-" + (getSaisonToAff() + 1)}</h1>
<p>L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de lannée
suivante.</p>
Pour saffilier, une association sportive doit réunir les conditions suivantes :
<ul>
<li>Avoir son siège social en France ou Principauté de Monaco</li>
<li>Être constituée conformément au chapitre 1er du titre II du livre 1er du Code du Sport</li>
<li>Poursuivre un objet social entrant dans la définition de larticle 1 des statuts de la Fédération</li>
<li>Disposer de statuts compatibles avec les principes dorganisation et de fonctionnement de la
Fédération
</li>
<li>Assurer en son sein la liberté dopinion et le respect des droits de la défense, et sinterdire toute
discrimination
</li>
<li>Respecter les règles dencadrement, dhygiène et de sécurité établies par les règlements de la
Fédération
</li>
</ul>
{initData && <div className="card mb-4">
<form onSubmit={submit}>
<div className="card-body">
<h4>L'association</h4>
<AssoInfo initData={initData} needFile={needFile}/>
<h4>Membre n°1</h4>
<MembreInfo role="m1" initData={initData.members?.at(0) || {}}/>
<h4 style={{marginTop: '1em'}}>Membre n°2</h4>
<MembreInfo role="m2" initData={initData.members?.at(1) || {}}/>
<h4 style={{marginTop: '1em'}}>Membre n°3</h4>
<MembreInfo role="m3" initData={initData.members?.at(2) || {}}/>
<div className="mb-3" style={{marginTop: '1em'}}>
{!initData.id && <p>Après validation de votre demande, vous recevrez un identifiant et mot de passe provisoire pour
accéder à votre espace FFSAF</p>}
Notez que pour finaliser votre affiliation, il vous faudra :
<ul>
<li>Disposer dau moins trois membres licenciés, dont le président</li>
<li>S'être acquitté des cotisations prévues par les règlements fédéraux</li>
</ul>
</div>
{!initData.id ?
<div className="row">
<div className="d-grid gap-2 d-md-flex justify-content-md-center">
<button type="submit" value="new" className="btn btn-primary">Confirmer ma demande d'affiliation
</button>
</div>
</div> :
<div className="row">
<div className="d-grid gap-2 d-md-flex justify-content-md-center col">
<button type="submit" value="undo" className="btn btn-danger">Annuler ma demande
</button>
</div>
<div className="d-grid gap-2 d-md-flex justify-content-md-center col">
<button type="submit" value="edit" className="btn btn-primary">Enregistrer les modifications
</button>
</div>
</div>
}
</div>
</form>
</div>}
</div>
}
function AssoInfo({initData, needFile}) {
const [denomination, setDenomination] = useState("")
const [stateId, setStateId] = useState(initData.stateId ? String(initData.stateId) : (initData.state_id ? String(initData.state_id) : ""))
const [adresse, setAdresse] = useState(initData.address ? initData.address : "")
const [contact, setContact] = useState(initData.contact ? initData.contact : "")
const fetchStateId = () => {
const regex = /^(?:\d{14}|W\d{9})$/;
let sid = stateId;
if (!regex.test(stateId)) {
toast.error("Le format du SIRET/RNA est invalide");
return;
}else{
if (stateId[0] !== 'W')
sid = stateId.substring(0, 9); // Pour les SIRET, on ne garde que les 9 premiers chiffres (SIREN)
}
toast.promise(
apiAxios.get(`asso/state_id/${sid}`),
{
pending: "Recherche de l'association en cours",
success: "Association trouvée avec succès 🎉",
error: {
render({data}) {
return errFormater(data, "Échec de la recherche de l'association")
}
}
}
).then(data => {
const data2 = data.data
setDenomination(data2.identite.nom)
if (!initData.saison || adresse === "")
setAdresse(reconstruireAdresse2(data2.coordonnees.adresse_gestion))
})
}
return <>
<input name="saison" value={getSaisonToAff()} readOnly hidden/>
<div className="input-group mb-3">
<span className="input-group-text" id="basic-addon1">Nom de l'association*</span>
<input type="text" className="form-control" placeholder="Nom de l'association" name="name"
aria-label="Nom de l'association"
aria-describedby="basic-addon1" required defaultValue={initData.name ? initData.name : ""}/>
</div>
<div className="input-group mb-3">
<span className="input-group-text">N° SIRET ou RNA*</span>
<input type="text" className="form-control" placeholder="N° SIRET ou RNA*" name="state_id" required value={stateId} disabled={!needFile}
onChange={e => setStateId(e.target.value)}/>
<button className="btn btn-outline-secondary" type="button" id="button-addon2"
onClick={fetchStateId} hidden={false}>Rechercher
</button>
</div>
<div className="input-group mb-3" hidden={false}>
<span className="input-group-text" id="basic-addon1">Dénomination</span>
<input type="text" className="form-control" placeholder="Appuyer sur rechercher pour compléter"
aria-label="Dénomination"
aria-describedby="basic-addon1" disabled value={denomination} readOnly/>
</div>
<div className="mb-3">
<div className="input-group">
<span className="input-group-text" id="basic-addon1">Adresse administrative*</span>
<input type="text" className="form-control" placeholder="Adresse administrative" aria-label="Adresse administrative"
aria-describedby="basic-addon1"
required value={adresse} name="adresse" onChange={e => setAdresse(e.target.value)}/>
</div>
<div className="form-text" id="adresse">Vous pourrez par la suite, ajouter des adresses visibles publiquement pour vos lieux
d'entrainement
</div>
</div>
<div className="mb-3">
<div className="input-group">
<span className="input-group-text" id="basic-addon1">Contact administratif*</span>
<input type="email" className="form-control" placeholder="Contact administratif" aria-label="Contact administratif"
aria-describedby="basic-addon1"
required value={contact} name="contact" onChange={e => setContact(e.target.value)}/>
</div>
</div>
<div className="mb-3">
<div className="input-group">
<label className="input-group-text" htmlFor="logo">Blason{needFile && "*"}</label>
<input type="file" className="form-control" id="logo" name="logo" accept=".jpg,.jpeg,.gif,.png,.svg"
required={needFile}/>
</div>
{!needFile && <div className="form-text" id="status">Laissez vide pour ne rien changer. (Si un blason a déjà été envoyé lors de cette
demande, il sera utilisé, sinon nous utiliserons celui de la précédant affiliation)</div>}
</div>
<div className="mb-3">
<div className="input-group">
<label className="input-group-text" htmlFor="status">Statue{needFile && "*"}</label>
<input type="file" className="form-control" id="status" name="status" accept=".pdf,.txt" required={needFile}/>
</div>
{!needFile && <div className="form-text" id="status">Laissez vide pour ne rien changer. (Si un statu a déjà été envoyé lors de cette
demande, il sera utilisé, sinon nous utiliserons celui de la précédant affiliation)</div>}
</div>
</>;
}
function MembreInfo({role, initData}) {
const [switchOn, setSwitchOn] = useState(!!initData.licence);
return <>
<div className="input-group mb-3">
<label className="input-group-text" htmlFor="inputGroupSelect01">Rôles</label>
<select className="form-select" id="inputGroupSelect01" defaultValue={initData.role ? initData.role : (role === "m1" ? "PRESIDENT" : 0)}
disabled={initData.role ? initData.role === "PRESIDENT" : role === "m1"} name={role + "_role"} required>
<option value="0">Sélectionner...</option>
<option value="PRESIDENT">Président</option>
<option value="TRESORIER">Trésorier</option>
<option value="SECRETAIRE">Secrétaire</option>
<option value="VPRESIDENT">Vise-Président</option>
<option value="VTRESORIER">Vise-Trésorier</option>
<option value="VSECRETAIRE">Vise-Secrétaire</option>
<option value="MEMBREBUREAU">Membre du bureau</option>
</select>
</div>
<div className="row g-3 mb-3">
<div className="col-sm-3">
<div className="form-floating">
<input type="text" className="form-control" id="floatingInput" placeholder="Nom" name={role + "_nom"}
defaultValue={initData.lname ? initData.lname : ""} required/>
<label htmlFor="floatingInput">Nom*</label>
</div>
</div>
<div className="col-sm-3">
<div className="form-floating">
<input type="text" className="form-control" id="floatingInput" placeholder="Prénom"
name={role + "_prenom"} defaultValue={initData.fname ? initData.fname : ""} required/>
<label htmlFor="floatingInput">Prénom*</label>
</div>
</div>
<div className="col-sm-5">
<div className="form-floating">
<input type="email" className="form-control" id="floatingInput" placeholder="name@example.com"
name={role + "_mail"} defaultValue={initData.email ? initData.email : ""} required/>
<label htmlFor="floatingInput">Email*</label>
</div>
</div>
</div>
<div className="input-group mb-3">
<label className="input-group-text" htmlFor="inputGroupSelect01">Dispose déjà d'une licence</label>
<div className="input-group-text">
<input type="checkbox" id="inputGroupSelect01" className="form-check-input mt-0"
checked={switchOn} onChange={() => setSwitchOn(!switchOn)}/>
</div>
</div>
{switchOn &&
<div className="col-sm-3">
<div className="form-floating">
<input type="number" className="form-control" id="floatingInput" placeholder="N° Licence"
name={role + "_licence"} defaultValue={initData.licence ? Number(initData.licence) : ""} required/>
<label htmlFor="floatingInput">N° Licence</label>
</div>
</div>
}
</>
}
export function DemandeAffOk() {
return (
<div>
<h1 className="text-green-800 text-4xl">Demande d'affiliation envoyée avec succès</h1>
<p>Une fois votre demande validée, vous recevrez un identifiant et mot de passe provisoire pour accéder à votre
espace FFSAF</p>
</div>
);
}