initial commit: rocket.new export of broadcastbeat
This commit is contained in:
124
src/app/admin/email/page.tsx
Normal file
124
src/app/admin/email/page.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
'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 = ['broadcastbeat','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-[#1a1a1a] 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'); }, [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-[#3b82f6] 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-[#252525] 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-[#252525] 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-[#3b82f6] 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-[#252525] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#3b82f6] transition-colors">
|
||||
{l.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user