Compare commits

..

No commits in common. "c7c7b3ba202851f87012cbd449d6d4e4309bb78c" and "d1c7f37a945966cb87bfe92f2afbf654605ff286" have entirely different histories.

55 changed files with 351 additions and 819 deletions

View File

@ -76,7 +76,6 @@ jobs:
key: ${{ secrets.SSH_KEY }}
script: |
cd ${{ secrets.TARGET_DIR }}
docker logs ffsaf > "log/ffsaf_logs_$(date +"%Y-%m-%d_%H-%M-%S").log" 2>&1
docker stop ffsaf
docker rm ffsaf
docker compose up --build -d ffsaf

View File

@ -21,7 +21,8 @@ public class AffiliationRequestModel {
Long id;
String name;
String state_id;
long siret;
String RNA;
String address;
String contact;

View File

@ -55,8 +55,11 @@ public class ClubModel implements LoggableModel {
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
String address;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
String StateId;
@Schema(description = "RNA du club", example = "W123456789")
String RNA;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long SIRET;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation;

View File

@ -30,10 +30,8 @@ public class LogModel {
Long target_id;
@Column(columnDefinition = "TEXT")
String target_name;
@Column(columnDefinition = "TEXT")
String message;
public enum ActionType {

View File

@ -82,22 +82,4 @@ public class MembreModel implements LoggableModel {
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Membre;
}
@Override
public String toString() {
return "MembreModel{" +
"id=" + id +
", userId='" + userId + '\'' +
", lname='" + lname + '\'' +
", fname='" + fname + '\'' +
", categorie=" + categorie +
", genre=" + genre +
", licence=" + licence +
", country='" + country + '\'' +
", birth_date=" + birth_date +
", email='" + email + '\'' +
", role=" + role +
", grade_arbitrage=" + grade_arbitrage +
'}';
}
}

View File

@ -8,8 +8,6 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Getter
@Setter
@ -40,7 +38,6 @@ public class RegisterModel {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club")
@OnDelete(action = OnDeleteAction.SET_NULL)
ClubModel club = null;
@Column(nullable = false, columnDefinition = "boolean default false")

View File

@ -22,7 +22,8 @@ public class ClubEntity {
private String training_location;
private String training_day_time;
private String contact_intern;
private String StateId;
private String RNA;
private Long SIRET;
private Long no_affiliation;
private boolean international;
@ -40,7 +41,8 @@ public class ClubEntity {
.training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern())
.StateId(model.getStateId())
.RNA(model.getRNA())
.SIRET(model.getSIRET())
.no_affiliation(model.getNo_affiliation())
.international(model.isInternational())
.build();

View File

@ -2,8 +2,6 @@ package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
@ -23,19 +21,15 @@ import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class AffiliationService {
private static final Logger LOGGER = Logger.getLogger(AffiliationService.class);
@Inject
CombRepository combRepository;
@ -64,12 +58,6 @@ public class AffiliationService {
@Inject
LoggerService ls;
@RestClient
StateIdService stateIdService;
@RestClient
SirenService sirenService;
@ConfigProperty(name = "upload_dir")
String media;
@ -83,8 +71,6 @@ public class AffiliationService {
public Uni<AffiliationRequestModel> pre_save(AffiliationRequestForm form, boolean unique) {
AffiliationRequestModel affModel = form.toModel();
int currentSaison = Utils.getSaison();
List<String> out = new ArrayList<>();
out.add(affModel.getState_id());
return Uni.createFrom().item(affModel)
.invoke(Unchecked.consumer(model -> {
@ -92,26 +78,14 @@ public class AffiliationService {
throw new DBadRequestException("Saison non valid");
}
}))
.chain(() -> ((affModel.getState_id().charAt(0) == 'W') ? stateIdService.get_rna(
affModel.getState_id()) : sirenService.get_unite(affModel.getState_id())
.chain(stateIdService::getAssoDataFromUnit)).onItem().transform(o -> {
if (o.getRna() != null && !o.getRna().isBlank())
out.add(o.getRna());
if (o.getSiren() != null && !o.getSiren().isBlank())
out.add(o.getSiren());
if (o.getIdentite().getSiret_siege() != null && !o.getIdentite().getSiret_siege().isBlank())
out.add(o.getIdentite().getSiret_siege());
return out;
}).onFailure().recoverWithItem(out)
.chain(a -> repositoryRequest.count("state_id IN ?1 and saison = ?2",
out, affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
)
.chain(() -> clubRepository.find("StateId IN ?1", out).firstResult().chain(club ->
.chain(() -> repositoryRequest.count("siret = ?1 and saison = ?2", affModel.getSiret(),
affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
.chain(() -> clubRepository.find("SIRET = ?1", affModel.getSiret()).firstResult().chain(club ->
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) {
@ -148,6 +122,7 @@ public class AffiliationService {
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(origine -> {
origine.setName(model.getName());
origine.setRNA(model.getRNA());
origine.setAddress(model.getAddress());
origine.setContact(model.getContact());
origine.setM1_lname(model.getM1_lname());
@ -171,9 +146,6 @@ public class AffiliationService {
}
public Uni<String> save(AffiliationRequestForm form) {
LOGGER.debug("Affiliation Request Created");
LOGGER.debug(form.toString());
// noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher
return pre_save(form, true)
.chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model)))
@ -197,14 +169,12 @@ public class AffiliationService {
}
public Uni<?> saveAdmin(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Saved");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> {
model.setName(form.getName());
model.setState_id(form.getState_id());
model.setSiret(form.getSiret());
model.setRNA(form.getRna());
model.setAddress(form.getAddress());
model.setContact(form.getContact());
@ -289,9 +259,7 @@ public class AffiliationService {
}).call(m -> Panache.withTransaction(() -> combRepository.persist(m)));
}
})
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId())
.onFailure().invoke(t -> LOGGER.warnf("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull() :
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId()) :
keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId()))
.call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage()))
.call(userId -> keycloakService.setEmail(userId, m.getEmail())))
@ -299,24 +267,19 @@ public class AffiliationService {
.call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ?
Uni.createFrom().nullItem() :
Panache.withTransaction(() -> licenceRepository.persist(
new LicenceModel(null, m, club.getId(), saison, null, true, false)))
new LicenceModel(null, m, club.getId(), saison, null, true, false)))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(),
licenceModel))));
}
public Uni<?> accept(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Accepted");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req ->
clubRepository.find("StateId = ?1", form.getState_id()).firstResult()
clubRepository.find("SIRET = ?1", form.getSiret()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
.call(club -> setMembre(form.new Member(1), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(__ -> setMembre(form.new Member(2), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(club -> setMembre(form.new Member(1), club, req.getSaison())
.call(__ -> setMembre(form.new Member(2), club, req.getSaison())
.call(___ -> setMembre(form.new Member(3), club, req.getSaison()))))
.onItem()
.invoke(model -> Uni.createFrom()
@ -335,13 +298,13 @@ public class AffiliationService {
}
private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) {
LOGGER.debug("New Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
ClubModel club = new ClubModel();
club.setName(form.getName());
club.setCountry("FR");
club.setStateId(form.getState_id());
club.setSIRET(form.getSiret());
club.setRNA(form.getRna());
club.setAddress(form.getAddress());
club.setContact_intern(form.getContact());
club.setAffiliations(new ArrayList<>());
@ -373,24 +336,17 @@ public class AffiliationService {
}
private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) {
AtomicBoolean nameChange = new AtomicBoolean(false);
LOGGER.debug("Old Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
if (!form.getName().equals(club.getName())) {
club.setName(form.getName());
nameChange.set(true);
}
club.setName(form.getName());
club.setCountry("FR");
club.setStateId(form.getState_id());
club.setSIRET(form.getSiret());
club.setRNA(form.getRna());
club.setAddress(form.getAddress());
club.setContact_intern(form.getContact());
return Panache.withTransaction(() -> clubRepository.persist(club)
.chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison())))
.chain(() -> repositoryRequest.delete(model)))
.call(() -> nameChange.get() ? keycloakService.updateGroupFromClub(
club) // update group in keycloak
: Uni.createFrom().nullItem());
.chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison())))
.chain(() -> repositoryRequest.delete(model)));
})
.map(__ -> club);
}
@ -398,7 +354,7 @@ public class AffiliationService {
public Uni<SimpleReqAffiliation> getRequest(long id) {
return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel)
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.call(out -> clubRepository.find("StateId = ?1", out.getStateId()).firstResult().invoke(c -> {
.call(out -> clubRepository.find("SIRET = ?1", out.getSiret()).firstResult().invoke(c -> {
if (c != null) {
out.setClub(c.getId());
out.setClub_name(c.getName());
@ -411,7 +367,7 @@ public class AffiliationService {
public Uni<List<SimpleAffiliation>> getCurrentSaisonAffiliation() {
return repositoryRequest.list("saison = ?1 or saison = ?1 + 1", Utils.getSaison())
.map(models -> models.stream()
.map(model -> new SimpleAffiliation(model.getId() * -1, model.getState_id(), model.getSaison(),
.map(model -> new SimpleAffiliation(model.getId() * -1, model.getSiret(), model.getSaison(),
false)).toList())
.chain(aff -> repository.list("saison = ?1", Utils.getSaison())
.map(models -> models.stream().map(SimpleAffiliation::fromModel).toList())
@ -423,9 +379,9 @@ public class AffiliationService {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.chain(model -> repositoryRequest.list("state_id = ?1", model.getStateId())
.chain(model -> repositoryRequest.list("siret = ?1", model.getSIRET())
.map(reqs -> reqs.stream().map(req ->
new SimpleAffiliation(req.getId() * -1, model.getStateId(), req.getSaison(), false)))
new SimpleAffiliation(req.getId() * -1, model.getId(), req.getSaison(), false)))
.map(aff2 -> Stream.concat(aff2,
model.getAffiliations().stream().map(SimpleAffiliation::fromModel)).toList())
);
@ -455,9 +411,9 @@ public class AffiliationService {
return Panache.withTransaction(() -> repository.deleteById(id));
}
public Uni<?> deleteReqAffiliation(long id, String reason, boolean federationAdmin) {
public Uni<?> deleteReqAffiliation(long id, String reason) {
return repositoryRequest.findById(id)
.call(aff -> federationAdmin ? reactiveMailer.send(
.call(aff -> reactiveMailer.send(
Mail.withText(aff.getM1_email(),
"FFSAF - Votre demande d'affiliation a été rejetée.",
String.format(
@ -474,7 +430,7 @@ public class AffiliationService {
""", aff.getName(), reason)
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
.addTo(aff.getM2_email(), aff.getM3_email())
) : Uni.createFrom().nullItem())
))
.chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff)))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/logo"))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/status"));

View File

@ -32,7 +32,6 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
@ -194,17 +193,12 @@ public class ClubService {
}
public Uni<String> update(long id, FullClubForm input) {
AtomicBoolean nameChange = new AtomicBoolean(false);
return repository.findById(id).call(m -> Mutiny.fetch(m.getContact()))
.onItem().transformToUni(Unchecked.function(m -> {
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
};
if (!input.getName().equals(m.getName())) {
m.setName(input.getName());
nameChange.set(true);
}
m.setName(input.getName());
m.setCountry(input.getCountry());
m.setInternational(input.isInternational());
@ -217,9 +211,11 @@ public class ClubService {
m.setTraining_day_time(input.getTraining_day_time());
ls.logChange("Contact interne", m.getContact_intern(), input.getContact_intern(), m);
m.setContact_intern(input.getContact_intern());
if (input.getState_id() != null && !input.getState_id().isBlank()) {
ls.logChange("N° SIRET", m.getClubId(), input.getState_id(), m);
m.setStateId(input.getState_id());
ls.logChange("N° RNA", m.getRNA(), input.getRna(), m);
m.setRNA(input.getRna());
if (input.getSiret() != null && !input.getSiret().isBlank()) {
ls.logChange("N° SIRET", m.getSIRET(), input.getSiret(), m);
m.setSIRET(Long.parseLong(input.getSiret()));
}
ls.logChange("Adresse administrative", m.getAddress(), input.getAddress(), m);
m.setAddress(input.getAddress());
@ -234,8 +230,6 @@ public class ClubService {
}
return Panache.withTransaction(() -> repository.persist(m)).call(() -> ls.append());
}))
.call(clubModel -> nameChange.get() ? keycloakService.updateGroupFromClub(clubModel) // update group in keycloak
: Uni.createFrom().nullItem())
.invoke(membreModel -> SReqClub.sendIfNeed(serverCustom.clients,
SimpleClubModel.fromModel(membreModel)))
.map(__ -> "OK");
@ -257,8 +251,9 @@ public class ClubService {
clubModel.setTraining_location(input.getTraining_location());
clubModel.setTraining_day_time(input.getTraining_day_time());
clubModel.setContact_intern(input.getContact_intern());
if (input.getState_id() != null && !input.getState_id().isBlank())
clubModel.setStateId(input.getState_id());
clubModel.setRNA(input.getRna());
if (input.getSiret() != null && !input.getSiret().isBlank())
clubModel.setSIRET(Long.parseLong(input.getSiret()));
clubModel.setAddress(input.getAddress());
try {
@ -305,9 +300,9 @@ public class ClubService {
.call(clubModel -> Mutiny.fetch(clubModel.getAffiliations()))
.invoke(clubModel -> {
data.setName(clubModel.getName());
data.setState_id(clubModel.getStateId());
data.setSiret(clubModel.getSIRET());
data.setRna(clubModel.getRNA());
data.setAddress(clubModel.getAddress());
data.setContact(clubModel.getContact_intern());
data.setSaison(
clubModel.getAffiliations().stream().max(Comparator.comparing(AffiliationModel::getSaison))
.map(AffiliationModel::getSaison).map(i -> Math.min(i + 1, Utils.getSaison() + 1))

View File

@ -85,31 +85,6 @@ public class KeycloakService {
return Uni.createFrom().item(club::getClubId);
}
public Uni<String> updateGroupFromClub(ClubModel club) {
if (club.getClubId() == null) {
return getGroupFromClub(club);
} else {
LOGGER.infof("Updating name of club group %d-%s...", club.getId(), club.getName());
return vertx.getOrCreateContext().executeBlocking(() -> {
GroupRepresentation clubGroup =
keycloak.realm(realm).groups().groups().stream().filter(g -> g.getName().equals("club"))
.findAny()
.orElseThrow(() -> new KeycloakException("Fail to fetch group %s".formatted("club")));
keycloak.realm(realm).groups().group(clubGroup.getId()).getSubGroups(0, 1000, true).stream()
.filter(g -> g.getName().startsWith(club.getId() + "-")).findAny()
.ifPresent(groupRepresentation -> {
groupRepresentation.setName(club.getId() + "-" + club.getName());
keycloak.realm(realm).groups().group(groupRepresentation.getId())
.update(groupRepresentation);
});
return club.getClubId();
}
);
}
}
public Uni<String> getUserFromMember(MembreModel membreModel) {
if (membreModel.getUserId() == null) {
return Uni.createFrom()
@ -224,16 +199,16 @@ public class KeycloakService {
public Uni<String> initCompte(long id) {
return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> {
if (membreModel.getUserId() != null)
throw new KeycloakException("User already linked to the user id=" + id);
if (membreModel.getEmail() == null)
throw new KeycloakException("User email is null");
if (membreModel.getFname() == null || membreModel.getLname() == null)
throw new KeycloakException("User name is null");
})).chain(membreModel -> creatUser(membreModel).chain(user -> {
LOGGER.infof("Set user id %s to membre %s", user.getId(), membreModel.getId());
return membreService.setUserId(membreModel.getId(), user.getId()).map(__ -> user.getId());
}));
if (membreModel.getUserId() != null)
throw new KeycloakException("User already linked to the user id=" + id);
if (membreModel.getEmail() == null)
throw new KeycloakException("User email is null");
if (membreModel.getFname() == null || membreModel.getLname() == null)
throw new KeycloakException("User name is null");
})).chain(membreModel -> creatUser(membreModel).chain(user -> {
LOGGER.infof("Set user id %s to membre %s", user.getId(), membreModel.getId());
return membreService.setUserId(membreModel.getId(), user.getId()).map(__ -> user.getId());
}));
}
private Uni<UserRepresentation> creatUser(MembreModel membreModel) {
@ -256,6 +231,9 @@ public class KeycloakService {
user.setEmail(membreModel.getEmail());
user.setEnabled(true);
user.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
try (Response response = keycloak.realm(realm).users().create(user)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo()
.equals(Response.Status.CONFLICT))
@ -267,6 +245,13 @@ public class KeycloakService {
return getUser(login).orElseThrow(
() -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin)));
})
.call(user -> enabled_email ?
vertx.getOrCreateContext().executeBlocking(() -> {
keycloak.realm(realm).users().get(user.getId())
.executeActionsEmail(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
return null;
}) : Uni.createFrom().nullItem())
.invoke(user -> membreModel.setUserId(user.getId()))
.call(user -> updateRole(user.getId(), List.of("safca_user"), List.of()))
.call(user -> enabled_email ? reactiveMailer.send(
@ -276,14 +261,14 @@ public class KeycloakService {
"""
Bonjour,
Suite à votre première inscription % la Fédération Française de Soft Armored Fighting (FFSAF), votre compte intranet a été créé.
Ce compte vous permettra de consulter vos informations et, dans un futur proche, de vous inscrire aux compétitions ainsi que d'en consulter les résultats.
Suite à votre première inscription % la Fédération Française de Soft Armored Fighting (FFSAF), votre compte pour accéder à l'intranet a été créé.
Ce compte vous permettra de consulter vos informations, de vous inscrire aux compétitions et de consulter vos résultats.
Vous allez recevoir dans les prochaines minutes un email vous demandant de vérifier votre email et de définir un mot de passe.
L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr
Votre nom d'utilisateur est : %s
Pour définir votre mot de passe, rendez-vous sur l'intranet > "Connexion" > "Mot de passe oublié ?"
Si vous n'avez pas demandé cette inscription, veuillez contacter le support à l'adresse support@ffsaf.fr.
(Pas de panique, nous ne vous enverrons pas de message autre que ce concernant votre compte)

View File

@ -18,7 +18,6 @@ import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.function.Consumer;
@ -27,7 +26,6 @@ import java.util.function.Function;
@WithSession
@ApplicationScoped
public class LicenceService {
private static final Logger LOGGER = Logger.getLogger(LicenceService.class);
@Inject
LicenceRepository repository;
@ -127,9 +125,7 @@ public class LicenceService {
.chain(() -> combRepository.persist(membreModel))
: Uni.createFrom().nullItem())
.call(__ -> (membreModel.getUserId() == null) ?
keycloakService.initCompte(membreModel.getId()).onFailure()
.invoke(t -> LOGGER.infof("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull()
keycloakService.initCompte(membreModel.getId())
: Uni.createFrom().nullItem());
}

View File

@ -13,7 +13,6 @@ import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.hibernate.reactive.panache.Panache;
@ -103,47 +102,15 @@ public class MembreService {
return baseUni;
}
private Sort getSort(String order) {
Sort sort;
if (order == null || order.isBlank()) {
sort = Sort.ascending("fname", "lname");
} else {
sort = Sort.empty();
for (String e : order.split(",")) {
String[] split = e.split(" ");
if (split.length == 2) {
sort = sort.and(split[0],
split[1].equals("n") ? Sort.Direction.Ascending : Sort.Direction.Descending);
} else {
return null;
}
}
}
return sort;
}
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club,
int licenceRequest, int payState, String order, String categorie) {
int licenceRequest, int payState) {
if (search == null)
search = "";
search = "%" + search.replaceAll(" ", "% %") + "%";
String categorieFilter;
if (categorie == null || categorie.isBlank())
categorieFilter = " True";
else
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
String finalSearch = search;
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
Sort sort = getSort(order);
if (sort == null)
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return baseUni
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
@ -153,18 +120,18 @@ public class MembreService {
if (club == null || club.isBlank()) {
query = repository.find(
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, ids)
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, ids)
.page(Page.ofSize(limit));
} else {
if (club.equals("null")) {
query = repository.find(
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, ids).page(Page.ofSize(limit));
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, ids).page(Page.ofSize(limit));
} else {
query = repository.find(
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, club, ids)
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, club + "%", ids)
.page(Page.ofSize(limit));
}
}
@ -173,7 +140,7 @@ public class MembreService {
}
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
String order, String categorie, String subject) {
String subject) {
if (search == null)
search = "";
search = "%" + search.replaceAll(" ", "% %") + "%";
@ -182,16 +149,6 @@ public class MembreService {
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
String categorieFilter;
if (categorie == null || categorie.isBlank())
categorieFilter = " True";
else
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
Sort sort = getSort(order);
if (sort == null)
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return baseUni
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
@ -200,8 +157,8 @@ public class MembreService {
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
PanacheQuery<MembreModel> query = repository.find(
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, membreModel.getClub(), ids)
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, membreModel.getClub(), ids)
.page(Page.ofSize(limit));
return getPageResult(query, limit, page);
});
@ -240,11 +197,6 @@ public class MembreService {
return Uni.createFrom().nullItem();
AtomicReference<ClubModel> clubModel = new AtomicReference<>();
LOGGER.debugf("Membre import (size=%d)", data2.size());
for (SimpleMembreInOutData simpleMembreInOutData : data2) {
LOGGER.debugf("-> %s", simpleMembreInOutData.toString());
}
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
clubModel.set(membreModel.getClub());
@ -253,24 +205,20 @@ public class MembreService {
return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3",
data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(),
data2.stream().map(SimpleMembreInOutData::getEmail).filter(o -> o != null && !o.isBlank())
.toList());
data2.stream().map(SimpleMembreInOutData::getEmail).filter(Objects::nonNull).toList());
})
.call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get())) {
LOGGER.info("Similar membres found: " + membreModel);
if (!Objects.equals(membreModel.getClub(), clubModel.get()))
throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
}
}
Uni<Void> uniResult = Uni.createFrom().voidItem();
for (SimpleMembreInOutData dataIn : data2) {
MembreModel model = membres.stream()
.filter(m -> (dataIn.getLicence() != null && Objects.equals(m.getLicence(),
dataIn.getLicence())) || m.getLname().equals(dataIn.getNom()) && m.getFname()
.equals(dataIn.getPrenom()) || (dataIn.getEmail() != null && !dataIn.getEmail()
.isBlank() && Objects.equals(m.getFname(), dataIn.getEmail()))).findFirst()
.filter(m -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname()
.equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom()) ||
Objects.equals(m.getFname(), dataIn.getEmail())).findFirst()
.orElseGet(() -> {
MembreModel mm = new MembreModel();
mm.setClub(clubModel.get());
@ -278,23 +226,16 @@ public class MembreService {
mm.setCountry("FR");
return mm;
});
if (model.getId() != null) {
LOGGER.debugf("updating -> %s", dataIn.toString());
} else {
LOGGER.debugf("creating -> %s", dataIn.toString());
}
if (model.getEmail() != null && !model.getEmail().isBlank()) {
if (model.getEmail() != null) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
throw new DBadRequestException("Email déja utiliser");
}
if (StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
throw new DBadRequestException("Email déja utiliser");
}
}
@ -303,7 +244,6 @@ public class MembreService {
if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
}
@ -379,7 +319,7 @@ public class MembreService {
return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0 && !membre.getEmail().isBlank())
if (c > 0)
throw new DBadRequestException("Email déjà utiliser");
})))
.chain(membreModel -> clubRepository.findById(membre.getClub())
@ -401,7 +341,7 @@ public class MembreService {
return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0 && !membre.getEmail().isBlank())
if (c > 0)
throw new DBadRequestException("Email déjà utiliser");
})))
.invoke(Unchecked.consumer(membreModel -> {

View File

@ -92,7 +92,7 @@ public class AffiliationRequestEndpoints {
@DELETE
@Path("/{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " +
"d'affiliation pour l'identifiant spécifié.")
@ -107,7 +107,7 @@ public class AffiliationRequestEndpoints {
if (o.getClub() == null && !securityCtx.roleHas("federation_admin"))
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()))
.chain(o -> service.deleteReqAffiliation(id, reason, securityCtx.roleHas("federation_admin")));
.chain(o -> service.deleteReqAffiliation(id, reason));
}
@PUT

View File

@ -1,8 +1,7 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*;
@ -13,24 +12,18 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("api/asso")
public class AssoEndpoints {
@RestClient
StateIdService stateIdService;
@RestClient
SirenService sirenService;
@GET
@Path("state_id/{stateId}")
@Path("siren/{siren}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<AssoData> getAssoInfo(@PathParam("stateId") String stateId) {
return ((stateId.charAt(0) == 'W') ? stateIdService.get_rna(stateId) : sirenService.get_unite(
stateId).chain(stateIdService::getAssoDataFromUnit)).onFailure().transform(throwable -> {
public Uni<UniteLegaleRoot> getInfoSiren(@PathParam("siren") String siren) {
return sirenService.get_unite(siren).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 404)
return new DNotFoundException("Service momentanément indisponible");
if (exception.getResponse().getStatus() == 400)
return new DNotFoundException("Asso introuvable");
return new DNotFoundException("Siret introuvable");
}
return throwable;
});

View File

@ -29,7 +29,6 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
@ -70,8 +69,7 @@ public class ClubEndpoints {
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleClubModel>> getAll() {
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).sorted(
Comparator.comparing(SimpleClubModel::getName)).toList());
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
}
@GET

View File

@ -58,15 +58,13 @@ public class MembreAdminEndpoints {
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Club à filter") @QueryParam("club") String club,
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
@Parameter(description = "État de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "État du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
@Parameter(description = "Ordre") @QueryParam("order") String order) {
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment, order, categorie);
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment);
}
@GET

View File

@ -50,15 +50,13 @@ public class MembreClubEndpoints {
@Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit,
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
@Parameter(description = "Ordre") @QueryParam("order") String order) {
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.search(limit, page - 1, search, licenceRequest, payment, order, categorie, securityCtx.getSubject());
return membreService.search(limit, page - 1, search, licenceRequest, payment, securityCtx.getSubject());
}
@GET

View File

@ -1,7 +1,6 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.quarkus.cache.CacheResult;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@ -16,6 +15,5 @@ public interface SirenService {
@GET
@Path("/v3/unites_legales/{SIREN}")
@CacheResult(cacheName = "AssoData_siren")
Uni<UniteLegaleRoot> get_unite(@PathParam("SIREN") String siren);
}
}

View File

@ -1,48 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.quarkus.cache.CacheResult;
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.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
public interface StateIdService {
@GET
@Path("/associations/{rna}")
@CacheResult(cacheName = "AssoData_rna")
Uni<AssoData> get_rna(@PathParam("rna") String rna);
default Uni<AssoData> getAssoDataFromUnit(UniteLegaleRoot u) {
AssoData assoData = new AssoData();
assoData.setSiren(u.getUnite_legale().getSiren());
assoData.setRna(u.getUnite_legale().getIdentifiant_association());
AssoData.Identite identite = new AssoData.Identite();
identite.setNom(u.getUnite_legale().getDenomination());
identite.setSiret_siege(u.getUnite_legale().getEtablissement_siege().getSiret());
assoData.setIdentite(identite);
AssoData.Address address = new AssoData.Address();
StringBuilder voie = new StringBuilder();
if (u.getUnite_legale().getEtablissement_siege().getNumero_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getNumero_voie()).append(' ');
if (u.getUnite_legale().getEtablissement_siege().getType_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getType_voie()).append(' ');
if (u.getUnite_legale().getEtablissement_siege().getLibelle_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getLibelle_voie()).append(' ');
address.setVoie(voie.toString().trim());
address.setComplement(u.getUnite_legale().getEtablissement_siege().getComplement_adresse());
address.setCode_postal(u.getUnite_legale().getEtablissement_siege().getCode_postal());
address.setCommune(
new AssoData.Commune(u.getUnite_legale().getEtablissement_siege().getLibelle_commune()));
assoData.setCoordonnees(new AssoData.Coordonnee(address));
return Uni.createFrom().item(assoData);
}
}

View File

@ -1,48 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@RegisterForReflection
public class AssoData {
String siren;
String rna;
Identite identite;
Coordonnee coordonnees;
@Data
@RegisterForReflection
public static class Identite {
String nom;
String siret_siege;
}
@Data
@RegisterForReflection
@NoArgsConstructor
@AllArgsConstructor
public static class Coordonnee {
Address adresse_gestion;
}
@Data
@RegisterForReflection
public static class Address {
String voie;
String complement;
String code_postal;
String pays;
Commune commune;
}
@Data
@RegisterForReflection
@NoArgsConstructor
@AllArgsConstructor
public static class Commune {
String nom;
}
}

View File

@ -17,9 +17,9 @@ import java.util.List;
@RegisterForReflection
public class RenewAffData {
String name;
String state_id;
Long siret;
String rna;
String address;
String contact;
int saison;
List<RenewMember> members;

View File

@ -14,8 +14,8 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
public class SimpleAffiliation {
@Schema(description = "L'identifiant de l'affiliation.", example = "1")
private Long id;
@Schema(description = "L'identifiant du club associé à l'affiliation si id > 0 sinon n° SIRET ou RNA du club.", example = "123")
private String club;
@Schema(description = "L'identifiant du club associé à l'affiliation.", example = "123")
private Long club;
@Schema(description = "La saison de l'affiliation.", example = "2022")
private int saison;
@Schema(description = "Indique si l'affiliation est validée ou non.", example = "true")
@ -27,7 +27,7 @@ public class SimpleAffiliation {
return new SimpleAffiliationBuilder()
.id(model.getId())
.club(String.valueOf(model.getClub().getId()))
.club(model.getClub().getId())
.saison(model.getSaison())
.validate(true)
.build();

View File

@ -36,8 +36,10 @@ public class SimpleClub {
private String contact_intern;
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
private String address;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
private String state_id;
@Schema(description = "RNA du club", example = "W123456789")
private String RNA;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
private Long SIRET;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
private Long no_affiliation;
@Schema(description = "Club international", example = "false")
@ -58,7 +60,8 @@ public class SimpleClub {
.training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern())
.state_id(model.getStateId())
.RNA(model.getRNA())
.SIRET(model.getSIRET())
.no_affiliation(model.getNo_affiliation())
.international(model.isInternational())
.address(model.getAddress())

View File

@ -20,8 +20,8 @@ public class SimpleClubList {
String name;
@Schema(description = "Pays du club", example = "FR")
String country;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
String state_id;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long siret;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation;
@ -29,7 +29,7 @@ public class SimpleClubList {
if (model == null)
return null;
return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getStateId(),
return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getSIRET(),
model.getNo_affiliation());
}
}

View File

@ -25,8 +25,10 @@ public class SimpleReqAffiliation {
Long club_no_aff;
@Schema(description = "Nom du club demander", example = "Association sportive")
String name;
@Schema(description = "Numéro SIRET ou RNA de l'association", example = "12345678901234")
String stateId;
@Schema(description = "Numéro SIRET de l'association", example = "12345678901234")
long siret;
@Schema(description = "Numéro RNA de l'association", example = "W123456789")
String RNA;
@Schema(description = "Adresse de l'association", example = "1 rue de l'exemple, 75000 Paris")
String address;
@Schema(description = "Email de contact de l'association", example = "test@test.fr")
@ -43,7 +45,8 @@ public class SimpleReqAffiliation {
return new SimpleReqAffiliation.SimpleReqAffiliationBuilder()
.id(model.getId())
.name(model.getName())
.stateId(model.getState_id())
.siret(model.getSiret())
.RNA(model.getRNA())
.address(model.getAddress())
.saison(model.getSaison())
.contact(model.getContact())

View File

@ -16,8 +16,8 @@ public class SimpleReqAffiliationResume {
Long id;
@Schema(description = "Le nom de l'association.", example = "Association sportive")
String name;
@Schema(description = "Le numéro SIRET ou RNA de l'association.", example = "12345678901234")
String stateId;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234")
long siret;
@Schema(description = "La saison de l'affiliation.", example = "2025")
int saison;
@ -25,10 +25,10 @@ public class SimpleReqAffiliationResume {
if (model == null)
return null;
return new SimpleReqAffiliationResumeBuilder()
return new SimpleReqAffiliationResume.SimpleReqAffiliationResumeBuilder()
.id(model.getId())
.name(model.getName())
.stateId(model.getState_id())
.siret(model.getSiret())
.saison(model.getSaison())
.build();
}

View File

@ -33,8 +33,8 @@ public class UniteLegaleRoot {
public String etat_administratif;
public String identifiant_association;
public String nic_siege;
public String nom;
public String nom_usage;
public Object nom;
public Object nom_usage;
public int nombre_periodes;
public String nomenclature_activite_principale;
public Object prenom_1;
@ -67,7 +67,7 @@ public class UniteLegaleRoot {
private Object code_pays_etranger_2;
private String code_postal;
private Object code_postal_2;
private String complement_adresse;
private Object complement_adresse;
private Object complement_adresse2;
private String date_creation;
private String date_debut;

View File

@ -11,7 +11,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType;
@Getter
@ToString(exclude = {"status", "logo"})
@ToString
public class AffiliationRequestForm {
@Schema(description = "L'identifiant de l'affiliation. (null si nouvelle demande d'affiliation)")
@FormParam("id")
@ -21,9 +21,13 @@ public class AffiliationRequestForm {
@FormParam("name")
private String name = null;
@Schema(description = "Le numéro SIRET/RNA de l'association.", example = "12345678901234", required = true)
@FormParam("state_id")
private String state_id = null;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234", required = true)
@FormParam("siret")
private Long siret = null;
@Schema(description = "Le numéro RNA de l'association. (peut être null)", example = "W123456789")
@FormParam("rna")
private String rna = null;
@Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true)
@FormParam("adresse")
@ -110,7 +114,8 @@ public class AffiliationRequestForm {
public AffiliationRequestModel toModel() {
AffiliationRequestModel model = new AffiliationRequestModel();
model.setName(this.getName());
model.setState_id(this.getState_id());
model.setSiret(this.getSiret());
model.setRNA(this.getRna());
model.setAddress(this.getAdresse());
model.setSaison(this.getSaison());
model.setContact(this.getContact());

View File

@ -4,10 +4,12 @@ import fr.titionfire.ffsaf.utils.RoleAsso;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MediaType;
import lombok.Getter;
import lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType;
@Getter
@ToString
public class AffiliationRequestSaveForm {
@Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true)
@FormParam("id")
@ -17,9 +19,13 @@ public class AffiliationRequestSaveForm {
@FormParam("name")
private String name = null;
@Schema(description = "Le numéro SIRET ou RNA de l'association.", example = "12345678901234", required = true)
@FormParam("state_id")
private String state_id = null;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234", required = true)
@FormParam("siret")
private Long siret = null;
@Schema(description = "Le numéro RNA de l'association. (peut être null)", example = "W123456789")
@FormParam("rna")
private String rna = null;
@Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true)
@FormParam("address")
@ -165,38 +171,4 @@ public class AffiliationRequestSaveForm {
}
}
}
@Override
public String toString() {
return "AffiliationRequestSaveForm{" +
"id=" + id +
", name='" + name + '\'' +
", state_id=" + state_id +
", address='" + address + '\'' +
", contact='" + contact + '\'' +
", status_len=" + status.length +
", logo_len=" + logo.length +
", m1_mode=" + m1_mode +
", m1_role=" + m1_role +
", m1_lincence='" + m1_lincence + '\'' +
", m1_lname='" + m1_lname + '\'' +
", m1_fname='" + m1_fname + '\'' +
", m1_email='" + m1_email + '\'' +
", m1_email_mode=" + m1_email_mode +
", m2_mode=" + m2_mode +
", m2_role=" + m2_role +
", m2_lincence='" + m2_lincence + '\'' +
", m2_lname='" + m2_lname + '\'' +
", m2_fname='" + m2_fname + '\'' +
", m2_email='" + m2_email + '\'' +
", m2_email_mode=" + m2_email_mode +
", m3_mode=" + m3_mode +
", m3_role=" + m3_role +
", m3_lincence='" + m3_lincence + '\'' +
", m3_lname='" + m3_lname + '\'' +
", m3_fname='" + m3_fname + '\'' +
", m3_email='" + m3_email + '\'' +
", m3_email_mode=" + m3_email_mode +
'}';
}
}

View File

@ -43,9 +43,13 @@ public class FullClubForm {
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris", required = true)
private String address = null;
@FormParam("state_id")
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234", required = true)
private String state_id = null;
@FormParam("rna")
@Schema(description = "RNA du club", example = "W123456789")
private String rna = null;
@FormParam("siret")
@Schema(description = "Numéro SIRET du club", example = "12345678901234", required = true)
private String siret = null;
@FormParam("international")
@Schema(description = "Club international", example = "false", required = true)

View File

@ -43,7 +43,6 @@ notif.affRequest.mail=
siren-api.key=siren-ap
quarkus.rest-client."fr.titionfire.ffsaf.rest.client.SirenService".url=https://data.siren-api.fr/
quarkus.rest-client."fr.titionfire.ffsaf.rest.client.StateIdService".url=https://www.data-asso.fr/api/
#Login
quarkus.oidc.token-state-manager.split-tokens=true

View File

@ -38,22 +38,18 @@ async function getData() {
let icon = null;
if (d.uuid !== null) {
try {
const img = await getMeta(`${api_url}/api/club/${d.uuid}/logo`);
let ratio = img.naturalHeight / img.naturalWidth;
const img = await getMeta(`${api_url}/api/club/${d.uuid}/logo`);
let ratio = img.naturalHeight / img.naturalWidth;
icon = L.icon({
iconUrl: `${api_url}/api/club/${d.uuid}/logo`,
icon = L.icon({
iconUrl: `${api_url}/api/club/${d.uuid}/logo`,
iconSize: [50, 50 * ratio], // size of the icon
//shadowSize: [50, 64], // size of the shadow
iconAnchor: [25, 50 * ratio], // point of the icon which will correspond to marker's location
//shadowAnchor: [4, 62], // the same for the shadow
popupAnchor: [0, -50 * ratio] // point from which the popup should open relative to the iconAnchor
});
}catch (e) {
console.log("Error loading image for club", d.name, e);
}
iconSize: [50, 50 * ratio], // size of the icon
//shadowSize: [50, 64], // size of the shadow
iconAnchor: [25, 50 * ratio], // point of the icon which will correspond to marker's location
//shadowAnchor: [4, 62], // the same for the shadow
popupAnchor: [0, -50 * ratio] // point from which the popup should open relative to the iconAnchor
});
}
for (const m of d.training_location) {

View File

@ -1,11 +1,11 @@
import {useEffect, useRef} from 'react'
import {Nav} from "./components/Nav.jsx";
import {createBrowserRouter, Outlet, RouterProvider, useLocation, useRouteError} from "react-router-dom";
import {createBrowserRouter, Outlet, RouterProvider, useRouteError} from "react-router-dom";
import {Home} from "./pages/Homepage.jsx";
import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx";
import {AuthCallback} from "./components/auhCallback.jsx";
import {KeycloakContextProvider, useAuth, useAuthDispatch} from "./hooks/useAuth.jsx";
import {check_validity, login} from "./utils/auth.js";
import {KeycloakContextProvider, useAuthDispatch} from "./hooks/useAuth.jsx";
import {check_validity} from "./utils/auth.js";
import {ToastContainer} from "react-toastify";
import './App.css'
@ -15,8 +15,6 @@ import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx";
import {MePage} from "./pages/MePage.jsx";
import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx";
import {getResultChildren, ResultRoot} from "./pages/result/ResultRoot.jsx";
import {FallingLines} from "react-loader-spinner";
import {getResultChildren, ResultRoot} from "./pages/result/ResultRoot.jsx";
const router = createBrowserRouter([
{
@ -121,43 +119,6 @@ function Root() {
theme="light"
transition: Flip
/>
<ReAuthMsg/>
</div>
</>
}
function ReAuthMsg() {
const {is_authenticated} = useAuth()
const location = useLocation()
const notAuthPaths = [
/^\/$/s,
/^\/affiliation(\/)?$/s,
/^\/affiliation\/ok(\/)?$/s,
/^\/complete\/auth.*$/s
]
if (is_authenticated || notAuthPaths.some(r => r.test(location.pathname)))
return <></>
return <>
<div className="overlayBG" style={{position: 'fixed'}}>
<div className="overlayContent" onClick={(e) => {
e.stopPropagation()
}}>
<div className="card">
<div className="card-header">
<h5>Session expirée</h5>
</div>
<div className="card-body">
<p className="card-text">Votre session a expirée, veuillez vous reconnecter pour continuer à
utiliser l'application.</p>
</div>
<div className="card-footer">
<button className="btn btn-primary" onClick={() => login()} style={{marginRight: "0.5em"}}>Se reconnecter</button>
<a className="btn btn-secondary" href="/">Accueil</a>
</div>
</div>
</div>
</div>
</>
}

View File

@ -38,7 +38,7 @@ export function HoraireEditor({data}) {
return <div className="row mb-3">
<input name="training_day_time" value={JSON.stringify(out_data)} readOnly hidden/>
<span className="input-group-text">Horaires d'entraînements</span>
<span className="input-group-text">Horaires d'entrainements</span>
<ul className="list-group form-control">
{state.map((d, index) => {
return <div key={index} className="input-group">
@ -92,4 +92,4 @@ export function HoraireEditor({data}) {
</div>
</ul>
</div>
}
}

View File

@ -42,7 +42,7 @@ export function LocationEditor({data, setModal, sendData}) {
return <div className="row mb-3">
<input name="training_location" value={JSON.stringify(out_data)} readOnly hidden/>
<span className="input-group-text">Lieux d'entraînements</span>
<span className="input-group-text">Lieux d'entrainements</span>
<ul className="list-group form-control">
{state.map((d, index) => {
return <div key={index} className="input-group">

View File

@ -1,5 +1,5 @@
import {useEffect, useState} from "react";
import {getCategoryFormBirthDate, getCatName} from "../utils/Tools.js";
import {getCategoryFormBirthDate} from "../utils/Tools.js";
import {useCountries} from "../hooks/useCountries.jsx";
export function BirthDayField({inti_date, inti_category, required = true}) {
@ -27,7 +27,7 @@ export function BirthDayField({inti_date, inti_category, required = true}) {
<div className="input-group mb-3">
<span className="input-group-text" id="category">Catégorie</span>
<input type="text" className="form-control" placeholder="" name="category"
aria-label="category" value={category ? getCatName(category) : ""} aria-describedby="category"
aria-label="category" value={category ? category : ""} aria-describedby="category"
disabled/>
{canUpdate && <button className="btn btn-outline-secondary" type="button" id="button-addon1"
onClick={updateCat}>Mettre à jours</button>}
@ -88,14 +88,13 @@ export function CountryList({name, text, value, values = undefined, disabled = f
</div>
}
export function TextField({name, text, value, placeholder, type = "text", disabled = false, required = true, ttip = null}) {
return <div className="row mb-3">
<div className="input-group">
export function TextField({name, text, value, placeholder, type = "text", disabled = false, required = true}) {
return <div className="row">
<div className="input-group mb-3">
<span className="input-group-text" id={name}>{text}</span>
<input type={type} className="form-control" placeholder={placeholder ? placeholder : text} aria-label={name}
name={name} aria-describedby={name} defaultValue={value} disabled={disabled} required={required}/>
</div>
{ttip}
</div>
}

View File

@ -72,7 +72,7 @@ function ClubMenu() {
</div>
<ul className="dropdown-menu">
<li className="nav-item"><NavLink className="nav-link" to="/club/me">Mon club</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/club/member">Membres</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/club/member">Member</NavLink></li>
</ul>
</li>
}
@ -88,7 +88,7 @@ function AdminMenu() {
Administration
</div>
<ul className="dropdown-menu">
<li className="nav-item"><NavLink className="nav-link" to="/admin/member">Membres</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/member">Member</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/club">Club</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/stats">Statistiques</NavLink></li>
</ul>

View File

@ -7,8 +7,8 @@ const removeDiacritics = str => {
}
export function SearchBar({search, defaultValue = ""}) {
const [searchInput, setSearchInput] = useState(defaultValue);
export function SearchBar({search}) {
const [searchInput, setSearchInput] = useState("");
const handelChange = (e) => {
setSearchInput(e.target.value);
@ -40,4 +40,4 @@ export function SearchBar({search, defaultValue = ""}) {
</button>
</div>
</div>
}
}

View File

@ -3,7 +3,7 @@ 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'];
const notUpperCase = ["de", "la", "le", "les", "des", "du", "d'", "l'", "sur"];
function formatAdresse(data) {
const words = data.split(" ");
@ -16,17 +16,28 @@ function formatAdresse(data) {
}).join(" ");
}
function reconstruireAdresse2(infos) {
function reconstruireAdresse(infos) {
console.log(infos);
let adresseReconstruite = "";
if (infos?.complement)
adresseReconstruite += formatAdresse(infos.complement) + ', ';
if(infos.numero_voie === null){
if (infos.complement_adresse) {
adresseReconstruite += formatAdresse(infos.complement_adresse) + ', ';
}
}else{
adresseReconstruite += infos.numero_voie + ' ';
}
adresseReconstruite += formatAdresse(infos.voie) + ', ';
adresseReconstruite += infos.code_postal + ' ' + infos.commune.nom + ', ';
adresseReconstruite += formatAdresse(infos.type_voie) + ' ';
adresseReconstruite += formatAdresse(infos.libelle_voie) + ', ';
adresseReconstruite += infos.code_postal + ' ' + infos.libelle_commune + ', ';
if (infos?.pays)
adresseReconstruite += formatAdresse(infos.pays) + ', ';
if (infos.complement_adresse && infos.numero_voie !== null) {
adresseReconstruite += formatAdresse(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);
@ -35,13 +46,6 @@ function reconstruireAdresse2(infos) {
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();
@ -74,7 +78,8 @@ export function DemandeAff() {
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)
formData.append("rna", event.target.rna?.value)
formData.append("siret", event.target.siret?.value)
let error = false;
for (let i = 1; i <= 3; i++) {
@ -140,7 +145,7 @@ export function DemandeAff() {
}
return <div>
<h1>Demande d'affiliation {getSaisonToAff() + "-" + (getSaisonToAff() + 1)}</h1>
<h1>Demande d'affiliation</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 :
@ -207,23 +212,21 @@ export function DemandeAff() {
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 [siret, setSiret] = useState(initData.siret ? String(initData.siret) : "")
const [rna, setRna] = useState(initData.rna ? initData.rna : "")
const [rnaEnable, setRnaEnable] = useState(false)
const [adresse, setAdresse] = useState(initData.address ? initData.address : "")
const [saison, setSaison] = useState(initData.saison ? initData.saison : getSaison())
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");
const fetchSiret = () => {
if (siret.length < 14) {
toast.error("Le SIRET doit contenir 14 chiffres")
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}`),
apiAxios.get(`asso/siren/${siret.substring(0, siret.length - 5)}`),
{
pending: "Recherche de l'association en cours",
success: "Association trouvée avec succès 🎉",
@ -234,14 +237,34 @@ function AssoInfo({initData, needFile}) {
}
}
).then(data => {
const data2 = data.data
setDenomination(data2.identite.nom)
const data2 = data.data.unite_legale
setDenomination(data2.denomination)
setRnaEnable(data2.identifiant_association === null)
setRna(data2.identifiant_association ? data2.identifiant_association : "")
if (!initData.saison || adresse === "")
setAdresse(reconstruireAdresse2(data2.coordonnees.adresse_gestion))
setAdresse(reconstruireAdresse(data2.etablissement_siege))
})
}
const currentSaison = getSaison();
return <>
<input name="saison" value={getSaisonToAff()} readOnly hidden/>
<div className="input-group mb-3">
<div className="input-group-text">
<input className="form-check-input mt-0" type="radio" value={currentSaison} aria-label={currentSaison + "-" + (currentSaison + 1)}
name={"saison"} checked={saison === currentSaison}
onChange={e => setSaison(Number(e.target.value))}/>
{currentSaison + "-" + (currentSaison + 1)}
</div>
<span className="input-group-text">OU</span>
<div className="input-group-text">
<input className="form-check-input mt-0" type="radio" value={currentSaison + 1}
aria-label={(currentSaison + 1) + "-" + (currentSaison + 2)}
name={"saison"} checked={saison === currentSaison + 1}
onChange={e => setSaison(Number(e.target.value))}/>
{(currentSaison + 1) + "-" + (currentSaison + 2)}
</div>
</div>
<div className="input-group mb-3">
<span className="input-group-text" id="basic-addon1">Nom de l'association*</span>
@ -251,21 +274,28 @@ function AssoInfo({initData, needFile}) {
</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)}/>
<span className="input-group-text">N° SIRET*</span>
<input type="number" className="form-control" placeholder="siret" name="siret" required value={siret} disabled={!needFile}
onChange={e => setSiret(e.target.value)}/>
<button className="btn btn-outline-secondary" type="button" id="button-addon2"
onClick={fetchStateId} hidden={false}>Rechercher
onClick={fetchSiret}>Rechercher
</button>
</div>
<div className="input-group mb-3" hidden={false}>
<div className="input-group mb-3">
<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="input-group mb-3">
<span className="input-group-text" id="basic-addon1">RNA</span>
<input type="text" className="form-control" placeholder="RNA" aria-label="RNA"
aria-describedby="basic-addon1"
disabled={!rnaEnable} name="rna" value={rna} onChange={e => setRna(e.target.value)}/>
</div>
<div className="mb-3">
<div className="input-group">
<span className="input-group-text" id="basic-addon1">Adresse administrative*</span>

View File

@ -5,22 +5,22 @@ export const Home = () => {
return <>
<div className="container">
<div style={{textAlign: "center", margin: "2em"}}>
<h1 className="text-green-800 text-4xl">Bienvenue sur lintranet de la Fédération France Soft Armored Fighting</h1>
<h1 className="text-green-800 text-4xl">Bienvenu sur l'intranet de Fédération Française de Soft Armored Fighting</h1>
</div>
<div className="row" style={{marginTop: "3em"}}>
<div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}>
<div className="align-content-center"
style={{textAlign: "center", backgroundColor: "#FFFFFF79", padding: "1em 1em 0em 1em", borderRadius: "3em 3em 0 0"}}>
<h2><FontAwesomeIcon icon={faUser} size="2xl"/></h2>
<h2>Pour les licenciés</h2>
<h2>Pour les combatants</h2>
</div>
<p style={{padding: "0.5em 1em 0.5em 1em"}}>
Vous y retrouverez toutes vos informations ainsi que l'état de votre inscription à la fédération. Vous pouvez également
télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi que consulter vos résultats sous réserve que
le club organisateur les ait renseignés. <br/>
télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi qu'en consultée vos résultats sous réserve
que le club organisateur les ait renseignés. <br/>
<br/>
Lors de votre première inscription, vous recevrez un email contenant vos informations d'identification, ce mail sera envoyé
une fois votre licence validée par le secrétariat.
Lors de votre première inscription, vous réservez un email contenant vos
informations d'identification sur ce site, ce mail sera envoyé une fois votre inscription validée par nos soins.
</p>
</div>
<div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}>
@ -30,12 +30,12 @@ export const Home = () => {
<h2>Pour les clubs</h2>
</div>
<p style={{padding: "0.5em 1em 0.5em 1em"}}>
C'est ici que vous pouvez prendre les licences fédérales pour vos adhérents, que vous pouvez demander ou renouveler votre
affiliation, renseigner vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur
le site ffsaf.fr.<br/>
C'est ici que vous pouvez faire l'inscription de vos membres à la fédération, que vous pouvez demander renouveler votre
demande d'affiliation, renseigné vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur le
site ffsaf.fr.<br/>
Vous aurez par ailleurs la possibilité de publier des formulaires d'inscriptions pour vos compétitions ainsi
que d'enregistrer les résultats.<br/><br/>
Vous n'êtes pas encore affilié à la fédération ? Cliquez <a href="/affiliation">içi</a> pour faire votre première demande.
que d'un publié les résultats.<br/><br/>
Vous n'étes pas encore affilié à la fédération ? Vous pouvez faire une demande d'affiliation en cliquant <a href="/affiliation">içi</a>.
</p>
</div>
</div>
@ -55,4 +55,4 @@ export const Home = () => {
}}>
</div>
</>
};
};

View File

@ -6,60 +6,38 @@ import {useEffect, useState} from "react";
import {useLocation, useNavigate} from "react-router-dom";
import {Checkbox} from "../components/MemberCustomFiels.jsx";
import * as Tools from "../utils/Tools.js";
import {apiAxios, errFormater, getCatName} from "../utils/Tools.js";
import {apiAxios, errFormater} from "../utils/Tools.js";
import {toast} from "react-toastify";
import {SearchBar} from "../components/SearchBar.jsx";
import * as XLSX from "xlsx-js-style";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEuroSign} from "@fortawesome/free-solid-svg-icons";
let lastRefresh = "";
export function MemberList({source}) {
const {hash} = useLocation();
const navigate = useNavigate();
let page = Number(hash.substring(1));
page = (page > 0) ? page : 1;
const [memberData, setMemberData] = useState([]);
const [licenceData, setLicenceData] = useState([]);
const [showLicenceState, setShowLicenceState] = useState(false);
const setFilter = (filter) => {
navigate("#" + encodeURI(JSON.stringify(filter)))
}
const filter = {
page: 1,
search: "",
club: "",
licenceRequest: 4,
payment: 2,
order: "",
categorie: "",
...JSON.parse(decodeURI(hash.substring(1)) || "{}"),
}
const [clubFilter, setClubFilter] = useState("");
const [stateFilter, setStateFilter] = useState(4)
const [lastSearch, setLastSearch] = useState("");
const [paymentFilter, setPaymentFilter] = useState(2);
const setLoading = useLoadingSwitcher()
const {
data,
error,
refresh
} = useFetch(`/member/find/${source}?page=${filter.page}&search=${filter.search}&club=${filter.club}&licenceRequest=${filter.licenceRequest}&payment=${filter.payment}&order=${filter.order}&categorie=${filter.categorie}`, setLoading, 1)
const {data, error, refresh} = useFetch(`/member/find/${source}?page=${page}&licenceRequest=${stateFilter}&payment=${paymentFilter}`, setLoading, 1)
useEffect(() => {
const tmp = `/member/find/${source}?page=${filter.page}&search=${filter.search}&club=${filter.club}&licenceRequest=${filter.licenceRequest}&payment=${filter.payment}&order=${filter.order}&categorie=${filter.categorie}`;
if (tmp === lastRefresh)
return;
lastRefresh = tmp
refresh(lastRefresh);
}, [hash]);
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}`);
}, [hash, clubFilter, stateFilter, lastSearch, paymentFilter]);
useEffect(() => {
if (!data)
return;
if (data.page_count < filter.page) {
setFilter({...filter, page: 1});
}
const data2 = [];
for (const e of data.result) {
data2.push({
@ -96,19 +74,19 @@ export function MemberList({source}) {
}, [showLicenceState]);
const search = (search) => {
if (search === filter.search)
if (search === lastSearch)
return;
setFilter({...filter, search: search});
setLastSearch(search);
}
return <>
<div>
<div className="row">
<div className="col-lg-9">
<SearchBar search={search} defaultValue={filter.search}/>
<SearchBar search={search}/>
{data
? <MakeCentralPanel data={data} visibleMember={memberData} navigate={navigate} showLicenceState={showLicenceState}
page={filter.page} setPage={e => setFilter({...filter, page: e})} source={source}/>
page={page} source={source}/>
: error
? <AxiosError error={error}/>
: <Def/>
@ -124,28 +102,13 @@ export function MemberList({source}) {
<button className="btn btn-primary" onClick={() => navigate("pay")} style={{marginTop: "0.5rem"}}>Paiement des
licences</button>}
</div>
<div className="card mb-4">
<div className="card-header">Trie</div>
<div className="card-body">
<OrderBar onOrderChange={e => setFilter({...filter, order: e.join(",")})} defaultValues={filter.order} source={source}/>
</div>
</div>
<div className="card mb-4">
<div className="card-header">Filtre</div>
<div className="card-body">
<FiltreBar showLicenceState={showLicenceState}
setShowLicenceState={setShowLicenceState}
clubFilter={filter.club}
setClubFilter={e => setFilter({...filter, club: e})}
source={source}
stateFilter={filter.licenceRequest}
setStateFilter={e => setFilter({...filter, licenceRequest: e})}
paymentFilter={filter.payment}
setPaymentFilter={e => setFilter({...filter, payment: e})}
catFilter={filter.categorie}
setCatFilter={e => setFilter({...filter, categorie: e})}/>
<FiltreBar showLicenceState={showLicenceState} setShowLicenceState={setShowLicenceState} data={data}
clubFilter={clubFilter} setClubFilter={setClubFilter} source={source}
stateFilter={stateFilter} setStateFilter={setStateFilter} paymentFilter={paymentFilter}
setPaymentFilter={setPaymentFilter}/>
</div>
</div>
@ -206,10 +169,6 @@ function FileOutput() {
certifDate: e.certif ? new Date(e.certif.split("¤")[1]) : '',
}
if (isNaN(tmp.certifDate) || tmp.certifDate === 'NaN') {
tmp.certifDate = ''
}
//tmp.birthdate.setMilliseconds(0);
//tmp.birthdate.setSeconds(0);
//tmp.birthdate.setMinutes(0);
@ -373,11 +332,11 @@ function FileInput() {
);
}
function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page, setPage, source}) {
function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page, source}) {
const pages = []
for (let i = 1; i <= data.page_count; i++) {
pages.push(<li key={i} className={"page-item " + ((page === i) ? "active" : "")}>
<span className="page-link" onClick={() => setPage(i)}>{i}</span>
<span className="page-link" onClick={() => navigate("#" + i)}>{i}</span>
</li>);
}
@ -394,10 +353,10 @@ function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page
<nav aria-label="Page navigation">
<ul className="pagination justify-content-center">
<li className={"page-item" + ((page <= 1) ? " disabled" : "")}>
<span className="page-link" onClick={() => setPage(page - 1)}>&laquo;</span></li>
<span className="page-link" onClick={() => navigate("#" + (page - 1))}>&laquo;</span></li>
{pages}
<li className={"page-item" + ((page >= data.page_count) ? " disabled" : "")}>
<span className="page-link" onClick={() => setPage(page + 1)}>&raquo;</span></li>
<span className="page-link" onClick={() => navigate("#" + (page + 1))}>&raquo;</span></li>
</ul>
</nav>
</div>
@ -406,146 +365,45 @@ function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page
function MakeRow({member, showLicenceState, navigate, source}) {
const rowContent = <>
<div className="row" style={{padding: "0.6em 0"}}>
<div className="row">
<span className="col-auto">{(member.licence_number ? String(member.licence_number).padStart(5, '0') : "-------") + " "}
{(showLicenceState && member.licence != null && member.licence.pay) ? <FontAwesomeIcon icon={faEuroSign}/> : <>&nbsp;&nbsp;</>}</span>
{(showLicenceState && member.licence != null && member.licence.pay)? <FontAwesomeIcon icon={faEuroSign}/> : <>&nbsp;&nbsp;</>}</span>
<div className="ms-2 col-auto">
<div className="fw-bold">{member.fname} {member.lname}</div>
</div>
</div>
<div style={{verticalAlign: "center", margin: "auto 0"}}>
{source === "club" ?
<small>{getCatName(member.categorie)}</small>
: <div style={{
textAlign: "right",
fontSize: "small"
}}>{member.club?.name || "Sans club"}<br/><small>{getCatName(member.categorie)}</small></div>}
</div>
{source === "club" ?
<small>{member.categorie}</small>
: <small>{member.club?.name || "Sans club"}</small>}
</>
if (showLicenceState && member.licence != null) {
return <a className={"list-group-item d-flex justify-content-between align-items-start list-group-item-action list-group-item-"
+ (member.licence.validate ? "success" : (member.licence.certificate.length > 1 ? "warning" : "danger"))}
style={{padding: "0 1em"}}
onClick={e => {
e.preventDefault();
navigate("" + member.id)
}}
href={"member/" + member.id}>
{rowContent}
</a>
return <div
className={"list-group-item d-flex justify-content-between align-items-start list-group-item-action list-group-item-"
+ (member.licence.validate ? "success" : (member.licence.certificate.length > 1 ? "warning" : "danger"))}
onClick={() => navigate("" + member.id)}>{rowContent}</div>
} else {
return <a className="list-group-item d-flex justify-content-between align-items-start list-group-item-action"
style={{padding: "0 1em"}}
onClick={e => {
e.preventDefault();
navigate("" + member.id)
}}
href={"member/" + member.id}>
return <div className="list-group-item d-flex justify-content-between align-items-start list-group-item-action"
onClick={() => navigate("" + member.id)}>
{rowContent}
</a>
}
}
function OrderBar({onOrderChange, defaultValues = "", source}) {
const [orderCriteria, setOrderCriteria] = useState([...defaultValues.split(",").filter(c => c !== ''), '']);
const handleChange = (index, value) => {
const newCriteria = [...orderCriteria];
newCriteria[index] = value;
// Si le dernier critère est rempli, on en ajoute un nouveau
if (index === orderCriteria.length - 1 && value !== '') {
newCriteria.push('');
}
// Si un critère (sauf le premier) est réinitialisé, on le supprime
else if (value === '' && (index !== 0 || orderCriteria.length > 1)) {
newCriteria.splice(index, 1);
}
setOrderCriteria(newCriteria);
onOrderChange(newCriteria.filter(c => c !== ''));
};
// Liste de toutes les options possibles
const allOptions = [
{value: 'lname n', label: 'Nom ↓', base: 'lname'},
{value: 'lname i', label: 'Nom ↑', base: 'lname'},
{value: 'fname n', label: 'Prénom ↓', base: 'fname'},
{value: 'fname i', label: 'Prénom ↑', base: 'fname'},
{value: 'categorie n', label: 'Catégorie ↓', base: 'categorie'},
{value: 'categorie i', label: 'Catégorie ↑', base: 'categorie'},
{value: 'licence n', label: 'Licence ↓', base: 'licence'},
{value: 'licence i', label: 'Licence ↑', base: 'licence'},
];
if (source === "admin") {
allOptions.push(
{value: 'club.name n', label: 'Club ↓', base: 'club.name'},
{value: 'club.name i', label: 'Club ↑', base: 'club.name'},
);
}
return (
<div className="mb-3">
{orderCriteria.map((criteria, index) => {
// Récupère les bases des critères déjà sélectionnés (sauf le courant)
const usedBases = orderCriteria
.filter((c, i) => c !== '' && i !== index)
.map(c => allOptions.find(o => o.value === c)?.base);
// Filtre les options disponibles
const availableOptions = allOptions.filter(option =>
!usedBases.includes(option.base) || option.value === criteria
);
return (
<select
key={index}
className="form-select mb-2"
value={criteria}
onChange={(e) => handleChange(index, e.target.value)}
>
<option value="">----</option>
{availableOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
);
})}
</div>
);
}
}
function FiltreBar({
showLicenceState,
setShowLicenceState,
clubFilter,
setClubFilter,
source,
stateFilter,
setStateFilter,
paymentFilter,
setPaymentFilter,
catFilter,
setCatFilter,
}) {
let allClub = []
function FiltreBar({showLicenceState, setShowLicenceState, data, clubFilter, setClubFilter, source, stateFilter, setStateFilter, paymentFilter, setPaymentFilter}) {
useEffect(() => {
if (!data)
return;
allClub.push(...data.result.map((e) => e.club?.name))
allClub = allClub.filter((value, index, self) => self.indexOf(value) === index).filter(value => value != null).sort()
}, [data]);
return <div>
<div className="mb-3">
<Checkbox value={showLicenceState} onChange={setShowLicenceState} label="Afficher l'état des licences"/>
</div>
<div className="mb-3">
<select className="form-select" value={catFilter} onChange={event => setCatFilter(event.target.value)}>
<option value="">--- toute les catégories ---</option>
{Tools.CatList.map(cat => (
<option key={cat} value={cat}>{getCatName(cat)}</option>
))}
</select>
</div>
{source !== "club" && <ClubSelectFilter clubFilter={clubFilter} setClubFilter={setClubFilter}/>}
<div className="mb-3">
<select className="form-select" value={stateFilter} onChange={event => setStateFilter(Number(event.target.value))}>

View File

@ -68,7 +68,7 @@ function MakeRow({request, navigate}) {
<div className="ms-2 col-auto">
<div className="fw-bold">{request.name}</div>
</div>
<small style={{textAlign: 'right'}}>{request.saison}-{request.saison + 1}<br/>{request.state_id}</small>
<small style={{textAlign: 'right'}}>{request.saison}-{request.saison + 1}<br/>{request.siret}</small>
</div>
}
@ -104,4 +104,4 @@ function Def() {
<li className="list-group-item"><ThreeDots/></li>
<li className="list-group-item"><ThreeDots/></li>
</div>
}
}

View File

@ -8,6 +8,7 @@ import {RoleList, TextField} from "../../../components/MemberCustomFiels.jsx";
import {useEffect, useRef, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
const vite_url = import.meta.env.VITE_URL;
@ -66,7 +67,8 @@ function Content({data, refresh}) {
formData.append('id', data.id);
formData.append('name', event.target.name.value);
formData.append('state_id', event.target.state_id.value);
formData.append('siret', event.target.siret.value);
formData.append('rna', event.target.rna.value);
formData.append('address', event.target.address.value);
formData.append('contact', event.target.contact.value);
@ -164,7 +166,7 @@ function Content({data, refresh}) {
<input name="id" value={data.id} readOnly hidden/>
<div className="card-header">Demande d'affiliation</div>
<div className="card-body text-center">
{data.club && <h5>Ce club a déjà été affilié (affiliation n°{data.club_no_aff})</h5>}
{data.club && <h5>Ce club a déjà ete affilier (affiliation n°{data.club_no_aff})</h5>}
<h4 id="saison">Saison {data.saison}-{data.saison + 1}</h4>
<div className="row mb-3">
@ -176,7 +178,8 @@ function Content({data, refresh}) {
{data.club && <div className="form-text" id="name">Ancien nom: {data.club_name}</div>}
</div>
<TextField name="state_id" text="SIRET ou RNA" value={data.stateId} disabled={true}/>
<TextField type="number" name="siret" text="SIRET" value={data.siret} disabled={true}/>
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
<TextField name="address" text="Adresse" value={data.address}/>
<TextField name="contact" text="Contact administratif" value={data.contact}/>

View File

@ -40,7 +40,7 @@ export function ClubList() {
country: e.country,
siret: e.siret,
no_affiliation: e.no_affiliation,
affiliation: showAffiliationState ? affiliationData.find(aff => (aff.id >= 0) ? Number(aff.club) === e.id : aff.club === e.state_id) : null
affiliation: showAffiliationState ? affiliationData.find(aff => (aff.id >= 0) ? aff.club === e.id : aff.club === e.siret) : null
})
}
setClubData(data2);
@ -197,4 +197,4 @@ function Def() {
<li className="list-group-item"><ThreeDots/></li>
<li className="list-group-item"><ThreeDots/></li>
</div>
}
}

View File

@ -130,7 +130,8 @@ function InformationForm({data}) {
</div>
</div>
{!switchOn && <>
<TextField name="state_id" text="SIRET ou RNA" value={data.state_id} required={false}/>
<TextField name="siret" text="SIRET" value={data.siret} required={false} type="number"/>
<TextField name="rna" text="RNA" value={data.rna} required={false}/>
<TextField name="contact_intern" text="Contact interne" value={data.contact_intern} required={false}
placeholder="example@test.com"/>
<TextField name="address" text="Adresse administrative" value={data.address} required={false}
@ -171,7 +172,6 @@ function InformationForm({data}) {
export function BureauCard({clubData}) {
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/club/desk/${clubData.id}`, setLoading, 1)
const navigate = useNavigate();
return <>
<div className="card mb-4">
@ -179,8 +179,7 @@ export function BureauCard({clubData}) {
<div className="card-body">
<ul className="list-group">
{data && data.map((d, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start list-group-item-action"
onClick={__ => navigate(`/admin/member/${d.id}`)}>
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
<div className="me-auto"><small>{d.role}</small><br/>{d.lname} {d.fname}</div>
</div>
})}
@ -189,4 +188,4 @@ export function BureauCard({clubData}) {
</div>
{error && <AxiosError error={error}/>}
</>
}
}

View File

@ -84,7 +84,8 @@ function InformationForm() {
</div>
</div>
{!switchOn && <>
<TextField name="state_id" text="SIRET ou RNA" required={false}/>
<TextField name="siret" text="SIRET" required={false} type="number"/>
<TextField name="rna" text="RNA" required={false}/>
<TextField name="contact_intern" text="Contact interne" required={false} placeholder="example@test.com"/>
<TextField name="address" text="Adresse administrative" required={false} placeholder="Adresse administrative"/>

View File

@ -34,13 +34,13 @@ export function MemberPage() {
}
}
).then(_ => {
navigate(-1)
navigate("/admin/member")
})
}
return <>
<h2>Page membre</h2>
<button type="button" className="btn btn-link" onClick={() => navigate(-1)}>
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
&laquo; retour
</button>
{data

View File

@ -23,7 +23,7 @@ export function ClubRoot() {
return <>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap'}}>
<h3 style={{marginLeft: '0.75em'}}>Club: {club}</h3></div>
<h1>Espace club</h1><h3 style={{marginLeft: '0.75em'}}>{club}</h3></div>
<LoadingProvider>
<Outlet/>
</LoadingProvider>

View File

@ -1,11 +1,12 @@
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {useState} from "react";
import {useEffect, useReducer, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEye, faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {faEye, faFilePdf, faPen} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, getSaison} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
import {useNavigate} from "react-router-dom";
const vite_url = import.meta.env.VITE_URL;
@ -41,8 +42,8 @@ export function AffiliationCard({clubData}) {
<a href={`${vite_url}/api/club/me/affiliation`} target='#'>
<button className="btn btn-primary" type="button" id="button-addon1" style={{marginTop: '1em'}}
onClick={_ => null}>
Télécharger lattestation daffiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
onClick={e => null}>
Téléchargée l'attestation d'affiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
</button>
</a>
</div>
@ -139,10 +140,10 @@ function ModalContent2({clubData, data}) {
}
}
while (list.length < 3) {
list.push(-1)
if (list.length !== 3) {
toast.error("Il faut sélectionner 3 membres pour renouveler l'affiliation")
return
}
apiAxios.get(`/club/renew/${clubData.id}?m1=${list[0]}&m2=${list[1]}&m3=${list[2]}`).then(data => {
navigate('/affiliation#d' + encodeURI(JSON.stringify(data.data)))
})

View File

@ -1,7 +1,9 @@
import {useNavigate, useParams} from "react-router-dom";
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {toast} from "react-toastify";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {AffiliationCard, BureauCard} from "./AffiliationCard.jsx";
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
@ -20,7 +22,7 @@ export function MyClubPage() {
const {data, error} = useFetch(`/club/me`, setLoading, 1)
return <>
<h3>Données administratives</h3>
<h2>Mon club</h2>
{data
? <div>
<div className="row">
@ -75,7 +77,8 @@ function InformationForm({data}) {
<CountryList name="country" text="Pays" value={data.country} disabled={true}/>
{!data.international && <>
<TextField name="state_id" text="SIRET ou RNA" value={data.state_id} disabled={true}/>
<TextField name="siret" text="SIRET" value={data.siret} type="number" disabled={true}/>
<TextField name="rna" text="RNA" value={data.rna} required={false} disabled={true}/>
</>}
<div className="row mb-3">
@ -88,7 +91,7 @@ function InformationForm({data}) {
<div className="col-md-6">
<a href={`${vite_url}/api/club/${data.id}/status`} target='_blank'>
<button className="btn btn-outline-secondary" type="button" id="button-addon1"
onClick={_ => null}>
onClick={e => null}>
<FontAwesomeIcon icon={faFilePdf} size="5x"></FontAwesomeIcon><br/>
Voir les statues
</button>

View File

@ -49,9 +49,7 @@ export function InformationForm({data}) {
<TextField name="lname" text="Nom" value={data.lname}/>
<TextField name="fname" text="Prénom" value={data.fname}/>
<TextField name="email" text="Email" value={data.email} placeholder="name@example.com"
type="email" ttip={<small className="form-text">L'email sert à la création de compte pour se connecter au site et doit être unique. <br/>
Pour les mineurs, l'email des parents peut être utilisé plusieurs fois grâce à la syntaxe suivante : {'email.parent+<caractères alphanumériques>@exemple.com'}.<br/>
Exemples : mail.parent+1@exemple.com, mail.parent+titouan@exemple.com, mail.parent+cedrique@exemple.com</small>}/>
type="email"/>
<OptionField name="genre" text="Genre" value={data.genre}
values={{NA: 'N/A', H: 'H', F: 'F'}}/>
<CountryList name="country" text="Pays" value={data.country}/>

View File

@ -33,13 +33,13 @@ export function MemberPage() {
}
}
).then(_ => {
navigate(-1)
navigate("/club/member")
})
}
return <>
<h2>Page membre</h2>
<button type="button" className="btn btn-link" onClick={() => navigate(-1)}>
<button type="button" className="btn btn-link" onClick={() => navigate("/club/member")}>
&laquo; retour
</button>
{data

View File

@ -23,20 +23,6 @@ export const errFormater = (data, msg) => {
return `${msg} (${data.response.statusText}: ${JSON.stringify(data.response.data)}) 😕`
}
export const CatList = [
"SUPER_MINI",
"MINI_POUSSIN",
"POUSSIN",
"BENJAMIN",
"MINIME",
"CADET",
"JUNIOR",
"SENIOR1",
"SENIOR2",
"VETERAN1",
"VETERAN2"
];
export function getCategoryFormBirthDate(birth_date, currentDate = new Date()) {
const currentSaison = getSaison(currentDate)
const birthYear = birth_date.getFullYear()
@ -74,32 +60,3 @@ export function getSaison(currentDate = new Date()) {
return currentDate.getFullYear() - 1
}
}
export function getCatName(cat) {
switch (cat) {
case "SUPER_MINI":
return "Super Mini";
case "MINI_POUSSIN":
return "Mini Poussin";
case "POUSSIN":
return "Poussin";
case "BENJAMIN":
return "Benjamin";
case "MINIME":
return "Minime";
case "CADET":
return "Cadet";
case "JUNIOR":
return "Junior";
case "SENIOR1":
return "Senior 1";
case "SENIOR2":
return "Senior 2";
case "VETERAN1":
return "Vétéran 1";
case "VETERAN2":
return "Vétéran 2";
default:
return cat;
}
}

View File

@ -9,11 +9,8 @@ export function check_validity(online_callback = () => {
axios.get(`${vite_url}/api/auth/userinfo`).then(data => {
online_callback({state: true, userinfo: data.data});
})
}else{
online_callback({state: false});
}
}).catch(() => {
console.log("=> Not authenticated");
online_callback({state: false});
})
}
@ -35,4 +32,4 @@ export function login_redirect() {
export function logout() {
window.location.href = `${vite_url}/api/logout`;
}
}

View File

@ -24,4 +24,4 @@ export default ({mode}) => {
},
},
});
};
};