← back to Govarbitrage

src/app/newsletter/SubscribeForm.tsx

76 lines

"use client";

import { useState } from "react";

type State = { phase: "idle" | "busy" | "done" | "error"; message?: string };

export default function SubscribeForm() {
  const [email, setEmail] = useState("");
  const [website, setWebsite] = useState(""); // honeypot — humans never see it
  const [state, setState] = useState<State>({ phase: "idle" });

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setState({ phase: "busy" });
    try {
      const res = await fetch("/api/newsletter/subscribe", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, website }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        setState({ phase: "error", message: data.error || "Something went wrong. Please try again." });
        return;
      }
      setState({ phase: "done", message: data.message });
    } catch {
      setState({ phase: "error", message: "Network error. Please try again." });
    }
  }

  if (state.phase === "done") {
    return (
      <div style={{ background: "#f0fdf4", border: "1px solid #bbf7d0", borderRadius: 12, padding: "22px 24px", textAlign: "center" }}>
        <div style={{ fontSize: 20, fontWeight: 700, color: "#166534", marginBottom: 6 }}>Almost there — confirm your email</div>
        <p style={{ color: "#15803d", fontSize: 14, margin: 0 }}>{state.message}</p>
      </div>
    );
  }

  return (
    <form onSubmit={submit} style={{ display: "flex", gap: 10, justifyContent: "center", flexWrap: "wrap" }}>
      <input
        type="email"
        required
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="you@example.com"
        aria-label="Email address"
        style={{ flex: "1 1 260px", maxWidth: 340, padding: "11px 14px", fontSize: 15, border: "1px solid #cbd5e1", borderRadius: 10, background: "#fff", color: "#0f172a" }}
      />
      {/* Honeypot: visually hidden, tab-skipped. Bots that fill it are dropped. */}
      <input
        type="text"
        name="website"
        value={website}
        onChange={(e) => setWebsite(e.target.value)}
        tabIndex={-1}
        autoComplete="off"
        aria-hidden="true"
        style={{ position: "absolute", left: -9999, width: 1, height: 1, opacity: 0 }}
      />
      <button
        type="submit"
        disabled={state.phase === "busy"}
        style={{ padding: "11px 22px", fontSize: 15, fontWeight: 700, background: "#111827", color: "#fff", border: "none", borderRadius: 10, cursor: state.phase === "busy" ? "wait" : "pointer" }}
      >
        {state.phase === "busy" ? "Subscribing…" : "Get the digest"}
      </button>
      {state.phase === "error" && (
        <div style={{ flexBasis: "100%", textAlign: "center", color: "#b91c1c", fontSize: 13 }}>{state.message}</div>
      )}
    </form>
  );
}