import React from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { listInvites, revokeInvite } from "../api/invites"; import { BandInviteListItem } from "../types/invite"; interface InviteManagementProps { bandId: string; } /** * Component for managing band invites * - List pending invites * - Revoke invites * - Show invite status */ export function InviteManagement({ bandId }: InviteManagementProps) { // Fetch invites const { data, isLoading, isError, error } = useQuery({ queryKey: ["invites", bandId], queryFn: () => listInvites(bandId), retry: false, }); // Revoke mutation const revokeMutation = useMutation({ mutationFn: (inviteId: string) => revokeInvite(inviteId), }); // Calculate pending invites const pendingInvites = data?.invites.filter( (invite) => !invite.is_used && invite.expires_at !== null ) || []; // Format expiry date const formatExpiry = (expiresAt: string | null | undefined) => { if (!expiresAt) return "No expiry"; try { const date = new Date(expiresAt); const now = new Date(); const diffHours = Math.floor((date.getTime() - now.getTime()) / (1000 * 60 * 60)); if (diffHours <= 0) { return "Expired"; } else if (diffHours < 24) { return `Expires in ${diffHours} hour${diffHours === 1 ? "" : "s"}`; } else { return `Expires in ${Math.floor(diffHours / 24)} days`; } } catch { return "Invalid date"; } }; /** * Copy invite token to clipboard */ const copyToClipboard = (token: string) => { navigator.clipboard.writeText(window.location.origin + `/invite/${token}`); // Could add a toast notification here }; if (isLoading) { return (

Loading invites...

); } if (isError) { return (

Error loading invites: {error.message}

); } return (

Pending Invites

{pendingInvites.length} Pending
{data && data.total === 0 ? (

No invites yet. Create one to share with others!

) : ( <>
{data?.invites.map((invite: BandInviteListItem) => (
{invite.role} {invite.is_used ? "Used" : formatExpiry(invite.expires_at)}
{invite.token.substring(0, 8)}...{invite.token.substring(invite.token.length - 4)}
{!invite.is_used && invite.expires_at && new Date(invite.expires_at) > new Date() && (
)}
))}
Total invites: {data?.total} Pending: {pendingInvites.length}
)}
); } const styles: Record = { container: { background: "white", borderRadius: "8px", padding: "20px", marginBottom: "20px", boxShadow: "0 2px 4px rgba(0,0,0,0.1)", }, header: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px", }, title: { fontSize: "16px", fontWeight: "bold" as const, margin: "0", }, count: { color: "#6b7280", fontSize: "14px", }, empty: { color: "#6b7280", textAlign: "center" as const, padding: "20px", }, list: { display: "flex", flexDirection: "column" as const, gap: "12px", }, inviteCard: { border: "1px solid #e5e7eb", borderRadius: "6px", padding: "16px", background: "white", }, inviteHeader: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "12px", }, inviteRole: { padding: "4px 12px", borderRadius: "4px", fontSize: "12px", fontWeight: "500", background: "#f3f4f6", color: "#4b5563", }, inviteStatus: { fontSize: "12px", }, inviteDetails: { marginBottom: "12px", }, tokenContainer: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" as const, }, token: { fontFamily: "monospace", fontSize: "12px", color: "#6b7280", whiteSpace: "nowrap" as const, overflow: "hidden", textOverflow: "ellipsis", }, copyButton: { padding: "4px 12px", borderRadius: "4px", fontSize: "12px", background: "#3b82f6", color: "white", border: "none", cursor: "pointer", }, inviteActions: { display: "flex", gap: "8px", }, button: { padding: "8px 16px", borderRadius: "4px", fontSize: "12px", border: "none", cursor: "pointer", fontWeight: "500", }, stats: { display: "flex", gap: "16px", fontSize: "14px", }, statItem: { color: "#6b7280", }, highlight: { color: "#ef4444", fontWeight: "bold" as const, }, error: { color: "#dc2626", }, };