feat: add keycloak role configuration
This commit is contained in:
parent
e3306f4b5a
commit
8824a547bc
@ -13,6 +13,7 @@ import jakarta.ws.rs.core.Response;
|
|||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.resource.RoleScopeResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
@ -94,6 +95,21 @@ public class KeycloakService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Uni<List<String>> fetchRole(String id) {
|
||||||
|
return vertx.getOrCreateContext().executeBlocking(() ->
|
||||||
|
keycloak.realm(realm).users().get(id).roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<?> updateRole(String id, List<String> toAdd, List<String> toRemove) {
|
||||||
|
return vertx.getOrCreateContext().executeBlocking(() -> {
|
||||||
|
RoleScopeResource resource = keycloak.realm(realm).users().get(id).roles().realmLevel();
|
||||||
|
List<RoleRepresentation> roles = keycloak.realm(realm) .roles().list();
|
||||||
|
resource.add(roles.stream().filter(r -> toAdd.contains(r.getName())).toList());
|
||||||
|
resource.remove(roles.stream().filter(r -> toRemove.contains(r.getName())).toList());
|
||||||
|
return "OK";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public Uni<String> initCompte(long id) {
|
public Uni<String> initCompte(long id) {
|
||||||
return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> {
|
return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> {
|
||||||
if (membreModel.getUserId() != null)
|
if (membreModel.getUserId() != null)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package fr.titionfire.ffsaf.rest;
|
package fr.titionfire.ffsaf.rest;
|
||||||
|
|
||||||
import fr.titionfire.ffsaf.domain.service.KeycloakService;
|
import fr.titionfire.ffsaf.domain.service.KeycloakService;
|
||||||
|
import fr.titionfire.ffsaf.rest.from.MemberPermForm;
|
||||||
import io.smallrye.mutiny.Uni;
|
import io.smallrye.mutiny.Uni;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
@ -9,6 +10,9 @@ import jakarta.ws.rs.PUT;
|
|||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Path("api/compte")
|
@Path("api/compte")
|
||||||
public class CompteEndpoints {
|
public class CompteEndpoints {
|
||||||
|
|
||||||
@ -28,4 +32,30 @@ public class CompteEndpoints {
|
|||||||
public Uni<?> initCompte(@PathParam("id") long id) {
|
public Uni<?> initCompte(@PathParam("id") long id) {
|
||||||
return service.initCompte(id);
|
return service.initCompte(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{id}/roles")
|
||||||
|
@RolesAllowed("federation_admin")
|
||||||
|
public Uni<?> getRole(@PathParam("id") String id) {
|
||||||
|
return service.fetchRole(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("{id}/roles")
|
||||||
|
@RolesAllowed("federation_admin")
|
||||||
|
public Uni<?> updateRole(@PathParam("id") String id, MemberPermForm form) {
|
||||||
|
List<String> toAdd = new ArrayList<>();
|
||||||
|
List<String> toRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
if (form.isFederation_admin()) toAdd.add("federation_admin");
|
||||||
|
else toRemove.add("federation_admin");
|
||||||
|
if (form.isSafca_super_admin()) toAdd.add("safca_super_admin");
|
||||||
|
else toRemove.add("safca_super_admin");
|
||||||
|
if (form.isSafca_user()) toAdd.add("safca_user");
|
||||||
|
else toRemove.add("safca_user");
|
||||||
|
if (form.isSafca_create_compet()) toAdd.add("safca_create_compet");
|
||||||
|
else toRemove.add("safca_create_compet");
|
||||||
|
|
||||||
|
return service.updateRole(id, toAdd, toRemove);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
package fr.titionfire.ffsaf.rest.from;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.FormParam;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public class MemberPermForm {
|
||||||
|
@FormParam("federation_admin")
|
||||||
|
private boolean federation_admin;
|
||||||
|
|
||||||
|
@FormParam("safca_user")
|
||||||
|
private boolean safca_user;
|
||||||
|
|
||||||
|
@FormParam("safca_create_compet")
|
||||||
|
private boolean safca_create_compet;
|
||||||
|
|
||||||
|
@FormParam("safca_super_admin")
|
||||||
|
private boolean safca_super_admin;
|
||||||
|
}
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import {LoadingContextProvider, 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 {AxiosError} from "./AxiosError.jsx";
|
import {AxiosError} from "./AxiosError.jsx";
|
||||||
|
|
||||||
export function ClubSelect({defaultValue, name}) {
|
export function ClubSelect({defaultValue, name}) {
|
||||||
return <LoadingContextProvider>
|
return <LoadingProvider>
|
||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
<ClubSelect_ defaultValue={defaultValue} name={name}/>
|
<ClubSelect_ defaultValue={defaultValue} name={name}/>
|
||||||
</div>
|
</div>
|
||||||
</LoadingContextProvider>
|
</LoadingProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
function ClubSelect_({defaultValue, name}) {
|
function ClubSelect_({defaultValue, name}) {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export function useLoadingSwitcher() {
|
|||||||
return useContext(LoadingSwitcherContext);
|
return useContext(LoadingSwitcherContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LoadingContextProvider({children}) {
|
export function LoadingProvider({children}) {
|
||||||
const [showOverlay, setOverlay] = useState(0);
|
const [showOverlay, setOverlay] = useState(0);
|
||||||
|
|
||||||
return <LoadingContext.Provider value={showOverlay}>
|
return <LoadingContext.Provider value={showOverlay}>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import {NavLink, Outlet} from "react-router-dom";
|
import {NavLink, Outlet} from "react-router-dom";
|
||||||
import './AdminRoot.css'
|
import './AdminRoot.css'
|
||||||
import {LoadingContextProvider} from "../../hooks/useLoading.jsx";
|
import {LoadingProvider} from "../../hooks/useLoading.jsx";
|
||||||
import {MemberList} from "./MemberList.jsx";
|
import {MemberList} from "./MemberList.jsx";
|
||||||
import {MemberPage} from "./MemberPage.jsx";
|
import {MemberPage} from "./MemberPage.jsx";
|
||||||
|
|
||||||
export function AdminRoot() {
|
export function AdminRoot() {
|
||||||
return <>
|
return <>
|
||||||
<h1>Espace administration</h1>
|
<h1>Espace administration</h1>
|
||||||
<LoadingContextProvider>
|
<LoadingProvider>
|
||||||
<Outlet/>
|
<Outlet/>
|
||||||
</LoadingContextProvider>
|
</LoadingProvider>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {useNavigate, useParams} from "react-router-dom";
|
import {useNavigate, useParams} from "react-router-dom";
|
||||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
import {LoadingProvider, useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||||
import {useFetch, useFetchPut} from "../../hooks/useFetch.js";
|
import {useFetch, useFetchPut} from "../../hooks/useFetch.js";
|
||||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||||
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
||||||
@ -18,63 +18,6 @@ export function MemberPage() {
|
|||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
|
const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
|
||||||
|
|
||||||
const handleSubmit = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setLoading(1)
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("id", data.id);
|
|
||||||
formData.append("lname", event.target.lname?.value);
|
|
||||||
formData.append("fname", event.target.fname?.value);
|
|
||||||
formData.append("categorie", event.target.category?.value);
|
|
||||||
formData.append("club", event.target.club?.value);
|
|
||||||
formData.append("genre", event.target.genre?.value);
|
|
||||||
formData.append("country", event.target.country?.value);
|
|
||||||
formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString());
|
|
||||||
formData.append("email", event.target.email?.value);
|
|
||||||
formData.append("role", event.target.role?.value);
|
|
||||||
formData.append("grade_arbitrage", event.target.grade_arbitrage?.value);
|
|
||||||
|
|
||||||
const send = (formData_) => {
|
|
||||||
apiAxios.post(`/member/${id}`, formData_, {
|
|
||||||
headers: {
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
}
|
|
||||||
}).then(data => {
|
|
||||||
console.log(data.data) // TODO
|
|
||||||
}).catch(e => {
|
|
||||||
console.log(e.response) // TODO
|
|
||||||
}).finally(() => {
|
|
||||||
if (setLoading)
|
|
||||||
setLoading(0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageFile = event.target.url_photo.files[0];
|
|
||||||
if (imageFile) {
|
|
||||||
console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`);
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
maxSizeMB: 1,
|
|
||||||
maxWidthOrHeight: 1920,
|
|
||||||
useWebWorker: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
imageCompression(imageFile, options).then(compressedFile => {
|
|
||||||
console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB
|
|
||||||
formData.append("photo_data", compressedFile)
|
|
||||||
send(formData)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
send(formData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmitPerm = (event) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<h2>Page membre</h2>
|
<h2>Page membre</h2>
|
||||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
||||||
@ -85,11 +28,11 @@ export function MemberPage() {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-4">
|
<div className="col-lg-4">
|
||||||
<PhotoCard data={data}/>
|
<PhotoCard data={data}/>
|
||||||
<CompteInfo userData={data}/>
|
<LoadingProvider><CompteInfo userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-8">
|
<div className="col-lg-8">
|
||||||
<InformationForm handleSubmit={handleSubmit} data={data}/>
|
<InformationForm data={data}/>
|
||||||
<PremForm handleSubmitPerm={handleSubmitPerm}/>
|
<LoadingProvider><PremForm userData={data}/></LoadingProvider>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<LicenceCard/>
|
<LicenceCard/>
|
||||||
<SelectCard/>
|
<SelectCard/>
|
||||||
@ -116,7 +59,62 @@ function PhotoCard({data}) {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InformationForm({handleSubmit, data}) {
|
function InformationForm({data}) {
|
||||||
|
const setLoading = useLoadingSwitcher()
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setLoading(1)
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("id", data.id);
|
||||||
|
formData.append("lname", event.target.lname?.value);
|
||||||
|
formData.append("fname", event.target.fname?.value);
|
||||||
|
formData.append("categorie", event.target.category?.value);
|
||||||
|
formData.append("club", event.target.club?.value);
|
||||||
|
formData.append("genre", event.target.genre?.value);
|
||||||
|
formData.append("country", event.target.country?.value);
|
||||||
|
formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString());
|
||||||
|
formData.append("email", event.target.email?.value);
|
||||||
|
formData.append("role", event.target.role?.value);
|
||||||
|
formData.append("grade_arbitrage", event.target.grade_arbitrage?.value);
|
||||||
|
|
||||||
|
const send = (formData_) => {
|
||||||
|
apiAxios.post(`/member/${data.id}`, formData_, {
|
||||||
|
headers: {
|
||||||
|
'Accept': '*/*',
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
}
|
||||||
|
}).then(data => {
|
||||||
|
toast.success('Profile mis à jours avec succès 🎉');
|
||||||
|
}).catch(e => {
|
||||||
|
console.log(e.response)
|
||||||
|
toast.error('Échec de la mise à jours du profile 😕 (code: ' + e.response.status + ')');
|
||||||
|
}).finally(() => {
|
||||||
|
if (setLoading)
|
||||||
|
setLoading(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageFile = event.target.url_photo.files[0];
|
||||||
|
if (imageFile) {
|
||||||
|
console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
maxSizeMB: 1,
|
||||||
|
maxWidthOrHeight: 1920,
|
||||||
|
useWebWorker: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCompression(imageFile, options).then(compressedFile => {
|
||||||
|
console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB
|
||||||
|
formData.append("photo_data", compressedFile)
|
||||||
|
send(formData)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
send(formData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <form onSubmit={handleSubmit}>
|
return <form onSubmit={handleSubmit}>
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">Information</div>
|
<div className="card-header">Information</div>
|
||||||
@ -161,31 +159,85 @@ function InformationForm({handleSubmit, data}) {
|
|||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PremForm({handleSubmitPerm}) {
|
function PremForm({userData}) {
|
||||||
|
const setLoading = useLoadingSwitcher()
|
||||||
|
const handleSubmitPerm = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setLoading(1)
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("federation_admin", event.target.federation_admin?.checked);
|
||||||
|
formData.append("safca_user", event.target.safca_user?.checked);
|
||||||
|
formData.append("safca_create_compet", event.target.safca_create_compet?.checked);
|
||||||
|
formData.append("safca_super_admin", event.target.safca_super_admin?.checked);
|
||||||
|
|
||||||
|
apiAxios.put(`/compte/${userData.userId}/roles`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Accept': '*/*',
|
||||||
|
'Content-Type': 'form-data',
|
||||||
|
}
|
||||||
|
}).then(data => {
|
||||||
|
toast.success('Permission mise à jours avec succès 🎉');
|
||||||
|
}).catch(e => {
|
||||||
|
console.log(e.response)
|
||||||
|
toast.error('Échec de la mise à jours des permissions 😕 (code: ' + e.response.status + ')');
|
||||||
|
}).finally(() => {
|
||||||
|
if (setLoading)
|
||||||
|
setLoading(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return <form onSubmit={handleSubmitPerm}>
|
return <form onSubmit={handleSubmitPerm}>
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">Permission</div>
|
<div className="card-header">Permission</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="row g-3">
|
<div className="row g-3">
|
||||||
<div className="col">
|
{userData.userId
|
||||||
<h5>FFSAF intra</h5>
|
? <PremFormContent userData={userData}/>
|
||||||
<CheckField name="federation_admin" text="Accès à l'intra" value={false}/>
|
: <div className="col">
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<div>Ce membre ne dispose pas de compte...</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
|
||||||
<h5>SAFCA</h5>
|
|
||||||
<CheckField name="safca_user" text="Accès à l'application" value={false}/>
|
|
||||||
<CheckField name="safca_create_compet" text="Créer des compétion" value={false}/>
|
|
||||||
<CheckField name="safca_super_admin" text="Super administrateur" value={false}/>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12 text-right">
|
<div className="col-md-12 text-right">
|
||||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
{userData.userId && <button type="submit" className="btn btn-primary">Enregistrer</button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>;
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
function PremFormContent({userData}) {
|
||||||
|
const setLoading = useLoadingSwitcher()
|
||||||
|
const {data, error} = useFetch(`/compte/${userData.userId}/roles`, setLoading, 1)
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="col">
|
||||||
|
<h5>FFSAF intra</h5>
|
||||||
|
{data
|
||||||
|
? <>
|
||||||
|
<CheckField name="federation_admin" text="Accès à l'intra"
|
||||||
|
value={data.includes("federation_admin")}/>
|
||||||
|
</>
|
||||||
|
: error && <AxiosError error={error}/>}
|
||||||
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<h5>SAFCA</h5>
|
||||||
|
{data
|
||||||
|
? <>
|
||||||
|
<CheckField name="safca_user" text="Accès à l'application" value={data.includes("safca_user")}/>
|
||||||
|
<CheckField name="safca_create_compet" text="Créer des compétion"
|
||||||
|
value={data.includes("safca_create_compet")}/>
|
||||||
|
<CheckField name="safca_super_admin" text="Super administrateur"
|
||||||
|
value={data.includes("safca_super_admin")}/>
|
||||||
|
</>
|
||||||
|
: error && <AxiosError error={error}/>}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function LicenceCard() {
|
function LicenceCard() {
|
||||||
@ -214,12 +266,15 @@ function SelectCard() {
|
|||||||
function CompteInfo({userData}) {
|
function CompteInfo({userData}) {
|
||||||
|
|
||||||
const creatAccount = () => {
|
const creatAccount = () => {
|
||||||
|
let err = {};
|
||||||
toast.promise(
|
toast.promise(
|
||||||
apiAxios.put(`/compte/${userData.id}/init`),
|
apiAxios.put(`/compte/${userData.id}/init`).catch(e => {
|
||||||
|
err = e
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
pending: 'Création du compte en cours',
|
pending: 'Création du compte en cours',
|
||||||
success: 'Compte créé avec succès 🎉',
|
success: 'Compte créé avec succès 🎉',
|
||||||
error: 'Échec de la création du compte 😕'
|
error: 'Échec de la création du compte 😕 (code: ' + err.response.status + ')'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -229,7 +284,8 @@ function CompteInfo({userData}) {
|
|||||||
<div className="card-body text-center">
|
<div className="card-body text-center">
|
||||||
{userData.userId
|
{userData.userId
|
||||||
? <CompteInfoContent userData={userData}/>
|
? <CompteInfoContent userData={userData}/>
|
||||||
: <>
|
:
|
||||||
|
<>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
<div>Ce membre ne dispose pas de compte...</div>
|
<div>Ce membre ne dispose pas de compte...</div>
|
||||||
@ -240,13 +296,16 @@ function CompteInfo({userData}) {
|
|||||||
<button className="btn btn-primary" onClick={creatAccount}>Initialiser le compte</button>
|
<button className="btn btn-primary" onClick={creatAccount}>Initialiser le compte</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompteInfoContent({userData}) {
|
function CompteInfoContent({
|
||||||
|
userData
|
||||||
|
}) {
|
||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {data, error} = useFetch(`/compte/${userData.userId}`, setLoading, 1)
|
const {data, error} = useFetch(`/compte/${userData.userId}`, setLoading, 1)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user