"use client"; import { useEffect, useState, useCallback } from "react"; import { Permission } from "../../auth-context"; import { adminApi } from "../../api"; import { Header } from "../../components/Header"; import { useRequireAuth } from "../../hooks/useRequireAuth"; import { useMutation } from "../../hooks/useMutation"; import { components } from "../../generated/api"; import constants from "../../../../shared/constants.json"; import { layoutStyles, cardStyles, tableStyles, paginationStyles, formStyles, buttonStyles, badgeStyles, utilityStyles, } from "../../styles/shared"; const { READY, SPENT, REVOKED } = constants.inviteStatuses; // Use generated types from OpenAPI schema type _InviteRecord = components["schemas"]["InviteResponse"]; type PaginatedInvites = components["schemas"]["PaginatedResponse_InviteResponse_"]; type UserOption = components["schemas"]["AdminUserResponse"]; export default function AdminInvitesPage() { const [data, setData] = useState(null); const [error, setError] = useState(null); const [page, setPage] = useState(1); const [statusFilter, setStatusFilter] = useState(""); const [newGodfatherId, setNewGodfatherId] = useState(""); const [users, setUsers] = useState([]); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.MANAGE_INVITES, fallbackRedirect: "/", }); const fetchUsers = useCallback(async () => { try { const data = await adminApi.getUsers(); setUsers(data); } catch (err) { console.error("Failed to fetch users:", err); } }, []); const fetchInvites = useCallback(async (page: number, status: string) => { setError(null); try { const data = await adminApi.getInvites(page, 10, status || undefined); 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 { mutate: createInvite, isLoading: isCreating, error: createError, } = useMutation( (godfatherId: number) => adminApi.createInvite({ godfather_id: godfatherId, }), { onSuccess: () => { setNewGodfatherId(""); fetchInvites(1, statusFilter); setPage(1); }, } ); const { mutate: revokeInvite } = useMutation( (inviteId: number) => adminApi.revokeInvite(inviteId), { onSuccess: () => { setError(null); fetchInvites(page, statusFilter); }, onError: (err) => { setError(err instanceof Error ? err.message : "Failed to revoke invite"); }, } ); const handleCreateInvite = async () => { if (!newGodfatherId) { return; } try { await createInvite(parseInt(newGodfatherId)); } catch { // Error handled by useMutation } }; const handleRevoke = async (inviteId: number) => { try { await revokeInvite(inviteId); } catch { // Error handled by useMutation } }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleString(); }; const getStatusBadgeStyle = (status: string) => { switch (status) { case READY: return badgeStyles.badgeReady; case SPENT: return badgeStyles.badgeSuccess; case REVOKED: return badgeStyles.badgeError; 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}
)}
); } // Page-specific styles const styles: Record = { 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", }, inputLabel: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.8rem", color: "rgba(255, 255, 255, 0.5)", }, createError: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.85rem", color: "#f87171", }, 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", }, };