feat: add keycloak account creation and club groupe set

This commit is contained in:
2024-01-28 23:47:30 +01:00
parent d84ec9e1b4
commit 978c055834
22 changed files with 422 additions and 24 deletions

View File

@@ -18,7 +18,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.21.2"
"react-router-dom": "^6.21.2",
"react-toastify": "^10.0.4"
},
"devDependencies": {
"@types/react": "^18.2.43",
@@ -1631,6 +1632,14 @@
"node": ">=4"
}
},
"node_modules/clsx": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3609,6 +3618,18 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-toastify": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.4.tgz",
"integrity": "sha512-etR3RgueY8pe88SA67wLm8rJmL1h+CLqUGHuAoNsseW35oTGJEri6eBTyaXnFKNQ80v/eO10hBYLgz036XRGgA==",
"dependencies": {
"clsx": "^2.1.0"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",

View File

@@ -20,7 +20,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.21.2"
"react-router-dom": "^6.21.2",
"react-toastify": "^10.0.4"
},
"devDependencies": {
"@types/react": "^18.2.43",

View File

@@ -1,5 +1,4 @@
import {useEffect, useRef} from 'react'
import './App.css'
import {Nav} from "./components/Nav.jsx";
import {createBrowserRouter, Outlet, RouterProvider, useRouteError} from "react-router-dom";
import {Home} from "./pages/Homepage.jsx";
@@ -7,6 +6,10 @@ import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx";
import {AuthCallback} from "./components/auhCallback.jsx";
import {KeycloakContextProvider, useAuthDispatch} from "./hooks/useAuth.jsx";
import {check_validity} from "./utils/auth.js";
import {ToastContainer} from "react-toastify";
import './App.css'
import 'react-toastify/dist/ReactToastify.css';
const router = createBrowserRouter([
{
@@ -49,7 +52,7 @@ function Root() {
if (isInit.current)
return;
isInit.current = true
check_validity(b => dispatch({type: 'init', val: b}))
check_validity(data => dispatch({type: 'init', val: data}))
}, []);
return <>
@@ -58,6 +61,19 @@ function Root() {
</header>
<div className="container my-4">
<Outlet/>
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="light"
transition: Flip
/>
</div>
</>
}

View File

@@ -0,0 +1,9 @@
div .colored-circle {
display: inline-block;
margin-left: 5px;
margin-right: 5px;
margin-bottom: -2px;
border-radius: 50%;
height: 20px;
width: 20px;
}

View File

@@ -0,0 +1,16 @@
import {Fragment} from "react";
import './ColoredCircle.css'
export const ColoredCircle = ({color, boolean}) => {
const styles = {backgroundColor: '#F00'};
if (boolean === undefined) {
styles.backgroundColor = color
} else {
styles.backgroundColor = (boolean) ? '#00c700' : '#e50000';
}
return <Fragment>
<span className="colored-circle" style={styles}/>
</Fragment>
};

View File

@@ -32,9 +32,9 @@ export function Nav() {
}
function AdminMenu() {
const {is_authenticated, data} = useAuth()
const {is_authenticated, userinfo} = useAuth()
if (!is_authenticated || !data?.realm_access?.roles?.includes("federation_admin"))
if (!is_authenticated || !userinfo?.roles?.includes("federation_admin"))
return <></>
return <li className="nav-item dropdown">

View File

@@ -25,15 +25,14 @@ function authReducer(auth, action) {
switch (action.type) {
case 'init': {
return {
is_authenticated: action.val,
data: {realm_access: {roles: ["federation_admin"]}}
//data: action.val ? JSON.parse(atob(token.split('.')[1])) : null
is_authenticated: action.val.state,
userinfo: action.val.userinfo
}
}
case 'update': {
return {
...auth,
data: {realm_access: {roles: ["federation_admin"]}}
// data: {realm_access: {roles: ["federation_admin"]}}
// data: JSON.parse(atob(action.token.split('.')[1]))
}
}
@@ -51,5 +50,5 @@ function authReducer(auth, action) {
const initialAuth = {
is_authenticated: undefined,
data: undefined,
userinfo: undefined,
}

View File

@@ -1,11 +1,13 @@
import {useNavigate, useParams} from "react-router-dom";
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
import {useFetch} from "../../hooks/useFetch.js";
import {useFetch, useFetchPut} from "../../hooks/useFetch.js";
import {AxiosError} from "../../components/AxiosError.jsx";
import {ClubSelect} from "../../components/ClubSelect.jsx";
import {useEffect, useState} from "react";
import {apiAxios, getCategoryFormBirthDate} from "../../utils/Tools.js";
import imageCompression from "browser-image-compression";
import {ColoredCircle} from "../../components/ColoredCircle.jsx";
import {toast} from "react-toastify";
const vite_url = import.meta.env.VITE_URL;
@@ -41,9 +43,9 @@ export function MemberPage() {
'Content-Type': 'multipart/form-data',
}
}).then(data => {
console.log(data.data)
console.log(data.data) // TODO
}).catch(e => {
console.log(e.response)
console.log(e.response) // TODO
}).finally(() => {
if (setLoading)
setLoading(0)
@@ -82,8 +84,18 @@ export function MemberPage() {
</>
}
function MemberForm({data, handleSubmit}) {
const creatAccount = () => {
toast.promise(
apiAxios.put(`/compte/${data.id}/init`),
{
pending: 'Création du compte en cours',
success: 'Compte créé avec succès 🎉',
error: 'Échec de la création du compte 😕'
}
)
}
return <div>
<div className="row">
<div className="col-lg-4">
@@ -98,6 +110,25 @@ function MemberForm({data, handleSubmit}) {
</div>
</div>
</div>
<div className="card mb-4">
<div className="card-header">Compte</div>
<div className="card-body text-center">
{data.userId
? <CompteInfo userId={data.userId}/>
: <>
<div className="row">
<div className="input-group mb-3">
<div>Ce membre ne dispose pas de compte...</div>
</div>
</div>
<div className="row">
<div className="input-group mb-3">
<button className="btn btn-primary" onClick={creatAccount}>Initialiser le compte</button>
</div>
</div>
</>}
</div>
</div>
</div>
<div className="col-lg-8">
<form onSubmit={handleSubmit}>
@@ -166,6 +197,38 @@ function MemberForm({data, handleSubmit}) {
</div>
}
function CompteInfo({userId}) {
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/compte/${userId}`, setLoading, 1)
return <>
{data
? <CompteInfoContent data={data}/>
: error && <AxiosError error={error}/>
}
</>
}
function CompteInfoContent({data}) {
return <>
<div className="row">
<div className="input-group mb-3">
<div>Identifiant: {data.login}</div>
</div>
</div>
<div className="row">
<div className="input-group mb-3">
<div>Activer: <ColoredCircle boolean={data.enabled}/></div>
</div>
</div>
<div className="row">
<div className="input-group mb-3">
<div>Email vérifié: <ColoredCircle boolean={data.emailVerified}/></div>
</div>
</div>
</>
}
function BirthDayField({inti_date, inti_category}) {
const [date, setDate] = useState(inti_date)
const [category, setCategory] = useState(inti_category)

View File

@@ -5,10 +5,13 @@ const vite_url = import.meta.env.VITE_URL;
export function check_validity(online_callback = () => {
}) {
return axios.get(`${vite_url}/api/auth`).then(data => {
console.log(data.data)
online_callback(data.data);
if (data.data) {
axios.get(`${vite_url}/api/auth/userinfo`).then(data => {
online_callback({state: true, userinfo: data.data});
})
}
}).catch(() => {
online_callback(false);
online_callback({state: false});
})
}