125 lines
5.6 KiB
TypeScript
125 lines
5.6 KiB
TypeScript
'use client';
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import Link from 'next/link';
|
|
import { useAuth } from '@/contexts/AuthContext';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
|
|
|
|
function DnsCheck({ label, valid }: { label: string; valid: boolean | null }) {
|
|
return (
|
|
<span className={`inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded ${valid === true ? 'bg-green-400/10 text-green-400' : valid === false ? 'bg-red-400/10 text-red-400' : 'bg-[#231d18] text-[#555]'}`}>
|
|
{valid === true ? '✓' : valid === false ? '✗' : '?'} {label}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export default function EmailDashboard() {
|
|
const { user, loading } = useAuth();
|
|
const router = useRouter();
|
|
const [domains, setDomains] = useState<any[]>([]);
|
|
const [stats, setStats] = useState<any>(null);
|
|
const [loadingData, setLoadingData] = useState(true);
|
|
|
|
useEffect(() => { if (!loading && !user) router.push('/login?next=' + encodeURIComponent(window.location.pathname)); }, [user, loading, router]);
|
|
|
|
const load = useCallback(async () => {
|
|
setLoadingData(true);
|
|
const [domainsR, statsR] = await Promise.all([
|
|
fetch('/api/email/rmp/domains'),
|
|
fetch('/api/email/rmp/stats'),
|
|
]);
|
|
const [dd, sd] = await Promise.all([domainsR.json(), statsR.json()]);
|
|
setDomains(dd.domains ?? []);
|
|
setStats(sd);
|
|
setLoadingData(false);
|
|
}, []);
|
|
|
|
useEffect(() => { if (user) load(); }, [user, load]);
|
|
|
|
const bySite = SITES.map(site => ({
|
|
site,
|
|
domains: domains.filter((d: any) => d.site === site),
|
|
}));
|
|
|
|
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
|
if (!user) return null;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#0a0a0a] text-white p-6">
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">Email Infrastructure</h1>
|
|
<p className="text-[#888] text-sm mt-1">Sending domains, DNS health, suppressions</p>
|
|
</div>
|
|
|
|
{/* Network Stats */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
{[
|
|
{ label: 'Sends Today', value: stats?.sends_today ?? 0, color: 'text-blue-400' },
|
|
{ label: 'Sends This Month', value: stats?.sends_month ?? 0, color: 'text-blue-400' },
|
|
{ label: 'Total Suppressions', value: stats?.total_suppressions ?? 0, color: 'text-red-400' },
|
|
{ label: 'Blacklisted Domains', value: stats?.blacklisted ?? 0, color: 'text-red-400' },
|
|
].map(c => (
|
|
<div key={c.label} className="bg-[#111] border border-[#3a322b] rounded-lg p-4">
|
|
<p className={`text-2xl font-bold ${c.color}`}>{c.value}</p>
|
|
<p className="text-xs text-[#888] mt-1">{c.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Per-site property cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{bySite.map(({ site, domains: siteDomains }) => {
|
|
const primary = siteDomains.find((d: any) => d.is_primary);
|
|
const suppCount = stats?.suppressions_by_site?.[site] ?? 0;
|
|
return (
|
|
<div key={site} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold capitalize">{site}</h3>
|
|
<Link href={`/admin/email/domains?site=${site}`} className="text-xs text-[#F0A623] hover:underline">Manage →</Link>
|
|
</div>
|
|
<div className="text-xs text-[#888]">
|
|
<span className="text-white">{primary?.domain ?? 'No primary'}</span>
|
|
{primary && (
|
|
<span className={`ml-2 px-1.5 py-0.5 rounded text-xs ${primary.dns_fully_verified ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>
|
|
{primary.dns_fully_verified ? 'DNS OK' : 'DNS Pending'}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-3 text-xs text-[#555]">
|
|
<span>{siteDomains.length} domains</span>
|
|
<span>{siteDomains.filter((d: any) => d.is_active).length} active</span>
|
|
<span>{suppCount} suppressions</span>
|
|
</div>
|
|
{primary && (
|
|
<div className="flex gap-1 flex-wrap">
|
|
<DnsCheck label="SPF" valid={primary.spf_valid} />
|
|
<DnsCheck label="DKIM" valid={primary.dkim_valid} />
|
|
<DnsCheck label="DMARC" valid={primary.dmarc_valid} />
|
|
<DnsCheck label="PTR" valid={primary.ptr_valid} />
|
|
<DnsCheck label="MX" valid={primary.mx_valid} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Quick links */}
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{[
|
|
{ label: 'Sending Domains', href: '/admin/email/domains' },
|
|
{ label: 'Suppressions', href: '/admin/email/suppressions' },
|
|
{ label: 'Send Log', href: '/admin/email/send-log' },
|
|
].map(l => (
|
|
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#3a322b] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#F0A623] transition-colors">
|
|
{l.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|