Files
portfolio/components/ModernAdminDashboard.tsx
denshooter 07b155369d
All checks were successful
CI / CD / test-build (push) Successful in 10m9s
CI / CD / deploy-dev (push) Successful in 1m16s
CI / CD / deploy-production (push) Has been skipped
feat: redesign admin panel to match Liquid Editorial Bento design system
- Login page: stone/dark palette, liquid ambient blobs, dk0.dev branding,
  gradient accent bar, large rounded card, site-matching button/input styles
- Lockout/loading states: dark mode support, emerald spinner, red gradient bar
- Dashboard navbar: gradient accent bar, monospace branding, pill-style tab buttons
  with dark/light active state, improved mobile menu grid layout
- Stats cards: liquid-* gradient backgrounds per metric (emerald, sky, purple,
  amber, pink, teal) with matching icon colors and rounded-3xl corners
- Section headings: uppercase tracking-tighter font-black with emerald accent dot
- Activity/settings cards: white/dark-stone background, rounded-3xl, dark mode
- Removed framer-motion from manage/page.tsx; replaced admin-glass* CSS classes
  with proper Tailwind throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:37:03 +01:00

641 lines
28 KiB
TypeScript

'use client';
import React, { useState, useEffect, useCallback } from 'react';
import {
Mail,
Settings,
TrendingUp,
Plus,
Shield,
Users,
Activity,
Database,
Home,
LogOut,
Menu,
X
} from 'lucide-react';
import Link from 'next/link';
import dynamic from 'next/dynamic';
const EmailManager = dynamic(
() => import('./EmailManager').then((m) => m.EmailManager),
{ ssr: false, loading: () => <div className="p-6 text-stone-400">Loading emails</div> }
);
const AnalyticsDashboard = dynamic(
() => import('./AnalyticsDashboard').then((m) => m.default),
{ ssr: false, loading: () => <div className="p-6 text-stone-400">Loading analytics</div> }
);
const ImportExport = dynamic(
() => import('./ImportExport').then((m) => m.default),
{ ssr: false, loading: () => <div className="p-6 text-stone-400">Loading tools</div> }
);
const ProjectManager = dynamic(
() => import('./ProjectManager').then((m) => m.ProjectManager),
{ ssr: false, loading: () => <div className="p-6 text-stone-400">Loading projects</div> }
);
const ContentManager = dynamic(
() => import('./ContentManager').then((m) => m.default),
{ ssr: false, loading: () => <div className="p-6 text-stone-400">Loading content</div> }
);
interface Project {
id: string;
title: string;
description: string;
content?: string;
category: string;
difficulty?: string;
tags?: string[];
featured: boolean;
published: boolean;
github?: string;
live?: string;
image?: string;
createdAt: string;
updatedAt: string;
analytics?: {
views: number;
likes: number;
shares: number;
};
performance?: {
lighthouse: number;
};
}
interface ModernAdminDashboardProps {
isAuthenticated?: boolean;
}
type TabId = 'overview' | 'projects' | 'emails' | 'analytics' | 'content' | 'settings';
const ModernAdminDashboard: React.FC<ModernAdminDashboardProps> = ({ isAuthenticated = true }) => {
const [activeTab, setActiveTab] = useState<TabId>('overview');
const [projects, setProjects] = useState<Project[]>([]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isLoading, setIsLoading] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [analytics, setAnalytics] = useState<Record<string, unknown> | null>(null);
const [emails, setEmails] = useState<Record<string, unknown>[]>([]);
const [systemStats, setSystemStats] = useState<Record<string, unknown> | null>(null);
const loadProjects = useCallback(async () => {
try {
setIsLoading(true);
const sessionToken = sessionStorage.getItem('admin_session_token');
const response = await fetch('/api/projects', {
headers: {
'x-admin-request': 'true',
'x-session-token': sessionToken || ''
}
});
if (!response.ok) {
if (process.env.NODE_ENV === 'development') {
console.warn('Failed to load projects:', response.status);
}
setProjects([]);
return;
}
const data = await response.json();
setProjects(data.projects || []);
} catch {
setProjects([]);
} finally {
setIsLoading(false);
}
}, []);
const loadAnalytics = useCallback(async () => {
try {
const sessionToken = sessionStorage.getItem('admin_session_token');
const response = await fetch('/api/analytics/dashboard', {
headers: {
'x-admin-request': 'true',
'x-session-token': sessionToken || ''
}
});
if (response.ok) {
const data = await response.json();
setAnalytics(data);
}
} catch (error) {
console.error('Error loading analytics:', error);
}
}, []);
const loadEmails = useCallback(async () => {
try {
const sessionToken = sessionStorage.getItem('admin_session_token');
const response = await fetch('/api/contacts', {
headers: {
'x-admin-request': 'true',
'x-session-token': sessionToken || ''
}
});
if (response.ok) {
const data = await response.json();
setEmails(data.contacts || []);
}
} catch (error) {
console.error('Error loading emails:', error);
}
}, []);
const loadSystemStats = useCallback(async () => {
try {
const sessionToken = sessionStorage.getItem('admin_session_token');
const response = await fetch('/api/health', {
headers: {
'x-admin-request': 'true',
'x-session-token': sessionToken || ''
}
});
if (response.ok) {
const data = await response.json();
setSystemStats(data);
}
} catch (error) {
console.error('Error loading system stats:', error);
}
}, []);
const loadAllData = useCallback(async () => {
await Promise.all([
loadProjects(),
loadAnalytics(),
loadEmails(),
loadSystemStats()
]);
}, [loadProjects, loadAnalytics, loadEmails, loadSystemStats]);
// Real stats from API data
const stats = {
totalProjects: projects.length,
publishedProjects: projects.filter(p => p.published).length,
totalViews: ((analytics?.overview as Record<string, unknown>)?.totalViews as number) || (analytics?.totalViews as number) || projects.reduce((sum, p) => sum + (p.analytics?.views || 0), 0),
unreadEmails: emails.filter(e => !(e.read as boolean)).length,
avgPerformance: (() => {
const projectsWithPerf = projects.filter(p => {
const perf = p.performance as Record<string, unknown> || {};
return (perf.lighthouse as number || 0) > 0;
});
if (projectsWithPerf.length === 0) return 0;
return Math.round(projectsWithPerf.reduce((sum, p) => {
const perf = p.performance as Record<string, unknown> || {};
return sum + (perf.lighthouse as number || 0);
}, 0) / projectsWithPerf.length);
})(),
systemHealth: (systemStats?.status as string) || 'unknown',
totalUsers: ((analytics?.metrics as Record<string, unknown>)?.totalUsers as number) || (analytics?.totalUsers as number) || 0,
bounceRate: ((analytics?.metrics as Record<string, unknown>)?.bounceRate as number) || (analytics?.bounceRate as number) || 0,
avgSessionDuration: ((analytics?.metrics as Record<string, unknown>)?.avgSessionDuration as number) || (analytics?.avgSessionDuration as number) || 0
};
useEffect(() => {
void (async () => {
await Promise.all([loadProjects(), loadSystemStats()]);
const idle = (cb: () => void) => {
if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
(window as unknown as { requestIdleCallback: (fn: () => void) => void }).requestIdleCallback(cb);
} else {
setTimeout(cb, 300);
}
};
idle(() => {
void loadAnalytics();
void loadEmails();
});
})();
}, [loadProjects, loadSystemStats, loadAnalytics, loadEmails]);
const navigation = [
{ id: 'overview' as TabId, label: 'Dashboard', icon: Home, description: 'Overview & Statistics' },
{ id: 'projects' as TabId, label: 'Projects', icon: Database, description: 'Manage Projects' },
{ id: 'emails' as TabId, label: 'Emails', icon: Mail, description: 'Email Management' },
{ id: 'analytics' as TabId, label: 'Analytics', icon: Activity, description: 'Site Analytics' },
{ id: 'content' as TabId, label: 'Content', icon: Shield, description: 'Texts, pages & localization' },
{ id: 'settings' as TabId, label: 'Settings', icon: Settings, description: 'System Settings' }
];
const statCards = [
{
label: 'Projects',
value: stats.totalProjects,
sub: `${stats.publishedProjects} published`,
icon: Database,
tab: 'projects' as TabId,
gradient: 'from-emerald-400/20 to-emerald-400/5',
border: 'border-emerald-400/20 dark:border-emerald-400/10',
iconColor: 'text-emerald-500',
tooltip: 'REAL DATA: Total projects in your portfolio from the database. Shows published vs unpublished count.',
},
{
label: 'Page Views',
value: stats.totalViews.toLocaleString(),
sub: `${stats.totalUsers} users`,
icon: Activity,
tab: 'analytics' as TabId,
gradient: 'from-sky-400/20 to-sky-400/5',
border: 'border-sky-400/20 dark:border-sky-400/10',
iconColor: 'text-sky-500',
tooltip: 'REAL DATA: Total page views from PageView table (last 30 days). Users = unique IP addresses.',
},
{
label: 'Messages',
value: emails.length,
sub: stats.unreadEmails > 0 ? `${stats.unreadEmails} unread` : 'all read',
subColor: stats.unreadEmails > 0 ? 'text-red-500' : 'text-emerald-500',
icon: Mail,
tab: 'emails' as TabId,
gradient: 'from-purple-400/20 to-purple-400/5',
border: 'border-purple-400/20 dark:border-purple-400/10',
iconColor: 'text-purple-500',
},
{
label: 'Performance',
value: stats.avgPerformance || 'N/A',
sub: 'Lighthouse score',
icon: TrendingUp,
tab: 'analytics' as TabId,
gradient: 'from-amber-400/20 to-amber-400/5',
border: 'border-amber-400/20 dark:border-amber-400/10',
iconColor: 'text-amber-500',
tooltip: stats.avgPerformance > 0
? 'REAL DATA: Average Lighthouse score from real Web Vitals collected from page visits.'
: 'No performance data yet. Scores appear after visitors load pages.',
},
{
label: 'Bounce Rate',
value: `${stats.bounceRate}%`,
sub: 'Exit rate',
icon: Users,
tab: 'analytics' as TabId,
gradient: 'from-pink-400/20 to-pink-400/5',
border: 'border-pink-400/20 dark:border-pink-400/10',
iconColor: 'text-pink-500',
tooltip: 'REAL DATA: Percentage of sessions with only 1 pageview. Lower is better.',
},
{
label: 'System',
value: 'Online',
sub: 'Operational',
icon: Shield,
tab: 'settings' as TabId,
gradient: 'from-teal-400/20 to-teal-400/5',
border: 'border-teal-400/20 dark:border-teal-400/10',
iconColor: 'text-teal-500',
pulse: true,
},
];
return (
<div className="min-h-screen bg-stone-50 dark:bg-stone-950">
{/* Navbar */}
<div className="sticky top-0 z-50 bg-stone-50/90 dark:bg-stone-950/90 backdrop-blur-xl border-b border-stone-200 dark:border-stone-800">
{/* Gradient accent bar */}
<div className="h-0.5 bg-gradient-to-r from-emerald-400 via-sky-400 to-purple-400" />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-14">
{/* Left: branding */}
<div className="flex items-center gap-4">
<Link
href="/"
className="flex items-center gap-2 text-stone-500 hover:text-stone-900 dark:hover:text-stone-50 transition-colors"
>
<Home size={16} />
<span className="font-mono text-xs font-black tracking-tighter">
dk<span className="text-red-500">0</span>.dev
</span>
</Link>
<div className="h-4 w-px bg-stone-300 dark:bg-stone-700" />
<span className="font-black text-xs uppercase tracking-[0.15em] text-stone-900 dark:text-stone-50">
Admin
</span>
</div>
{/* Center: desktop tabs */}
<div className="hidden md:flex items-center gap-1">
{navigation.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-bold uppercase tracking-[0.1em] transition-all duration-200 ${
activeTab === item.id
? 'bg-stone-900 dark:bg-stone-50 text-white dark:text-stone-900'
: 'text-stone-500 dark:text-stone-400 hover:text-stone-900 dark:hover:text-stone-50 hover:bg-stone-100 dark:hover:bg-stone-900'
}`}
>
<item.icon size={13} />
{item.label}
</button>
))}
</div>
{/* Right: user + logout + mobile toggle */}
<div className="flex items-center gap-3">
<span className="hidden sm:block text-xs text-stone-400 dark:text-stone-500 font-mono">
Dennis
</span>
<button
onClick={async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
sessionStorage.removeItem('admin_authenticated');
sessionStorage.removeItem('admin_session_token');
window.location.href = '/manage';
} catch (error) {
console.error('Logout failed:', error);
sessionStorage.removeItem('admin_authenticated');
sessionStorage.removeItem('admin_session_token');
window.location.href = '/manage';
}
}}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-bold uppercase tracking-[0.1em] text-stone-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950/20 transition-all duration-200"
>
<LogOut size={13} />
<span className="hidden sm:inline">Logout</span>
</button>
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2 rounded-xl text-stone-600 dark:text-stone-400 hover:bg-stone-100 dark:hover:bg-stone-900 transition-colors"
>
{mobileMenuOpen ? <X size={18} /> : <Menu size={18} />}
</button>
</div>
</div>
</div>
{/* Mobile menu */}
{mobileMenuOpen && (
<div className="md:hidden border-t border-stone-200 dark:border-stone-800 bg-stone-50 dark:bg-stone-950">
<div className="px-4 py-3 grid grid-cols-2 gap-2">
{navigation.map((item) => (
<button
key={item.id}
onClick={() => {
setActiveTab(item.id);
setMobileMenuOpen(false);
}}
className={`flex items-center gap-2 px-4 py-3 rounded-2xl transition-all duration-200 text-left ${
activeTab === item.id
? 'bg-stone-900 dark:bg-stone-50 text-white dark:text-stone-900'
: 'text-stone-500 dark:text-stone-400 hover:text-stone-900 dark:hover:text-stone-50 bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800'
}`}
>
<item.icon size={16} />
<div>
<div className="font-bold text-xs uppercase tracking-[0.1em]">{item.label}</div>
<div className="text-[10px] opacity-60">{item.description}</div>
</div>
</button>
))}
</div>
</div>
)}
</div>
{/* Main content */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 xl:px-12 2xl:px-16 py-8">
{/* Overview tab */}
{activeTab === 'overview' && (
<div className="space-y-8">
<div>
<h1 className="text-4xl sm:text-5xl font-black tracking-tighter uppercase text-stone-900 dark:text-stone-50">
Dashboard<span className="text-emerald-500">.</span>
</h1>
<p className="text-stone-500 dark:text-stone-400 mt-1 text-sm">
Manage your portfolio and monitor performance
</p>
</div>
{/* Stats grid */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
{statCards.map((card) => (
<div
key={card.label}
className={`relative group bg-gradient-to-br ${card.gradient} border ${card.border} rounded-3xl p-5 cursor-pointer hover:scale-[1.02] active:scale-[0.98] transition-all duration-200`}
onClick={() => setActiveTab(card.tab)}
>
<div className="flex items-start justify-between mb-3">
<p className="text-[10px] font-black uppercase tracking-[0.15em] text-stone-500 dark:text-stone-400">
{card.label}
</p>
<card.icon size={15} className={card.iconColor} />
</div>
<p className="text-2xl font-black tracking-tighter text-stone-900 dark:text-stone-50">
{card.pulse ? (
<span className="flex items-center gap-1.5">
<span className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse" />
{card.value}
</span>
) : card.value}
</p>
<p className={`text-[11px] font-medium mt-1 ${card.subColor ?? 'text-stone-500 dark:text-stone-400'}`}>
{card.sub}
</p>
{card.tooltip && (
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-stone-900/95 dark:bg-stone-800 text-stone-50 text-[10px] font-medium rounded-xl opacity-0 group-hover:opacity-100 transition-opacity whitespace-normal max-w-[200px] z-50 shadow-xl pointer-events-none text-center">
{card.tooltip}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 w-2 h-2 bg-stone-900/95 dark:bg-stone-800 rotate-45" />
</div>
)}
</div>
))}
</div>
{/* Recent Activity + Quick Actions */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Recent Activity */}
<div className="lg:col-span-2 bg-white dark:bg-stone-900 border border-stone-100 dark:border-stone-800 rounded-3xl p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-lg font-black tracking-tight uppercase text-stone-900 dark:text-stone-50">
Recent Activity
</h2>
<button
onClick={() => loadAllData()}
className="text-xs font-bold uppercase tracking-[0.1em] text-stone-400 hover:text-stone-900 dark:hover:text-stone-50 px-3 py-1.5 bg-stone-50 dark:bg-stone-800 rounded-xl border border-stone-200 dark:border-stone-700 transition-all"
>
Refresh
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3">
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-stone-400">Projects</h3>
{projects.slice(0, 3).map((project) => (
<div
key={project.id}
className="flex items-start gap-3 p-3 bg-stone-50 dark:bg-stone-800/50 border border-stone-100 dark:border-stone-700 rounded-2xl hover:border-stone-300 dark:hover:border-stone-600 transition-all cursor-pointer"
onClick={() => setActiveTab('projects')}
>
<div className="flex-1 min-w-0">
<p className="text-stone-900 dark:text-stone-50 font-bold text-sm truncate">{project.title}</p>
<p className="text-stone-400 text-xs mt-0.5">{project.analytics?.views || 0} views</p>
<div className="flex items-center gap-2 mt-2">
<span className={`px-2 py-0.5 rounded-full text-[10px] font-bold ${project.published ? 'bg-emerald-100 dark:bg-emerald-950/30 text-emerald-700 dark:text-emerald-400' : 'bg-amber-100 dark:bg-amber-950/30 text-amber-700 dark:text-amber-400'}`}>
{project.published ? 'Live' : 'Draft'}
</span>
{project.featured && (
<span className="px-2 py-0.5 bg-stone-100 dark:bg-stone-700 text-stone-600 dark:text-stone-300 rounded-full text-[10px] font-bold">Featured</span>
)}
</div>
</div>
</div>
))}
{projects.length === 0 && (
<p className="text-stone-400 text-xs py-4">No projects yet</p>
)}
</div>
<div className="space-y-3">
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-stone-400">Messages</h3>
{emails.slice(0, 3).map((email, index) => (
<div
key={index}
className="flex items-center gap-3 p-3 bg-stone-50 dark:bg-stone-800/50 border border-stone-100 dark:border-stone-700 rounded-2xl hover:border-stone-300 dark:hover:border-stone-600 transition-all cursor-pointer"
onClick={() => setActiveTab('emails')}
>
<div className="w-8 h-8 bg-gradient-to-br from-purple-400/20 to-purple-400/5 border border-purple-400/20 rounded-xl flex items-center justify-center flex-shrink-0">
<Mail size={13} className="text-purple-500" />
</div>
<div className="flex-1 min-w-0">
<p className="text-stone-900 dark:text-stone-50 font-bold text-sm truncate">
{email.name as string}
</p>
<p className="text-stone-400 text-xs truncate">{(email.subject as string) || 'No subject'}</p>
</div>
{!(email.read as boolean) && (
<div className="w-2 h-2 bg-red-500 rounded-full flex-shrink-0" />
)}
</div>
))}
{emails.length === 0 && (
<p className="text-stone-400 text-xs py-4">No messages yet</p>
)}
</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-white dark:bg-stone-900 border border-stone-100 dark:border-stone-800 rounded-3xl p-6">
<h2 className="text-lg font-black tracking-tight uppercase text-stone-900 dark:text-stone-50 mb-6">
Quick Actions
</h2>
<div className="space-y-3">
{[
{ label: 'Ghost Editor', sub: 'Professional writing tool', icon: Plus, action: () => window.location.href = '/editor', color: 'from-emerald-400/20 to-emerald-400/5 border-emerald-400/20' },
{ label: 'View Messages', sub: `${stats.unreadEmails} unread`, icon: Mail, action: () => setActiveTab('emails'), color: 'from-purple-400/20 to-purple-400/5 border-purple-400/20' },
{ label: 'Analytics', sub: 'View detailed stats', icon: TrendingUp, action: () => setActiveTab('analytics'), color: 'from-sky-400/20 to-sky-400/5 border-sky-400/20' },
{ label: 'Settings', sub: 'System configuration', icon: Settings, action: () => setActiveTab('settings'), color: 'from-stone-400/20 to-stone-400/5 border-stone-400/20' },
].map((item) => (
<button
key={item.label}
onClick={item.action}
className={`w-full flex items-center gap-3 p-3 bg-gradient-to-r ${item.color} border rounded-2xl hover:scale-[1.02] active:scale-[0.98] transition-all duration-200 text-left`}
>
<div className="w-9 h-9 bg-white dark:bg-stone-800 rounded-xl border border-stone-100 dark:border-stone-700 flex items-center justify-center flex-shrink-0">
<item.icon size={15} className="text-stone-600 dark:text-stone-300" />
</div>
<div>
<p className="text-stone-900 dark:text-stone-50 font-bold text-sm">{item.label}</p>
<p className="text-stone-400 text-xs">{item.sub}</p>
</div>
</button>
))}
</div>
</div>
</div>
</div>
)}
{activeTab === 'projects' && (
<div className="space-y-6">
<div>
<h1 className="text-4xl sm:text-5xl font-black tracking-tighter uppercase text-stone-900 dark:text-stone-50">
Projects<span className="text-emerald-500">.</span>
</h1>
<p className="text-stone-500 dark:text-stone-400 mt-1 text-sm">Manage your portfolio projects</p>
</div>
<ProjectManager projects={projects} onProjectsChange={loadProjects} />
</div>
)}
{activeTab === 'emails' && (
<EmailManager />
)}
{activeTab === 'analytics' && (
<AnalyticsDashboard isAuthenticated={isAuthenticated} />
)}
{activeTab === 'content' && (
<ContentManager />
)}
{activeTab === 'settings' && (
<div className="space-y-8">
<div>
<h1 className="text-4xl sm:text-5xl font-black tracking-tighter uppercase text-stone-900 dark:text-stone-50">
Settings<span className="text-emerald-500">.</span>
</h1>
<p className="text-stone-500 dark:text-stone-400 mt-1 text-sm">Manage system configuration and preferences</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white dark:bg-stone-900 border border-stone-100 dark:border-stone-800 rounded-3xl p-6">
<h2 className="text-lg font-black tracking-tight uppercase text-stone-900 dark:text-stone-50 mb-2">
Import / Export
</h2>
<p className="text-stone-400 text-sm mb-6">Backup and restore your portfolio data</p>
<ImportExport />
</div>
<div className="bg-white dark:bg-stone-900 border border-stone-100 dark:border-stone-800 rounded-3xl p-6">
<h2 className="text-lg font-black tracking-tight uppercase text-stone-900 dark:text-stone-50 mb-6">
System Status
</h2>
<div className="space-y-3">
{[
{ label: 'Database', color: 'bg-emerald-400/20 border-emerald-400/20', dot: 'bg-emerald-500', text: 'text-emerald-600 dark:text-emerald-400' },
{ label: 'Redis Cache', color: 'bg-emerald-400/20 border-emerald-400/20', dot: 'bg-emerald-500', text: 'text-emerald-600 dark:text-emerald-400' },
{ label: 'API Services', color: 'bg-emerald-400/20 border-emerald-400/20', dot: 'bg-emerald-500', text: 'text-emerald-600 dark:text-emerald-400' },
].map((item) => (
<div
key={item.label}
className={`flex items-center justify-between p-4 bg-gradient-to-r ${item.color} border rounded-2xl`}
>
<span className="text-stone-600 dark:text-stone-300 font-medium text-sm">{item.label}</span>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 ${item.dot} rounded-full animate-pulse`} />
<span className={`${item.text} font-bold text-sm`}>Online</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default ModernAdminDashboard;