"use client"; import { useEffect, useState, useCallback } from "react"; import { api } from "../api"; import { sharedStyles } from "../styles/shared"; import { Header } from "../components/Header"; import { useRequireAuth } from "../hooks/useRequireAuth"; interface Invite { id: number; identifier: string; status: string; used_by_email: string | null; created_at: string; spent_at: string | null; } export default function InvitesPage() { const { user, isLoading, isAuthorized } = useRequireAuth({ requiredRole: "regular", fallbackRedirect: "/audit", }); const [invites, setInvites] = useState([]); const [isLoadingInvites, setIsLoadingInvites] = useState(true); const [copiedId, setCopiedId] = useState(null); const fetchInvites = useCallback(async () => { try { const data = await api.get("/api/invites"); setInvites(data); } catch (err) { console.error("Failed to load invites:", err); } finally { setIsLoadingInvites(false); } }, []); useEffect(() => { if (user && isAuthorized) { fetchInvites(); } }, [user, isAuthorized, fetchInvites]); const getInviteUrl = (identifier: string) => { if (typeof window !== "undefined") { return `${window.location.origin}/signup/${identifier}`; } return `/signup/${identifier}`; }; const copyToClipboard = async (invite: Invite) => { const url = getInviteUrl(invite.identifier); try { await navigator.clipboard.writeText(url); setCopiedId(invite.id); setTimeout(() => setCopiedId(null), 2000); } catch (err) { console.error("Failed to copy:", err); } }; if (isLoading || isLoadingInvites) { return (
Loading...
); } if (!user || !isAuthorized) { return null; } const readyInvites = invites.filter((i) => i.status === "ready"); const spentInvites = invites.filter((i) => i.status === "spent"); const revokedInvites = invites.filter((i) => i.status === "revoked"); return (

My Invites

Share your invite codes with friends to let them join

{invites.length === 0 ? (

You don't have any invites yet.

Contact an admin if you need invite codes to share.

) : (
{/* Ready Invites */} {readyInvites.length > 0 && (

Available ({readyInvites.length})

Share these links with people you want to invite

{readyInvites.map((invite) => (
{invite.identifier}
))}
)} {/* Spent Invites */} {spentInvites.length > 0 && (

Used ({spentInvites.length})

{spentInvites.map((invite) => (
{invite.identifier}
Used by {invite.used_by_email}
))}
)} {/* Revoked Invites */} {revokedInvites.length > 0 && (

Revoked ({revokedInvites.length})

{revokedInvites.map((invite) => (
{invite.identifier}
Revoked
))}
)}
)}
); } const pageStyles: Record = { pageCard: { background: "rgba(255, 255, 255, 0.03)", backdropFilter: "blur(10px)", border: "1px solid rgba(255, 255, 255, 0.08)", borderRadius: "24px", padding: "2.5rem", width: "100%", maxWidth: "600px", boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)", }, cardHeader: { marginBottom: "2rem", }, cardTitle: { fontFamily: "'Instrument Serif', Georgia, serif", fontSize: "2rem", fontWeight: 400, color: "#fff", margin: 0, letterSpacing: "-0.02em", }, cardSubtitle: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.5)", marginTop: "0.5rem", fontSize: "0.95rem", }, emptyState: { textAlign: "center", padding: "2rem 0", }, emptyText: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.6)", fontSize: "1rem", margin: 0, }, emptyHint: { fontFamily: "'DM Sans', system-ui, sans-serif", color: "rgba(255, 255, 255, 0.4)", fontSize: "0.85rem", marginTop: "0.5rem", }, sections: { display: "flex", flexDirection: "column", gap: "2rem", }, section: { display: "flex", flexDirection: "column", gap: "0.75rem", }, sectionTitle: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.875rem", fontWeight: 600, color: "rgba(255, 255, 255, 0.8)", margin: 0, textTransform: "uppercase", letterSpacing: "0.05em", }, sectionHint: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.8rem", color: "rgba(255, 255, 255, 0.4)", margin: 0, }, inviteList: { display: "flex", flexDirection: "column", gap: "0.75rem", }, inviteCard: { background: "rgba(99, 102, 241, 0.1)", border: "1px solid rgba(99, 102, 241, 0.3)", borderRadius: "12px", padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center", }, inviteCardSpent: { background: "rgba(34, 197, 94, 0.08)", border: "1px solid rgba(34, 197, 94, 0.2)", borderRadius: "12px", padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center", }, inviteCardRevoked: { background: "rgba(239, 68, 68, 0.08)", border: "1px solid rgba(239, 68, 68, 0.2)", borderRadius: "12px", padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center", opacity: 0.7, }, inviteCode: { fontFamily: "'DM Mono', monospace", fontSize: "0.95rem", color: "#fff", letterSpacing: "0.02em", }, inviteActions: { display: "flex", gap: "0.5rem", }, copyButton: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.8rem", fontWeight: 500, padding: "0.5rem 1rem", background: "rgba(99, 102, 241, 0.3)", color: "#fff", border: "1px solid rgba(99, 102, 241, 0.5)", borderRadius: "8px", cursor: "pointer", transition: "all 0.2s", }, inviteeMeta: { display: "flex", alignItems: "center", gap: "0.5rem", }, statusBadgeSpent: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.7rem", fontWeight: 500, padding: "0.25rem 0.5rem", background: "rgba(34, 197, 94, 0.2)", color: "rgba(34, 197, 94, 0.9)", borderRadius: "4px", textTransform: "uppercase", }, statusBadgeRevoked: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.7rem", fontWeight: 500, padding: "0.25rem 0.5rem", background: "rgba(239, 68, 68, 0.2)", color: "rgba(239, 68, 68, 0.9)", borderRadius: "4px", textTransform: "uppercase", }, inviteeEmail: { fontFamily: "'DM Sans', system-ui, sans-serif", fontSize: "0.8rem", color: "rgba(255, 255, 255, 0.6)", }, }; const styles = { ...sharedStyles, ...pageStyles };