"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { Loader2, ShieldCheck } from "lucide-react"; import { z } from "zod"; import { FormField } from "@/components/ui/form-field"; import { PasswordField } from "@/components/ui/password-field"; import { TurnstileWidget, type TurnstileWidgetHandle } from "@/components/ui/turnstile-widget"; import { useCountdown } from "@/hooks/use-countdown"; import { classifyLoginError, useCurrentUser, useLogin, useSystemStatus } from "@/hooks/use-auth"; const loginSchema = z.object({ email: z.string().email("Enter a valid email address"), password: z.string().min(1, "Enter your password"), }); type LoginForm = z.infer; function LoginFormCard() { const router = useRouter(); const searchParams = useSearchParams(); const loginMutation = useLogin(); const { data: currentUser } = useCurrentUser(); const { data: status } = useSystemStatus(); const turnstileRef = useRef(null); const [turnstileToken, setTurnstileToken] = useState(null); const countdown = useCountdown(); const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(loginSchema) }); // Mirrors the backend's turnstile_required(is_localhost, settings) gate - // skipped on loopback regardless of auth mode. const turnstileRequired = status ? !status.is_localhost : false; useEffect(() => { if (currentUser) { router.replace("/dashboard"); } }, [currentUser, router]); const onSubmit = handleSubmit(async (values) => { try { await loginMutation.mutateAsync({ ...values, turnstile_token: turnstileToken ?? undefined, }); router.push("/dashboard"); } catch (error) { const failure = classifyLoginError(error); if (failure.kind === "verify_email") { router.push(`/verify-email?email=${encodeURIComponent(values.email)}`); return; } if (failure.retryAfterSeconds) { countdown.start(failure.retryAfterSeconds); } } finally { // Tokens are single-use - always reset after a submit attempt, // success or failure, so a retry never reuses a stale token. turnstileRef.current?.reset(); setTurnstileToken(null); } }); const failure = loginMutation.isError ? classifyLoginError(loginMutation.error) : null; return (

Sign in

Welcome back to CI Agent.

{searchParams.get("registered") && (

Account created. Sign in below.

)} {searchParams.get("verified") && (

Email verified. Sign in below.

)} {searchParams.get("reset") && (

Password reset. Sign in with your new password.

)}
{turnstileRequired && ( )} {failure && (

{failure.message}

{failure.kind === "account_locked" && ( Reset your password to unlock it )} {failure.kind === "banned" && ( Request an unban )}
)}

Forgot your password?

Don't have an account?{" "} Create one

); } export default function LoginPage() { return (
); }