This commit is contained in:
@@ -1,8 +1,50 @@
|
||||
// src/pages/public/Register.jsx
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { registerUnifiedClient } from '../../services/api';
|
||||
|
||||
// ============================================================================
|
||||
// COMPOSANT : MODAL DE NOTIFICATION (Succès / Erreur)
|
||||
// ============================================================================
|
||||
const NotificationModal = ({ notification, onClose }) => {
|
||||
if (!notification) return null;
|
||||
const isError = notification.type === 'error';
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-[100] flex items-center justify-center p-4" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.8)', backdropFilter: 'blur(4px)', zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{
|
||||
backgroundColor: '#1A1A1A',
|
||||
border: `1px solid ${isError ? '#ff003c' : '#00E5FF'}`,
|
||||
boxShadow: `0 10px 30px ${isError ? 'rgba(255, 0, 60, 0.2)' : 'rgba(0, 229, 255, 0.2)'}`,
|
||||
borderRadius: '8px',
|
||||
maxWidth: '400px',
|
||||
width: '100%',
|
||||
padding: '24px',
|
||||
color: '#FFF',
|
||||
fontFamily: 'monospace'
|
||||
}}>
|
||||
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', letterSpacing: '1px', marginBottom: '10px', color: isError ? '#ff003c' : '#00E5FF' }}>
|
||||
{notification.title.toUpperCase()}
|
||||
</h4>
|
||||
<p style={{ fontSize: '0.9rem', color: '#AAA', marginBottom: '24px', whiteSpace: 'pre-line', lineHeight: '1.5' }}>
|
||||
{notification.message}
|
||||
</p>
|
||||
<button onClick={onClose} style={{
|
||||
width: '100%', padding: '10px',
|
||||
backgroundColor: isError ? '#ff003c' : '#00E5FF',
|
||||
color: isError ? '#FFF' : '#000',
|
||||
border: 'none', borderRadius: '4px',
|
||||
fontFamily: 'monospace', fontWeight: 'bold', cursor: 'pointer', letterSpacing: '1px'
|
||||
}}>
|
||||
COMPRIS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// COMPOSANT PRINCIPAL : REGISTER
|
||||
// ============================================================================
|
||||
export default function Register() {
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
@@ -10,60 +52,65 @@ export default function Register() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [customAlert, setCustomAlert] = useState(null); // Remplace l'état "error" et "alert()"
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleRegister = async (e) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setCustomAlert(null);
|
||||
|
||||
// 1. DÉFINITION DE LA POLITIQUE DE MOT DE PASSE (Le Checkpoint)
|
||||
// Explication de la Regex :
|
||||
// (?=.*[a-z]) : Au moins une minuscule
|
||||
// (?=.*[A-Z]) : Au moins une majuscule
|
||||
// (?=.*\d) : Au moins un chiffre
|
||||
// (?=.*[\W_]) : Au moins un caractère spécial (non-alphanumérique ou underscore)
|
||||
// .{8,} : Minimum 8 caractères au total
|
||||
const passwordPolicy = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/;
|
||||
|
||||
// 2. VÉRIFICATION
|
||||
// VÉRIFICATIONS FRONTEND (Affiche le Modal d'Erreur)
|
||||
if (!passwordPolicy.test(password)) {
|
||||
setError("ERREUR : Le mot de passe doit contenir 8 caractères min, une majuscule, une minuscule, un chiffre et un caractère spécial.");
|
||||
return; // On stoppe l'exécution ici. La requête ne part pas vers le serveur.
|
||||
}
|
||||
|
||||
// Sécurité Frontend : Validation des mots de passe avant envoi au serveur
|
||||
if (password !== confirmPassword) {
|
||||
setError("Les clés d'accès (mots de passe) ne correspondent pas.");
|
||||
setCustomAlert({ type: 'error', title: 'Sécurité compromise', message: "Le mot de passe doit contenir 8 caractères min, une majuscule, une minuscule, un chiffre et un caractère spécial." });
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setCustomAlert({ type: 'error', title: 'Erreur de saisie', message: "Les clés d'accès (mots de passe) ne correspondent pas." });
|
||||
return;
|
||||
}
|
||||
|
||||
// Sécurité Frontend : Regex simple pour valider le format exigé par HestiaCP
|
||||
const validUsername = /^[a-zA-Z0-9]{3,12}$/.test(username);
|
||||
if (!validUsername) {
|
||||
setError("Le nom d'utilisateur doit contenir uniquement des lettres ou chiffres (entre 3 et 12 caractères, sans espace).");
|
||||
setCustomAlert({ type: 'error', title: 'Identifiant invalide', message: "Le nom d'utilisateur doit contenir uniquement des lettres ou chiffres (entre 3 et 12 caractères, sans espace)." });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// Envoi de la requête groupée à notre orchestrateur PHP backend
|
||||
// API BACKEND
|
||||
await registerUnifiedClient(email, username, password, firstName, lastName);
|
||||
|
||||
alert("[ PROVISIONNEMENT RÉUSSI ]\nVos comptes FOSSBilling, HestiaCP et Nextcloud ont été initialisés.\nVous pouvez maintenant vous connecter.");
|
||||
// SUCCÈS (Affiche le Modal de Succès)
|
||||
setCustomAlert({
|
||||
type: 'success',
|
||||
title: 'PROVISIONNEMENT RÉUSSI',
|
||||
message: "Vos comptes FOSSBilling, HestiaCP et Nextcloud ont été initialisés.\n\nVous pouvez maintenant vous connecter à l'infrastructure."
|
||||
});
|
||||
|
||||
// Redirection automatique vers la page de login après succès
|
||||
navigate('/login');
|
||||
} catch (err) {
|
||||
setError(err.message || "Échec de l'initialisation de l'infrastructure.");
|
||||
// ERREUR API (Affiche le Modal d'Erreur API)
|
||||
setCustomAlert({ type: 'error', title: 'Échec du Déploiement', message: err.message || "Échec de l'initialisation de l'infrastructure." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Réutilisation de ton Design System "Bunker"
|
||||
// Fermeture du Modal : Redirige vers le Login SI c'était un succès
|
||||
const handleCloseModal = () => {
|
||||
if (customAlert?.type === 'success') {
|
||||
navigate('/login');
|
||||
} else {
|
||||
setCustomAlert(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Styles
|
||||
const inputStyle = {
|
||||
width: '100%', padding: '10px', marginBottom: '15px',
|
||||
backgroundColor: '#1A1A1A', color: '#00E5FF',
|
||||
@@ -91,20 +138,6 @@ export default function Register() {
|
||||
[ INITIALISATION DU PROVISIONNEMENT TRIPLE EN CASCADE ]
|
||||
</p>
|
||||
|
||||
{/* Affichage des alertes système */}
|
||||
{error && (
|
||||
<div style={{
|
||||
color: '#ff003c', // Un rouge néon agressif pour les erreurs
|
||||
border: '1px solid #ff003c',
|
||||
backgroundColor: 'rgba(255, 0, 60, 0.1)',
|
||||
padding: '10px',
|
||||
marginBottom: '15px',
|
||||
fontFamily: 'monospace'
|
||||
}}>
|
||||
[ ALERTE SYSTÈME ] : {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleRegister}>
|
||||
<div style={{ display: 'flex', gap: '15px' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
@@ -140,6 +173,9 @@ export default function Register() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Affichage du Modal par-dessus le formulaire */}
|
||||
<NotificationModal notification={customAlert} onClose={handleCloseModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user