diff --git a/src/components/AdminSidebar.tsx b/src/components/AdminSidebar.tsx index 7a308e6..981d88e 100644 --- a/src/components/AdminSidebar.tsx +++ b/src/components/AdminSidebar.tsx @@ -268,6 +268,12 @@ const GROUPS: NavItem[][] = [ icon: Percent, moduleKeys: ["DISCOUNT_MANAGEMENT", "DISCOUNTS"], }, + { + href: "/admin/waitlist", + label: "Waitlist", + icon: Mail, + moduleKeys: ["WAITLIST_MANAGEMENT", "WAITLIST"], + }, { href: "/admin/tax", label: "Tax Management", diff --git a/src/routes/admin/waitlist.tsx b/src/routes/admin/waitlist.tsx new file mode 100644 index 0000000..f8a2ceb --- /dev/null +++ b/src/routes/admin/waitlist.tsx @@ -0,0 +1,162 @@ +import { createSignal, createResource, Show, For } from "solid-js"; + +type WaitlistSignup = { + email: string; + created_at: string; +}; + +type ListWaitlistResponse = { + signups: WaitlistSignup[]; + total: number; + page: number; + limit: number; +}; + +const API = ""; + +function authHeaders(): Record { + const token = + typeof sessionStorage !== "undefined" + ? sessionStorage.getItem("nxtgauge_admin_access_token") || "" + : ""; + return token ? { Authorization: `Bearer ${token}` } : {}; +} + +async function fetchWaitlist(page: number): Promise { + const res = await fetch(`${API}/api/admin/waitlist?page=${page}&limit=200`, { + headers: authHeaders(), + credentials: "include", + }); + if (!res.ok) throw new Error(`Request failed (${res.status})`); + return res.json(); +} + +function toCsv(rows: WaitlistSignup[]): string { + const escape = (v: string) => `"${v.replace(/"/g, '""')}"`; + const header = "email,signed_up_at\n"; + const body = rows.map((r) => `${escape(r.email)},${escape(r.created_at)}`).join("\n"); + return header + body; +} + +function downloadCsv(rows: WaitlistSignup[]) { + const csv = toCsv(rows); + const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `nxtgauge-waitlist-${new Date().toISOString().slice(0, 10)}.csv`; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); +} + +export default function WaitlistPage() { + const [page, setPage] = createSignal(1); + const [data, { refetch }] = createResource(page, fetchWaitlist); + + const totalPages = () => { + const d = data(); + return d ? Math.max(1, Math.ceil(d.total / d.limit)) : 1; + }; + + return ( +
+
+
+

Waitlist

+

+ Emails captured from the nxtgauge.com "Notify Me" coming-soon form. +

+
+
+ + +
+
+ + +
Loading...
+
+ + +
+ {String(data.error?.message || data.error)} +
+
+ + +

+ {data()!.total} total signup{data()!.total === 1 ? "" : "s"} +

+ +
+
+ + + + + + + + + + + + + + + {(row) => ( + + + + + )} + + +
EmailSigned up
+ No signups yet. +
{row.email}{new Date(row.created_at).toLocaleString()}
+
+
+ + 1}> +
+ + + Page {page()} of {totalPages()} + + +
+
+
+
+ ); +}