'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 { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from 'recharts'; // ─── Types ──────────────────────────────────────────────────────────────────── interface SubscriberGrowthPoint { month: string; subscribers: number; unsubscribes: number; net: number; } interface DigestPerformance { id: string; subject: string; sent_at: string; recipients: number; opens: number; clicks: number; unsubscribes: number; open_rate: number; click_rate: number; } interface OverviewMetric { label: string; value: string; sub: string; accent: string; icon: React.ReactNode; } // ─── Helpers ────────────────────────────────────────────────────────────────── function fmt(n: number): string { if (n >= 1000) return (n / 1000).toFixed(1) + 'k'; return String(n); } function pct(n: number): string { return n.toFixed(1) + '%'; } function fmtDate(dateStr: string): string { return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', }); } // ─── Sub-components ─────────────────────────────────────────────────────────── function StatCard({ label, value, sub, accent = '#3b82f6', icon }: { label: string; value: string; sub: string; accent?: string; icon: React.ReactNode; }) { return (
{icon}

{label}

{value}

{sub}

); } 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}

))}
); } function SectionHeader({ title, subtitle }: { title: string; subtitle?: string }) { return (

{title}

{subtitle &&

{subtitle}

}
); } // ─── Mock data generators (replace with real API when available) ────────────── function generateGrowthData(): SubscriberGrowthPoint[] { const months = ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar']; let running = 120; return months.map((month) => { const newSubs = Math.floor(Math.random() * 80) + 20; const unsubs = Math.floor(Math.random() * 15) + 2; running += newSubs - unsubs; return { month, subscribers: running, unsubscribes: unsubs, net: newSubs - unsubs }; }); } function generateDigestData(): DigestPerformance[] { const subjects = [ 'Weekly Digest: Top Stories This Week', 'Breaking: Major Industry Shifts', 'Monthly Roundup — February 2026', 'Special Report: Trends to Watch', 'Weekly Digest: Editor\'s Picks', 'Flash Update: Breaking News', 'Weekly Digest: Community Highlights', 'Monthly Roundup — January 2026', ]; return subjects.map((subject, i) => { const recipients = Math.floor(Math.random() * 300) + 200; const opens = Math.floor(recipients * (Math.random() * 0.3 + 0.2)); const clicks = Math.floor(opens * (Math.random() * 0.25 + 0.05)); const unsubscribes = Math.floor(Math.random() * 5); const daysAgo = (i + 1) * 7; const sentAt = new Date(Date.now() - daysAgo * 86400000).toISOString(); return { id: `digest-${i + 1}`, subject, sent_at: sentAt, recipients, opens, clicks, unsubscribes, open_rate: (opens / recipients) * 100, click_rate: (clicks / recipients) * 100, }; }); } function generateRatesTrend(digests: DigestPerformance[]) { return [...digests].reverse().map((d) => ({ label: fmtDate(d.sent_at), 'Open Rate': parseFloat(d.open_rate.toFixed(1)), 'Click Rate': parseFloat(d.click_rate.toFixed(1)), })); } // ─── Main Page ──────────────────────────────────────────────────────────────── export default function NewsletterAnalyticsPage() { const { user, loading } = useAuth(); const router = useRouter(); const [growthData, setGrowthData] = useState([]); const [digestData, setDigestData] = useState([]); const [ratesTrend, setRatesTrend] = useState([]); const [totalSubscribers, setTotalSubscribers] = useState(0); const [loadingData, setLoadingData] = useState(true); const [sortField, setSortField] = useState('sent_at'); const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc'); useEffect(() => { if (!loading && !user) router.replace('/account'); }, [user, loading, router]); const fetchData = useCallback(async () => { setLoadingData(true); try { // Fetch real subscriber count const res = await fetch('/api/newsletter/subscribers'); if (res.ok) { const data = await res.json(); const subs: any[] = data.subscribers ?? []; setTotalSubscribers(subs.filter((s) => s.status === 'active').length); } } catch { // silent } finally { // Generate derived/mock analytics data const growth = generateGrowthData(); const digests = generateDigestData(); setGrowthData(growth); setDigestData(digests); setRatesTrend(generateRatesTrend(digests)); setLoadingData(false); } }, []); useEffect(() => { if (user) fetchData(); }, [user, fetchData]); const handleSort = (field: keyof DigestPerformance) => { if (sortField === field) { setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); } else { setSortField(field); setSortDir('desc'); } }; const sortedDigests = [...digestData].sort((a, b) => { const av = a[sortField]; const bv = b[sortField]; if (typeof av === 'number' && typeof bv === 'number') { return sortDir === 'asc' ? av - bv : bv - av; } return sortDir === 'asc' ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av)); }); // Aggregate metrics const avgOpenRate = digestData.length > 0 ? digestData.reduce((s, d) => s + d.open_rate, 0) / digestData.length : 0; const avgClickRate = digestData.length > 0 ? digestData.reduce((s, d) => s + d.click_rate, 0) / digestData.length : 0; const totalSent = digestData.reduce((s, d) => s + d.recipients, 0); const totalOpens = digestData.reduce((s, d) => s + d.opens, 0); if (loading || loadingData) { return (
); } const SortIcon = ({ field }: { field: keyof DigestPerformance }) => ( ); return (
{/* Header */}
Newsletter Analytics
Templates

{fmt(totalSubscribers)}

active subscribers

{/* Page title */}

Newsletter Analytics

Performance overview across all sent digests

{/* Overview stat cards */}
} /> } /> } /> } />
{/* Open & Click Rate Trend */}
`${v}%`} /> } />
{/* Subscriber Growth Chart */}
} />
{/* Per-Digest Performance Table */}
{[ { label: 'Subject', field: 'subject' as keyof DigestPerformance }, { label: 'Sent', field: 'sent_at' as keyof DigestPerformance }, { label: 'Recipients', field: 'recipients' as keyof DigestPerformance }, { label: 'Opens', field: 'opens' as keyof DigestPerformance }, { label: 'Open Rate', field: 'open_rate' as keyof DigestPerformance }, { label: 'Clicks', field: 'clicks' as keyof DigestPerformance }, { label: 'Click Rate', field: 'click_rate' as keyof DigestPerformance }, { label: 'Unsubs', field: 'unsubscribes' as keyof DigestPerformance }, ].map(({ label, field }) => ( ))} {sortedDigests.map((digest, i) => ( ))} {sortedDigests.length === 0 && ( )}
handleSort(field)} className="px-4 py-3 text-left text-xs text-[#555] uppercase tracking-wider cursor-pointer hover:text-white transition-colors select-none whitespace-nowrap" > {label}

{digest.subject}

{fmtDate(digest.sent_at)} {digest.recipients.toLocaleString()} {digest.opens.toLocaleString()} = 25 ? '#10b98120' : digest.open_rate >= 15 ? '#f59e0b20' : '#ef444420', color: digest.open_rate >= 25 ? '#10b981' : digest.open_rate >= 15 ? '#f59e0b' : '#ef4444', }} > {pct(digest.open_rate)} {digest.clicks.toLocaleString()} = 5 ? '#10b98120' : digest.click_rate >= 2 ? '#f59e0b20' : '#ef444420', color: digest.click_rate >= 5 ? '#10b981' : digest.click_rate >= 2 ? '#f59e0b' : '#ef4444', }} > {pct(digest.click_rate)} {digest.unsubscribes}
No digest data available yet.
); }