'use client'; import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { useAuth } from '@/contexts/AuthContext'; import { useRouter } from 'next/navigation'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, Legend, } from 'recharts'; // ─── Types ──────────────────────────────────────────────────────────────────── interface WeeklyGrowth { week: string; threads: number; replies: number; } interface TopCategory { id: string; name: string; icon: string; thread_count: number; post_count: number; } interface ActiveUser { author_id: string; author_name: string; threads: number; replies: number; total: number; } interface EngagementMetrics { totalThreads: number; totalReplies: number; totalViews: number; totalVotes: number; avgRepliesPerThread: number; engagementRate: number; newThreadsWeek: number; newRepliesWeek: number; } interface ModerationStats { flaggedThreads: number; lockedThreads: number; pinnedThreads: number; featuredThreads: number; flaggedReplies: number; hiddenReplies: number; } interface ForumStats { weeklyGrowth: WeeklyGrowth[]; topCategories: TopCategory[]; activeUsers: ActiveUser[]; engagement: EngagementMetrics; moderation: ModerationStats; } // ─── Helpers ────────────────────────────────────────────────────────────────── function fmt(n: number): string { if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`; if (n >= 1000) return `${(n / 1000).toFixed(1)}K`; return String(n); } // ─── Stat Card ──────────────────────────────────────────────────────────────── interface StatCardProps { label: string; value: number | string; sub?: string; accentColor: string; bgColor: string; icon: React.ReactNode; badge?: { text: string; color: string }; } function StatCard({ label, value, sub, accentColor, bgColor, icon, badge }: StatCardProps) { return (
{icon}
{badge && ( {badge.text} )}

{value}

{label}

{sub &&

{sub}

}
); } // ─── Custom Tooltip ─────────────────────────────────────────────────────────── function ChartTooltip({ active, payload, label }: any) { if (!active || !payload?.length) return null; return (
{label &&

{label}

} {payload.map((entry: any, i: number) => (

{entry.name}: {entry.value}

))}
); } // ─── Section Header ─────────────────────────────────────────────────────────── function SectionHeader({ title, subtitle, action }: { title: string; subtitle?: string; action?: React.ReactNode }) { return (

{title}

{subtitle &&

{subtitle}

}
{action}
); } // ─── Main Page ──────────────────────────────────────────────────────────────── const EMPTY_STATS: ForumStats = { weeklyGrowth: [], topCategories: [], activeUsers: [], engagement: { totalThreads: 0, totalReplies: 0, totalViews: 0, totalVotes: 0, avgRepliesPerThread: 0, engagementRate: 0, newThreadsWeek: 0, newRepliesWeek: 0, }, moderation: { flaggedThreads: 0, lockedThreads: 0, pinnedThreads: 0, featuredThreads: 0, flaggedReplies: 0, hiddenReplies: 0, }, }; const CATEGORY_COLORS = ['#1D4ED8', '#f59e0b', '#10b981', '#8b5cf6', '#ef4444', '#06b6d4', '#f97316', '#84cc16']; export default function ForumDashboardPage() { const { user, loading } = useAuth(); const router = useRouter(); const [stats, setStats] = useState(EMPTY_STATS); const [loadingData, setLoadingData] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!loading && !user) router.push('/login?next=' + encodeURIComponent(window.location.pathname)); }, [user, loading, router]); const fetchStats = useCallback(async () => { setLoadingData(true); setError(null); try { const res = await fetch('/api/admin/forum-stats'); if (!res.ok) throw new Error('Failed to fetch forum stats'); const data = await res.json(); setStats(data); } catch (err: any) { setError(err.message ?? 'Unknown error'); } finally { setLoadingData(false); } }, []); useEffect(() => { if (user) fetchStats(); }, [user, fetchStats]); if (loading || loadingData) { return (

Loading forum dashboard…

); } if (!user) return null; const { engagement, moderation, weeklyGrowth, topCategories, activeUsers } = stats; const totalFlagged = moderation.flaggedThreads + moderation.flaggedReplies; return (
{/* Header */}
Admin Forum Dashboard

Forum Dashboard

Thread growth, engagement, and moderation overview

Moderation
{/* Error Banner */} {error && (

{error}

)} {/* Flagged Content Alert */} {totalFlagged > 0 && (

{totalFlagged} flagged item{totalFlagged !== 1 ? 's' : ''} require moderation review

Review now →
)} {/* Engagement Metric Cards */}

Engagement Metrics

} /> } /> } /> } /> } /> } /> 0 ? { text: 'Needs review', color: 'text-red-400 bg-red-400/10' } : undefined} icon={ } /> } />
{/* Thread Growth Chart */}
{weeklyGrowth.length === 0 ? (

No data available

) : ( } /> )}
{/* Two-column: Top Categories + Active Users */}
{/* Top Categories */}

Top Categories

By thread count

View forum →
{topCategories.length === 0 ? (

No categories found

) : (
{topCategories.map((cat, idx) => { const maxThreads = topCategories[0]?.thread_count ?? 1; const pct = maxThreads > 0 ? Math.round((cat.thread_count / maxThreads) * 100) : 0; const color = CATEGORY_COLORS[idx % CATEGORY_COLORS.length]; return (
{cat.icon} {cat.name} {cat.thread_count} threads {cat.post_count} posts
); })}
)}
{/* Active Users */}

Active Users

Most active in the last 30 days

{activeUsers.length === 0 ? (

No active users found

) : (
{activeUsers.map((u, idx) => (
{idx + 1}
{u.author_name.charAt(0).toUpperCase()}

{u.author_name}

{u.threads} threads · {u.replies} replies

{u.total}

posts

))}
)}
{/* Category Bar Chart */} {topCategories.length > 0 && (
} />
)} {/* Moderation Stats */}

Moderation Stats

Current moderation state across all forum content

Open moderation →
{[ { label: 'Flagged Threads', value: moderation.flaggedThreads, color: 'text-red-400', bg: 'bg-red-500/10' }, { label: 'Flagged Replies', value: moderation.flaggedReplies, color: 'text-red-400', bg: 'bg-red-500/10' }, { label: 'Locked Threads', value: moderation.lockedThreads, color: 'text-orange-400', bg: 'bg-orange-500/10' }, { label: 'Pinned Threads', value: moderation.pinnedThreads, color: 'text-yellow-400', bg: 'bg-yellow-500/10' }, { label: 'Featured Threads', value: moderation.featuredThreads, color: 'text-blue-400', bg: 'bg-blue-500/10' }, { label: 'Hidden Replies', value: moderation.hiddenReplies, color: 'text-[#888]', bg: 'bg-[#FFFFFF]' }, ].map(({ label, value, color, bg }) => (

{value}

{label}

))}
); }