From e8ee5811a031b4b34711e10781abfc15ad21730b Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Fri, 2 Jan 2026 23:12:22 +0100 Subject: [PATCH 1/4] feat: avoid ws reconnect when user logged out --- src/main/webapp/src/hooks/useWS.jsx | 68 ++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/main/webapp/src/hooks/useWS.jsx b/src/main/webapp/src/hooks/useWS.jsx index bd05b73..ef5f0c0 100644 --- a/src/main/webapp/src/hooks/useWS.jsx +++ b/src/main/webapp/src/hooks/useWS.jsx @@ -1,6 +1,4 @@ import {createContext, useContext, useEffect, useId, useReducer, useRef, useState} from "react"; -import {apiAxios} from "../utils/Tools.js"; -import {toast} from "react-toastify"; import {useAuth} from "./useAuth.jsx"; function uuidv4() { @@ -45,6 +43,7 @@ export function WSProvider({url, onmessage, children}) { const id = useId(); const {is_authenticated} = useAuth() const [isReady, setIsReady] = useState(false) + const [doReconnect, setDoReconnect] = useState(false) const [state, dispatch] = useReducer(reducer, {listener: []}) const ws = useRef(null) const listenersRef = useRef([]) @@ -59,10 +58,33 @@ export function WSProvider({url, onmessage, children}) { listenersRef.current = state.listener }, [state.listener]) + useEffect(() => { + if (!doReconnect && !is_authenticated && isReady) + return; + + const timer = setInterval(() => { + if (isReady || !doReconnect || !is_authenticated) + return; + + console.log("WSProvider: reconnecting to", url); + try { + const newSocket = new WebSocket(url) + newSocket.onopen = ws.current.onopen + newSocket.onclose = ws.current.onclose + newSocket.onmessage = ws.current.onmessage + ws.current = newSocket + }catch (e) { + + } + }, 5000); + return () => clearInterval(timer); + }, [isReady, doReconnect, is_authenticated]); + useEffect(() => { if (!mountCounter[id]) mountCounter[id] = 0 mountCounter[id] += 1 + setDoReconnect(true) console.log(`WSProvider ${id} mounted ${mountCounter[id]} time(s)`); if (mountCounter[id] === 1 && (ws.current === null || ws.current.readyState >= WebSocket.CLOSING)){ @@ -72,24 +94,6 @@ export function WSProvider({url, onmessage, children}) { socket.onopen = () => setIsReady(true) socket.onclose = () => { setIsReady(false) - if (mountCounter[id] > 0) { - setTimeout(() => { - //if (is_authenticated){ - console.log("WSProvider: reconnecting to", url); - try { - const newSocket = new WebSocket(url) - ws.current = newSocket - newSocket.onopen = socket.onopen - newSocket.onclose = socket.onclose - newSocket.onmessage = socket.onmessage - }catch (e) { - - } - //}else{ - // console.log("WSProvider: not reconnecting, user is not authenticated"); - //} - }, 5000) - } } socket.onmessage = (event) => { const msg = JSON.parse(event.data) @@ -132,6 +136,7 @@ export function WSProvider({url, onmessage, children}) { setTimeout(() => { console.log(`WSProvider ${id} checking for close, ${mountCounter[id]} instance(s) remain`); if (mountCounter[id] === 0) { + setDoReconnect(false) console.log("WSProvider: closing connection to", url); ws.current.close() } @@ -139,13 +144,14 @@ export function WSProvider({url, onmessage, children}) { } }, []) - const send = (uuid, code, type, data, resolve = () => { + const send2 = (uuid, code, type, data, resolve = () => { }, reject = () => { }) => { if (!isReadyRef.current) { reject("WebSocket is not connected"); return; } + if (type === "REQUEST") { const timeout = setTimeout(() => { reject("timeout"); @@ -172,6 +178,26 @@ export function WSProvider({url, onmessage, children}) { data: data })) } + const send = (uuid, code, type, data, resolve = () => { + }, reject = () => { + }) => { + if (isReadyRef.current) { + send2(uuid, code, type, data, resolve, reject); + }else { + let counter = 0; + const waitInterval = setInterval(() => { + if (isReadyRef.current) { + clearInterval(waitInterval); + send2(uuid, code, type, data, resolve, reject); + } + counter += 1; + if (counter >= 300) { // 30 seconds timeout + clearInterval(waitInterval); + reject("WebSocket is not connected"); + } + }, 100); + } + } const ret = {isReady, dispatch, send, wait_length: callbackRef} -- 2.49.0 From 3b5955ff31bdc5e78059e36f4b1ff56d3b8e92a5 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Fri, 2 Jan 2026 23:44:53 +0100 Subject: [PATCH 2/4] fix: rm match with cardboard --- .../java/fr/titionfire/ffsaf/ws/recv/RCategorie.java | 9 +++++---- src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java | 8 +++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java index 68e01ac..ecb9cde 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java @@ -3,10 +3,7 @@ package fr.titionfire.ffsaf.ws.recv; import fr.titionfire.ffsaf.data.model.CategoryModel; import fr.titionfire.ffsaf.data.model.MatchModel; import fr.titionfire.ffsaf.data.model.TreeModel; -import fr.titionfire.ffsaf.data.repository.CategoryRepository; -import fr.titionfire.ffsaf.data.repository.CompetitionRepository; -import fr.titionfire.ffsaf.data.repository.MatchRepository; -import fr.titionfire.ffsaf.data.repository.TreeRepository; +import fr.titionfire.ffsaf.data.repository.*; import fr.titionfire.ffsaf.domain.entity.MatchEntity; import fr.titionfire.ffsaf.domain.entity.TreeEntity; import fr.titionfire.ffsaf.rest.exception.DForbiddenException; @@ -46,6 +43,9 @@ public class RCategorie { @Inject TreeRepository treeRepository; + @Inject + CardboardRepository cardboardRepository; + private Uni getById(long id, WebSocketConnection connection) { return categoryRepository.findById(id) .invoke(Unchecked.consumer(o -> { @@ -210,6 +210,7 @@ public class RCategorie { public Uni deleteCategory(WebSocketConnection connection, Long id) { return getById(id, connection) .call(cat -> Panache.withTransaction(() -> treeRepository.delete("category = ?1", cat.getId()) + .call(__ -> cardboardRepository.delete("match.category = ?1", cat)) .call(__ -> matchRepository.delete("category = ?1", cat)))) .chain(cat -> Panache.withTransaction(() -> categoryRepository.delete(cat))) .invoke(__ -> SSCategorie.sendDelCategory(connection, id)) diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java index d48707d..a3437c8 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java @@ -44,6 +44,9 @@ public class RMatch { @Inject CompetitionGuestRepository competitionGuestRepository; + @Inject + CardboardRepository cardboardRepository; + private Uni getById(long id, WebSocketConnection connection) { return matchRepository.findById(id) .invoke(Unchecked.consumer(o -> { @@ -277,7 +280,10 @@ public class RMatch { @WSReceiver(code = "deleteMatch", permission = PermLevel.ADMIN) public Uni deleteMatch(WebSocketConnection connection, Long idMatch) { return getById(idMatch, connection) - .chain(matchModel -> Panache.withTransaction(() -> matchRepository.delete(matchModel))) + .map(__ -> idMatch) + .chain(l -> Panache.withTransaction(() -> + cardboardRepository.delete("match.id = ?1", l) + .chain(__ -> matchRepository.delete("id = ?1", l)))) .invoke(__ -> SSMatch.sendDeleteMatch(connection, idMatch)) .replaceWithVoid(); } -- 2.49.0 From 959e356fb920bc0cdafc1e0d9903bdce032fcd83 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Sat, 3 Jan 2026 11:22:04 +0100 Subject: [PATCH 3/4] fix: quick add auto complet --- .../competition/CompetitionRegisterAdmin.jsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx b/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx index 45823ff..39d0dad 100644 --- a/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx @@ -38,7 +38,7 @@ export function CompetitionRegisterAdmin({source}) { }, [data, clubFilter, catFilter]); const sendRegister = (new_state) => { - toast.promise(apiAxios.post(`/competition/${id}/register/${source}`, new_state), { + return toast.promise(apiAxios.post(`/competition/${id}/register/${source}`, new_state), { pending: "Recherche en cours", success: "Combattant trouvé et ajouté/mis à jour", error: { render({data}) { return data.response.data || "Combattant non trouvé" @@ -153,9 +153,9 @@ function SearchMember({sendRegister}) { } sendRegister({ - licence: member.licence.trim(), - fname: member.fname.trim(), - lname: member.lname.trim(), + licence: member.licence, + fname: member.fname, + lname: member.lname, weight: "", overCategory: 0, lockEdit: false, @@ -169,7 +169,7 @@ function SearchMember({sendRegister}) { const names = data.map(member => `${member.fname} ${member.lname}`.trim()); names.sort((a, b) => a.localeCompare(b)); setSuggestions(names); - }, []); + }, [data]); return <> {data ?
@@ -335,9 +335,9 @@ function Modal({sendRegister, modalState, setModalState, source}) {
{ e.preventDefault() const new_state = { - licence: licence, - fname: fname, - lname: lname, + licence: Number.isInteger(licence) ? licence : licence.trim(), + fname: fname.trim(), + lname: lname.trim(), weight: weight, overCategory: cat, lockEdit: lockEdit, @@ -350,8 +350,10 @@ function Modal({sendRegister, modalState, setModalState, source}) { new_state.country = country_ new_state.genre = genre } - setModalState(new_state) sendRegister(new_state) + .then(() => { + setModalState(new_state) + }) }}>

Date: Sat, 3 Jan 2026 21:49:36 +0100 Subject: [PATCH 4/4] feat: add permission setting to admin page --- .../ffsaf/data/model/CompetitionModel.java | 7 ++ .../domain/service/CompetitionService.java | 82 ++++++++++++++++++- .../ffsaf/domain/service/KeycloakService.java | 6 +- .../ffsaf/rest/CompetitionEndpoints.java | 16 ++++ .../ffsaf/rest/data/CompetitionData.java | 11 ++- .../ffsaf/rest/data/SimpleCompetData.java | 41 ++++++++++ .../fr/titionfire/ffsaf/ws/CompetitionWS.java | 4 +- .../src/pages/competition/CompetitionEdit.jsx | 18 ++-- .../src/pages/competition/CompetitionView.jsx | 7 ++ .../src/pages/competition/editor/CMAdmin.jsx | 2 +- 10 files changed, 176 insertions(+), 18 deletions(-) diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java index 8834b92..c3e2d7c 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java @@ -67,8 +67,15 @@ public class CompetitionModel { @Column(name = "table_") List table = new ArrayList<>(); + @Column(columnDefinition = "TEXT") String data1; + @Column(columnDefinition = "TEXT") String data2; + @Column(columnDefinition = "TEXT") String data3; + @Column(columnDefinition = "TEXT") String data4; + + @Column(columnDefinition = "TEXT") + String config; } diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java index 9b000b3..332d93f 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java @@ -98,7 +98,13 @@ public class CompetitionService { Cache cacheNoneAccess; public Uni getById(SecurityCtx securityCtx, Long id) { - return permService.hasViewPerm(securityCtx, id).map(CompetitionData::fromModelLight); + return permService.hasViewPerm(securityCtx, id).map(cm -> { + CompetitionData out = CompetitionData.fromModelLight(cm); + if (cm.getAdmin() != null) { + out.setCanEditRegisters(cm.getAdmin().stream().anyMatch(u -> u.equals(securityCtx.getSubject()))); + } + return out; + }); } public Uni getByIdAdmin(SecurityCtx securityCtx, Long id) { @@ -106,7 +112,8 @@ public class CompetitionService { return Uni.createFrom() .item(new CompetitionData(null, "", "", "", "", new Date(), new Date(), CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true, - null, "", "", null, true, "", "", "", "")); + null, "", "", null, true, true, + "", "", "", "", "{}")); } return permService.hasAdminViewPerm(securityCtx, id) .chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc()) @@ -129,6 +136,14 @@ public class CompetitionService { out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList()); out.forEach(competition -> competition.setCanEdit(true)); })) + .chain(ids -> repository.list("id NOT IN ?1 AND ?2 IN admin", ids, securityCtx.getSubject()) + .map(cm -> { + out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList()); + out.forEach(competition -> competition.setCanEditRegisters(true)); + List ids2 = new ArrayList<>(ids); + ids2.addAll(cm.stream().map(CompetitionModel::getId).toList()); + return ids2; + })) .call(ids -> repository.list("id NOT IN ?1 AND (publicVisible = TRUE OR registerMode IN ?2)", ids, securityCtx.isClubAdmin() ? List.of(RegisterMode.FREE, RegisterMode.HELLOASSO, @@ -165,8 +180,9 @@ public class CompetitionService { public Uni> getAllSystemTable(SecurityCtx securityCtx, CompetitionSystem system) { return repository.list("system = ?1", system) - .chain(l -> Uni.join().all(l.stream().map(cm -> permService.hasTablePerm(securityCtx, cm)).toList()) - .andCollectFailures()) + .chain(l -> Uni.join().all(l.stream().map(cm -> + permService.hasTablePerm(securityCtx, cm).onFailure().recoverWithNull() + ).toList()).andCollectFailures()) .map(l -> l.stream().filter(Objects::nonNull).map(CompetitionData::fromModel).toList()); } @@ -513,6 +529,64 @@ public class CompetitionService { .call(__ -> cache.invalidate(id)); } + public Uni getInternalData(SecurityCtx securityCtx, Long id) { + return permService.hasEditPerm(securityCtx, id) + .invoke(Unchecked.consumer(cm -> { + if (cm.getSystem() != CompetitionSystem.INTERNAL) + throw new DBadRequestException("Competition is not INTERNAL"); + })) + .chain(competitionModel -> { + SimpleCompetData data = SimpleCompetData.fromModel(competitionModel); + return vertx.getOrCreateContext().executeBlocking(() -> { + if (competitionModel.getAdmin() != null) + data.setAdmin( + competitionModel.getAdmin().stream().map(uuid -> keycloakService.getUserById(uuid)) + .filter(Optional::isPresent) + .map(user -> user.get().getUsername()) + .toList()); + if (competitionModel.getTable() != null) + data.setTable( + competitionModel.getTable().stream().map(uuid -> keycloakService.getUserById(uuid)) + .filter(Optional::isPresent) + .map(user -> user.get().getUsername()) + .toList()); + return data; + }); + }); + } + + public Uni setInternalData(SecurityCtx securityCtx, SimpleCompetData data) { + return permService.hasEditPerm(securityCtx, data.getId()) + .invoke(Unchecked.consumer(cm -> { + if (cm.getSystem() != CompetitionSystem.INTERNAL) + throw new DBadRequestException("Competition is not INTERNAL"); + })) + .chain(cm -> vertx.getOrCreateContext().executeBlocking(() -> { + ArrayList admin = new ArrayList<>(); + ArrayList table = new ArrayList<>(); + for (String username : data.getAdmin()) { + Optional opt = keycloakService.getUser(username); + if (opt.isEmpty()) + throw new DBadRequestException("User " + username + " not found"); + admin.add(opt.get().getId()); + } + for (String username : data.getTable()) { + Optional opt = keycloakService.getUser(username); + if (opt.isEmpty()) + throw new DBadRequestException("User " + username + " not found"); + table.add(opt.get().getId()); + } + + cm.setAdmin(admin); + cm.setTable(table); + cm.setConfig(data.getConfigForInternal()); + + return cm; + })) + .chain(cm -> Panache.withTransaction(() -> repository.persist(cm))) + .replaceWithVoid(); + } + public Uni getSafcaData(SecurityCtx securityCtx, Long id) { return permService.getSafcaConfig(id) .call(Unchecked.function(o -> { diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java index cf23435..63cdfd5 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java @@ -377,7 +377,11 @@ public class KeycloakService { public Optional getUser(UUID userId) { - UserResource user = keycloak.realm(realm).users().get(userId.toString()); + return getUserById(userId.toString()); + } + + public Optional getUserById(String userId) { + UserResource user = keycloak.realm(realm).users().get(userId); if (user == null) return Optional.empty(); else diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java index 82fccc0..77cabc5 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java @@ -71,6 +71,14 @@ public class CompetitionEndpoints { return service.getSafcaData(securityCtx, id); } + @GET + @Path("{id}/internalData") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni getInternalData(@PathParam("id") Long id) { + return service.getInternalData(securityCtx, id); + } + @GET @Path("all") @@ -95,6 +103,14 @@ public class CompetitionEndpoints { return service.setSafcaData(securityCtx, data); } + @POST + @Path("/internalData") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni setInternalData(SimpleCompetData data) { + return service.setInternalData(securityCtx, data); + } + @DELETE @Path("{id}") @Authenticated diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java index 8b33c2c..15cf538 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java @@ -35,10 +35,12 @@ public class CompetitionData { private String owner; private List registers; private boolean canEdit; + private boolean canEditRegisters; private String data1; private String data2; private String data3; private String data4; + private String config; public static CompetitionData fromModel(CompetitionModel model) { if (model == null) @@ -47,8 +49,8 @@ public class CompetitionData { return new CompetitionData(model.getId(), model.getName(), model.getDescription(), model.getAdresse(), model.getUuid(), model.getDate(), model.getTodate(), model.getSystem(), model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(), - model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false, - model.getData1(), model.getData2(), model.getData3(), model.getData4()); + model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false, false, + model.getData1(), model.getData2(), model.getData3(), model.getData4(), model.getConfig()); } public static CompetitionData fromModelLight(CompetitionModel model) { @@ -58,14 +60,15 @@ public class CompetitionData { CompetitionData out = new CompetitionData(model.getId(), model.getName(), model.getDescription(), model.getAdresse(), "", model.getDate(), model.getTodate(), null, model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(), - null, model.getClub().getName(), "", null, false, - "", "", "", ""); + null, model.getClub().getName(), "", null, false, false, + "", "", "", "", "{}"); if (model.getRegisterMode() == RegisterMode.HELLOASSO) { out.setData1(model.getData1()); out.setData2(model.getData2()); } + return out; } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java index 3adaac7..10706c7 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java @@ -1,5 +1,9 @@ package fr.titionfire.ffsaf.rest.data; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.titionfire.ffsaf.data.model.CompetitionModel; import fr.titionfire.ffsaf.net2.data.SimpleCompet; import io.quarkus.runtime.annotations.RegisterForReflection; import lombok.AllArgsConstructor; @@ -25,4 +29,41 @@ public class SimpleCompetData { return new SimpleCompetData(compet.id(), compet.show_blason(), compet.show_flag(), new ArrayList<>(), new ArrayList<>()); } + + public static SimpleCompetData fromModel(CompetitionModel competitionModel) { + if (competitionModel == null) + return null; + + boolean show_blason = true; + boolean show_flag = false; + + if (competitionModel.getConfig() != null) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(competitionModel.getConfig()); + + if (rootNode.has("show_blason")) + show_blason = rootNode.get("show_blason").asBoolean(); + if (rootNode.has("show_flag")) + show_flag = rootNode.get("show_flag").asBoolean(); + + } catch (JsonProcessingException ignored) { + } + } + + return new SimpleCompetData(competitionModel.getId(), show_blason, show_flag, + new ArrayList<>(), new ArrayList<>()); + } + + public String getConfigForInternal(){ + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.createObjectNode() + .put("show_blason", this.show_blason) + .put("show_flag", this.show_flag); + try { + return objectMapper.writeValueAsString(rootNode); + } catch (JsonProcessingException e) { + return "{}"; + } + } } diff --git a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java index edd991b..c22e5ef 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java @@ -108,8 +108,8 @@ public class CompetitionWS { .call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> PermLevel.ADMIN) .onFailure() .recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> PermLevel.TABLE)) - .onFailure() - .recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW)) + //.onFailure() + //.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW)) .invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem.toString())) .invoke(prem -> LOGGER.infof("Connection permission: %s", prem)) .onFailure().transform(t -> new ForbiddenException())) diff --git a/src/main/webapp/src/pages/competition/CompetitionEdit.jsx b/src/main/webapp/src/pages/competition/CompetitionEdit.jsx index f08a153..f7eeedb 100644 --- a/src/main/webapp/src/pages/competition/CompetitionEdit.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionEdit.jsx @@ -51,9 +51,11 @@ export function CompetitionEdit() { {data.id !== null && } + onClick={_ => navigate(`/competition/${data.id}/register?type=${data.registerMode}`)}>Voir/Modifier + les participants} - {data.id !== null && data.system === "SAFCA" && } + {data.id !== null && (data.system === "SAFCA" || data.system === "INTERNAL") && + } {data.id !== null && <>
@@ -72,9 +74,12 @@ export function CompetitionEdit() { } -function ContentSAFCA({data2}) { +function ContentSAFCAAndInternal({data2, type = "SAFCA"}) { + const getDataPath = type === "SAFCA" ? `/competition/${data2.id}/safcaData` : `/competition/${data2.id}/internalData` + const setDataPath = type === "SAFCA" ? "/competition/safcaData" : "/competition/internalData" + const setLoading = useLoadingSwitcher() - const {data, error} = useFetch(`/competition/${data2.id}/safcaData`, setLoading, 1) + const {data, error} = useFetch(getDataPath, setLoading, 1) const [state, dispatch] = useReducer(SimpleReducer, []) const [state2, dispatch2] = useReducer(SimpleReducer, []) @@ -109,7 +114,7 @@ function ContentSAFCA({data2}) { out['table'] = state2.map(d => d.data) toast.promise( - apiAxios.post(`/competition/safcaData`, out), + apiAxios.post(setDataPath, out), { pending: "Enregistrement des paramètres en cours", success: "Paramètres enregistrée avec succès 🎉", @@ -402,7 +407,8 @@ function Content({data}) {
  • Configurer l'url de notification : afin que nous puissions recevoir une notification à chaque inscription, il est nécessaire de configurer l'url de notification de votre compte HelloAsso pour qu'il redirige vers "https://intra.ffsaf.fr/api/webhook/ha". Pour ce faire, depuis la page d'accueil de - votre association sur HelloAsso, allez dans Mon compte > Paramètres > + votre association sur HelloAsso, allez dans Mon compte > + Paramètres > Intégrations et API section Notification et copier-coller https://intra.ffsaf.fr/api/webhook/ha dans le champ Mon URL de callback et enregister.
  • diff --git a/src/main/webapp/src/pages/competition/CompetitionView.jsx b/src/main/webapp/src/pages/competition/CompetitionView.jsx index c179f73..259c3db 100644 --- a/src/main/webapp/src/pages/competition/CompetitionView.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionView.jsx @@ -79,6 +79,13 @@ function MakeContent({data}) { href={`https://www.helloasso.com/associations/${data.data1}/evenements/${data.data2}`} target="_blank" rel="noopener noreferrer">{`https://www.helloasso.com/associations/${data.data1}/evenements/${data.data2}`}

    } + {data.canEditRegisters && +
    + +
    + }

    } diff --git a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx index 8454f11..1fd1290 100644 --- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx @@ -342,7 +342,7 @@ function CategoryHeader({cat, setCatId}) { }, [cats]); useEffect(() => { - if (cats && cats.length > 0 && !cat || (cats && !cats.find(c => c.id === cat.id))) { + if (cats && cats.length > 0 && (!cat || (cats && !cats.find(c => c.id === cat.id)))) { setCatId(cats.sort((a, b) => a.name.localeCompare(b.name))[0].id); } else if (cats && cats.length === 0) { setModal({}); -- 2.49.0