ADD new frontend
This commit is contained in:
201
frontend/src/pages/public/LoginPage.tsx
Normal file
201
frontend/src/pages/public/LoginPage.tsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuthActions, useAuthError, useAuthLoading, useIsAuthenticated } from '../../stores';
|
||||
import { Button, Input, Card } from '../../components/ui';
|
||||
import { PublicLayout } from '../../components/layout';
|
||||
|
||||
const LoginPage: React.FC = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const { login } = useAuthActions();
|
||||
const error = useAuthError();
|
||||
const loading = useAuthLoading();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
const from = (location.state as any)?.from?.pathname || '/app';
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && !loading) {
|
||||
// Add a small delay to ensure the auth state has fully settled
|
||||
setTimeout(() => {
|
||||
navigate(from, { replace: true });
|
||||
}, 100);
|
||||
}
|
||||
}, [isAuthenticated, loading, navigate, from]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!email || !password) return;
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
} catch (err) {
|
||||
// Error is handled by the store
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PublicLayout
|
||||
variant="centered"
|
||||
maxWidth="md"
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: false,
|
||||
variant: "minimal"
|
||||
}}
|
||||
>
|
||||
<div className="w-full max-w-md mx-auto space-y-8">
|
||||
<div>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] rounded-lg flex items-center justify-center text-white font-bold text-lg">
|
||||
PI
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-[var(--text-primary)]">
|
||||
Inicia sesión en tu cuenta
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-[var(--text-secondary)]">
|
||||
O{' '}
|
||||
<Link
|
||||
to="/register"
|
||||
className="font-medium text-[var(--color-primary)] hover:text-[var(--color-primary-light)]"
|
||||
>
|
||||
regístrate para comenzar tu prueba gratuita
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="p-8">
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
{error && (
|
||||
<div className="bg-[var(--color-error)]/10 border border-[var(--color-error)]/20 rounded-md p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-[var(--color-error)]" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-[var(--color-error)]">
|
||||
Error de autenticación
|
||||
</h3>
|
||||
<div className="mt-2 text-sm text-[var(--color-error)]">
|
||||
{error}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="sr-only">
|
||||
Correo electrónico
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
placeholder="Correo electrónico"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Contraseña
|
||||
</label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
placeholder="Contraseña"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
name="remember-me"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-[var(--color-primary)] focus:ring-[var(--color-primary)] border-[var(--border-primary)] rounded"
|
||||
checked={rememberMe}
|
||||
onChange={(e) => setRememberMe(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="remember-me" className="ml-2 block text-sm text-[var(--text-primary)]">
|
||||
Recordarme
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="text-sm">
|
||||
<a href="#" className="font-medium text-[var(--color-primary)] hover:text-[var(--color-primary-light)]">
|
||||
¿Olvidaste tu contraseña?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full flex justify-center"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Iniciando sesión...' : 'Iniciar sesión'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-[var(--border-primary)]" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-[var(--bg-primary)] text-[var(--text-tertiary)]">Demo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
// TODO: Handle demo login
|
||||
console.log('Demo login');
|
||||
}}
|
||||
>
|
||||
Usar cuenta de demo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center text-xs text-[var(--text-tertiary)]">
|
||||
Al iniciar sesión, aceptas nuestros{' '}
|
||||
<a href="#" className="text-[var(--color-primary)] hover:text-[var(--color-primary-light)]">
|
||||
Términos de Servicio
|
||||
</a>
|
||||
{' '}y{' '}
|
||||
<a href="#" className="text-[var(--color-primary)] hover:text-[var(--color-primary-light)]">
|
||||
Política de Privacidad
|
||||
</a>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</PublicLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
Reference in New Issue
Block a user