diff --git a/apps/web/app/(auth)/layout.tsx b/apps/web/app/(auth)/layout.tsx new file mode 100644 index 0000000..37e5241 --- /dev/null +++ b/apps/web/app/(auth)/layout.tsx @@ -0,0 +1,31 @@ +import { AuthProvider } from '@/components/providers/auth-provider'; + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + +
+ {/* Decorative pattern background */} +
+
+
+ + {/* Decorative shapes */} +
+
+ + {/* Content */} +
{children}
+
+ + ); +} diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx new file mode 100644 index 0000000..4cff09e --- /dev/null +++ b/apps/web/app/(auth)/login/page.tsx @@ -0,0 +1,152 @@ +'use client'; + +import { Suspense } from 'react'; +import { LoginForm } from '@/components/auth/login-form'; + +function LoginContent() { + return ( +
+ {/* Left side - Branding (hidden on mobile) */} +
+
+ {/* Logo */} +
+
+ + {/* Padel racket stylized icon */} + + + + + + + + + + + +
+
+ + {/* Title */} +

Padel Pro

+ + {/* Tagline */} +

+ Sistema de Gestion para Clubes de Padel +

+ + {/* Features */} +
+
+
+ + + +
+
+

Gestion de Reservas

+

Administra tus canchas y horarios

+
+
+ +
+
+ + + +
+
+

Control de Clientes

+

Membresias y perfiles completos

+
+
+ +
+
+ + + +
+
+

Reportes y Estadisticas

+

Analiza el rendimiento de tu club

+
+
+
+
+
+ + {/* Right side - Login Form */} +
+ {/* Mobile Logo */} +
+
+ + + + + + + + + + + + +
+

Padel Pro

+

Sistema de Gestion para Clubes de Padel

+
+ + + + {/* Footer */} +

+ © {new Date().getFullYear()} Padel Pro. Todos los derechos reservados. +

+
+
+ ); +} + +export default function LoginPage() { + return ( + +
+
+ } + > + + + ); +} diff --git a/apps/web/components/auth/login-form.tsx b/apps/web/components/auth/login-form.tsx new file mode 100644 index 0000000..d88854f --- /dev/null +++ b/apps/web/components/auth/login-form.tsx @@ -0,0 +1,252 @@ +'use client'; + +import { useState } from 'react'; +import { signIn } from 'next-auth/react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; + +interface LoginFormProps { + className?: string; +} + +export function LoginForm({ className }: LoginFormProps) { + const router = useRouter(); + const searchParams = useSearchParams(); + const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'; + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [rememberMe, setRememberMe] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); + + const validateForm = (): boolean => { + const newErrors: { email?: string; password?: string } = {}; + + if (!email) { + newErrors.email = 'El correo electrónico es requerido'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + newErrors.email = 'Ingresa un correo electrónico válido'; + } + + if (!password) { + newErrors.password = 'La contraseña es requerida'; + } else if (password.length < 6) { + newErrors.password = 'La contraseña debe tener al menos 6 caracteres'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + if (!validateForm()) { + return; + } + + setIsLoading(true); + + try { + const result = await signIn('credentials', { + email, + password, + redirect: false, + }); + + if (result?.error) { + setError('Credenciales inválidas. Por favor, verifica tu correo y contraseña.'); + } else { + router.push(callbackUrl); + router.refresh(); + } + } catch (err) { + setError('Ocurrió un error al iniciar sesión. Por favor, intenta de nuevo.'); + } finally { + setIsLoading(false); + } + }; + + return ( + + + Iniciar Sesión + + Ingresa tus credenciales para acceder al sistema + + + +
+ {error && ( +
+
+ + + + {error} +
+
+ )} + +
+ + { + setEmail(e.target.value); + if (errors.email) { + setErrors((prev) => ({ ...prev, email: undefined })); + } + }} + disabled={isLoading} + className={cn(errors.email && 'border-red-500 focus-visible:ring-red-500')} + /> + {errors.email && ( +

{errors.email}

+ )} +
+ +
+ +
+ { + setPassword(e.target.value); + if (errors.password) { + setErrors((prev) => ({ ...prev, password: undefined })); + } + }} + disabled={isLoading} + className={cn( + 'pr-10', + errors.password && 'border-red-500 focus-visible:ring-red-500' + )} + /> + +
+ {errors.password && ( +

{errors.password}

+ )} +
+ +
+ + + ¿Olvidaste tu contraseña? + +
+ + +
+
+
+ ); +} + +export default LoginForm;