"use client"; import { useEffect, useState, useCallback } from "react"; import { Permission } from "../../auth-context"; import { api } from "../../api"; import { sharedStyles } from "../../styles/shared"; import { Header } from "../../components/Header"; import { useRequireAuth } from "../../hooks/useRequireAuth"; interface InviteRecord { id: number; identifier: string; godfather_id: number; godfather_email: string; status: string; used_by_id: number | null; used_by_email: string | null; created_at: string; spent_at: string | null; revoked_at: string | null; } interface PaginatedResponse { records: T[]; total: number; page: number; per_page: number; total_pages: number; } interface UserOption { id: number; email: string; } export default function AdminInvitesPage() { const [data, setData] = useState | null>(null); const [error, setError] = useState(null); const [page, setPage] = useState(1); const [statusFilter, setStatusFilter] = useState(""); const [isCreating, setIsCreating] = useState(false); const [newGodfatherId, setNewGodfatherId] = useState(""); const [createError, setCreateError] = useState(null); const [users, setUsers] = useState([]); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.MANAGE_INVITES, fallbackRedirect: "/", }); const fetchUsers = useCallback(async () => { try { const data = await api.get("/api/admin/users"); setUsers(data); } catch (err) { console.error("Failed to fetch users:", err); } }, []); const fetchInvites = useCallback(async (page: number, status: string) => { setError(null); try { let url = `/api/admin/invites?page=${page}&per_page=10`; if (status) { url += `&status=${status}`; } const data = await api.get>(url); setData(data); } catch (err) { setData(null); setError(err instanceof Error ? err.message : "Failed to load invites"); } }, []); useEffect(() => { if (user && isAuthorized) { fetchUsers(); fetchInvites(page, statusFilter); } }, [user, page, statusFilter, isAuthorized, fetchUsers, fetchInvites]); const handleCreateInvite = async () => { if (!newGodfatherId) { setCreateError("Please select a godfather"); return; } setIsCreating(true); setCreateError(null); try { await api.post("/api/admin/invites", { godfather_id: parseInt(newGodfatherId), }); setNewGodfatherId(""); fetchInvites(1, statusFilter); setPage(1); } catch (err) { setCreateError(err instanceof Error ? err.message : "Failed to create invite"); } finally { setIsCreating(false); } }; const handleRevoke = async (inviteId: number) => { try { await api.post(`/api/admin/invites/${inviteId}/revoke`); setError(null); fetchInvites(page, statusFilter); } catch (err) { setError(err instanceof Error ? err.message : "Failed to revoke invite"); } }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleString(); }; const getStatusBadgeStyle = (status: string) => { switch (status) { case "ready": return styles.statusReady; case "spent": return styles.statusSpent; case "revoked": return styles.statusRevoked; default: return {}; } }; if (isLoading) { return (
Loading...
); } if (!user || !isAuthorized) { return null; } return (
{/* Create Invite Section */}

Create Invite

{users.length === 0 && ( No users loaded yet. Create at least one invite to populate the list. )}
{createError &&
{createError}
}
{/* Invites Table */}

All Invites

{data?.total ?? 0} invites
{error && ( )} {!error && data?.records.map((record) => ( ))} {!error && (!data || data.records.length === 0) && ( )}
Code Godfather Status Used By Created Actions
{error}
{record.identifier} {record.godfather_email} {record.status} {record.used_by_email || "-"} {formatDate(record.created_at)} {record.status === "ready" && ( )}
No invites yet
{data && data.total_pages > 1 && (
{page} / {data.total_pages}
)}
); } const pageStyles: Record = { content: { flex: 1, padding: "2rem", overflowY: "auto", }, pageContainer: { display: "flex", flexDirection: "column", gap: "2rem", maxWidth: "1000px", margin: "0 auto", }, createCard: { background: "rgba(99, 102, 241, 0.08)", border: "1px solid rgba(99, 102, 241, 0.2)", borderRadius: "16px", padding: "1.5rem", }, createTitle: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "1rem", fontWeight: 600, color: "rgba(255, 255, 255, 0.9)", margin: "0 0 1rem 0", }, createForm: { display: "flex", flexDirection: "column", gap: "1rem", }, inputGroup: { display: "flex", flexDirection: "column", gap: "0.5rem", }, inputLabel: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.8rem", color: "rgba(255, 255, 255, 0.5)", }, select: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.9rem", padding: "0.75rem", background: "rgba(255, 255, 255, 0.05)", border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: "8px", color: "#fff", maxWidth: "400px", cursor: "pointer", }, createError: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.85rem", color: "#f87171", }, createButton: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.9rem", fontWeight: 500, padding: "0.75rem 1.5rem", background: "rgba(99, 102, 241, 0.3)", color: "#fff", border: "1px solid rgba(99, 102, 241, 0.5)", borderRadius: "8px", cursor: "pointer", alignSelf: "flex-start", }, createButtonDisabled: { opacity: 0.5, cursor: "not-allowed", }, inputHint: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.75rem", color: "rgba(255, 255, 255, 0.4)", fontStyle: "italic", }, tableCard: { background: "rgba(255, 255, 255, 0.03)", backdropFilter: "blur(10px)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "20px", padding: "1.5rem", boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)", }, tableHeader: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "1rem", flexWrap: "wrap", gap: "1rem", }, tableTitle: { fontFamily: "'Instrument Serif', Georgia, serif", fontSize: "1.5rem", fontWeight: 400, color: "#fff", margin: 0, }, filterGroup: { display: "flex", alignItems: "center", gap: "1rem", }, filterSelect: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.85rem", padding: "0.5rem 1rem", background: "rgba(255, 255, 255, 0.05)", border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: "8px", color: "#fff", cursor: "pointer", }, totalCount: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.4)", }, tableWrapper: { overflowX: "auto", }, table: { width: "100%", borderCollapse: "collapse", fontFamily: "'DM Sans', system-ui, sans-serif", }, th: { textAlign: "left", padding: "0.75rem 1rem", fontSize: "0.75rem", fontWeight: 600, color: "rgba(255, 255, 255, 0.4)", textTransform: "uppercase", letterSpacing: "0.05em", borderBottom: "1px solid rgba(255, 255, 255, 0.08)", }, tr: { borderBottom: "1px solid rgba(255, 255, 255, 0.04)", }, td: { padding: "0.875rem 1rem", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.7)", }, tdCode: { padding: "0.875rem 1rem", fontSize: "0.875rem", color: "#fff", fontFamily: "'DM Mono', monospace", }, tdDate: { padding: "0.875rem 1rem", fontSize: "0.75rem", color: "rgba(255, 255, 255, 0.4)", }, statusBadge: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.7rem", fontWeight: 500, padding: "0.25rem 0.5rem", borderRadius: "4px", textTransform: "uppercase", }, statusReady: { background: "rgba(99, 102, 241, 0.2)", color: "rgba(129, 140, 248, 0.9)", }, statusSpent: { background: "rgba(34, 197, 94, 0.2)", color: "rgba(34, 197, 94, 0.9)", }, statusRevoked: { background: "rgba(239, 68, 68, 0.2)", color: "rgba(239, 68, 68, 0.9)", }, revokeButton: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.75rem", padding: "0.4rem 0.75rem", background: "rgba(239, 68, 68, 0.15)", color: "rgba(239, 68, 68, 0.9)", border: "1px solid rgba(239, 68, 68, 0.3)", borderRadius: "6px", cursor: "pointer", }, emptyRow: { padding: "2rem 1rem", textAlign: "center", color: "rgba(255, 255, 255, 0.3)", fontSize: "0.875rem", }, errorRow: { padding: "2rem 1rem", textAlign: "center", color: "#f87171", fontSize: "0.875rem", }, pagination: { display: "flex", justifyContent: "center", alignItems: "center", gap: "1rem", marginTop: "1rem", paddingTop: "1rem", borderTop: "1px solid rgba(255, 255, 255, 0.06)", }, pageBtn: { fontFamily: "'DM Sans', system-ui, sans-serif", padding: "0.5rem 1rem", fontSize: "1rem", background: "rgba(255, 255, 255, 0.05)", color: "rgba(255, 255, 255, 0.7)", border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: "8px", cursor: "pointer", transition: "all 0.2s", }, pageInfo: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", color: "rgba(255, 255, 255, 0.5)", }, }; const styles = { ...sharedStyles, ...pageStyles };