- Added city, state, and country optional fields to Complex model - Updated onboarding to include optional location fields - Created new settings page with sidebar navigation (Datos del Complejo, Canchas) - Replaced ESLint with Biome for frontend and backend linting - Added parallel dev script with concurrently - Migrated register-routes to use direct app.route() pattern
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
|
import { Input } from '@/components/ui/input';
|
|
import { useAuth } from '@/lib/auth';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { Link, useNavigate } from '@tanstack/react-router';
|
|
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { z } from 'zod';
|
|
|
|
const loginSchema = z.object({
|
|
email: z.string().email('Ingresá un email válido.'),
|
|
password: z.string().min(6, 'La contraseña debe tener al menos 6 caracteres.'),
|
|
});
|
|
|
|
type LoginForm = z.infer<typeof loginSchema>;
|
|
|
|
type LoginPageProps = {
|
|
redirectTo?: string;
|
|
};
|
|
|
|
export function LoginPage({ redirectTo = '/' }: LoginPageProps) {
|
|
const navigate = useNavigate();
|
|
const { signInWithPassword } = useAuth();
|
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors, isSubmitting, isValid },
|
|
} = useForm<LoginForm>({
|
|
resolver: zodResolver(loginSchema),
|
|
mode: 'onChange',
|
|
defaultValues: {
|
|
email: '',
|
|
password: '',
|
|
},
|
|
});
|
|
|
|
const onSubmit = async (values: LoginForm) => {
|
|
setSubmitError(null);
|
|
|
|
try {
|
|
await signInWithPassword(values);
|
|
await navigate({ to: redirectTo });
|
|
} catch {
|
|
setSubmitError('No pudimos iniciar sesión. Verificá tus credenciales.');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<main className="mx-auto flex min-h-screen w-full max-w-6xl items-center px-6">
|
|
<section className="w-full max-w-3xl rounded-xl border bg-card p-6 text-card-foreground shadow-sm">
|
|
<h1 className="mb-2 text-2xl font-semibold">Iniciar sesión</h1>
|
|
<p className="mb-4 text-sm text-muted-foreground">
|
|
Ingresá con tu cuenta para acceder a Home.
|
|
</p>
|
|
|
|
<form className="space-y-3" onSubmit={handleSubmit(onSubmit)}>
|
|
<Field data-invalid={Boolean(errors.email)}>
|
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
placeholder="tu@email.com"
|
|
aria-invalid={Boolean(errors.email)}
|
|
{...register('email')}
|
|
/>
|
|
<FieldError errors={[errors.email]} />
|
|
</Field>
|
|
|
|
<Field data-invalid={Boolean(errors.password)}>
|
|
<FieldLabel htmlFor="password">Contraseña</FieldLabel>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
placeholder="••••••••"
|
|
aria-invalid={Boolean(errors.password)}
|
|
{...register('password')}
|
|
/>
|
|
<FieldError errors={[errors.password]} />
|
|
</Field>
|
|
|
|
{submitError && <p className="text-sm text-destructive">{submitError}</p>}
|
|
|
|
<Button type="submit" className="w-full" disabled={!isValid || isSubmitting}>
|
|
{isSubmitting ? 'Ingresando...' : 'Ingresar'}
|
|
</Button>
|
|
</form>
|
|
|
|
<p className="mt-4 text-center text-sm text-muted-foreground">
|
|
¿Solo querés explorar?{' '}
|
|
<Link to="/about" className="text-primary hover:underline">
|
|
Ir a About
|
|
</Link>
|
|
</p>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|