97 lines · 3.9 KB
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { redirectSignedIn } from "~/lib/route-guards";
import { returnSearch, safeReturnPath } from "~/lib/return-to";
import { getAuthOptions, signIn } from "~/fns/auth";
import { finishPasskeyLogin, startPasskeyLogin } from "~/fns/login";
import { useAction } from "~/hooks/use-action";
import { useTurnstile } from "~/hooks/use-turnstile";
import { usePasskeyAssertion } from "~/hooks/use-passkey";
import { AuthCard } from "~/components/AuthCard";
import { SignupConsent } from "~/components/SignupConsent";
import { Button, buttonClass } from "~/ui/Button";
import { TextField } from "~/ui/Field";
import { ErrorBanner } from "~/ui/Callout";
import { TextLink } from "~/ui/TextLink";

export const Route = createFileRoute("/login")({
  validateSearch: returnSearch,
  beforeLoad: redirectSignedIn,
  loader: () => getAuthOptions(),
  component: LoginPage,
});

function LoginPage() {
  const options = Route.useLoaderData();
  // Validated again here: router search state can still carry the raw value during SSR.
  const next = safeReturnPath(Route.useSearch().next) ?? undefined;
  const navigate = useNavigate();
  const action = useAction(signIn);
  const turnstile = useTurnstile(options.turnstileSiteKey, "login");
  const passkey = usePasskeyAssertion(startPasskeyLogin, finishPasskeyLogin);

  async function submit(event: React.SubmitEvent<HTMLFormElement>) {
    event.preventDefault();
    const form = new FormData(event.currentTarget);
    const result = await action.run({
      data: {
        login: String(form.get("login") ?? ""),
        password: String(form.get("password") ?? ""),
        turnstileToken: turnstile.token,
      },