agilentics / boiler
{% extends "base.html" %}
{% block title %}Sign in{% endblock %}

{# No topbar on the sign-in page: every link in it needs a session. #}
{% block chrome %}{% endblock %}

{% block content %}
<form class="card" id="login-form">
  <h1>Sign in</h1>

  {# The OAuth callback redirects here with ?error=google on any failure. The
     reason is in the log, not the query string - which of "no such account" and
     "bad token" it was is not a stranger's business. #}
  {% if request.args.get("error") == "google" %}
  <p class="error">That Google sign-in did not work. Try again, or use a password.</p>
  {% endif %}

  <label>Email
    <input type="email" name="email" autocomplete="username" required autofocus>
  </label>
  <label>Password
    <input type="password" name="password" autocomplete="current-password" required>
  </label>
  <p class="error" id="login-error" hidden></p>
  <button type="submit">Sign in</button>

  {% if google_enabled() %}
  <div class="divider"><span>or</span></div>
  {# A link, not a fetch: the OAuth flow is a browser redirect to Google. #}
  <a class="button-secondary" href="/auth/google/start">Sign in with Google</a>
  {% endif %}
</form>
{% endblock %}

{% block scripts %}
<script>
  const form = document.getElementById("login-form");
  const error = document.getElementById("login-error");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    error.hidden = true;
    const button = form.querySelector("button");
    button.disabled = true;
    try {
      const data = new FormData(form);
      await api.post("/api/login", {
        email: data.get("email"),
        password: data.get("password"),
      });
      // A full navigation, not a fetch: the session cookie is set now, and the
      // next page must be rendered by the server with it.
      window.location.href = "/";
    } catch (err) {
      error.textContent = err.message;
      error.hidden = false;
      button.disabled = false;
    }
  });
</script>
{% endblock %}