gohorsejobs/frontend/src/app/login/page.tsx
2026-01-19 15:58:33 -03:00

275 lines
9.5 KiB
TypeScript

"use client";
import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import {
Briefcase,
AlertCircle,
Eye,
EyeOff,
User as UserIcon,
Lock,
Building2,
} from "lucide-react";
import { login } from "@/lib/auth";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { motion } from "framer-motion";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useTranslation } from "@/lib/i18n";
type LoginFormData = {
email: string;
password: string;
rememberMe?: boolean;
};
export default function LoginPage() {
const router = useRouter();
const { t } = useTranslation();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const loginSchema = useMemo(() => z.object({
email: z.string().min(3, t("auth.login.validation.username")),
password: z.string().min(3, t("auth.login.validation.password")),
rememberMe: z.boolean().optional(),
}), [t]);
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: "",
password: "",
rememberMe: false,
},
});
const handleLogin = async (data: LoginFormData) => {
setError("");
setLoading(true);
try {
// Login sem passar role, o backend decide
console.log('🚀 [LOGIN FRONT] Tentando login com:', data.email);
const user = await login(data.email, data.password);
console.log('✅ [LOGIN FRONT] Sucesso:', user);
if (user) {
// Se "lembrar de mim" estiver marcado, salvar no localStorage
if (data.rememberMe) {
localStorage.setItem("rememberedEmail", data.email);
}
router.push("/dashboard");
} else {
setError(t("auth.login.errors.invalidCredentials"));
}
} catch (err: any) {
console.error('🔥 [LOGIN FRONT] Erro no login:', err);
console.error('🔥 [LOGIN FRONT] Detalhes:', err.response?.data || err.message);
const errorMessage = err.message;
if (errorMessage === "AUTH_INVALID_CREDENTIALS") {
setError(t("auth.login.errors.invalidCredentials"));
} else if (errorMessage === "AUTH_SERVER_ERROR") {
setError(t("auth.login.errors.serverError"));
} else {
setError(t("auth.login.errors.generic"));
}
} finally {
setLoading(false);
}
};
const onSubmit = (data: LoginFormData) => {
handleLogin(data);
};
return (
<div className="min-h-screen flex flex-col lg:flex-row">
{/* Left Side - Branding */}
<div className="lg:flex-1 bg-gradient-to-br from-primary to-primary/80 p-8 flex flex-col justify-center items-center text-primary-foreground">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md text-center"
>
<div className="flex items-center justify-center gap-3 mb-8">
<Image
src="/logohorseee.png"
alt="GoHorseJobs"
width={140}
height={140}
className="rounded-lg"
/>
</div>
<h1 className="text-4xl font-bold mb-4">
{t("auth.login.hero.title")}
</h1>
<p className="text-lg opacity-90 leading-relaxed">
{t("auth.login.hero.subtitle")}
</p>
<div className="mt-8 space-y-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
<UserIcon className="w-4 h-4" />
</div>
<span>{t("auth.login.hero.bulletProfile")}</span>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
<Building2 className="w-4 h-4" />
</div>
<span>{t("auth.login.hero.bulletCompanies")}</span>
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
<Briefcase className="w-4 h-4" />
</div>
<span>{t("auth.login.hero.bulletJobs")}</span>
</div>
</div>
</motion.div>
</div>
{/* Right Side - Login Form */}
<div className="lg:flex-1 flex items-center justify-center p-8 bg-background">
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
className="w-full max-w-md space-y-6"
>
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold">{t("auth.login.title")}</h2>
<p className="text-muted-foreground">
{t("auth.login.subtitle")}
</p>
</div>
<Card className="border-0 shadow-lg">
<CardContent className="pt-6">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{error && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
>
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
</motion.div>
)}
<div className="space-y-2">
<Label htmlFor="email">{t("auth.login.fields.username")}</Label>
<div className="relative" suppressHydrationWarning>
<UserIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="email"
type="text"
placeholder={t("auth.login.fields.usernamePlaceholder")}
className="pl-10"
{...register("email")}
/>
</div>
{errors.email && (
<p className="text-sm text-destructive">
{errors.email.message}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">{t("auth.login.fields.password")}</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder={t("auth.login.fields.passwordPlaceholder")}
className="pl-10 pr-10"
{...register("password")}
/>
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
{errors.password && (
<p className="text-sm text-destructive">
{errors.password.message}
</p>
)}
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox id="rememberMe" {...register("rememberMe")} />
<Label
htmlFor="rememberMe"
className="text-sm font-normal cursor-pointer"
>
{t("auth.login.rememberMe")}
</Label>
</div>
<Link
href="/forgot-password"
className="text-sm text-primary hover:underline"
>
{t("auth.login.forgotPassword")}
</Link>
</div>
<Button
type="submit"
className="w-full h-11 cursor-pointer"
disabled={loading}
>
{loading ? t("auth.login.loading") : t("auth.login.submit")}
</Button>
</form>
</CardContent>
</Card>
<div className="text-center">
<Link
href="/"
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-2"
>
{t("auth.login.backHome")}
</Link>
</div>
</motion.div>
</div>
</div>
);
}