From 70a1925c950edf0aab1f6455f21985bbf0428fa8 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Thu, 23 Apr 2026 13:29:15 -0400 Subject: [PATCH] feat: add Ko-fi donations tab to admin dashboard --- backend/src/routes/admin.ts | 24 +++++++- frontend/src/app/admin/page.tsx | 101 ++++++++++++++++++++++++++++---- frontend/src/lib/api.ts | 1 + 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index 7b1a8c7..a6889e5 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -1,13 +1,13 @@ import { Router } from "express"; +import { io } from "../index"; import { + type AuthenticatedRequest, authenticate, - AuthenticatedRequest, requireAdmin, } from "../middleware/auth"; -import { prisma } from "../utils/prisma"; import { asyncHandler } from "../middleware/errorHandler"; -import { io } from "../index"; import { backfillUserHistory } from "../services/tautulli"; +import { prisma } from "../utils/prisma"; const router = Router(); @@ -229,4 +229,22 @@ router.get( }), ); +router.get( + "/kofi-payments", + authenticate, + requireAdmin, + asyncHandler(async (_req: AuthenticatedRequest, res) => { + const payments = await prisma.kofiPayment.findMany({ + orderBy: { createdAt: "desc" }, + take: 100, + include: { + user: { + select: { plexUsername: true }, + }, + }, + }); + res.json({ payments }); + }), +); + export { router as adminRouter }; diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx index 4fe9f86..4dc252e 100644 --- a/frontend/src/app/admin/page.tsx +++ b/frontend/src/app/admin/page.tsx @@ -1,19 +1,20 @@ "use client"; -import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import { - Users, - Database, - Settings, - TrendingUp, - ShieldAlert, - Search, - RefreshCcw, ArrowLeft, - Save, + Coffee, + Database, Gift, + RefreshCcw, + Save, + Search, + Settings, + ShieldAlert, + TrendingUp, + Users, } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { @@ -25,7 +26,7 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { api, adminApi } from "@/lib/api"; +import { adminApi, api } from "@/lib/api"; import { useStore } from "@/lib/store"; import { formatNumber } from "@/lib/utils"; @@ -61,12 +62,25 @@ interface SystemSettings { minWatchMinutes: number; } +interface KofiPayment { + id: string; + kofiTransactionId: string; + email: string; + fromName: string; + amount: number; + creditsGranted: number; + status: string; + createdAt: string; + user?: { plexUsername: string } | null; +} + export default function AdminPage() { const router = useRouter(); const { user, isAuthenticated } = useStore(); const [analytics, setAnalytics] = useState(null); const [users, setUsers] = useState([]); const [settings, setSettings] = useState(null); + const [kofiPayments, setKofiPayments] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [isLoading, setIsLoading] = useState(true); const [isSavingSettings, setIsSavingSettings] = useState(false); @@ -82,14 +96,16 @@ export default function AdminPage() { const loadData = async () => { try { - const [analyticsRes, usersRes, settingsRes] = await Promise.all([ + const [analyticsRes, usersRes, settingsRes, kofiRes] = await Promise.all([ adminApi.getAnalytics(), adminApi.getUsers(), adminApi.getSettings(), + adminApi.getKofiPayments(), ]); setAnalytics(analyticsRes.data); setUsers(usersRes.data.users || []); setSettings(settingsRes.data); + setKofiPayments(kofiRes.data.payments || []); } catch (err) { console.error("Admin load error:", err); toast.error("Failed to load coop secrets"); @@ -185,7 +201,7 @@ export default function AdminPage() { - + BARN RULES + + DONATIONS + + +
+
+

Ko-fi Donations

+

Incoming feed purchases

+
+ + {kofiPayments.length === 0 ? ( +
+
+
No donations yet. The well is dry.
+
+ ) : ( +
+ {kofiPayments.map((p) => ( +
+
+
+ {p.fromName || "Anonymous"} + + {p.status} + +
+
+ {p.email} +
+ {p.user && ( +
+ Linked: {p.user.plexUsername} +
+ )} +
+
+
+ ${p.amount.toFixed(2)} +
+
+ {formatNumber(p.creditsGranted)} $COOP +
+
+ {new Date(p.createdAt).toLocaleDateString()} +
+
+
+ ))} +
+ )} +
+
+
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 6527dae..0c9c427 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -94,6 +94,7 @@ export const adminApi = { api.get(`/transactions/admin/all?page=${page}&limit=${limit}`), getSystemStats: () => api.get("/transactions/admin/stats"), backfillUser: (userId: string) => api.post(`/admin/users/${userId}/backfill`), + getKofiPayments: () => api.get("/admin/kofi-payments"), }; // Overseer API