Skip to content
import { Head } from "fresh/runtime";
import { define, type State } from "../utils.ts";
import type { FreshContext } from "fresh";
import {
  createSession,
  createUser,
  findUserByEmail,
  hashPassword,
  sessionCookie,
  verifyPassword,
} from "../lib/auth.ts";
import { clearDevSignOutCookie, devAutoLoginEnabled } from "@/lib/dev-auth.ts";

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function fail(ctx: FreshContext<State>, error: string) {
  return ctx.render(<LoginPage error={error} />);
}

export const handler = define.handlers({
  GET(ctx) {
    if (ctx.state.user) return ctx.redirect("/");
    return ctx.render(<LoginPage />);
  },
  async POST(ctx) {
    const form = await ctx.req.formData();
    const intent = String(form.get("intent") ?? "");
    const email = String(form.get("email") ?? "").trim().toLowerCase();
    const password = String(form.get("password") ?? "");

    if (intent !== "signin" && intent !== "signup") {
      return fail(ctx, "Invalid request.");
    }
    if (!EMAIL_RE.test(email)) {
      return fail(ctx, "Please enter a valid email address.");
    }
    if (password.length < 8) {
      return fail(ctx, "Password must be at least 8 characters long.");
    }

    let userId: number;

    if (intent === "signup") {
      const name = String(form.get("name") ?? "").trim();
      try {
        const user = await createUser(
          email,
          name === "" ? null : name,
          hashPassword(password),
        );
        userId = user.id;
      } catch (error) {
        if (
          error instanceof Error &&
          error.message.includes("UNIQUE constraint failed")
        ) {
          return fail(ctx, "An account with this email already exists.");
        }
        throw error;
      }
    } else {
      const user = await findUserByEmail(email);
      if (!user || !verifyPassword(password, user.password_hash)) {
        return fail(ctx, "Invalid email or password.");
      }
      userId = user.id;
    }

    const token = await createSession(userId);
    const headers = new Headers({ location: "/" });
    headers.append("set-cookie", sessionCookie(ctx.req, token));
    if (devAutoLoginEnabled()) {
      headers.append("set-cookie", clearDevSignOutCookie());
    }
    return new Response(null, { status: 303, headers });
  },
});

export function LoginPage({ error }: { error?: string }) {
  return (
    <div class="tapestry margin-vines min-h-screen flex flex-col items-center justify-center px-4 py-12">
      <Head>
        <title>Profectus — Sign in</title>
      </Head>

      <header class="relative z-1 text-center mb-10">
        <h1 class="font-gothic text-5xl md:text-6xl tracking-[0.2em] text-accent">
          Profectus
        </h1>
        <div class="fleuron-rule mt-4 w-72 mx-auto">
          <span aria-hidden="true">☾ ✦ ☽</span>
        </div>
        <p class="mt-4 text-xs uppercase tracking-[0.35em] text-base-content/60">
          A deck-building dungeon adventure
        </p>
      </header>

      {error && (
        <div
          role="alert"
          class="alert alert-error alert-soft relative z-1 mb-8 w-full max-w-2xl"
        >
          <span>{error}</span>
        </div>
      )}

      <div class="relative z-1 grid w-full max-w-4xl gap-8 md:grid-cols-2">
        <section class="arch-panel vellum card">
          <div class="corner-fleurons card-body">
            <h2 class="card-title font-heading tracking-[0.25em] uppercase justify-center">
              Sign in
            </h2>
            <div class="fleuron-rule my-2">
              <span aria-hidden="true">☾ ✧ ☽</span>
            </div>
            <form method="post" class="flex flex-col gap-4">
              <input type="hidden" name="intent" value="signin" />
              <fieldset class="fieldset">
                <legend class="fieldset-legend">Email</legend>
                <input
                  type="email"
                  name="email"
                  required
                  placeholder="you@example.com"
                  autocomplete="email"
                  class="input w-full"
                />
              </fieldset>
              <fieldset class="fieldset">
                <legend class="fieldset-legend">Password</legend>
                <input
                  type="password"
                  name="password"
                  required
                  placeholder="••••••••"
                  autocomplete="current-password"
                  class="input w-full"
                />
              </fieldset>
              <button type="submit" class="btn btn-primary iron-band mt-2">
                Enter the city
              </button>
            </form>
          </div>
        </section>

        <section class="arch-panel vellum card">
          <div class="corner-fleurons card-body">
            <h2 class="card-title font-heading tracking-[0.25em] uppercase justify-center">
              Create account
            </h2>
            <div class="fleuron-rule my-2">
              <span aria-hidden="true">☾ ✧ ☽</span>
            </div>
            <form method="post" class="flex flex-col gap-4">
              <input type="hidden" name="intent" value="signup" />
              <fieldset class="fieldset">
                <legend class="fieldset-legend">Hero name</legend>
                <input
                  type="text"
                  name="name"
                  placeholder="Optional"
                  autocomplete="nickname"
                  class="input w-full"
                />
              </fieldset>
              <fieldset class="fieldset">
                <legend class="fieldset-legend">Email</legend>
                <input
                  type="email"
                  name="email"
                  required
                  placeholder="you@example.com"
                  autocomplete="email"
                  class="input w-full"
                />
              </fieldset>
              <fieldset class="fieldset">
                <legend class="fieldset-legend">Password</legend>
                <input
                  type="password"
                  name="password"
                  required
                  minlength={8}
                  placeholder="At least 8 characters"
                  autocomplete="new-password"
                  class="input w-full"
                />
              </fieldset>
              <button type="submit" class="btn btn-outline iron-band mt-2">
                Forge an account
              </button>
            </form>
          </div>
        </section>
      </div>
    </div>
  );
}

export default LoginPage;