- Redesigned all email templates with professional corporate style - Created base layout with dark header, status cards, and footer - Updated: subscription-cancelled, account-activation, welcome, welcome-new-user, due-payments-alert - Removed emojis and gradients for cleaner look - Added multi-language support (ES, PT-BR, EN) - Fixed email delivery (sync instead of queue) - Fixed PayPal already-cancelled subscription handling - Cleaned orphan subscriptions from deleted users
210 lines
7.5 KiB
JavaScript
210 lines
7.5 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { Link, useNavigate } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import Footer from '../components/Footer';
|
|
import api from '../services/api';
|
|
import logo from '../assets/logo-white.png';
|
|
|
|
const Login = () => {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const { login } = useAuth();
|
|
const [formData, setFormData] = useState({
|
|
email: '',
|
|
password: '',
|
|
});
|
|
const [errors, setErrors] = useState({});
|
|
const [loading, setLoading] = useState(false);
|
|
const [needsActivation, setNeedsActivation] = useState(false);
|
|
const [resendingEmail, setResendingEmail] = useState(false);
|
|
const [resendSuccess, setResendSuccess] = useState(false);
|
|
|
|
const handleChange = (e) => {
|
|
setFormData({
|
|
...formData,
|
|
[e.target.name]: e.target.value,
|
|
});
|
|
// Limpiar error del campo cuando el usuario escribe
|
|
if (errors[e.target.name]) {
|
|
setErrors({ ...errors, [e.target.name]: null });
|
|
}
|
|
// Reset activation state when email changes
|
|
if (e.target.name === 'email') {
|
|
setNeedsActivation(false);
|
|
setResendSuccess(false);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setErrors({});
|
|
setNeedsActivation(false);
|
|
setResendSuccess(false);
|
|
|
|
try {
|
|
const response = await login(formData);
|
|
if (response.success) {
|
|
navigate('/dashboard');
|
|
}
|
|
} catch (error) {
|
|
const errorData = error.response?.data;
|
|
|
|
// Check if it's an activation error
|
|
if (errorData?.error === 'email_not_verified') {
|
|
setNeedsActivation(true);
|
|
setErrors({ general: errorData.message });
|
|
} else if (errorData?.error === 'no_subscription') {
|
|
setErrors({ general: t('login.noSubscription', 'Você não possui uma assinatura ativa. Por favor, complete o pagamento.') });
|
|
} else if (errorData?.errors) {
|
|
setErrors(errorData.errors);
|
|
} else if (errorData?.message) {
|
|
setErrors({ general: errorData.message });
|
|
} else {
|
|
setErrors({ general: t('errors.connection', 'Erro de conexão. Tente novamente.') });
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleResendActivation = async () => {
|
|
setResendingEmail(true);
|
|
setResendSuccess(false);
|
|
try {
|
|
const response = await api.post('/resend-activation', { email: formData.email });
|
|
if (response.data.success) {
|
|
setResendSuccess(true);
|
|
}
|
|
} catch (error) {
|
|
const message = error.response?.data?.message || t('errors.resendFailed', 'Erro ao reenviar email');
|
|
setErrors({ general: message });
|
|
} finally {
|
|
setResendingEmail(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container">
|
|
<div className="row justify-content-center align-items-center min-vh-100">
|
|
<div className="col-md-5">
|
|
<div className="card shadow-lg border-0">
|
|
<div className="card-body p-5">
|
|
<div className="text-center mb-4">
|
|
<Link to="/">
|
|
<img src={logo} alt="WebMoney" className="mb-3" style={{ height: '80px', width: 'auto' }} />
|
|
</Link>
|
|
<h2 className="fw-bold text-primary">WebMoney</h2>
|
|
<p className="text-muted">{t('landing.hero.subtitle', 'Gestión Financiera Inteligente')}</p>
|
|
</div>
|
|
|
|
{errors.general && (
|
|
<div className={`alert ${needsActivation ? 'alert-warning' : 'alert-danger'}`} role="alert">
|
|
<i className={`bi ${needsActivation ? 'bi-envelope-exclamation' : 'bi-exclamation-circle'} me-2`}></i>
|
|
{errors.general}
|
|
</div>
|
|
)}
|
|
|
|
{needsActivation && (
|
|
<div className="mb-3">
|
|
{resendSuccess ? (
|
|
<div className="alert alert-success">
|
|
<i className="bi bi-check-circle me-2"></i>
|
|
{t('activate.resendSuccess', 'Email de ativação reenviado! Verifique sua caixa de entrada.')}
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
className="btn btn-outline-warning w-100"
|
|
onClick={handleResendActivation}
|
|
disabled={resendingEmail || !formData.email}
|
|
>
|
|
{resendingEmail ? (
|
|
<span className="spinner-border spinner-border-sm me-2"></span>
|
|
) : (
|
|
<i className="bi bi-envelope me-2"></i>
|
|
)}
|
|
{t('activate.resend', 'Reenviar email de ativação')}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="mb-3">
|
|
<label htmlFor="email" className="form-label">
|
|
{t('auth.email', 'Email')}
|
|
</label>
|
|
<input
|
|
type="email"
|
|
className={`form-control ${errors.email ? 'is-invalid' : ''}`}
|
|
id="email"
|
|
name="email"
|
|
autoComplete="email"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
placeholder="tu@email.com"
|
|
required
|
|
/>
|
|
{errors.email && (
|
|
<div className="invalid-feedback">{errors.email}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label htmlFor="password" className="form-label">
|
|
{t('auth.password', 'Contraseña')}
|
|
</label>
|
|
<input
|
|
type="password"
|
|
className={`form-control ${errors.password ? 'is-invalid' : ''}`}
|
|
id="password"
|
|
name="password"
|
|
autoComplete="current-password"
|
|
value={formData.password}
|
|
onChange={handleChange}
|
|
placeholder="••••••••"
|
|
required
|
|
/>
|
|
{errors.password && (
|
|
<div className="invalid-feedback">{errors.password}</div>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
className="btn btn-primary w-100 py-2"
|
|
disabled={loading}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
{t('common.processing', 'Procesando...')}
|
|
</>
|
|
) : (
|
|
t('auth.login', 'Iniciar Sesión')
|
|
)}
|
|
</button>
|
|
</form>
|
|
|
|
<div className="text-center mt-4">
|
|
<p className="mb-0">
|
|
{t('login.noAccount', '¿No tienes cuenta?')}{' '}
|
|
<Link to="/register" className="text-decoration-none fw-semibold">
|
|
{t('login.createAccount', 'Crea una aquí')}
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Footer variant="compact" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Login;
|