"use client"; import React, { useCallback, useEffect, useState } from "react"; import { useAuth } from "@/hooks/useAuth"; import { useRouter } from "next/navigation"; import { apiFetch } from "@/lib/api"; import { formatDateTime } from "@/lib/date"; interface PaymentItem { id: string; amount: number; method: string | null; createdAt: string; registrationId: string | null; eventId: string | null; registration?: { event?: { title?: string } | null } | null; event?: { title?: string } | null; } const formatRand = (n: number) => `R ${Math.abs(n).toFixed(2)}`; // The API normalizes Payment.method to this set before it ever reaches the dashboard — any // gateway-reported wallet type (apple_pay, google_pay, yoco, ...) is folded into "other". const METHOD_LABELS: Record = { cash: "Cash", card: "Card", eft: "EFT", voucher: "Voucher", other: "Other", }; const formatMethod = (method: string | null | undefined) => { if (!method) return "Payment"; return METHOD_LABELS[method] || method; }; export default function UserPaymentsPage() { const { user, loading, token } = useAuth(); const router = useRouter(); useEffect(() => { if (loading) return; if (!user) router.replace("/login"); }, [user, loading, router]); const [payments, setPayments] = useState([]); const [fetching, setFetching] = useState(false); const [error, setError] = useState(null); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [total, setTotal] = useState(0); // Filters const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const [method, setMethod] = useState(""); const [kind, setKind] = useState<"" | "payment" | "refund">(""); const buildQuery = useCallback((p: number) => { const qs = new URLSearchParams({ page: String(p), limit: "25" }); if (startDate) qs.set("startDate", startDate); if (endDate) qs.set("endDate", endDate); if (method) qs.set("method", method); if (kind) qs.set("kind", kind); return `/api/payments/mypayments?${qs.toString()}`; }, [startDate, endDate, method, kind]); const loadPayments = useCallback(async (p = 1) => { if (!token) return; setError(null); setFetching(true); try { const res = await apiFetch(buildQuery(p), { authToken: token }); setPayments(Array.isArray(res?.data) ? res.data : []); setTotal(res?.total ?? 0); setTotalPages(res?.pages ?? 1); setPage(p); } catch (e: any) { setError(e?.message || "Failed to load payments"); } finally { setFetching(false); } }, [token, buildQuery]); useEffect(() => { if (token) loadPayments(1); }, [token, startDate, endDate, method, kind]); const goToPage = (p: number) => loadPayments(p); return (

My Payments

{error &&

{error}

}
setStartDate(e.target.value)} />
setEndDate(e.target.value)} />
{(startDate || endDate || method || kind) && ( )}
{total} payment{total !== 1 ? "s" : ""} total {total > 0 && ` — page ${page} of ${totalPages}`}
    {payments.map(p => { const amt = p.amount || 0; const isRefund = amt < 0; const eventTitle = p.registration?.event?.title || p.event?.title; return (
  • {isRefund ? "-" : ""}{formatRand(amt)} {isRefund && (refund)}
    {formatDateTime(p.createdAt)}
    Method: {formatMethod(p.method)}
    {eventTitle &&
    Event: {eventTitle}
    }
  • ); })} {payments.length === 0 && !fetching && (
  • No payments found.
  • )} {fetching &&
  • Loading…
  • }
{totalPages > 1 && (
Page {page} of {totalPages}
{Array.from({ length: totalPages }, (_, i) => i + 1) .filter(p => p === 1 || p === totalPages || Math.abs(p - page) <= 1) .reduce<(number | "…")[]>((acc, p, i, arr) => { if (i > 0 && (p as number) - (arr[i - 1] as number) > 1) acc.push("…"); acc.push(p); return acc; }, []) .map((p, i) => p === "…" ? ( ) : ( ) )}
)}
); }