diff --git a/app/manage/page.tsx b/app/manage/page.tsx index 39feaff..5a4cc75 100644 --- a/app/manage/page.tsx +++ b/app/manage/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useState, useEffect, useCallback } from 'react'; -import { motion } from 'framer-motion'; import { Lock, Loader2 } from 'lucide-react'; import ModernAdminDashboard from '@/components/ModernAdminDashboard'; @@ -58,7 +57,7 @@ const AdminPage = () => { // Check if user is locked out const checkLockout = useCallback(() => { if (typeof window === 'undefined') return false; - + try { const lockoutData = localStorage.getItem('admin_lockout'); if (lockoutData) { @@ -103,11 +102,11 @@ const AdminPage = () => { try { const sessionToken = sessionStorage.getItem('admin_session_token'); if (!sessionToken) { - setAuthState(prev => ({ - ...prev, - isAuthenticated: false, - showLogin: true, - isLoading: false + setAuthState(prev => ({ + ...prev, + isAuthenticated: false, + showLogin: true, + isLoading: false })); return; } @@ -118,38 +117,38 @@ const AdminPage = () => { 'Content-Type': 'application/json', 'X-CSRF-Token': authState.csrfToken }, - body: JSON.stringify({ - sessionToken, - csrfToken: authState.csrfToken + body: JSON.stringify({ + sessionToken, + csrfToken: authState.csrfToken }) }); const data = await response.json(); - + if (response.ok && data.valid) { - setAuthState(prev => ({ - ...prev, - isAuthenticated: true, - showLogin: false, - isLoading: false + setAuthState(prev => ({ + ...prev, + isAuthenticated: true, + showLogin: false, + isLoading: false })); sessionStorage.setItem('admin_authenticated', 'true'); } else { sessionStorage.removeItem('admin_authenticated'); sessionStorage.removeItem('admin_session_token'); - setAuthState(prev => ({ - ...prev, - isAuthenticated: false, - showLogin: true, - isLoading: false + setAuthState(prev => ({ + ...prev, + isAuthenticated: false, + showLogin: true, + isLoading: false })); } } catch { - setAuthState(prev => ({ - ...prev, - isAuthenticated: false, - showLogin: true, - isLoading: false + setAuthState(prev => ({ + ...prev, + isAuthenticated: false, + showLogin: true, + isLoading: false })); } }, [authState.csrfToken]); @@ -158,13 +157,13 @@ const AdminPage = () => { useEffect(() => { const init = async () => { if (checkLockout()) return; - + const token = await fetchCSRFToken(); if (token) { setAuthState(prev => ({ ...prev, csrfToken: token })); } }; - + init(); }, [checkLockout, fetchCSRFToken]); @@ -178,7 +177,7 @@ const AdminPage = () => { // Handle login form submission const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); - + if (!authState.password.trim() || authState.isLoading) return; setAuthState(prev => ({ ...prev, isLoading: true, error: '' })); @@ -259,10 +258,12 @@ const AdminPage = () => { // Loading state if (authState.isLoading) { return ( -
-
- -

Loading...

+
+
+
+ dk0.dev +
+
); @@ -271,26 +272,38 @@ const AdminPage = () => { // Lockout state if (authState.isLocked) { return ( -
-
-
- +
+
+
+
+
+
+ +
+

+ dk0.dev · admin +

+

+ Account Locked +

+

+ Too many failed attempts. Please try again in 15 minutes. +

+ +
-

Account Locked

-

Too many failed attempts. Please try again in 15 minutes.

-
); @@ -299,70 +312,84 @@ const AdminPage = () => { // Login form if (authState.showLogin || !authState.isAuthenticated) { return ( -
- - -
-
-
- -
-

Admin Access

-

Enter your password to continue

-
+
+ {/* Liquid ambient blobs */} +
+
+
+
-
-
-
- setAuthState(prev => ({ ...prev, password: e.target.value }))} - placeholder="Enter password" - className="w-full px-4 py-3.5 bg-white border border-[#d7ccc8] rounded-xl text-[#3e2723] placeholder:text-[#a1887f] focus:outline-none focus:ring-2 focus:ring-[#bcaaa4] focus:border-[#5d4037] transition-all shadow-sm" - disabled={authState.isLoading} - /> - +
+
+
+ +
+
+

