début du design system

This commit is contained in:
Ana-Lucia Munteanu 2025-04-02 19:57:04 -04:00
parent ee7a7a0544
commit 09955be8d4
12 changed files with 203 additions and 232 deletions

1
.gitignore vendored
View file

@ -132,3 +132,4 @@ launch.json
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
db-backup/ db-backup/
/.vs

View file

@ -17,8 +17,11 @@
"@mui/icons-material": "^6.4.6", "@mui/icons-material": "^6.4.6",
"@mui/lab": "^5.0.0-alpha.153", "@mui/lab": "^5.0.0-alpha.153",
"@mui/material": "^6.4.6", "@mui/material": "^6.4.6",
"@types/bootstrap": "^5.2.10",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"axios": "^1.8.1", "axios": "^1.8.1",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"gift-pegjs": "^2.0.0-beta.1", "gift-pegjs": "^2.0.0-beta.1",
@ -4521,6 +4524,14 @@
"@babel/types": "^7.20.7" "@babel/types": "^7.20.7"
} }
}, },
"node_modules/@types/bootstrap": {
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@ -5536,6 +5547,39 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/bootstrap": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",

View file

@ -21,8 +21,11 @@
"@mui/icons-material": "^6.4.6", "@mui/icons-material": "^6.4.6",
"@mui/lab": "^5.0.0-alpha.153", "@mui/lab": "^5.0.0-alpha.153",
"@mui/material": "^6.4.6", "@mui/material": "^6.4.6",
"@types/bootstrap": "^5.2.10",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"axios": "^1.8.1", "axios": "^1.8.1",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"gift-pegjs": "^2.0.0-beta.1", "gift-pegjs": "^2.0.0-beta.1",

View file

@ -6,6 +6,7 @@ import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material'; import { ThemeProvider, createTheme } from '@mui/material';
import '@fortawesome/fontawesome-free/css/all.min.css'; import '@fortawesome/fontawesome-free/css/all.min.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import './cssReset.css'; import './cssReset.css';
import './index.css'; import './index.css';

View file

@ -1,40 +1,40 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import './authDrawer.css';
import SimpleLogin from './providers/SimpleLogin/Login'; import SimpleLogin from './providers/SimpleLogin/Login';
import authService from '../../services/AuthService'; import authService from '../../services/AuthService';
import { ENV_VARIABLES } from '../../constants'; import { ENV_VARIABLES } from '../../constants';
import ButtonAuth from './providers/OAuth-Oidc/ButtonAuth'; import ButtonAuth from './providers/OAuth-Oidc/ButtonAuth';
import 'bootstrap/dist/css/bootstrap.min.css';
const AuthSelection: React.FC = () => { const AuthSelection: React.FC = () => {
const [authData, setAuthData] = useState<any>(null); // Stocke les données d'auth const [authData, setAuthData] = useState<any>(null);
const navigate = useNavigate(); const navigate = useNavigate();
ENV_VARIABLES.VITE_BACKEND_URL; ENV_VARIABLES.VITE_BACKEND_URL;
// Récupérer les données d'authentification depuis l'API
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
const data = await authService.fetchAuthData(); const data = await authService.fetchAuthData();
setAuthData(data); setAuthData(data);
}; };
fetchData(); fetchData();
}, []); }, []);
return ( return (
<div className="auth-selection-page"> <div className="d-flex flex-column align-items-center p-4 w-100">
<h1>Connexion</h1> <h1 className="mb-4">Connexion</h1>
{/* Formulaire de connexion Simple Login */} {/* Simple Login Form - Responsive width */}
{authData && authData['simpleauth'] && ( {authData && authData['simpleauth'] && (
<div className="form-container"> <div className="border rounded-3 p-4 my-2 shadow-sm w-100" style={{ maxWidth: '400px' }}>
<SimpleLogin /> <SimpleLogin />
</div> </div>
)} )}
{/* Conteneur OAuth/OIDC */} {/* OAuth/OIDC Providers - Responsive width */}
{authData && Object.keys(authData).some(key => authData[key].type === 'oidc' || authData[key].type === 'oauth') && ( {authData && Object.keys(authData).some(key =>
<div className="auth-button-container"> authData[key].type === 'oidc' || authData[key].type === 'oauth') && (
<div className="d-flex flex-column my-3 w-100" style={{ maxWidth: '400px' }}>
{Object.keys(authData).map((providerKey) => { {Object.keys(authData).map((providerKey) => {
const providerType = authData[providerKey].type; const providerType = authData[providerKey].type;
if (providerType === 'oidc' || providerType === 'oauth') { if (providerType === 'oidc' || providerType === 'oauth') {
@ -51,9 +51,12 @@ const AuthSelection: React.FC = () => {
</div> </div>
)} )}
<div> <button
<button className="home-button-container" onClick={() => navigate('/')}>Retour à l'accueil</button> className="btn btn-link text-dark p-0 mt-3"
</div> onClick={() => navigate('/')}
>
Retour à l'accueil
</button>
</div> </div>
); );
}; };

View file

@ -1,49 +0,0 @@
.auth-selection-page {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
h1 {
margin-bottom: 20px;
}
.form-container {
border: 1px solid #ccc;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
width: 400px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
form {
display: flex;
flex-direction: column;
}
input {
margin: 5px 0;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 10px;
border: none;
border-radius: 4px;
background-color: #5271ff;
color: white;
cursor: pointer;
}
/* This hover was affecting the entire App */
/* button:hover {
background-color: #5271ff;
} */
.home-button-container {
background: none;
color: black;
}
.home-button-container:hover {
background: none;
color: black;
text-decoration: underline;
}

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ENV_VARIABLES } from '../../../../constants'; import { ENV_VARIABLES } from '../../../../constants';
import '../css/buttonAuth.css'; import 'bootstrap/dist/css/bootstrap.min.css';
interface ButtonAuthContainerProps { interface ButtonAuthContainerProps {
providerName: string; providerName: string;
@ -13,14 +13,16 @@ const handleAuthLogin = (provider: string) => {
const ButtonAuth: React.FC<ButtonAuthContainerProps> = ({ providerName, providerType }) => { const ButtonAuth: React.FC<ButtonAuthContainerProps> = ({ providerName, providerType }) => {
return ( return (
<> <div className={`border rounded-3 p-3 my-3 mx-auto shadow-sm ${providerName}-${providerType}-container`} style={{ maxWidth: '400px' }}>
<div className={`${providerName}-${providerType}-container button-container`}> <h2 className="h5 mb-3">Se connecter avec {providerType.toUpperCase()}</h2>
<h2>Se connecter avec {providerType.toUpperCase()}</h2> <button
<button key={providerName} className={`provider-btn ${providerType}-btn`} onClick={() => handleAuthLogin(providerName)}> key={providerName}
className={`btn btn-outline-secondary w-100 ${providerType}-btn`}
onClick={() => handleAuthLogin(providerName)}
>
Continuer avec {providerName} Continuer avec {providerName}
</button> </button>
</div> </div>
</>
); );
}; };

View file

@ -1,88 +1,80 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { TextField, Button, CircularProgress } from '@mui/material';
// JoinRoom.tsx import LoginContainer from '../../../../components/LoginContainer/LoginContainer';
import React, { useEffect, useState } from 'react';
import '../css/simpleLogin.css';
import { TextField } from '@mui/material';
import LoadingButton from '@mui/lab/LoadingButton';
import LoginContainer from '../../../../components/LoginContainer/LoginContainer'
import ApiService from '../../../../services/ApiService'; import ApiService from '../../../../services/ApiService';
import 'bootstrap/dist/css/bootstrap.min.css';
const SimpleLogin: React.FC = () => { const SimpleLogin: React.FC = () => {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [connectionError, setConnectionError] = useState<string>(''); const [connectionError, setConnectionError] = useState<string>('');
const [isConnecting] = useState<boolean>(false); const [isConnecting, setIsConnecting] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
return () => { return () => {
// Cleanup if needed
}; };
}, []); }, []);
const login = async () => { const login = async () => {
console.log(`SimpleLogin: login: email: ${email}, password: ${password}`); setIsConnecting(true);
const result = await ApiService.login(email, password); const result = await ApiService.login(email, password);
setIsConnecting(false);
if (result !== true) { if (result !== true) {
setConnectionError(result); setConnectionError(result);
return; return;
} }
}; };
return ( return (
<LoginContainer <LoginContainer title='' error={connectionError}>
title='' {/* Email Input */}
error={connectionError}>
<TextField <TextField
label="Email" label="Email"
variant="outlined" variant="outlined"
className="mb-3 w-100" // Bootstrap classes for spacing and width
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="Nom d'utilisateur" placeholder="Nom d'utilisateur"
sx={{ marginBottom: '1rem' }} fullWidth // Material-UI fullWidth
fullWidth
/> />
{/* Password Input */}
<TextField <TextField
label="Mot de passe" label="Mot de passe"
variant="outlined" variant="outlined"
type="password" type="password"
className="mb-3 w-100" // Bootstrap classes
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="Nom de la salle" placeholder="Mot de passe"
sx={{ marginBottom: '1rem' }}
fullWidth fullWidth
/> />
<LoadingButton {/* Login Button */}
loading={isConnecting} <Button
onClick={login}
variant="contained" variant="contained"
sx={{ marginBottom: `${connectionError && '2rem'}` }} className={`w-100 mb-${connectionError ? '4' : '3'}`} // Dynamic margin-bottom
disabled={!email || !password} onClick={login}
disabled={!email || !password || isConnecting}
startIcon={isConnecting ? <CircularProgress size={20} /> : null}
size="large"
> >
Login Login
</LoadingButton> </Button>
<div className="login-links"> {/* Links Section */}
<div className="d-flex flex-column align-items-center pt-3">
<del className="py-1 text-muted">Réinitialiser le mot de passe</del>
{/* <Link to="/resetPassword"> */} <Link
<del>Réinitialiser le mot de passe</del> to="/register"
{/* </Link> */} className="py-1 text-decoration-none text-primary"
>
<Link to="/register">
Créer un compte Créer un compte
</Link> </Link>
</div> </div>
</LoginContainer> </LoginContainer>
); );
}; };

View file

@ -1,32 +1,35 @@
// JoinRoom.tsx import React, { useState, useEffect } from 'react';
import React, { useEffect, useState } from 'react'; import {
TextField,
import { TextField, FormLabel, RadioGroup, FormControlLabel, Radio, Box } from '@mui/material'; FormLabel,
import LoadingButton from '@mui/lab/LoadingButton'; RadioGroup,
FormControlLabel,
Radio,
Box,
Button,
CircularProgress
} from '@mui/material';
import LoginContainer from '../../../../components/LoginContainer/LoginContainer'; import LoginContainer from '../../../../components/LoginContainer/LoginContainer';
import ApiService from '../../../../services/ApiService'; import ApiService from '../../../../services/ApiService';
import 'bootstrap/dist/css/bootstrap.min.css';
const Register: React.FC = () => { const Register: React.FC = () => {
const [name, setName] = useState('');
const [name, setName] = useState(''); // State for name
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [roles, setRoles] = useState<string[]>(['teacher']); // Set 'student' as the default role const [roles, setRoles] = useState<string[]>(['teacher']);
const [connectionError, setConnectionError] = useState<string>(''); const [connectionError, setConnectionError] = useState<string>('');
const [isConnecting] = useState<boolean>(false); const [isConnecting, setIsConnecting] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
return () => { }; return () => { };
}, []); }, []);
const handleRoleChange = (role: string) => { const handleRoleChange = (role: string) => {
setRoles([role]); // Update the roles array to contain the selected role setRoles([role]);
}; };
const isValidEmail = (email: string) => { const isValidEmail = (email: string) => {
// Basic email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email); return emailRegex.test(email);
}; };
@ -37,7 +40,9 @@ const Register: React.FC = () => {
return; return;
} }
setIsConnecting(true);
const result = await ApiService.register(name, email, password, roles); const result = await ApiService.register(name, email, password, roles);
setIsConnecting(false);
if (result !== true) { if (result !== true) {
setConnectionError(result); setConnectionError(result);
@ -46,46 +51,49 @@ const Register: React.FC = () => {
}; };
return ( return (
<LoginContainer <LoginContainer title="Créer un compte" error={connectionError}>
title="Créer un compte" {/* Name Field */}
error={connectionError}
>
<TextField <TextField
label="Nom" label="Nom"
variant="outlined" variant="outlined"
className="mb-3 w-100"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="Votre nom" placeholder="Votre nom"
sx={{ marginBottom: '1rem' }}
fullWidth fullWidth
/> />
{/* Email Field */}
<TextField <TextField
label="Email" label="Email"
variant="outlined" variant="outlined"
className="mb-3 w-100"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="Adresse courriel" placeholder="Adresse courriel"
sx={{ marginBottom: '1rem' }}
fullWidth fullWidth
type="email" type="email"
error={!!connectionError && !isValidEmail(email)} error={!!connectionError && !isValidEmail(email)}
helperText={connectionError && !isValidEmail(email) ? "Adresse email invalide." : ""} helperText={connectionError && !isValidEmail(email) ? "Adresse email invalide." : ""}
/> />
{/* Password Field */}
<TextField <TextField
label="Mot de passe" label="Mot de passe"
variant="outlined" variant="outlined"
className="mb-3 w-100"
value={password} value={password}
type="password" type="password"
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="Mot de passe" placeholder="Mot de passe"
sx={{ marginBottom: '1rem' }}
fullWidth fullWidth
/> />
<Box sx={{ display: 'flex', alignItems: 'center', marginBottom: '1rem' }}> {/* Role Selection */}
<FormLabel component="legend" sx={{ marginRight: '1rem' }}>Choisir votre rôle</FormLabel> <Box className="d-flex align-items-center mb-3">
<FormLabel component="legend" className="me-3">
Choisir votre rôle
</FormLabel>
<RadioGroup <RadioGroup
row row
aria-label="role" aria-label="role"
@ -98,15 +106,17 @@ const Register: React.FC = () => {
</RadioGroup> </RadioGroup>
</Box> </Box>
<LoadingButton {/* Register Button */}
loading={isConnecting} <Button
onClick={register}
variant="contained" variant="contained"
sx={{ marginBottom: `${connectionError && '2rem'}` }} className={`w-100 mb-${connectionError ? '4' : '3'}`}
disabled={!name || !email || !password} onClick={register}
disabled={!name || !email || !password || isConnecting}
startIcon={isConnecting ? <CircularProgress size={20} /> : null}
size="large"
> >
S'inscrire S'inscrire
</LoadingButton> </Button>
</LoginContainer> </LoginContainer>
); );
}; };

View file

@ -1,30 +1,25 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { TextField, Button, CircularProgress } from '@mui/material';
// JoinRoom.tsx import LoginContainer from '../../../../components/LoginContainer/LoginContainer';
import React, { useEffect, useState } from 'react';
import { TextField } from '@mui/material';
import LoadingButton from '@mui/lab/LoadingButton';
import LoginContainer from '../../../../components/LoginContainer/LoginContainer'
import ApiService from '../../../../services/ApiService'; import ApiService from '../../../../services/ApiService';
import 'bootstrap/dist/css/bootstrap.min.css';
const ResetPassword: React.FC = () => { const ResetPassword: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [connectionError, setConnectionError] = useState<string>(''); const [connectionError, setConnectionError] = useState<string>('');
const [isConnecting] = useState<boolean>(false); const [isConnecting, setIsConnecting] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
return () => { return () => {
// Cleanup if needed
}; };
}, []); }, []);
const reset = async () => { const reset = async () => {
setIsConnecting(true);
try {
const result = await ApiService.resetPassword(email); const result = await ApiService.resetPassword(email);
if (!result) { if (!result) {
@ -32,35 +27,44 @@ const ResetPassword: React.FC = () => {
return; return;
} }
navigate("/login") navigate("/login");
} finally {
setIsConnecting(false);
}
}; };
const isValidEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
return ( return (
<LoginContainer <LoginContainer title='Récupération du compte' error={connectionError}>
title='Récupération du compte' {/* Email Field */}
error={connectionError}>
<TextField <TextField
label="Email" label="Email"
variant="outlined" variant="outlined"
className="mb-3 w-100"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="Adresse courriel" placeholder="Adresse courriel"
sx={{ marginBottom: '1rem' }}
fullWidth fullWidth
type="email"
error={!!connectionError && !isValidEmail(email)}
helperText={connectionError && !isValidEmail(email) ? "Adresse email invalide." : ""}
/> />
<LoadingButton {/* Reset Button */}
loading={isConnecting} <Button
onClick={reset}
variant="contained" variant="contained"
sx={{ marginBottom: `${connectionError && '2rem'}` }} className={`w-100 mb-${connectionError ? '4' : '3'}`}
disabled={!email} onClick={reset}
disabled={!email || isConnecting}
startIcon={isConnecting ? <CircularProgress size={20} /> : null}
size="large"
> >
Réinitialiser le mot de passe Réinitialiser le mot de passe
</LoadingButton> </Button>
</LoginContainer> </LoginContainer>
); );
}; };

View file

@ -1,23 +0,0 @@
.provider-btn {
background-color: #ffffff;
border: 1px solid #ccc;
color: black;
margin: 4px 0 4px 0;
}
.provider-btn:hover {
background-color: #dbdbdb;
border: 1px solid #ccc;
color: black;
margin: 4px 0 4px 0;
}
.button-container {
border: 1px solid #ccc;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
width: 400px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}

View file

@ -1,17 +0,0 @@
.login-links {
padding-top: 10px;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.login-links a {
padding: 4px;
color: #333;
text-decoration: none;
}
.login-links a:hover {
text-decoration: underline;
}