import { type FormEvent, useEffect, useState } from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { useQueryClient } from "@tanstack/react-query"; import { http } from "../api/http"; import { useAuthSession } from "../hooks/useAuthSession"; function useNextPath() { const location = useLocation(); const params = new URLSearchParams(location.search); return params.get("next") || "/"; } export default function LoginPage() { const navigate = useNavigate(); const next = useNextPath(); const qc = useQueryClient(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); const [pending, setPending] = useState(false); const [touched, setTouched] = useState({ email: false, password: false }); const [submitted, setSubmitted] = useState(false); const session = useAuthSession({ retry: false }); const isEmailValid = (value: string) => /\S+@\S+\.\S+/.test(value); const emailError = (touched.email || submitted) && !email.trim() ? "Email is required." : (touched.email || submitted) && !isEmailValid(email) ? "Enter a valid email address." : ""; const passwordError = (touched.password || submitted) && !password ? "Password is required." : (touched.password || submitted) && password.length < 8 ? "Password must be at least 8 characters." : ""; useEffect(() => { if (session.data?.userId) { navigate(next || "/", { replace: true }); } }, [session.data, navigate, next]); async function handleSubmit(e: FormEvent) { e.preventDefault(); setError(null); setPending(true); setSubmitted(true); if (emailError || passwordError) { setPending(false); return; } try { await http<{ ok: true }>("/auth/login", { method: "POST", body: { email, password }, skipAuthRedirect: true, }); qc.clear(); navigate(next || "/", { replace: true }); } catch (err) { const status = (err as { status?: number; code?: string })?.status; const code = (err as { code?: string })?.code; if (status === 403 && code === "EMAIL_NOT_VERIFIED") { navigate( `/verify?email=${encodeURIComponent(email)}&next=${encodeURIComponent(next || "/")}`, { replace: true } ); return; } const message = status === 401 ? "Email or password is incorrect." : status === 400 ? "Enter a valid email and password." : err instanceof Error ? err.message : "Unable to login. Try again."; setError(message); } finally { setPending(false); } } return (

Login

Sign in to continue budgeting.

{error &&
{error}
} {/* Session errors are expected on login page, so don't show them */}

Need an account?{" "} Register

); }