Files
avbeat-com/src/app/admin/page.tsx

620 lines
31 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';
// ─── Types ────────────────────────────────────────────────────────────────────
interface DashboardMetrics {
totalArticles: number;
pendingApprovals: number;
activeContributors: number;
publishedArticles: number;
draftArticles: number;
archivedArticles: number;
totalUsers: number;
importedArticles: number;
}
interface ActivityItem {
id: string;
type: 'published' | 'pending' | 'imported' | 'draft' | 'archived';
title: string;
author: string;
source: 'imported' | 'native';
date: string;
status: string;
}
// ─── Helpers ──────────────────────────────────────────────────────────────────
function timeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 1) return 'just now';
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
const days = Math.floor(hrs / 24);
return `${days}d ago`;
}
const STATUS_COLORS: Record<string, string> = {
published: 'text-green-400 bg-green-400/10',
pending: 'text-yellow-400 bg-yellow-400/10',
draft: 'text-[#888] bg-[#1a1a1a]',
archived: 'text-[#555] bg-[#1a1a1a]',
imported: 'text-blue-400 bg-blue-400/10',
};
const ACTIVITY_ICONS: Record<string, React.ReactNode> = {
published: (
<svg className="w-3.5 h-3.5 text-green-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
),
pending: (
<svg className="w-3.5 h-3.5 text-yellow-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path strokeLinecap="round" d="M12 6v6l4 2" />
</svg>
),
draft: (
<svg className="w-3.5 h-3.5 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
),
imported: (
<svg className="w-3.5 h-3.5 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
),
archived: (
<svg className="w-3.5 h-3.5 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
),
};
// ─── Metric Card ──────────────────────────────────────────────────────────────
interface MetricCardProps {
label: string;
value: number | string;
sub?: string;
accent: string;
icon: React.ReactNode;
href?: string;
badge?: { text: string; color: string };
}
function MetricCard({ label, value, sub, accent, icon, href, badge }: MetricCardProps) {
const content = (
<div className={`bg-[#111] border border-[#252525] rounded-lg p-5 flex flex-col gap-3 hover:border-[#333] transition-colors ${href ? 'cursor-pointer' : ''}`}>
<div className="flex items-start justify-between">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${accent}`}>
{icon}
</div>
{badge && (
<span className={`text-xs font-body font-bold px-2 py-0.5 rounded-full ${badge.color}`}>
{badge.text}
</span>
)}
</div>
<div>
<p className="text-2xl font-display font-bold text-white">{value}</p>
<p className="text-sm text-[#888] font-body mt-0.5">{label}</p>
{sub && <p className="text-xs text-[#555] font-body mt-1">{sub}</p>}
</div>
</div>
);
if (href) return <Link href={href}>{content}</Link>;
return content;
}
// ─── Quick Action Widget ───────────────────────────────────────────────────────
interface QuickActionProps {
label: string;
description: string;
href: string;
icon: React.ReactNode;
accent: string;
}
function QuickAction({ label, description, href, icon, accent }: QuickActionProps) {
return (
<Link href={href} className="flex items-center gap-3 p-3 bg-[#111] border border-[#252525] rounded-lg hover:border-[#3b82f6] hover:bg-[#0d1a2d] transition-all group">
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${accent} group-hover:scale-105 transition-transform`}>
{icon}
</div>
<div className="min-w-0">
<p className="text-sm font-body font-semibold text-white group-hover:text-[#3b82f6] transition-colors">{label}</p>
<p className="text-xs text-[#555] font-body truncate">{description}</p>
</div>
<svg className="w-4 h-4 text-[#444] group-hover:text-[#3b82f6] ml-auto flex-shrink-0 transition-colors" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</Link>
);
}
// ─── Main Page ────────────────────────────────────────────────────────────────
export default function AdminDashboardPage() {
const { user, loading } = useAuth();
const router = useRouter();
const [metrics, setMetrics] = useState<DashboardMetrics>({
totalArticles: 0,
pendingApprovals: 0,
activeContributors: 0,
publishedArticles: 0,
draftArticles: 0,
archivedArticles: 0,
totalUsers: 0,
importedArticles: 0,
});
const [recentActivity, setRecentActivity] = useState<ActivityItem[]>([]);
const [loadingData, setLoadingData] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchDashboardData = useCallback(async () => {
try {
setLoadingData(true);
setError(null);
// Fetch articles data
const articlesRes = await fetch('/api/admin/articles');
if (!articlesRes.ok) throw new Error('Failed to fetch articles');
const articlesData = await articlesRes.json();
const allArticles: any[] = [
...(articlesData.imported || []),
...(articlesData.native || []),
];
// Compute metrics
const totalArticles = allArticles.length;
const pendingApprovals = allArticles.filter((a) => a.status === 'pending').length;
const publishedArticles = allArticles.filter((a) => a.status === 'published').length;
const draftArticles = allArticles.filter((a) => a.status === 'draft').length;
const archivedArticles = allArticles.filter((a) => a.status === 'archived').length;
const importedArticles = (articlesData.imported || []).length;
// Active contributors: unique author_names with at least one published article
const contributorSet = new Set(
allArticles
.filter((a) => a.status === 'published' && a.author_name)
.map((a) => a.author_name)
);
const activeContributors = contributorSet.size;
setMetrics({
totalArticles,
pendingApprovals,
activeContributors,
publishedArticles,
draftArticles,
archivedArticles,
totalUsers: 0,
importedArticles,
});
// Build recent activity feed (latest 12 articles sorted by date)
const sorted = [...allArticles]
.sort((a, b) => new Date(b.date || b.created_at || 0).getTime() - new Date(a.date || a.created_at || 0).getTime())
.slice(0, 12);
setRecentActivity(
sorted.map((a) => ({
id: String(a.id),
type: a.status as ActivityItem['type'],
title: a.title || 'Untitled',
author: a.author_name || 'Unknown',
source: a.source,
date: a.date || a.created_at || '',
status: a.status,
}))
);
} catch (err: any) {
setError(err.message || 'Failed to load dashboard data');
} finally {
setLoadingData(false);
}
}, []);
useEffect(() => {
if (!loading && !user) {
router.push('/login');
}
}, [user, loading, router]);
useEffect(() => {
if (user) fetchDashboardData();
}, [user, fetchDashboardData]);
if (loading || loadingData) {
return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading dashboard</p>
</div>
</div>
);
}
if (!user) return null;
return (
<div className="min-h-screen bg-[#0a0a0a]">
<div className="max-w-7xl mx-auto px-4 py-8">
{/* Header */}
<div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl font-display font-bold text-white">Admin Dashboard</h1>
<p className="text-[#555] text-sm font-body mt-1">Overview of your broadcast platform</p>
</div>
<div className="flex items-center gap-2 text-xs text-[#555] font-body">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path strokeLinecap="round" d="M12 6v6l4 2" />
</svg>
Last updated: {new Date().toLocaleTimeString()}
</div>
</div>
{/* Error Banner */}
{error && (
<div className="mb-6 bg-red-500/10 border border-red-500/30 rounded-lg px-4 py-3 flex items-center gap-3">
<svg className="w-4 h-4 text-red-400 flex-shrink-0" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 4h.01" />
</svg>
<p className="text-red-400 text-sm font-body">{error}</p>
<button onClick={fetchDashboardData} className="ml-auto text-xs text-red-400 hover:text-red-300 underline font-body">Retry</button>
</div>
)}
{/* Pending Approvals Alert */}
{metrics.pendingApprovals > 0 && (
<div className="mb-6 bg-yellow-500/10 border border-yellow-500/30 rounded-lg px-4 py-3 flex items-center gap-3">
<svg className="w-4 h-4 text-yellow-400 flex-shrink-0" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<p className="text-yellow-400 text-sm font-body">
<span className="font-bold">{metrics.pendingApprovals} article{metrics.pendingApprovals !== 1 ? 's' : ''}</span> awaiting approval
</p>
<Link href="/admin/articles" className="ml-auto text-xs text-yellow-400 hover:text-yellow-300 underline font-body">Review now </Link>
</div>
)}
{/* Key Metric Cards */}
<section className="mb-8">
<h2 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-4">Key Metrics</h2>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-4">
<MetricCard
label="Total Articles"
value={metrics.totalArticles}
sub={`${metrics.importedArticles} imported · ${metrics.totalArticles - metrics.importedArticles} native`}
accent="bg-blue-500/10"
href="/admin/articles"
icon={
<svg className="w-5 h-5 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
}
/>
<MetricCard
label="Pending Approvals"
value={metrics.pendingApprovals}
sub="Awaiting admin review"
accent="bg-yellow-500/10"
href="/admin/articles"
badge={metrics.pendingApprovals > 0 ? { text: 'Action needed', color: 'text-yellow-400 bg-yellow-400/10' } : undefined}
icon={
<svg className="w-5 h-5 text-yellow-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
}
/>
<MetricCard
label="Active Contributors"
value={metrics.activeContributors}
sub="Authors with published articles"
accent="bg-purple-500/10"
href="/admin/users"
icon={
<svg className="w-5 h-5 text-purple-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
}
/>
<MetricCard
label="Published"
value={metrics.publishedArticles}
accent="bg-green-500/10"
href="/admin/articles"
icon={
<svg className="w-5 h-5 text-green-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
}
/>
<MetricCard
label="Drafts"
value={metrics.draftArticles}
accent="bg-[#1a1a1a]"
href="/admin/articles"
icon={
<svg className="w-5 h-5 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
}
/>
<MetricCard
label="Archived"
value={metrics.archivedArticles}
accent="bg-[#1a1a1a]"
href="/admin/articles"
icon={
<svg className="w-5 h-5 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
}
/>
</div>
</section>
{/* Main Content: Activity Feed + Quick Actions */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Recent Activity Feed */}
<div className="lg:col-span-2">
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#1a1a1a] flex items-center justify-between">
<h2 className="text-sm font-display font-bold text-white">Recent Activity</h2>
<Link href="/admin/articles" className="text-xs text-[#3b82f6] hover:text-blue-300 font-body transition-colors">View all </Link>
</div>
<div className="divide-y divide-[#1a1a1a]">
{recentActivity.length === 0 ? (
<div className="px-5 py-10 text-center">
<p className="text-[#555] text-sm font-body">No recent activity found</p>
</div>
) : (
recentActivity.map((item) => (
<div key={`${item.source}-${item.id}`} className="px-5 py-3 flex items-start gap-3 hover:bg-[#0d0d0d] transition-colors">
<div className="w-6 h-6 rounded-full bg-[#1a1a1a] flex items-center justify-center flex-shrink-0 mt-0.5">
{ACTIVITY_ICONS[item.type] || ACTIVITY_ICONS['draft']}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-white font-body truncate">{item.title}</p>
<div className="flex items-center gap-2 mt-0.5 flex-wrap">
<span className="text-xs text-[#555] font-body">{item.author}</span>
<span className="text-[#333]">·</span>
<span className={`text-xs font-body font-semibold px-1.5 py-0.5 rounded ${STATUS_COLORS[item.status] || STATUS_COLORS['draft']}`}>
{item.status}
</span>
<span className="text-[#333]">·</span>
<span className="text-xs text-[#444] font-body capitalize">{item.source}</span>
</div>
</div>
<span className="text-xs text-[#444] font-body flex-shrink-0 mt-0.5">
{item.date ? timeAgo(item.date) : '—'}
</span>
</div>
))
)}
</div>
</div>
</div>
{/* Quick Actions */}
<div className="flex flex-col gap-4">
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#1a1a1a]">
<h2 className="text-sm font-display font-bold text-white">Quick Actions</h2>
</div>
<div className="p-4 flex flex-col gap-2">
<QuickAction
label="Review Pending Articles"
description="Approve or reject submissions"
href="/admin/articles"
accent="bg-yellow-500/10"
icon={
<svg className="w-4 h-4 text-yellow-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
}
/>
<QuickAction
label="Manage Articles"
description="Edit, publish, or archive content"
href="/admin/articles"
accent="bg-blue-500/10"
icon={
<svg className="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
}
/>
<QuickAction
label="Invite Contributor"
description="Send email invitation to new authors"
href="/admin/users"
accent="bg-purple-500/10"
icon={
<svg className="w-4 h-4 text-purple-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
</svg>
}
/>
<QuickAction
label="Manage Users & Roles"
description="Assign roles and permissions"
href="/admin/users"
accent="bg-green-500/10"
icon={
<svg className="w-4 h-4 text-green-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
}
/>
<QuickAction
label="Import from WordPress"
description="Bulk import WP articles"
href="/admin/import"
accent="bg-orange-500/10"
icon={
<svg className="w-4 h-4 text-orange-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
}
/>
<QuickAction
label="View Analytics"
description="Performance metrics and trends"
href="/admin/analytics"
accent="bg-cyan-500/10"
icon={
<svg className="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
}
/>
<QuickAction
label="Email Analytics"
description="Send volume, open rates, CTR & subscriber growth"
href="/admin/email-analytics"
accent="bg-blue-500/10"
icon={
<svg className="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</svg>
}
/>
<QuickAction
label="Forum Moderation"
description="Pin, lock, flag threads & hide replies"
href="/admin/forum-moderation"
accent="bg-red-500/10"
icon={
<svg className="w-4 h-4 text-red-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
}
/>
<QuickAction
label="Forum Dashboard"
description="Thread growth, engagement & moderation stats"
href="/admin/forum-dashboard"
accent="bg-indigo-500/10"
icon={
<svg className="w-4 h-4 text-indigo-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
}
/>
<QuickAction
label="Roles & Permissions"
description="Assign moderator/contributor roles and manage permissions"
href="/admin/roles"
accent="bg-amber-500/10"
icon={
<svg className="w-4 h-4 text-amber-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
}
/>
<QuickAction
label="Suspensions & Bans"
description="Suspend or ban users with reason and duration"
href="/admin/suspensions"
accent="bg-red-900/30"
icon={
<svg className="w-4 h-4 text-red-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path strokeLinecap="round" strokeLinejoin="round" d="M4.93 4.93l14.14 14.14" />
</svg>
}
/>
<QuickAction
label="Company Coverage"
description="AI story suggestions from article mentions"
href="/admin/company-coverage"
accent="bg-teal-500/10"
icon={
<svg className="w-4 h-4 text-teal-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 12h6m-6-4h.01" />
</svg>
}
/>
<QuickAction
label="Company Story Engine"
description="Tracked companies, story queue, pen names"
href="/admin/company-tracker"
accent="bg-emerald-500/10"
icon={
<svg className="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v4M9 3v18m0 0h10a2 2 0 002-2V9M9 21H5a2 2 0 01-2-2V9m0 0h18" />
</svg>
}
/>
<QuickAction
label="Banned Terms"
description="Manage restricted keywords and companies"
href="/admin/banned-terms"
accent="bg-red-900/20"
icon={
<svg className="w-4 h-4 text-red-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
}
/>
<QuickAction
label="Forum Maintenance Tool"
description="Database cleanup, repair, and maintenance utilities"
href="/admin/forum-maintenance"
accent="bg-slate-500/10"
icon={
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
}
/>
</div>
</div>
{/* Status Summary Mini-Card */}
<div className="bg-[#111] border border-[#252525] rounded-lg p-4">
<h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">Content Status</h3>
<div className="space-y-2">
{[
{ label: 'Published', value: metrics.publishedArticles, color: 'bg-green-400', total: metrics.totalArticles },
{ label: 'Pending', value: metrics.pendingApprovals, color: 'bg-yellow-400', total: metrics.totalArticles },
{ label: 'Draft', value: metrics.draftArticles, color: 'bg-[#444]', total: metrics.totalArticles },
{ label: 'Archived', value: metrics.archivedArticles, color: 'bg-[#2a2a2a]', total: metrics.totalArticles },
].map(({ label, value, color, total }) => (
<div key={label}>
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-[#888] font-body">{label}</span>
<span className="text-xs text-white font-body font-semibold">{value}</span>
</div>
<div className="h-1.5 bg-[#1a1a1a] rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${color} transition-all duration-500`}
style={{ width: total > 0 ? `${Math.round((value / total) * 100)}%` : '0%' }}
/>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
);
}