feat: add reject reason for affiliationRequest

This commit is contained in:
Thibaut Valentin 2025-07-04 17:24:51 +02:00
parent 97ca8766af
commit dea91714fa
3 changed files with 56 additions and 9 deletions

View File

@ -392,8 +392,27 @@ public class AffiliationService {
return Panache.withTransaction(() -> repository.deleteById(id));
}
public Uni<?> deleteReqAffiliation(long id) {
return Panache.withTransaction(() -> repositoryRequest.deleteById(id))
public Uni<?> deleteReqAffiliation(long id, String reason) {
return repositoryRequest.findById(id)
.call(aff -> reactiveMailer.send(
Mail.withText(aff.getM1_email(),
"FFSAF - Votre demande d'affiliation a été rejetée.",
String.format(
"""
Bonjour,
Votre demande d'affiliation pour le club %s a été rejetée pour la/les raison(s) suivante(s):
%s
Si vous rencontrez un problème ou si vous avez des questions, n'hésitez pas à nous contacter à l'adresse contact@ffsaf.fr.
Cordialement,
L'équipe de la FFSAF
""", aff.getName(), reason)
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
.addTo(aff.getM2_email(), aff.getM3_email())
))
.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

@ -102,12 +102,12 @@ public class AffiliationRequestEndpoints {
@APIResponse(responseCode = "404", description = "Demande d'affiliation non trouvée")
})
public Uni<?> getDelAffRequest(
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) {
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id, @QueryParam("reason") String reason) {
return service.getRequest(id).invoke(Unchecked.consumer(o -> {
if (o.getClub() == null && !securityCtx.roleHas("federation_admin"))
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()))
.chain(o -> service.deleteReqAffiliation(id));
.chain(o -> service.deleteReqAffiliation(id, reason));
}
@PUT

View File

@ -38,9 +38,9 @@ export function AffiliationReqPage() {
function Content({data, refresh}) {
const navigate = useNavigate();
const handleRm = (e) => {
const handleRm = (reason) => {
toast.promise(
apiAxios.delete(`/affiliation/request/${data.id}`),
apiAxios.delete(`/affiliation/request/${data.id}?reason=${encodeURIComponent(reason)}`),
{
pending: "Suppression de la demande d'affiliation en cours",
success: "Demande d'affiliation supprimée avec succès 🎉",
@ -219,8 +219,8 @@ function Content({data, refresh}) {
<button type="submit" value="accept" className="btn btn-success">Accepter</button>
<button type="submit" value="save" className="btn btn-primary">Enregistrer</button>
<button className="btn btn-danger" value="rm" data-bs-toggle="modal" data-bs-target="#confirm-delete">Refuser</button>
<ConfirmDialog title="Refuser la demande" message="Êtes-vous sûr de vouloir refuser cette demande ?"
onConfirm={handleRm}/>
<ConfirmReasonDialog title="Refuser la demande" message="Êtes-vous sûr de vouloir refuser cette demande ?"
onConfirm={handleRm}/>
</div>
</div>
</div>
@ -228,6 +228,34 @@ function Content({data, refresh}) {
</>
}
function ConfirmReasonDialog({onConfirm, id = "confirm-delete"}) {
const [reason, setReason] = useState("")
return <div className="modal fade" id={id} tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title" id="myModalLabel">"Refuser la demande"</h4>
</div>
<div className="modal-body">
<div className="row mb-3">
<div className="col">
<label htmlFor="reason" className="form-label">Raison du refus</label>
<textarea className="form-control" id="reason" name="reason" rows="3"
placeholder="Veuillez indiquer la raison du refus" value={reason}
onChange={e => setReason(e.target.value)}></textarea>
</div>
</div>
<span>Êtes-vous sûr de vouloir refuser cette demande ?</span>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal" data-bs-dismiss="modal">Annuler</button>
<a className="btn btn-danger btn-ok" data-bs-dismiss="modal" onClick={_ => onConfirm(reason)}>Confirmer</a>
</div>
</div>
</div>
</div>
}
function MemberPart({index, member}) {
const [mode, setMode] = useState(member.licence >= 0 ? 0 : 2)
const [current, setCurrent] = useState(-1)
@ -394,4 +422,4 @@ function MemberSimilar({member, current, setCurrent, mode, index, setEmail}) {
</button>
})}
</div>
}
}