Se soluciono autenticacion
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { getCurrentUser } from '../api/users.ts';
|
||||
import { useNotification } from '../context/NotificationContext';
|
||||
import { fetchWithAuth, patchWithAuth, postWithAuth, postFormDataWithAuth } from '../fetchWithAuth';
|
||||
|
||||
// Animación fade-in/slide-up para el componente
|
||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||
@@ -27,6 +28,16 @@ const Settings = () => {
|
||||
rfc: ''
|
||||
});
|
||||
|
||||
// Estados para cambio de contraseña
|
||||
const [passwordData, setPasswordData] = useState({
|
||||
current_password: '',
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
});
|
||||
|
||||
const [passwordErrors, setPasswordErrors] = useState({});
|
||||
const [changingPassword, setChangingPassword] = useState(false);
|
||||
|
||||
// Estado para controlar la animación de entrada
|
||||
const [showAnimation, setShowAnimation] = useState(false);
|
||||
const [hasAnimated, setHasAnimated] = useState(false);
|
||||
@@ -79,28 +90,10 @@ const Settings = () => {
|
||||
|
||||
// Función para actualizar el usuario
|
||||
const updateUser = async (userData) => {
|
||||
const token = localStorage.getItem('access');
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/user/users/${currentUser.id}/`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return null;
|
||||
}
|
||||
const response = await patchWithAuth(`${API_URL}/user/users/${currentUser.id}/`, userData);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
@@ -123,6 +116,105 @@ const Settings = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
// Manejar cambios en el formulario de contraseña
|
||||
const handlePasswordChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setPasswordData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
// Limpiar errores cuando el usuario empiece a escribir
|
||||
if (passwordErrors[name]) {
|
||||
setPasswordErrors(prev => ({
|
||||
...prev,
|
||||
[name]: ''
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Validar contraseña
|
||||
const validatePassword = (password) => {
|
||||
const errors = [];
|
||||
if (password.length < 8) {
|
||||
errors.push('Debe tener al menos 8 caracteres');
|
||||
}
|
||||
if (!/(?=.*[a-z])/.test(password)) {
|
||||
errors.push('Debe contener al menos una letra minúscula');
|
||||
}
|
||||
if (!/(?=.*[A-Z])/.test(password)) {
|
||||
errors.push('Debe contener al menos una letra mayúscula');
|
||||
}
|
||||
if (!/(?=.*\d)/.test(password)) {
|
||||
errors.push('Debe contener al menos un número');
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
// Cambiar contraseña
|
||||
const handleChangePassword = async () => {
|
||||
const errors = {};
|
||||
|
||||
// Validaciones
|
||||
if (!passwordData.current_password.trim()) {
|
||||
errors.current_password = 'La contraseña actual es requerida';
|
||||
}
|
||||
|
||||
if (!passwordData.new_password.trim()) {
|
||||
errors.new_password = 'La nueva contraseña es requerida';
|
||||
} else {
|
||||
const passwordValidationErrors = validatePassword(passwordData.new_password);
|
||||
if (passwordValidationErrors.length > 0) {
|
||||
errors.new_password = passwordValidationErrors[0]; // Mostrar el primer error
|
||||
}
|
||||
}
|
||||
|
||||
if (!passwordData.confirm_password.trim()) {
|
||||
errors.confirm_password = 'Confirma tu nueva contraseña';
|
||||
} else if (passwordData.new_password !== passwordData.confirm_password) {
|
||||
errors.confirm_password = 'Las contraseñas no coinciden';
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setPasswordErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
setChangingPassword(true);
|
||||
try {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
const response = await postWithAuth(`${API_URL}/user/users/${currentUser.id}/change_password/`, {
|
||||
old_password: passwordData.current_password,
|
||||
new_password: passwordData.new_password
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
if (errorData.old_password) {
|
||||
setPasswordErrors({ current_password: 'La contraseña actual es incorrecta' });
|
||||
} else {
|
||||
throw new Error(errorData.detail || 'Error al cambiar la contraseña');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Limpiar formulario y mostrar mensaje de éxito
|
||||
setPasswordData({
|
||||
current_password: '',
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
});
|
||||
setPasswordErrors({});
|
||||
showMessage('Contraseña cambiada exitosamente', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error changing password:', error);
|
||||
showMessage(error.message || 'Error al cambiar la contraseña', 'error');
|
||||
} finally {
|
||||
setChangingPassword(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Guardar cambios del perfil
|
||||
const handleSaveProfile = async () => {
|
||||
if (!currentUser) return;
|
||||
@@ -642,9 +734,19 @@ const Settings = () => {
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
||||
name="current_password"
|
||||
value={passwordData.current_password}
|
||||
onChange={handlePasswordChange}
|
||||
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||
passwordErrors.current_password
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||
}`}
|
||||
placeholder="Ingresa tu contraseña actual"
|
||||
/>
|
||||
{passwordErrors.current_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{passwordErrors.current_password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -653,9 +755,19 @@ const Settings = () => {
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
||||
name="new_password"
|
||||
value={passwordData.new_password}
|
||||
onChange={handlePasswordChange}
|
||||
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||
passwordErrors.new_password
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||
}`}
|
||||
placeholder="Ingresa una nueva contraseña"
|
||||
/>
|
||||
{passwordErrors.new_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{passwordErrors.new_password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -664,28 +776,52 @@ const Settings = () => {
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
||||
name="confirm_password"
|
||||
value={passwordData.confirm_password}
|
||||
onChange={handlePasswordChange}
|
||||
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||
passwordErrors.confirm_password
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||
}`}
|
||||
placeholder="Confirma tu nueva contraseña"
|
||||
/>
|
||||
{passwordErrors.confirm_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{passwordErrors.confirm_password}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center px-6 py-3 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
|
||||
onClick={handleChangePassword}
|
||||
disabled={changingPassword}
|
||||
className="inline-flex items-center px-6 py-3 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Actualizar contraseña
|
||||
{changingPassword ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Cambiando contraseña...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Actualizar contraseña
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Información de seguridad adicional */}
|
||||
<div className="bg-gray-50 rounded-xl p-6">
|
||||
<h4 className="text-lg font-semibold text-gray-900 mb-4">Consejos de seguridad</h4>
|
||||
<h4 className="text-lg font-semibold text-gray-900 mb-4">Requisitos de contraseña</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
@@ -694,7 +830,7 @@ const Settings = () => {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
Usa al menos 8 caracteres con una combinación de letras, números y símbolos
|
||||
Mínimo 8 caracteres de longitud
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
@@ -704,7 +840,7 @@ const Settings = () => {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
Evita usar información personal como nombres o fechas de nacimiento
|
||||
Al menos una letra minúscula (a-z)
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
@@ -714,9 +850,55 @@ const Settings = () => {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
No reutilices contraseñas de otras cuentas
|
||||
Al menos una letra mayúscula (A-Z)
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-5 h-5 text-green-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
Al menos un número (0-9)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-gray-200">
|
||||
<h5 className="text-sm font-semibold text-gray-900 mb-2">Consejos adicionales</h5>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-2 text-xs text-gray-600">
|
||||
Evita usar información personal como nombres o fechas de nacimiento
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-2 text-xs text-gray-600">
|
||||
No reutilices contraseñas de otras cuentas
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-2 text-xs text-gray-600">
|
||||
Considera usar símbolos especiales para mayor seguridad
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user