+ dk0.dev · admin +

+
+
- {authState.error && ( - - - {authState.error} - - )} +

+ Admin Access +

+

+ Enter your password to continue +

-
- ) : ( - Sign In - )} - - + {authState.error && ( +

+ + {authState.error} +

+ )} +
+ + + +
- + + {authState.attempts > 0 && ( +

+ {5 - authState.attempts} attempt{5 - authState.attempts !== 1 ? 's' : ''} remaining +

+ )} +
); } @@ -375,4 +402,4 @@ const AdminPage = () => { ); }; -export default AdminPage; \ No newline at end of file +export default AdminPage; diff --git a/components/ModernAdminDashboard.tsx b/components/ModernAdminDashboard.tsx index 5081a32..8cf346f 100644 --- a/components/ModernAdminDashboard.tsx +++ b/components/ModernAdminDashboard.tsx @@ -1,7 +1,6 @@ 'use client'; import React, { useState, useEffect, useCallback } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; import { Mail, Settings, @@ -21,23 +20,23 @@ import dynamic from 'next/dynamic'; const EmailManager = dynamic( () => import('./EmailManager').then((m) => m.EmailManager), - { ssr: false, loading: () =>
Loading emails…
} + { ssr: false, loading: () =>
Loading emails…
} ); const AnalyticsDashboard = dynamic( () => import('./AnalyticsDashboard').then((m) => m.default), - { ssr: false, loading: () =>
Loading analytics…
} + { ssr: false, loading: () =>
Loading analytics…
} ); const ImportExport = dynamic( () => import('./ImportExport').then((m) => m.default), - { ssr: false, loading: () =>
Loading tools…
} + { ssr: false, loading: () =>
Loading tools…
} ); const ProjectManager = dynamic( () => import('./ProjectManager').then((m) => m.ProjectManager), - { ssr: false, loading: () =>
Loading projects…
} + { ssr: false, loading: () =>
Loading projects…
} ); const ContentManager = dynamic( () => import('./ContentManager').then((m) => m.default), - { ssr: false, loading: () =>
Loading content…
} + { ssr: false, loading: () =>
Loading content…
} ); interface Project { @@ -69,8 +68,10 @@ interface ModernAdminDashboardProps { isAuthenticated?: boolean; } +type TabId = 'overview' | 'projects' | 'emails' | 'analytics' | 'content' | 'settings'; + const ModernAdminDashboard: React.FC = ({ isAuthenticated = true }) => { - const [activeTab, setActiveTab] = useState<'overview' | 'projects' | 'emails' | 'analytics' | 'content' | 'settings'>('overview'); + const [activeTab, setActiveTab] = useState('overview'); const [projects, setProjects] = useState([]); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [isLoading, setIsLoading] = useState(false); @@ -180,7 +181,6 @@ const ModernAdminDashboard: React.FC = ({ isAuthentic totalViews: ((analytics?.overview as Record)?.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: (() => { - // Only show real performance data, not defaults const projectsWithPerf = projects.filter(p => { const perf = p.performance as Record || {}; return (perf.lighthouse as number || 0) > 0; @@ -198,7 +198,6 @@ const ModernAdminDashboard: React.FC = ({ isAuthentic }; useEffect(() => { - // Prioritize the data needed for the initial dashboard render void (async () => { await Promise.all([loadProjects(), loadSystemStats()]); @@ -218,467 +217,424 @@ const ModernAdminDashboard: React.FC = ({ isAuthentic }, [loadProjects, loadSystemStats, loadAnalytics, loadEmails]); const navigation = [ - { id: 'overview', label: 'Dashboard', icon: Home, color: 'blue', description: 'Overview & Statistics' }, - { id: 'projects', label: 'Projects', icon: Database, color: 'green', description: 'Manage Projects' }, - { id: 'emails', label: 'Emails', icon: Mail, color: 'purple', description: 'Email Management' }, - { id: 'analytics', label: 'Analytics', icon: Activity, color: 'orange', description: 'Site Analytics' }, - { id: 'content', label: 'Content', icon: Shield, color: 'teal', description: 'Texts, pages & localization' }, - { id: 'settings', label: 'Settings', icon: Settings, color: 'gray', description: 'System Settings' } + { 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 ( -
- {/* Animated Background - same as main site */} -
+
- {/* Admin Navbar - Horizontal Navigation */} -
-
-
-
- {/* Left side - Logo and Admin Panel */} -
- - - Portfolio - -
-
- - Admin Panel -
-
+ {/* Navbar */} +
+ {/* Gradient accent bar */} +
- {/* Center - Desktop Navigation */} -
- {navigation.map((item) => ( - - ))} -
+
+
- {/* Right side - User info and Logout */} -
-
- Welcome, Dennis -
+ {/* Left: branding */} +
+ + + + dk0.dev + + +
+ + Admin + +
+ + {/* Center: desktop tabs */} +
+ {navigation.map((item) => ( + ))} +
- {/* Mobile menu button */} - -
+ {/* Right: user + logout + mobile toggle */} +
+ + Dennis + + + +
+
- {/* Mobile Navigation Menu */} - - {mobileMenuOpen && ( - -
- {navigation.map((item) => ( + {/* Mobile menu */} + {mobileMenuOpen && ( +
+
+ {navigation.map((item) => ( + + ))} +
+
+ )} +
+ + {/* Main content */} +
+ + {/* Overview tab */} + {activeTab === 'overview' && ( +
+
+

+ Dashboard. +

+

+ Manage your portfolio and monitor performance +

+
+ + {/* Stats grid */} +
+ {statCards.map((card) => ( +
setActiveTab(card.tab)} + > +
+

+ {card.label} +

+ +
+

+ {card.pulse ? ( + + + {card.value} + + ) : card.value} +

+

+ {card.sub} +

+ + {card.tooltip && ( +
+ {card.tooltip} +
+
+ )} +
+ ))} +
+ + {/* Recent Activity + Quick Actions */} +
+ + {/* Recent Activity */} +
+
+

+ Recent Activity +

+ +
+ +
+
+

Projects

+ {projects.slice(0, 3).map((project) => ( +
setActiveTab('projects')} + > +
+

{project.title}

+

{project.analytics?.views || 0} views

+
+ + {project.published ? 'Live' : 'Draft'} + + {project.featured && ( + Featured + )} +
+
+
+ ))} + {projects.length === 0 && ( +

No projects yet

+ )} +
+ +
+

Messages

+ {emails.slice(0, 3).map((email, index) => ( +
setActiveTab('emails')} + > +
+ +
+
+

+ {email.name as string} +

+

{(email.subject as string) || 'No subject'}

+
+ {!(email.read as boolean) && ( +
+ )} +
+ ))} + {emails.length === 0 && ( +

No messages yet

+ )} +
+
+
+ + {/* Quick Actions */} +
+

+ Quick Actions +

+
+ {[ + { 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) => ( ))}
- - )} - -
+
+
+
+ )} - {/* Main Content - Full Width Horizontal Layout */} -
- {/* Content */} - - - {activeTab === 'overview' && ( -
-
-
-

Admin Dashboard

-

Manage your portfolio and monitor performance

-
-
+ {activeTab === 'projects' && ( +
+
+

+ Projects. +

+

Manage your portfolio projects

+
+ +
+ )} - {/* Stats Grid - Mobile: 2x3, Desktop: 6x1 horizontal */} -
-
setActiveTab('projects')} + {activeTab === 'emails' && ( + + )} + + {activeTab === 'analytics' && ( + + )} + + {activeTab === 'content' && ( + + )} + + {activeTab === 'settings' && ( +
+
+

+ Settings. +

+

Manage system configuration and preferences

+
+ +
+
+

+ Import / Export +

+

Backup and restore your portfolio data

+ +
+ +
+

+ System Status +

+
+ {[ + { 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) => ( +
-
-
-

Projects

- -
-

{stats.totalProjects}

-

{stats.publishedProjects} published

-
-
- ✅ REAL DATA: Total projects in your portfolio from the database. Shows published vs unpublished count. -
+ {item.label} +
+
+ Online
- -
setActiveTab('analytics')} - > -
-
-

Page Views

- -
-

{stats.totalViews.toLocaleString()}

-

{stats.totalUsers} users

-
-
- ✅ REAL DATA: Total page views from PageView table (last 30 days). Each visit is tracked with IP, user agent, and timestamp. Users = unique IP addresses. -
-
-
- -
setActiveTab('emails')} - > -
-
-

Messages

- -
-

{emails.length}

-

{stats.unreadEmails} unread

-
-
- -
setActiveTab('analytics')} - > -
-
-

Performance

- -
-

{stats.avgPerformance || 'N/A'}

-

Lighthouse Score

-
-
- {stats.avgPerformance > 0 - ? "✅ REAL DATA: Average Lighthouse score (0-100) calculated from real Web Vitals (LCP, FCP, CLS, FID, TTFB) collected from actual page visits. Only averages projects with real performance data." - : "No performance data yet. Scores appear after visitors load pages and Web Vitals are tracked."} -
-
-
- -
setActiveTab('analytics')} - > -
-
-

Bounce Rate

- -
-

{stats.bounceRate}%

-

Exit rate

-
-
- ✅ REAL DATA: Percentage of sessions with only 1 pageview (calculated from PageView records grouped by IP). Lower is better. Shows how many visitors leave after viewing just one page. -
-
-
- -
setActiveTab('settings')} - > -
-
-

System

- -
-

Online

-
-
-

Operational

-
-
-
-
- - {/* Recent Activity & Quick Actions - Mobile: vertical, Desktop: horizontal */} -
- {/* Recent Activity */} -
-
-

Recent Activity

- -
- - {/* Mobile: vertical stack, Desktop: horizontal columns */} -
-
-

Projects

-
- {projects.slice(0, 3).map((project) => ( -
setActiveTab('projects')}> -
-

{project.title}

-

{project.published ? 'Published' : 'Draft'} • {project.analytics?.views || 0} views

-
- - {project.published ? 'Live' : 'Draft'} - - {project.featured && ( - Featured - )} -
-
-
- ))} -
-
- -
-

Messages

-
- {emails.slice(0, 3).map((email, index) => ( -
setActiveTab('emails')}> -
- -
-
-

From {email.name as string}

-

{(email.subject as string) || 'No subject'}

-
- {!(email.read as boolean) && ( -
- )} -
- ))} -
-
-
-
- - {/* Quick Actions */} -
-

Quick Actions

-
- - - - - - - - - -
-
-
+ ))}
- )} +
+
+
+ )} - {activeTab === 'projects' && ( -
-
-
-

Project Management

-

Manage your portfolio projects

-
-
- - -
- )} - - {activeTab === 'emails' && ( - - )} - - {activeTab === 'analytics' && ( - - )} - - {activeTab === 'content' && ( - - )} - - {activeTab === 'settings' && ( -
-
-

System Settings

-

Manage system configuration and preferences

-
- -
-
-

Import / Export

-

Backup and restore your portfolio data

- -
- -
-

System Status

-
-
- Database -
-
- Online -
-
-
- Redis Cache -
-
- Online -
-
-
- API Services -
-
- Online -
-
-
-
-
-
- )} - - -
); }; -export default ModernAdminDashboard; \ No newline at end of file +export default ModernAdminDashboard;