Replace the four accent-family hex codes site-wide with the teal palette (keeps the dark theme; only swaps the accent family, not the dark base): #3b82f6 (accent primary CTA) → #5B7C8D (teal) [839×] #2563eb (accent-dark / hover) → #4A6473 (darker teal) [44×] #60a5fa (info link, lighter) → #8FB0C3 (mid teal) [68×] #1e3a5f (accent-muted bg) → #2F4F5F (navy) [44×] tailwind.config.js tokens updated to match (accent/accent-dark/accent-muted). Sweep also covers tailwind.css. 108 files touched. The dark base (#0d0d0d, #111, #161616, #1a1a1a etc.) is intentionally untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
4.6 KiB
TypeScript
90 lines
4.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 = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering'];
|
|
|
|
export default function SendLogPage() {
|
|
const { user, loading } = useAuth();
|
|
const router = useRouter();
|
|
const [logs, setLogs] = useState<any[]>([]);
|
|
const [loadingData, setLoadingData] = useState(true);
|
|
const [filterSite, setFilterSite] = useState('');
|
|
const [filterStatus, setFilterStatus] = useState('');
|
|
|
|
useEffect(() => { if (!loading && !user) router.push('/login'); }, [user, loading, router]);
|
|
|
|
const load = useCallback(async () => {
|
|
setLoadingData(true);
|
|
const params = new URLSearchParams();
|
|
if (filterSite) params.set('site', filterSite);
|
|
if (filterStatus) params.set('status', filterStatus);
|
|
const r = await fetch(`/api/email/rmp/send-log?${params}`);
|
|
const d = await r.json();
|
|
setLogs(d.logs ?? []);
|
|
setLoadingData(false);
|
|
}, [filterSite, filterStatus]);
|
|
|
|
useEffect(() => { if (user) load(); }, [user, load]);
|
|
|
|
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#5B7C8D] 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-4">
|
|
<div>
|
|
<h1 className="text-xl font-bold">Email Send Log</h1>
|
|
<Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]">← Email</Link>
|
|
</div>
|
|
|
|
<div className="flex gap-2 flex-wrap">
|
|
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
|
<option value="">All Sites</option>
|
|
{SITES.map(s => <option key={s} value={s}>{s}</option>)}
|
|
</select>
|
|
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
|
<option value="">All Statuses</option>
|
|
{['queued','sent','failed','suppressed'].map(s => <option key={s} value={s}>{s}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b border-[#1a1a1a]">
|
|
{['Site','Recipient','Subject','Type','Domain','Status','Sent At','Opens','Clicks'].map(h => (
|
|
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{loadingData ? (
|
|
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">Loading…</td></tr>
|
|
) : logs.length === 0 ? (
|
|
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No send log entries</td></tr>
|
|
) : logs.map((l: any) => (
|
|
<tr key={l.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
|
<td className="px-3 py-2 text-[#888] text-xs capitalize">{l.site}</td>
|
|
<td className="px-3 py-2 text-white text-xs">{l.recipient_email}</td>
|
|
<td className="px-3 py-2 text-[#ccc] text-xs max-w-xs truncate">{l.subject ?? '—'}</td>
|
|
<td className="px-3 py-2 text-[#555] text-xs">{l.send_type}</td>
|
|
<td className="px-3 py-2 text-[#555] text-xs">{l.rmp_email_sending_domains?.domain ?? '—'}</td>
|
|
<td className="px-3 py-2">
|
|
<span className={`px-2 py-0.5 rounded-full text-xs ${l.status === 'sent' ? 'bg-green-400/10 text-green-400' : l.status === 'failed' ? 'bg-red-400/10 text-red-400' : l.status === 'suppressed' ? 'bg-yellow-400/10 text-yellow-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{l.status}</span>
|
|
</td>
|
|
<td className="px-3 py-2 text-[#555] text-xs">{l.sent_at ? new Date(l.sent_at).toLocaleString() : '—'}</td>
|
|
<td className="px-3 py-2 text-[#888] text-xs">{l.opens}</td>
|
|
<td className="px-3 py-2 text-[#888] text-xs">{l.clicks}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|