9 Commits

Author SHA1 Message Date
7e380ccb69 feat: add re-login message 2025-11-07 15:31:25 +01:00
94d1148eb1 fix: null email on import 2025-11-07 15:30:56 +01:00
79dbbdaaec fix: log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m27s
2025-11-05 22:06:36 +01:00
dec98f9508 fix: log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m22s
2025-11-05 21:43:05 +01:00
b89ed62795 feat: add aff req log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m33s
fix: membre import null email filter
2025-11-05 21:18:29 +01:00
5e48bc4623 fix: typo
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 9m51s
2025-09-05 20:31:10 +02:00
c6659f8d85 feat: keep log
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m19s
2025-09-03 21:46:26 +02:00
e86fe42b3d fix: club order 2025-09-03 21:35:36 +02:00
ef528aa524 fix: log message length 2025-09-03 19:53:19 +02:00
17 changed files with 168 additions and 43 deletions

View File

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

View File

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

View File

@@ -82,4 +82,22 @@ public class MembreModel implements LoggableModel {
public LogModel.ObjectType getObjectType() { public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Membre; 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

@@ -22,6 +22,7 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -30,6 +31,7 @@ import java.util.stream.Stream;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class AffiliationService { public class AffiliationService {
private static final Logger LOGGER = Logger.getLogger(AffiliationService.class);
@Inject @Inject
CombRepository combRepository; CombRepository combRepository;
@@ -146,6 +148,9 @@ public class AffiliationService {
} }
public Uni<String> save(AffiliationRequestForm form) { public Uni<String> save(AffiliationRequestForm form) {
LOGGER.debug("Affiliation Request Created");
LOGGER.debug(form.toString());
// noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher // noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher
return pre_save(form, true) return pre_save(form, true)
.chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model))) .chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model)))
@@ -169,6 +174,9 @@ public class AffiliationService {
} }
public Uni<?> saveAdmin(AffiliationRequestSaveForm form) { public Uni<?> saveAdmin(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Saved");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId()) return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> { .map(model -> {
@@ -259,7 +267,9 @@ public class AffiliationService {
}).call(m -> Panache.withTransaction(() -> combRepository.persist(m))); }).call(m -> Panache.withTransaction(() -> combRepository.persist(m)));
} }
}) })
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId()) : .call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId())
.onFailure().invoke(t -> LOGGER.warnf("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull() :
keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId())) keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId()))
.call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage())) .call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage()))
.call(userId -> keycloakService.setEmail(userId, m.getEmail()))) .call(userId -> keycloakService.setEmail(userId, m.getEmail())))
@@ -267,12 +277,15 @@ public class AffiliationService {
.call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ? .call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ?
Uni.createFrom().nullItem() : Uni.createFrom().nullItem() :
Panache.withTransaction(() -> licenceRepository.persist( 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(), .call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(),
licenceModel)))); licenceModel))));
} }
public Uni<?> accept(AffiliationRequestSaveForm form) { public Uni<?> accept(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Accepted");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId()) return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé")) .onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req -> .chain(req ->
@@ -298,6 +311,7 @@ public class AffiliationService {
} }
private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) { private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) {
LOGGER.debug("New Club Accepted");
return Uni.createFrom().nullItem() return Uni.createFrom().nullItem()
.chain(() -> { .chain(() -> {
ClubModel club = new ClubModel(); ClubModel club = new ClubModel();
@@ -336,6 +350,7 @@ public class AffiliationService {
} }
private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) { private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) {
LOGGER.debug("Old Club Accepted");
return Uni.createFrom().nullItem() return Uni.createFrom().nullItem()
.chain(() -> { .chain(() -> {
club.setName(form.getName()); club.setName(form.getName());

View File

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

View File

@@ -197,6 +197,11 @@ public class MembreService {
return Uni.createFrom().nullItem(); return Uni.createFrom().nullItem();
AtomicReference<ClubModel> clubModel = new AtomicReference<>(); 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() return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> { .chain(membreModel -> {
clubModel.set(membreModel.getClub()); clubModel.set(membreModel.getClub());
@@ -205,20 +210,24 @@ public class MembreService {
return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3", 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(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(), data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(),
data2.stream().map(SimpleMembreInOutData::getEmail).filter(Objects::nonNull).toList()); data2.stream().map(SimpleMembreInOutData::getEmail).filter(o -> o != null && !o.isBlank())
.toList());
}) })
.call(Unchecked.function(membres -> { .call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) { for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get())) if (!Objects.equals(membreModel.getClub(), clubModel.get())) {
LOGGER.info("Similar membres found: " + membreModel);
throw new DForbiddenException( throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club"); "Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
}
} }
Uni<Void> uniResult = Uni.createFrom().voidItem(); Uni<Void> uniResult = Uni.createFrom().voidItem();
for (SimpleMembreInOutData dataIn : data2) { for (SimpleMembreInOutData dataIn : data2) {
MembreModel model = membres.stream() MembreModel model = membres.stream()
.filter(m -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname() .filter(m -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname()
.equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom()) || .equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom()) ||
Objects.equals(m.getFname(), dataIn.getEmail())).findFirst() (dataIn.getEmail() != null && !dataIn.getEmail().isBlank() && Objects.equals(
m.getFname(), dataIn.getEmail()))).findFirst()
.orElseGet(() -> { .orElseGet(() -> {
MembreModel mm = new MembreModel(); MembreModel mm = new MembreModel();
mm.setClub(clubModel.get()); mm.setClub(clubModel.get());
@@ -229,12 +238,14 @@ public class MembreService {
if (model.getEmail() != null) { if (model.getEmail() != null) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) { if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email déja utiliser"); throw new DBadRequestException("Email déja utiliser");
} }
if (StringSimilarity.similarity(model.getLname().toUpperCase(), if (StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity( dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) { model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email déja utiliser"); throw new DBadRequestException("Email déja utiliser");
} }
} }
@@ -244,6 +255,7 @@ public class MembreService {
if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(), if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity( dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) { model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException( throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide."); "Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
} }

View File

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

View File

@@ -4,12 +4,10 @@ import fr.titionfire.ffsaf.utils.RoleAsso;
import jakarta.ws.rs.FormParam; import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType; import org.jboss.resteasy.reactive.PartType;
@Getter @Getter
@ToString
public class AffiliationRequestSaveForm { public class AffiliationRequestSaveForm {
@Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true) @Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true)
@FormParam("id") @FormParam("id")
@@ -171,4 +169,39 @@ public class AffiliationRequestSaveForm {
} }
} }
} }
@Override
public String toString() {
return "AffiliationRequestSaveForm{" +
"id=" + id +
", name='" + name + '\'' +
", siret=" + siret +
", rna='" + rna + '\'' +
", 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

@@ -1,11 +1,11 @@
import {useEffect, useRef} from 'react' import {useEffect, useRef} from 'react'
import {Nav} from "./components/Nav.jsx"; import {Nav} from "./components/Nav.jsx";
import {createBrowserRouter, Outlet, RouterProvider, useRouteError} from "react-router-dom"; import {createBrowserRouter, Outlet, RouterProvider, useLocation, useRouteError} from "react-router-dom";
import {Home} from "./pages/Homepage.jsx"; import {Home} from "./pages/Homepage.jsx";
import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx"; import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx";
import {AuthCallback} from "./components/auhCallback.jsx"; import {AuthCallback} from "./components/auhCallback.jsx";
import {KeycloakContextProvider, useAuthDispatch} from "./hooks/useAuth.jsx"; import {KeycloakContextProvider, useAuth, useAuthDispatch} from "./hooks/useAuth.jsx";
import {check_validity} from "./utils/auth.js"; import {check_validity, login} from "./utils/auth.js";
import {ToastContainer} from "react-toastify"; import {ToastContainer} from "react-toastify";
import './App.css' import './App.css'
@@ -14,6 +14,7 @@ import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx";
import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx"; import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx";
import {MePage} from "./pages/MePage.jsx"; import {MePage} from "./pages/MePage.jsx";
import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx"; import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx";
import {FallingLines} from "react-loader-spinner";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@@ -113,6 +114,44 @@ function Root() {
theme="light" theme="light"
transition: Flip transition: Flip
/> />
<ReAuthMsg/>
</div>
</>
}
function ReAuthMsg() {
const {is_authenticated} = useAuth()
const location = useLocation()
const notAuthPaths = [
/^\/$/s,
/^\/affiliation(\/)?$/s,
/^\/complete\/auth.*$/s
]
console.log(location.pathname, notAuthPaths.some(r => r.test(location.pathname)))
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> </div>
</> </>
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -172,6 +172,7 @@ function InformationForm({data}) {
export function BureauCard({clubData}) { export function BureauCard({clubData}) {
const setLoading = useLoadingSwitcher() const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/club/desk/${clubData.id}`, setLoading, 1) const {data, error} = useFetch(`/club/desk/${clubData.id}`, setLoading, 1)
const navigate = useNavigate();
return <> return <>
<div className="card mb-4"> <div className="card mb-4">
@@ -179,7 +180,8 @@ export function BureauCard({clubData}) {
<div className="card-body"> <div className="card-body">
<ul className="list-group"> <ul className="list-group">
{data && data.map((d, index) => { {data && data.map((d, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start"> 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}`)}>
<div className="me-auto"><small>{d.role}</small><br/>{d.lname} {d.fname}</div> <div className="me-auto"><small>{d.role}</small><br/>{d.lname} {d.fname}</div>
</div> </div>
})} })}

View File

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

View File

@@ -1,12 +1,11 @@
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js"; import {useFetch} from "../../../hooks/useFetch.js";
import {useEffect, useReducer, useState} from "react"; import {useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEye, faFilePdf, faPen} from "@fortawesome/free-solid-svg-icons"; import {faEye, faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx"; import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js"; import {apiAxios} from "../../../utils/Tools.js";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
const vite_url = import.meta.env.VITE_URL; const vite_url = import.meta.env.VITE_URL;
@@ -42,8 +41,8 @@ export function AffiliationCard({clubData}) {
<a href={`${vite_url}/api/club/me/affiliation`} target='#'> <a href={`${vite_url}/api/club/me/affiliation`} target='#'>
<button className="btn btn-primary" type="button" id="button-addon1" style={{marginTop: '1em'}} <button className="btn btn-primary" type="button" id="button-addon1" style={{marginTop: '1em'}}
onClick={e => null}> onClick={_ => null}>
Téléchargée l'attestation d'affiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon> Télécharger lattestation daffiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
</button> </button>
</a> </a>
</div> </div>

View File

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