webmoney/frontend/src/pages/Login.jsx
marcoitaloesp-ai 6292b62315
feat: complete email system redesign with corporate templates
- 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
2025-12-18 00:44:37 +00:00

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;