/* Admin dashboard (/admin) - restricted to signed-in users whose Google email is
   on the server's ADMIN_EMAILS allowlist. There is no separate password: every
   /api/admin/* call sends the user's own session token as a Bearer header, and the
   backend (requireAdmin) checks the email claim. Non-admins are redirected to the
   homepage - but that's only UX; the server is the real gate. */

const ADMIN_API = (typeof window !== "undefined" && window.IDEAL_OFFER_API) || "/api";

async function adminFetch(path, token, opts = {}) {
  const res = await fetch(ADMIN_API + path, {
    ...opts,
    headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, ...(opts.headers || {}) },
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) { const err = new Error(data.error || ("Request failed (" + res.status + ")")); err.status = res.status; throw err; }
  return data;
}

function money(amount, currency) {
  try { return (amount / 100).toLocaleString(undefined, { style: "currency", currency: (currency || "cad").toUpperCase() }); }
  catch (e) { return "$" + (amount / 100).toFixed(2); }
}
function fmtEpoch(sec) { try { return new Date(sec * 1000).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }); } catch (e) { return ""; } }
function fmtISO(iso) { try { return new Date(iso).toLocaleDateString(undefined, { dateStyle: "medium" }); } catch (e) { return ""; } }

function useAdminData(path, token) {
  const [state, set] = useState({ loading: true, data: null, error: null });
  useEffect(() => {
    let alive = true;
    set({ loading: true, data: null, error: null });
    adminFetch(path, token)
      .then((d) => alive && set({ loading: false, data: d, error: null }))
      .catch((e) => alive && set({ loading: false, data: null, error: e.message }));
    return () => { alive = false; };
  }, [path, token]);
  return state;
}

const ADMIN_TABS = [
  { k: "overview", label: "Overview" }, { k: "stats", label: "Stats" }, { k: "modules", label: "Modules" },
  { k: "payments", label: "Payments" }, { k: "users", label: "Users" },
  { k: "feedback", label: "Feedback" }, { k: "outcomes", label: "Outcomes" },
  { k: "leads", label: "Leads" }, { k: "refunds", label: "Refunds" }, { k: "email", label: "Email" },
];

function Admin() {
  const user = useUser();
  const token = user && user.token;
  const [access, setAccess] = useState("checking"); // checking | ok | denied
  const [tab, setTab] = useState("overview");

  // Ask the server whether this signed-in user is allowed (email on ADMIN_EMAILS).
  useEffect(() => {
    if (!token) return; // not signed in — the redirect effect handles it
    let alive = true;
    setAccess("checking");
    adminFetch("/admin/overview", token)
      // 401/403 → not an admin; any other failure (e.g. DB hiccup) still means the
      // token was accepted, so keep them in and let each tab surface its own error.
      .then(() => { if (alive) setAccess("ok"); })
      .catch((e) => { if (alive) setAccess(e.status === 401 || e.status === 403 ? "denied" : "ok"); });
    return () => { alive = false; };
  }, [token]);

  // Not signed in, or not on the allowlist → bounce to the homepage.
  useEffect(() => {
    if (!user || access === "denied") window.navigate("/", { replace: true });
  }, [user, access]);

  if (!user || access === "denied") return null; // redirecting
  if (access === "checking") return <main className="po-admin"><div className="wrap"><p className="mono po-admin-loading">Checking access…</p></div></main>;

  return (
    <main className="po-admin">
      <div className="wrap">
        <header className="po-admin-head">
          <div>
            <span className="eyebrow"><Icon name="badge" size={15} /> Admin</span>
            <h1>Dashboard</h1>
          </div>
          <button className="po-admin-lock" onClick={() => window.navigate("/")}><Icon name="close" size={15} /> Exit</button>
        </header>
        <nav className="po-admin-tabs">
          {ADMIN_TABS.map((t) => (
            <button key={t.k} className={"po-admin-tab" + (tab === t.k ? " is-on" : "")} onClick={() => setTab(t.k)}>{t.label}</button>
          ))}
        </nav>
        {tab === "overview" && <AdminOverview token={token} />}
        {tab === "stats" && <AdminStats token={token} />}
        {tab === "modules" && <AdminModules token={token} />}
        {tab === "payments" && <AdminPayments token={token} />}
        {tab === "users" && <AdminUsers token={token} />}
        {tab === "feedback" && <AdminFeedback token={token} />}
        {tab === "outcomes" && <AdminOutcomes token={token} />}
        {tab === "leads" && <AdminLeads token={token} />}
        {tab === "refunds" && <AdminRefunds token={token} />}
        {tab === "email" && <AdminEmail token={token} />}
      </div>
    </main>
  );
}

