refactor: reorganize feature modules with consistent naming conventions and flattened structure

This commit is contained in:
2026-04-22 14:12:15 -04:00
parent 256df9102c
commit 61388f04a6
41 changed files with 0 additions and 824 deletions
@@ -0,0 +1,222 @@
'use client';
/**
* Reset Password Page Component
*/
import { useState } from 'react';
import { PasswordStrengthIndicator } from '@zen/core/shared/components';
export default function ResetPasswordPage({ onSubmit, onNavigate, email, token }) {
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [success, setSuccess] = useState('');
const [formData, setFormData] = useState({
newPassword: '',
confirmPassword: ''
});
// Validation functions
const validatePassword = (password) => {
const errors = [];
if (password.length < 8) {
errors.push('Le mot de passe doit contenir au moins 8 caractères');
}
if (password.length > 128) {
errors.push('Le mot de passe doit contenir 128 caractères ou moins');
}
if (!/[A-Z]/.test(password)) {
errors.push('Le mot de passe doit contenir au moins une majuscule');
}
if (!/[a-z]/.test(password)) {
errors.push('Le mot de passe doit contenir au moins une minuscule');
}
if (!/\d/.test(password)) {
errors.push('Le mot de passe doit contenir au moins un chiffre');
}
return errors;
};
const isFormValid = () => {
const passwordErrors = validatePassword(formData.newPassword);
return passwordErrors.length === 0 &&
formData.newPassword === formData.confirmPassword &&
formData.newPassword.length > 0;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
async function handleSubmit(e) {
e.preventDefault();
setError('');
setSuccess('');
setIsLoading(true);
// Frontend validation
const passwordErrors = validatePassword(formData.newPassword);
if (passwordErrors.length > 0) {
setError(passwordErrors[0]); // Show first error
setIsLoading(false);
return;
}
// Validate password match
if (formData.newPassword !== formData.confirmPassword) {
setError('Les mots de passe ne correspondent pas');
setIsLoading(false);
return;
}
const submitData = new FormData();
submitData.append('newPassword', formData.newPassword);
submitData.append('confirmPassword', formData.confirmPassword);
submitData.append('email', email);
submitData.append('token', token);
try {
const result = await onSubmit(submitData);
if (result.success) {
setSuccess(result.message);
setIsLoading(false);
// Redirect to login after 2 seconds
setTimeout(() => {
onNavigate('login');
}, 2000);
} else {
setError(result.error || 'Échec de la réinitialisation du mot de passe');
setIsLoading(false);
}
} catch (err) {
console.error('Reset password error:', err);
setError('Une erreur inattendue s\'est produite');
setIsLoading(false);
}
}
const inputClasses = 'w-full px-3 py-2.5 rounded-lg text-sm focus:outline-none transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900 dark:border-neutral-700/50 dark:text-white dark:placeholder-neutral-500 dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20';
return (
<div className="group bg-white/80 dark:bg-neutral-900/40 backdrop-blur-sm border border-neutral-200/80 dark:border-neutral-800/50 rounded-2xl px-4 py-6 md:px-6 md:py-8 hover:bg-neutral-50/90 hover:border-neutral-300/80 dark:hover:bg-neutral-900/50 dark:hover:border-neutral-700/50 transition-all duration-300 w-full max-w-md">
{/* Header */}
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white mb-2">
Réinitialiser le mot de passe
</h1>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
Saisissez votre nouveau mot de passe ci-dessous.
</p>
</div>
{/* Error Message */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg dark:bg-red-500/10 dark:border-red-500/20">
<div className="flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-red-500 rounded-full flex-shrink-0"></div>
<span className="text-xs text-red-700 dark:text-red-400">{error}</span>
</div>
</div>
)}
{/* Success Message */}
{success && (
<div className="mb-4 p-3 bg-green-50 border border-green-200 rounded-lg dark:bg-green-500/10 dark:border-green-500/20">
<div className="flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-green-500 rounded-full flex-shrink-0"></div>
<span className="text-xs text-green-700 dark:text-green-400">{success}</span>
</div>
</div>
)}
{/* Reset Password Form */}
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div>
<label htmlFor="newPassword" className="block text-xs font-medium text-neutral-700 dark:text-white mb-1.5">
Nouveau mot de passe
</label>
<input
id="newPassword"
name="newPassword"
type="password"
required
minLength="8"
maxLength="128"
value={formData.newPassword}
onChange={handleChange}
disabled={success}
className={inputClasses}
placeholder="••••••••"
autoComplete="new-password"
/>
<PasswordStrengthIndicator password={formData.newPassword} showRequirements={true} />
</div>
<div>
<label htmlFor="confirmPassword" className="block text-xs font-medium text-neutral-700 dark:text-white mb-1.5">
Confirmer le mot de passe
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
required
minLength="8"
maxLength="128"
value={formData.confirmPassword}
onChange={handleChange}
disabled={success}
className={inputClasses}
placeholder="••••••••"
autoComplete="new-password"
/>
</div>
<button
type="submit"
disabled={isLoading || success || !isFormValid()}
className="cursor-pointer w-full bg-neutral-900 text-white mt-2 py-2.5 px-4 rounded-lg text-sm font-medium hover:bg-neutral-800 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-neutral-900/20 dark:bg-white dark:text-black dark:hover:bg-neutral-100 dark:focus:ring-white/20"
>
{isLoading ? (
<div className="flex items-center justify-center space-x-2">
<div className="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin dark:border-black/20 dark:border-t-black"></div>
<span>Réinitialisation...</span>
</div>
) : (
'Réinitialiser le mot de passe'
)}
</button>
</form>
{/* Back to Login Link */}
<div className="mt-6 text-center">
<a
href="#"
onClick={(e) => {
e.preventDefault();
onNavigate('login');
}}
className="text-sm text-neutral-900 hover:text-neutral-600 font-medium transition-colors duration-200 dark:text-white dark:hover:text-neutral-300"
>
Retour à la connexion
</a>
</div>
</div>
);
}