Improve frontend 5
This commit is contained in:
@@ -1,19 +1,13 @@
|
||||
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 React, { useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useIsAuthenticated, useAuthLoading } from '../../stores';
|
||||
import { LoginForm } from '../../components/domain/auth';
|
||||
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();
|
||||
|
||||
@@ -28,15 +22,12 @@ const LoginPage: React.FC = () => {
|
||||
}
|
||||
}, [isAuthenticated, loading, navigate, from]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!email || !password) return;
|
||||
const handleLoginSuccess = () => {
|
||||
navigate(from, { replace: true });
|
||||
};
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
} catch (err) {
|
||||
// Error is handled by the store
|
||||
}
|
||||
const handleRegisterClick = () => {
|
||||
navigate('/register');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -49,151 +40,11 @@ const LoginPage: React.FC = () => {
|
||||
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>
|
||||
<LoginForm
|
||||
onSuccess={handleLoginSuccess}
|
||||
onRegisterClick={handleRegisterClick}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</PublicLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,371 +1,34 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Button, Input, Card, Select } from '../../components/ui';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { RegisterForm } from '../../components/domain/auth';
|
||||
import { PublicLayout } from '../../components/layout';
|
||||
|
||||
const RegisterPage: React.FC = () => {
|
||||
const [step, setStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
// Personal info
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
|
||||
// Company info
|
||||
companyName: '',
|
||||
companyType: '',
|
||||
employeeCount: '',
|
||||
|
||||
// Account info
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
acceptTerms: false,
|
||||
acceptMarketing: false,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleInputChange = (field: string, value: string | boolean) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
const handleRegistrationSuccess = () => {
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
setStep(prev => prev + 1);
|
||||
const handleLoginClick = () => {
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const handlePrevStep = () => {
|
||||
setStep(prev => prev - 1);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Redirect to onboarding
|
||||
navigate('/onboarding');
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isStep1Valid = formData.firstName && formData.lastName && formData.email && formData.phone;
|
||||
const isStep2Valid = formData.companyName && formData.companyType && formData.employeeCount;
|
||||
const isStep3Valid = formData.password && formData.confirmPassword &&
|
||||
formData.password === formData.confirmPassword && formData.acceptTerms;
|
||||
|
||||
return (
|
||||
<PublicLayout
|
||||
variant="centered"
|
||||
maxWidth="md"
|
||||
maxWidth="xl"
|
||||
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)]">
|
||||
Crea tu cuenta
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-[var(--text-secondary)]">
|
||||
O{' '}
|
||||
<Link
|
||||
to="/login"
|
||||
className="font-medium text-[var(--color-primary)] hover:text-[var(--color-primary-light)]"
|
||||
>
|
||||
inicia sesión si ya tienes una cuenta
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="p-8">
|
||||
{/* Progress indicator */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-start justify-between">
|
||||
{[
|
||||
{ step: 1, label: 'Datos personales' },
|
||||
{ step: 2, label: 'Información empresarial' },
|
||||
{ step: 3, label: 'Crear cuenta' }
|
||||
].map((stepInfo) => (
|
||||
<div key={stepInfo.step} className="flex flex-col items-center">
|
||||
<div
|
||||
className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
||||
step >= stepInfo.step
|
||||
? 'bg-[var(--color-primary)] text-white'
|
||||
: 'bg-[var(--bg-quaternary)] text-[var(--text-tertiary)]'
|
||||
}`}
|
||||
>
|
||||
{stepInfo.step}
|
||||
</div>
|
||||
<span className="mt-2 text-xs text-[var(--text-secondary)] text-center max-w-[80px]">
|
||||
{stepInfo.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="firstName" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Nombre *
|
||||
</label>
|
||||
<Input
|
||||
id="firstName"
|
||||
type="text"
|
||||
required
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
placeholder="Tu nombre"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="lastName" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Apellido *
|
||||
</label>
|
||||
<Input
|
||||
id="lastName"
|
||||
type="text"
|
||||
required
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
placeholder="Tu apellido"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Correo electrónico *
|
||||
</label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||
placeholder="tu@email.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Teléfono *
|
||||
</label>
|
||||
<Input
|
||||
id="phone"
|
||||
type="tel"
|
||||
required
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||
placeholder="+34 600 000 000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleNextStep}
|
||||
disabled={!isStep1Valid}
|
||||
className="w-full"
|
||||
>
|
||||
Continuar
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="companyName" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Nombre de la panadería *
|
||||
</label>
|
||||
<Input
|
||||
id="companyName"
|
||||
type="text"
|
||||
required
|
||||
value={formData.companyName}
|
||||
onChange={(e) => handleInputChange('companyName', e.target.value)}
|
||||
placeholder="Panadería San Miguel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="companyType" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Tipo de negocio *
|
||||
</label>
|
||||
<Select
|
||||
value={formData.companyType}
|
||||
onChange={(value) => handleInputChange('companyType', value as string)}
|
||||
placeholder="Selecciona el tipo"
|
||||
options={[
|
||||
{ value: "traditional", label: "Panadería tradicional" },
|
||||
{ value: "artisan", label: "Panadería artesanal" },
|
||||
{ value: "industrial", label: "Panadería industrial" },
|
||||
{ value: "bakery-cafe", label: "Panadería-cafetería" },
|
||||
{ value: "specialty", label: "Panadería especializada" }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="employeeCount" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Número de empleados *
|
||||
</label>
|
||||
<Select
|
||||
value={formData.employeeCount}
|
||||
onChange={(value) => handleInputChange('employeeCount', value as string)}
|
||||
placeholder="Selecciona el rango"
|
||||
options={[
|
||||
{ value: "1", label: "Solo yo" },
|
||||
{ value: "2-5", label: "2-5 empleados" },
|
||||
{ value: "6-15", label: "6-15 empleados" },
|
||||
{ value: "16-50", label: "16-50 empleados" },
|
||||
{ value: "51+", label: "Más de 50 empleados" }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handlePrevStep}
|
||||
className="flex-1"
|
||||
>
|
||||
Atrás
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleNextStep}
|
||||
disabled={!isStep2Valid}
|
||||
className="flex-1"
|
||||
>
|
||||
Continuar
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Contraseña *
|
||||
</label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
value={formData.password}
|
||||
onChange={(e) => handleInputChange('password', e.target.value)}
|
||||
placeholder="Mínimo 8 caracteres"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-[var(--text-primary)]">
|
||||
Confirmar contraseña *
|
||||
</label>
|
||||
<Input
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
required
|
||||
value={formData.confirmPassword}
|
||||
onChange={(e) => handleInputChange('confirmPassword', e.target.value)}
|
||||
placeholder="Repite la contraseña"
|
||||
/>
|
||||
{formData.confirmPassword && formData.password !== formData.confirmPassword && (
|
||||
<p className="mt-1 text-sm text-[var(--color-error)]">Las contraseñas no coinciden</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start">
|
||||
<input
|
||||
id="acceptTerms"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-[var(--color-primary)] focus:ring-[var(--color-primary)] border-[var(--border-secondary)] rounded mt-0.5"
|
||||
checked={formData.acceptTerms}
|
||||
onChange={(e) => handleInputChange('acceptTerms', e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="acceptTerms" className="ml-2 block text-sm text-[var(--text-primary)]">
|
||||
Acepto los{' '}
|
||||
<a href="#" className="text-[var(--color-primary)] hover:text-[var(--color-primary-light)]">
|
||||
Términos de Servicio
|
||||
</a>{' '}
|
||||
y la{' '}
|
||||
<a href="#" className="text-[var(--color-primary)] hover:text-[var(--color-primary-light)]">
|
||||
Política de Privacidad
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start">
|
||||
<input
|
||||
id="acceptMarketing"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-[var(--color-primary)] focus:ring-[var(--color-primary)] border-[var(--border-secondary)] rounded mt-0.5"
|
||||
checked={formData.acceptMarketing}
|
||||
onChange={(e) => handleInputChange('acceptMarketing', e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="acceptMarketing" className="ml-2 block text-sm text-[var(--text-primary)]">
|
||||
Quiero recibir newsletters y novedades sobre el producto (opcional)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handlePrevStep}
|
||||
className="flex-1"
|
||||
>
|
||||
Atrás
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!isStep3Valid || loading}
|
||||
className="flex-1"
|
||||
>
|
||||
{loading ? 'Creando cuenta...' : 'Crear cuenta'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center text-xs text-[var(--text-secondary)]">
|
||||
¿Necesitas ayuda?{' '}
|
||||
<a href="#" className="text-[var(--color-primary)] hover:text-[var(--color-primary-light)]">
|
||||
Contáctanos
|
||||
</a>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<RegisterForm
|
||||
onSuccess={handleRegistrationSuccess}
|
||||
onLoginClick={handleLoginClick}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</PublicLayout>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user