function KPI({ label, value, icon }) {
  return (
    <div className="po-admin-kpi card">
      <span className="po-admin-kpi-ic"><Icon name={icon} size={18} /></span>
      <b className="po-admin-kpi-val">{value}</b>
      <span className="po-admin-kpi-lbl">{label}</span>
    </div>
  );
}

function AdminOverview({ token }) {
  const { loading, data, error } = useAdminData("/admin/overview", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  return (
    <>
      {!data.stripeConfigured && <p className="po-admin-banner"><Icon name="bolt" size={14} /><span>Stripe isn’t configured - payment metrics stay at zero until you add STRIPE_SECRET_KEY.</span></p>}
      <div className="po-admin-kpis">
        <KPI label="Revenue" value={money(data.revenue, data.currency)} icon="badge" />
        <KPI label="Paid orders" value={data.sales} icon="check" />
        <KPI label="Avg order" value={money(data.avgOrder, data.currency)} icon="spark" />
        <KPI label="Registered users" value={data.users} icon="user" />
        <KPI label="Paying users" value={data.paidUsers} icon="star" />
      </div>
      {data.funnel && <AdminFunnel f={data.funnel} />}
      <h2 className="po-admin-h2">Recent payments</h2>
      <PaymentsTable rows={data.recent} token={token} compact />
    </>
  );
}

function AdminFunnel({ f }) {
  const v = f.visitors || 0, mod = f.moduleVisitors || 0, buy = f.buyers || 0;
  const max = Math.max(1, v, mod, buy);
  const rate = (a, b) => (b ? Math.round((a / b) * 100) : 0);
  const stages = [
    { label: "Visited the site", value: v, sub: null },
    { label: "Entered the modules", value: mod, sub: rate(mod, v) + "% of visitors" },
    { label: "Bought the course", value: buy, sub: rate(buy, mod) + "% of module readers · " + rate(buy, v) + "% of visitors" },
  ];
  return (
    <>
      <h2 className="po-admin-h2">Conversion funnel</h2>
      <div className="po-funnel">
        {stages.map((s, i) => (
          <div key={i} className="po-funnel-row">
            <div className="po-funnel-meta"><b>{s.value.toLocaleString()}</b><span>{s.label}</span></div>
            <div className="po-funnel-track"><span className="po-funnel-fill" style={{ width: Math.round((s.value / max) * 100) + "%" }} /></div>
            {s.sub && <span className="po-funnel-rate mono">{s.sub}</span>}
          </div>
        ))}
      </div>
      <p className="po-funnel-note mono">Visits are anonymous & deduped by browser; buyers = paid orders.</p>
    </>
  );
}

function AdminModules({ token }) {
  const { loading, data, error } = useAdminData("/admin/modules", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  const mods = data.modules || [];
  const maxKeep = Math.max(1, ...mods.map((m) => m.keepReading));
  return (
    <>
      <p className="po-admin-banner"><Icon name="spark" size={14} /><span>Unique readers who clicked “Keep reading” on each module, and how many finished it.</span></p>
      <div className="po-admin-tablewrap">
        <table className="po-admin-table">
          <thead><tr><th>Module</th><th>“Keep reading” clicks</th><th>Completed</th></tr></thead>
          <tbody>
            {mods.map((m) => (
              <tr key={m.id}>
                <td><span className="po-mod-name"><b className="mono">{m.n}</b> {m.label}{m.free && <em className="po-mod-free">free</em>}</span></td>
                <td>
                  {m.free ? (
                    <span className="po-admin-muted">- no paywall</span>
                  ) : (
                    <span className="po-mod-keep">
                      <span className="po-mod-keepbar"><span style={{ width: Math.round((m.keepReading / maxKeep) * 100) + "%" }} /></span>
                      <b className="mono">{m.keepReading}</b>
                    </span>
                  )}
                </td>
                <td className="mono">{m.completed}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </>
  );
}

function pctChange(cur, prev) { if (!prev) return cur > 0 ? 100 : 0; return Math.round(((cur - prev) / prev) * 100); }
function fmtDay(d) { try { return new Date(d + "T00:00:00Z").toLocaleDateString(undefined, { month: "short", day: "numeric", timeZone: "UTC" }); } catch (e) { return d; } }

function StatHead({ label, value, delta }) {
  const flat = delta === 0;
  const up = delta > 0;
  return (
    <div className="po-admin-kpi card">
      <b className="po-admin-kpi-val">{value}</b>
      <span className="po-admin-kpi-lbl">{label}</span>
      <span className={"po-stat-delta " + (flat ? "is-flat" : up ? "is-up" : "is-down")}>
        {flat ? "±" : up ? "▲" : "▼"} {Math.abs(delta)}% <em>vs prev</em>
      </span>
    </div>
  );
}

function AdminStats({ token }) {
  const [days, setDays] = useState(30);
  const { loading, data, error } = useAdminData("/admin/stats?days=" + days, token);
  return (
    <>
      <div className="po-stat-ranges">
        {[7, 30, 90].map((r) => (
          <button key={r} className={"po-stat-range" + (days === r ? " is-on" : "")} onClick={() => setDays(r)}>{r} days</button>
        ))}
      </div>
      {loading ? <p className="po-admin-loading mono">Loading…</p>
        : error ? <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>
        : !data.stripeConfigured ? <p className="po-admin-banner"><Icon name="bolt" size={14} /><span>Stripe isn’t configured - date stats fill in automatically once STRIPE_SECRET_KEY is set and orders come in.</span></p>
        : <StatsBody data={data} days={days} />}
    </>
  );
}

function StatsBody({ data, days }) {
  const series = data.series || [];
  const maxRev = Math.max(1, ...series.map((s) => s.revenue));
  const best = series.reduce((b, s) => (s.revenue > (b ? b.revenue : -1) ? s : b), null);
  const hasOrders = data.totals.orders > 0;
  return (
    <>
      <div className="po-admin-kpis">
        <StatHead label={"Revenue · last " + days + "d"} value={money(data.totals.revenue, data.currency)} delta={pctChange(data.totals.revenue, data.prevTotals.revenue)} />
        <StatHead label={"Orders · last " + days + "d"} value={data.totals.orders} delta={pctChange(data.totals.orders, data.prevTotals.orders)} />
        <KPI label="Best day" value={best && best.revenue ? money(best.revenue, data.currency) : "-"} icon="star" />
      </div>
      <h2 className="po-admin-h2">Revenue by day</h2>
      {hasOrders ? (
        <>
          <div className="po-stat-chart" role="img" aria-label={"Daily revenue, last " + days + " days"}>
            {series.map((s) => (
              <div key={s.date} className="po-stat-col" title={fmtDay(s.date) + " - " + money(s.revenue, data.currency) + " · " + s.orders + " order" + (s.orders === 1 ? "" : "s")}>
                <span className="po-stat-bar" style={{ height: Math.round((s.revenue / maxRev) * 100) + "%" }} />
              </div>
            ))}
          </div>
          <div className="po-stat-axis"><span>{fmtDay(series[0].date)}</span><span>{fmtDay(series[series.length - 1].date)}</span></div>
        </>
      ) : (
        <p className="po-admin-empty">No orders in this window yet - bars appear as sales come in.</p>
      )}
    </>
  );
}

function AdminPayments({ token }) {
  const { loading, data, error } = useAdminData("/admin/payments", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  if (!data.stripeConfigured) return <p className="po-admin-banner"><Icon name="bolt" size={14} /><span>Stripe isn’t configured - add STRIPE_SECRET_KEY to see payments.</span></p>;
  return <PaymentsTable rows={data.payments} token={token} />;
}

function PaymentsTable({ rows, token, compact }) {
  const [refunding, setRefunding] = useState(null);
  const [result, setResult] = useState({}); // id -> "refunded" | error message
  if (!rows || !rows.length) return <p className="po-admin-empty">No payments yet.</p>;
  const refund = async (row) => {
    if (!row.paymentIntent) return;
    if (!window.confirm(`Refund ${money(row.amount, row.currency)} to ${row.email || "this customer"}?`)) return;
    setRefunding(row.id);
    try {
      await adminFetch("/admin/refund", token, { method: "POST", body: JSON.stringify({ paymentIntent: row.paymentIntent }) });
      setResult((m) => ({ ...m, [row.id]: "refunded" }));
    } catch (e) { setResult((m) => ({ ...m, [row.id]: e.message })); }
    finally { setRefunding(null); }
  };
  return (
    <div className="po-admin-tablewrap">
      <table className="po-admin-table">
        <thead><tr><th>Date</th><th>Customer</th><th>Amount</th><th>Status</th>{!compact && <th aria-label="actions"></th>}</tr></thead>
        <tbody>
          {rows.map((r) => (
            <tr key={r.id}>
              <td className="mono po-admin-nowrap">{fmtEpoch(r.created)}</td>
              <td>{r.email || <span className="po-admin-muted">-</span>}</td>
              <td className="mono">{money(r.amount, r.currency)}</td>
              <td><span className={"po-admin-pill po-admin-pill-" + (r.status === "paid" ? "ok" : "warn")}>{r.status}</span></td>
              {!compact && (
                <td className="po-admin-actions">
                  {result[r.id] === "refunded"
                    ? <span className="po-admin-muted">Refunded</span>
                    : r.status === "paid" && r.paymentIntent
                      ? <button className="po-admin-refund" disabled={refunding === r.id} onClick={() => refund(r)}>{refunding === r.id ? "…" : "Refund"}</button>
                      : null}
                  {result[r.id] && result[r.id] !== "refunded" && <span className="po-admin-err-inline">{result[r.id]}</span>}
                </td>
              )}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function AdminUsers({ token }) {
  const { loading, data, error } = useAdminData("/admin/users", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  const users = data.users || [];
  const total = data.modulesTotal || 6;
  if (!users.length) return <p className="po-admin-empty">No registered users yet - they appear here after signing in with Google.</p>;
  return (
    <div className="po-admin-tablewrap">
      <table className="po-admin-table">
        <thead><tr><th>User</th><th>Email</th><th>Joined</th><th>Modules</th><th>Logins</th><th>Course</th></tr></thead>
        <tbody>
          {users.map((u) => (
            <tr key={u.id || u.email}>
              <td>
                <span className="po-admin-user">
                  {u.picture
                    ? <img src={u.picture} alt="" referrerPolicy="no-referrer" />
                    : <span className="po-admin-uav">{(u.name || u.email || "?").charAt(0).toUpperCase()}</span>}
                  {u.name || <span className="po-admin-muted">-</span>}
                </span>
              </td>
              <td>{u.email}</td>
              <td className="mono po-admin-nowrap">{fmtISO(u.joined)}</td>
              <td>
                <span className="po-admin-modprog">
                  <span className="po-admin-modbar"><span style={{ width: Math.round(((u.completed || 0) / total) * 100) + "%" }} /></span>
                  <b className="mono">{u.completed || 0}/{total}</b>
                </span>
              </td>
              <td className="mono">{u.logins}</td>
              <td>{u.purchased ? <span className="po-admin-pill po-admin-pill-ok">Purchased</span> : <span className="po-admin-muted">Free</span>}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ── data-collection views ──────────────────────────────────────────────────── */
function AdminFeedback({ token }) {
  const { loading, data, error } = useAdminData("/admin/feedback", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  const byModule = data.byModule || [], comments = data.comments || [];
  if (!byModule.length) return <p className="po-admin-empty">No feedback yet - it appears as readers rate modules.</p>;
  return (
    <>
      <h2 className="po-admin-h2">Helpful by module</h2>
      <div className="po-admin-tablewrap">
        <table className="po-admin-table">
          <thead><tr><th>Module</th><th>👍</th><th>👎</th><th>Helpful</th></tr></thead>
          <tbody>
            {byModule.map((m) => { const pct = m.total ? Math.round((m.up / m.total) * 100) : 0; return (
              <tr key={m.module_id}>
                <td className="mono">{m.module_id}</td><td className="mono">{m.up}</td><td className="mono">{m.down}</td>
                <td><span className="po-admin-modprog"><span className="po-admin-modbar"><span style={{ width: pct + "%" }} /></span><b className="mono">{pct}%</b></span></td>
              </tr>
            ); })}
          </tbody>
        </table>
      </div>
      {comments.length > 0 && (
        <>
          <h2 className="po-admin-h2">Recent comments</h2>
          <div className="po-admin-feed">
            {comments.map((c, i) => (
              <div key={i} className="po-admin-feeditem">
                <span className={"po-admin-pill po-admin-pill-" + (c.helpful ? "ok" : "warn")}>{c.helpful ? "👍" : "👎"} {c.module_id}</span>
                <p>{c.comment}</p>
                <span className="po-admin-muted mono">{fmtISO(c.created)}</span>
              </div>
            ))}
          </div>
        </>
      )}
    </>
  );
}

function AdminOutcomes({ token }) {
  const { loading, data, error } = useAdminData("/admin/outcomes", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  const agg = data.aggregate || {}, rows = data.outcomes || [];
  if (!rows.length) return <p className="po-admin-empty">No outcome responses yet - they come from the account-page survey.</p>;
  const testimonials = rows.filter((r) => r.share_consent && r.quote);
  return (
    <>
      <div className="po-admin-kpis">
        <KPI label="Responses" value={agg.total} icon="user" />
        <KPI label="Got interviews" value={agg.interviews} icon="chat" />
        <KPI label="Got offers" value={agg.offers} icon="badge" />
        <KPI label="Hired" value={agg.hired} icon="star" />
      </div>
      <h2 className="po-admin-h2">Responses</h2>
      <div className="po-admin-tablewrap">
        <table className="po-admin-table">
          <thead><tr><th>When</th><th>Email</th><th>Status</th><th>Interview</th><th>Offer</th><th>Company / role / city</th></tr></thead>
          <tbody>
            {rows.map((r) => (
              <tr key={r.id}>
                <td className="mono po-admin-nowrap">{fmtISO(r.created)}</td><td>{r.email || <span className="po-admin-muted">-</span>}</td>
                <td>{r.status}</td><td className="mono">{r.got_interview ? "✓" : "-"}</td><td className="mono">{r.got_offer ? "✓" : "-"}</td>
                <td>{[r.company, r.role, r.city].filter(Boolean).join(" · ") || <span className="po-admin-muted">-</span>}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      {testimonials.length > 0 && (
        <>
          <h2 className="po-admin-h2">Consented testimonials <span className="po-admin-muted">({testimonials.length}) - OK to publish</span></h2>
          <div className="po-admin-feed">
            {testimonials.map((t, i) => (
              <div key={i} className="po-admin-feeditem">
                <p>“{t.quote}”</p>
                <span className="po-admin-muted mono">{[t.company, t.role, t.city].filter(Boolean).join(" · ")}{t.email ? " · " + t.email : ""}</span>
              </div>
            ))}
          </div>
        </>
      )}
    </>
  );
}

function AdminLeads({ token }) {
  const { loading, data, error } = useAdminData("/admin/leads", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  const leads = data.leads || [];
  if (!leads.length) return <p className="po-admin-empty">No leads captured yet.</p>;
  const csv = "data:text/csv;charset=utf-8," + encodeURIComponent("email,source,created\n" + leads.map((l) => [l.email, l.source || "", l.created || ""].join(",")).join("\n"));
  return (
    <>
      <div className="po-admin-toolbar">
        <span className="po-admin-muted">{leads.length} email{leads.length === 1 ? "" : "s"}</span>
        <a className="po-admin-refund" href={csv} download="behired-leads.csv">Export CSV</a>
      </div>
      <div className="po-admin-tablewrap">
        <table className="po-admin-table">
          <thead><tr><th>Email</th><th>Source</th><th>Captured</th></tr></thead>
          <tbody>{leads.map((l, i) => (<tr key={i}><td>{l.email}</td><td className="mono">{l.source}</td><td className="mono po-admin-nowrap">{fmtISO(l.created)}</td></tr>))}</tbody>
        </table>
      </div>
    </>
  );
}

function AdminRefunds({ token }) {
  const { loading, data, error } = useAdminData("/admin/refund-requests", token);
  if (loading) return <p className="po-admin-loading mono">Loading…</p>;
  if (error) return <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{error}</span></p>;
  const reqs = data.requests || [];
  if (!reqs.length) return <p className="po-admin-empty">No refund reasons submitted yet.</p>;
  return (
    <div className="po-admin-tablewrap">
      <table className="po-admin-table">
        <thead><tr><th>When</th><th>Email</th><th>Reason</th><th>Note</th></tr></thead>
        <tbody>{reqs.map((r, i) => (<tr key={i}><td className="mono po-admin-nowrap">{fmtISO(r.created)}</td><td>{r.email || <span className="po-admin-muted">-</span>}</td><td>{r.reason}</td><td>{r.note || <span className="po-admin-muted">-</span>}</td></tr>))}</tbody>
      </table>
    </div>
  );
}

function AdminEmail({ token }) {
  const [audience, setAudience] = useState("leads");
  const [subject, setSubject] = useState("");
  const [body, setBody] = useState("");
  const [testEmail, setTestEmail] = useState("");
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState(null);

  const send = async (isTest) => {
    if (!subject.trim() || !body.trim()) { setResult({ error: "Subject and body are required." }); return; }
    if (isTest && !testEmail.trim()) { setResult({ error: "Enter a test address first." }); return; }
    if (!isTest && !window.confirm("Send this to the entire '" + audience + "' list?")) return;
    setBusy(true); setResult(null);
    try {
      const payload = { audience, subject: subject.trim(), html: body.trim().replace(/\n/g, "<br>") };
      if (isTest) payload.testEmail = testEmail.trim();
      const d = await adminFetch("/admin/send-email", token, { method: "POST", body: JSON.stringify(payload) });
      setResult(d);
    } catch (e) { setResult({ error: e.message }); }
    finally { setBusy(false); }
  };

  return (
    <div className="po-admin-email">
      <p className="po-admin-banner"><Icon name="bolt" size={14} /><span>Every email auto-appends sender identification + a one-click unsubscribe link (CASL); unsubscribed addresses are skipped. Requires RESEND_API_KEY, EMAIL_FROM and EMAIL_SENDER_ADDRESS in .env.</span></p>
      <label className="po-acct-label">Audience</label>
      <div className="po-stat-ranges">
        {[["leads", "Free-reader leads"], ["customers", "Paying customers"], ["all-users", "All signed-in users"]].map(([v, l]) => (
          <button key={v} className={"po-stat-range" + (audience === v ? " is-on" : "")} onClick={() => setAudience(v)}>{l}</button>
        ))}
      </div>
      <input className="po-admin-input" placeholder="Subject" value={subject} onChange={(e) => setSubject(e.target.value)} />
      <textarea className="po-admin-textarea" rows={10} placeholder="Write your email… (plain text; line breaks are preserved)" value={body} onChange={(e) => setBody(e.target.value)} />
      <div className="po-admin-email-actions">
        <div className="po-admin-test">
          <input className="po-admin-input" placeholder="you@email.com" value={testEmail} onChange={(e) => setTestEmail(e.target.value)} />
          <button className="po-admin-refund" disabled={busy} onClick={() => send(true)}>Send test</button>
        </div>
        <button className="btn btn-primary" disabled={busy} onClick={() => send(false)}>{busy ? "Sending…" : "Send to list"}</button>
      </div>
      {result && (result.error
        ? <p className="po-admin-error"><Icon name="bolt" size={14} /><span>{result.error}</span></p>
        : <p className="po-admin-success"><Icon name="check" size={14} stroke={2.6} /> Sent {result.sent} of {result.total}{result.test ? " (test)" : ""}.{result.note ? " " + result.note : ""}{result.errors && result.errors.length ? " Issues: " + result.errors.join("; ") : ""}</p>)}
    </div>
  );
}

Object.assign(window, { Admin });
