diff --git a/pom.xml b/pom.xml index b7b140a..64b4168 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,10 @@ io.quarkus quarkus-resteasy-reactive + + io.quarkus + quarkus-rest-client-reactive-jackson + io.vertx diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java new file mode 100644 index 0000000..d254a1b --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java @@ -0,0 +1,28 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.rest.client.SirenService; +import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot; +import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("api/asso") +public class AssoEndpoints { + + @RestClient + SirenService sirenService; + + @GET + @Path("siren/{siren}") + @Produces(MediaType.APPLICATION_JSON) + public Uni getInfoSiren(@PathParam("siren") String siren) { + return sirenService.get_unite(siren).onFailure().transform(throwable -> { + if (throwable instanceof WebApplicationException exception){ + if (exception.getResponse().getStatus() == 400) + return new BadRequestException("Not found"); + } + return throwable; + }); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/client/SirenService.java b/src/main/java/fr/titionfire/ffsaf/rest/client/SirenService.java new file mode 100644 index 0000000..413ddc7 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/client/SirenService.java @@ -0,0 +1,19 @@ +package fr.titionfire.ffsaf.rest.client; + +import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot; +import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("/") +@RegisterRestClient +@ClientHeaderParam(name = "X-Client-Secret", value = "${siren-api.key}") +public interface SirenService { + + @GET + @Path("/v3/unites_legales/{SIREN}") + Uni get_unite(@PathParam("SIREN") String siren); +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/UniteLegaleRoot.java b/src/main/java/fr/titionfire/ffsaf/rest/data/UniteLegaleRoot.java new file mode 100644 index 0000000..2b5c34a --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/UniteLegaleRoot.java @@ -0,0 +1,107 @@ +package fr.titionfire.ffsaf.rest.data; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Date; + +@Data +@RegisterForReflection +public class UniteLegaleRoot { + public UniteLegale unite_legale; + + @Data + @RegisterForReflection + public static class UniteLegale { + public String activite_principale; + public Object annee_categorie_entreprise; + public Object annee_effectifs; + public Object caractere_employeur; + public Object categorie_entreprise; + public String categorie_juridique; + public String date_creation; + public String date_debut; + public Date date_dernier_traitement; + public String denomination; + public Object denomination_usuelle_1; + public Object denomination_usuelle_2; + public Object denomination_usuelle_3; + public String economie_sociale_solidaire; + public Etablissement etablissement_siege; + public ArrayList etablissements; + public String etat_administratif; + public String identifiant_association; + public String nic_siege; + public Object nom; + public Object nom_usage; + public int nombre_periodes; + public String nomenclature_activite_principale; + public Object prenom_1; + public Object prenom_2; + public Object prenom_3; + public Object prenom_4; + public Object prenom_usuel; + public Object pseudonyme; + public Object sexe; + public Object sigle; + public String siren; + public String societe_mission; + public String statut_diffusion; + public Object tranche_effectifs; + public Object unite_purgee; + } + + @Data + @RegisterForReflection + public static class Etablissement { + private String activite_principale; + private Object activite_principale_registre_metiers; + private Object annee_effectifs; + private String caractere_employeur; + private Object code_cedex; + private Object code_cedex_2; + private String code_commune; + private Object code_commune_2; + private Object code_pays_etranger; + private Object code_pays_etranger_2; + private String code_postal; + private Object code_postal_2; + private Object complement_adresse; + private Object complement_adresse2; + private String date_creation; + private String date_debut; + private Date date_dernier_traitement; + private Object denomination_usuelle; + private Object distribution_speciale; + private Object distribution_speciale_2; + private Object enseigne_1; + private Object enseigne_2; + private Object enseigne_3; + private boolean etablissement_siege; + private String etat_administratif; + private Object indice_repetition; + private Object indice_repetition_2; + private Object libelle_cedex; + private Object libelle_cedex_2; + private String libelle_commune; + private Object libelle_commune_2; + private Object libelle_commune_etranger; + private Object libelle_commune_etranger_2; + private Object libelle_pays_etranger; + private Object libelle_pays_etranger_2; + private String libelle_voie; + private Object libelle_voie_2; + private String nic; + private int nombre_periodes; + private String nomenclature_activite_principale; + private String numero_voie; + private Object numero_voie_2; + private String siren; + private String siret; + private String statut_diffusion; + private Object tranche_effectifs; + private String type_voie; + private Object type_voie_2; + } +} \ No newline at end of file diff --git a/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java b/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java new file mode 100644 index 0000000..16d34fa --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java @@ -0,0 +1,31 @@ +package fr.titionfire.ffsaf.rest.from; + +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.core.MediaType; +import lombok.Getter; +import lombok.ToString; +import org.jboss.resteasy.reactive.PartType; + +@Getter +@ToString +public class AffiliationRequestForm { + @FormParam("name") + private String name = null; + + @FormParam("siren") + private String siren = null; + + @FormParam("rna") + private String rna = null; + + @FormParam("adresse") + private String adresse = null; + + @FormParam("status") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + private byte[] status = new byte[0]; + + @FormParam("logo") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + private byte[] logo = new byte[0]; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 69feeed..92aa416 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,6 +34,9 @@ database.port=3306 database.user=root database.pass= +siren-api.key=siren-ap +quarkus.rest-client."fr.titionfire.ffsaf.rest.client.SirenService".url=https://data.siren-api.fr/ + #Login quarkus.oidc.token-state-manager.split-tokens=true quarkus.oidc.token.refresh-expired=true diff --git a/src/main/webapp/src/App.jsx b/src/main/webapp/src/App.jsx index 9c0ff8d..e5e6d24 100644 --- a/src/main/webapp/src/App.jsx +++ b/src/main/webapp/src/App.jsx @@ -11,6 +11,7 @@ import {ToastContainer} from "react-toastify"; import './App.css' import 'react-toastify/dist/ReactToastify.css'; import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx"; +import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx"; const router = createBrowserRouter([ { @@ -31,6 +32,19 @@ const router = createBrowserRouter([ path: 'club', element: , children: getClubChildren() + }, + { + path: 'affiliation', + children: [ + { + path: '', + element: + }, + { + path: 'ok', + element: + } + ] } ] }, diff --git a/src/main/webapp/src/components/Nav.jsx b/src/main/webapp/src/components/Nav.jsx index 886644d..3e5c6fb 100644 --- a/src/main/webapp/src/components/Nav.jsx +++ b/src/main/webapp/src/components/Nav.jsx @@ -24,6 +24,7 @@ export function Nav() {
  • Accueil
  • + @@ -32,6 +33,15 @@ export function Nav() { } + +function AffiliationMenu() { + const {is_authenticated} = useAuth() + + if (is_authenticated) + return <> + return
  • Demande d'affiliation
  • +} + function ClubMenu() { const {is_authenticated, userinfo} = useAuth() diff --git a/src/main/webapp/src/pages/DemandeAff.jsx b/src/main/webapp/src/pages/DemandeAff.jsx new file mode 100644 index 0000000..585bae7 --- /dev/null +++ b/src/main/webapp/src/pages/DemandeAff.jsx @@ -0,0 +1,187 @@ +import {useState} from "react"; +import {apiAxios} from "../utils/Tools.js"; +import {toast} from "react-toastify"; +import {useNavigate} from "react-router-dom"; + +function reconstruireAdresse(infos) { + let adresseReconstruite = ""; + adresseReconstruite += infos.numero_voie + ' ' + infos.type_voie + ' '; + adresseReconstruite += infos.libelle_voie + ', '; + adresseReconstruite += infos.code_postal + ' ' + infos.libelle_commune + ', '; + + if (infos.complement_adresse) { + adresseReconstruite += infos.complement_adresse + ', '; + } + if (infos.code_cedex && infos.libelle_cedex) { + adresseReconstruite += 'Cedex ' + infos.code_cedex + ' - ' + infos.libelle_cedex; + } + + if (adresseReconstruite.endsWith(', ')) { + adresseReconstruite = adresseReconstruite.slice(0, -2); + } + + return adresseReconstruite; +} + + +export function DemandeAff() { + const navigate = useNavigate(); + + const submit = (event) => { + event.preventDefault() + const formData = new FormData(event.target) + toast.promise( + apiAxios.post(`asso/affiliation`, formData), + { + pending: "Enregistrement de la demande d'affiliation en cours", + success: "Demande d'affiliation enregistrée avec succès 🎉", + error: "Échec de la demande d'affiliation 😕" + } + ).then(_ => { + navigate("/affiliation/ok") + }) + } + + return
    +

    Demande d'affiliation

    +

    L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de l’année suivante.

    + Pour s’affilier, une association sportive doit réunir les conditions suivantes : +
      +
    • Avoir son siège social en France ou Principauté de Monaco
    • +
    • Être constituée conformément au chapitre 1er du titre II du livre 1er du Code du Sport
    • +
    • Poursuivre un objet social entrant dans la définition de l’article 1 des statuts de la Fédération
    • +
    • Disposer de statuts compatibles avec les principes d’organisation et de fonctionnement de la Fédération
    • +
    • Assurer en son sein la liberté d’opinion et le respect des droits de la défense, et s’interdire toute discrimination
    • +
    • Respecter les règles d’encadrement, d’hygiène et de sécurité établies par les règlements de la Fédération
    • +
    + +
    +
    +
    +

    L'association

    + +

    Le président

    + +

    Le trésorier

    + +

    Le secrétaire

    + + +
    +

    Après validation de votre demande, vous recevrez un login et mot de passe provisoire pour accéder à votre espace FFSAF

    + Notez que pour finaliser votre affiliation, il vous faudra : +
      +
    • Disposer d’au moins trois membres licenciés, dont le président, le trésorier et le secrétaire
    • +
    • S'être acquitté des cotisations prévues par les règlements fédéraux
    • +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +} + +function AssoInfo() { + const [denomination, setDenomination] = useState("") + const [siren, setSiren] = useState("") + const [rna, setRna] = useState("") + const [rnaEnable, setRnaEnable] = useState(false) + const [adresse, setAdresse] = useState("") + + const fetchSiren = () => { + toast.promise( + apiAxios.get(`asso/siren/${siren}`), + { + pending: "Recherche de l'association en cours", + success: "Association trouvée avec succès 🎉", + error: "Échec de la recherche de l'association 😕" + } + ).then(data => { + const data2 = data.data.unite_legale + setDenomination(data2.denomination) + setRnaEnable(data2.identifiant_association === null) + setRna(data2.identifiant_association ? data2.identifiant_association : "") + setAdresse(reconstruireAdresse(data2.etablissement_siege)) + }) + } + + return <> +
    + Nom de l'association* + +
    + +
    + N° SIREN* + setSiren(e.target.value)}/> + +
    + +
    + Dénomination + +
    + +
    + RNA + setRna(e.target.value)}/> +
    + +
    + Adresse* + setAdresse(e.target.value)}/> +
    + +
    + + +
    + +
    + + +
    + ; +} + +function MembreInfo({role}) { + return
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +} + +export function DemandeAffOk() { + return ( +
    +

    Demande d'affiliation envoyée avec succès

    +

    Une fois votre demande validée, vous recevrez un login et mot de passe provisoire pour accéder à votre espace FFSAF

    +
    + ); +} \ No newline at end of file