405 lines
20 KiB
TypeScript
405 lines
20 KiB
TypeScript
'use client'
|
|
|
|
import PageLayout from '../components/PageLayout'
|
|
import Waves from '../components/waves'
|
|
import {
|
|
UsersIcon,
|
|
ExclamationTriangleIcon,
|
|
CpuChipIcon,
|
|
ServerStackIcon,
|
|
ArrowRightIcon,
|
|
Squares2X2Icon,
|
|
BanknotesIcon,
|
|
ClipboardDocumentListIcon
|
|
} from '@heroicons/react/24/outline'
|
|
import { useMemo, useState, useEffect } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { useAdminUsers } from '../hooks/useAdminUsers'
|
|
|
|
// env-based feature flags
|
|
const DISPLAY_MATRIX = process.env.NEXT_PUBLIC_DISPLAY_MATRIX !== 'false'
|
|
const DISPLAY_ABONEMENTS = process.env.NEXT_PUBLIC_DISPLAY_ABONEMMENTS !== 'false'
|
|
const DISPLAY_NEWS = process.env.NEXT_PUBLIC_DISPLAY_NEWS !== 'false'
|
|
|
|
export default function AdminDashboardPage() {
|
|
const router = useRouter()
|
|
const { userStats, isAdmin } = useAdminUsers()
|
|
const [isClient, setIsClient] = useState(false)
|
|
|
|
// Handle client-side mounting
|
|
useEffect(() => {
|
|
setIsClient(true)
|
|
}, [])
|
|
|
|
// Fallback for loading/no data
|
|
const displayStats = userStats || {
|
|
totalUsers: 0,
|
|
adminUsers: 0,
|
|
verificationPending: 0,
|
|
activeUsers: 0,
|
|
personalUsers: 0,
|
|
companyUsers: 0
|
|
}
|
|
|
|
const permissionStats = useMemo(() => ({
|
|
permissions: 1 // TODO: fetch permission definitions
|
|
}), [])
|
|
|
|
const serverStats = useMemo(() => ({
|
|
status: 'Online',
|
|
uptime: '4 days, 8 hours',
|
|
cpu: '0%',
|
|
memory: '0.1 / 7.8',
|
|
recentErrors: [] as { id: string; ts: string; msg: string }[]
|
|
}), [])
|
|
|
|
// Show loading during SSR/initial client render
|
|
if (!isClient) {
|
|
return (
|
|
<PageLayout>
|
|
<div className="min-h-screen flex items-center justify-center bg-blue-50">
|
|
<div className="text-center">
|
|
<div className="h-12 w-12 rounded-full border-2 border-blue-900 border-b-transparent animate-spin mx-auto mb-4" />
|
|
<p className="text-blue-900">Loading...</p>
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
)
|
|
}
|
|
|
|
// Access check (only after client-side hydration)
|
|
if (!isAdmin) {
|
|
return (
|
|
<PageLayout>
|
|
<div className="min-h-screen flex items-center justify-center bg-blue-50">
|
|
<div className="mx-auto w-full max-w-xl rounded-2xl bg-white shadow ring-1 ring-red-500/20 p-8">
|
|
<div className="text-center">
|
|
<h1 className="text-2xl font-bold text-red-600 mb-2">Access Denied</h1>
|
|
<p className="text-gray-600">You need admin privileges to access this page.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<PageLayout>
|
|
<div
|
|
className="relative w-full flex flex-col min-h-screen overflow-hidden"
|
|
style={{ backgroundImage: 'none', background: 'none' }}
|
|
>
|
|
<Waves
|
|
className="pointer-events-none"
|
|
lineColor="#0f172a"
|
|
backgroundColor="rgba(245, 245, 240, 1)"
|
|
waveSpeedX={0.02}
|
|
waveSpeedY={0.01}
|
|
waveAmpX={40}
|
|
waveAmpY={20}
|
|
friction={0.9}
|
|
tension={0.01}
|
|
maxCursorMove={120}
|
|
xGap={12}
|
|
yGap={36}
|
|
/>
|
|
|
|
<div className="relative z-10 min-h-screen flex flex-col">
|
|
<main className="flex-1 max-w-7xl mx-auto px-2 sm:px-6 lg:px-8 py-8 w-full">
|
|
<div className="rounded-3xl bg-white/70 backdrop-blur-md border border-white/60 shadow-lg p-6 sm:p-8">
|
|
{/* Header */}
|
|
<header className="flex flex-col gap-4 mb-8">
|
|
<div>
|
|
<h1 className="text-4xl font-extrabold text-blue-900 tracking-tight">Admin Dashboard</h1>
|
|
<p className="text-lg text-blue-700 mt-2">
|
|
Manage all administrative features, user management, permissions, and global settings.
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Warning banner */}
|
|
<div className="rounded-2xl border border-red-300 bg-red-50 text-red-700 px-8 py-6 flex gap-3 items-start text-base mb-8 shadow">
|
|
<ExclamationTriangleIcon className="h-6 w-6 flex-shrink-0 text-red-500 mt-0.5" />
|
|
<div className="leading-relaxed">
|
|
<p className="font-semibold mb-0.5">
|
|
Warning: Settings and actions below this point can have consequences for the entire system!
|
|
</p>
|
|
<p className="text-red-600/80 hidden sm:block">
|
|
Manage all administrative features, user management, permissions, and global settings.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Card */}
|
|
<div className="mb-8 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-6">
|
|
<div className="rounded-xl bg-white border border-gray-100 p-5 text-center shadow">
|
|
<div className="text-xs text-gray-500">Total Users</div>
|
|
<div className="text-xl font-semibold text-blue-900">{displayStats.totalUsers}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-white border border-gray-100 p-5 text-center shadow">
|
|
<div className="text-xs text-gray-500">Admins</div>
|
|
<div className="text-xl font-semibold text-indigo-700">{displayStats.adminUsers}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-white border border-gray-100 p-5 text-center shadow">
|
|
<div className="text-xs text-gray-500">Active</div>
|
|
<div className="text-xl font-semibold text-green-700">{displayStats.activeUsers}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-white border border-gray-100 p-5 text-center shadow">
|
|
<div className="text-xs text-gray-500">Pending Verification</div>
|
|
<div className="text-xl font-semibold text-amber-700">{displayStats.verificationPending}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-white border border-gray-100 p-5 text-center shadow">
|
|
<div className="text-xs text-gray-500">Personal</div>
|
|
<div className="text-xl font-semibold text-blue-700">{displayStats.personalUsers}</div>
|
|
</div>
|
|
<div className="rounded-xl bg-white border border-gray-100 p-5 text-center shadow">
|
|
<div className="text-xs text-gray-500">Company</div>
|
|
<div className="text-xl font-semibold text-purple-700">{displayStats.companyUsers}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Management Shortcuts Card */}
|
|
<div className="mb-8">
|
|
<div className="rounded-2xl border border-gray-100 bg-white p-8 shadow-lg hover:shadow-xl transition">
|
|
<div className="flex items-start gap-4 mb-6">
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100">
|
|
<Squares2X2Icon className="h-7 w-7 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-blue-900">Management Shortcuts</h2>
|
|
<p className="text-sm text-blue-700 mt-0.5">
|
|
Quick access to common admin modules.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* Matrix Management */}
|
|
<button
|
|
type="button"
|
|
disabled={!DISPLAY_MATRIX}
|
|
onClick={DISPLAY_MATRIX ? () => router.push('/admin/matrix-management') : undefined}
|
|
className={`group w-full flex items-center justify-between rounded-lg px-4 py-4 ${
|
|
DISPLAY_MATRIX
|
|
? 'border border-blue-200 bg-blue-50 hover:bg-blue-100 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md'
|
|
: 'border border-gray-200 bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<span
|
|
className={`inline-flex h-10 w-10 items-center justify-center rounded-md border ${
|
|
DISPLAY_MATRIX
|
|
? 'bg-blue-100 border-blue-200 group-hover:animate-pulse'
|
|
: 'bg-gray-100 border-gray-300'
|
|
}`}
|
|
>
|
|
<Squares2X2Icon className={`h-6 w-6 ${DISPLAY_MATRIX ? 'text-blue-600' : 'text-gray-400'}`} />
|
|
</span>
|
|
<div className="text-left">
|
|
<div className="text-base font-semibold text-blue-900">Matrix Management</div>
|
|
<div className="text-xs text-blue-700">Configure matrices and users</div>
|
|
{!DISPLAY_MATRIX && (
|
|
<p className="mt-1 text-xs text-gray-500 italic">
|
|
This module is currently disabled in the system configuration.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<ArrowRightIcon
|
|
className={`h-5 w-5 ${
|
|
DISPLAY_MATRIX ? 'text-blue-600 opacity-70 group-hover:opacity-100' : 'text-gray-400 opacity-60'
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
{/* Coffee Subscription Management */}
|
|
<button
|
|
type="button"
|
|
disabled={!DISPLAY_ABONEMENTS}
|
|
onClick={DISPLAY_ABONEMENTS ? () => router.push('/admin/subscriptions') : undefined}
|
|
className={`group w-full flex items-center justify-between rounded-lg px-4 py-4 ${
|
|
DISPLAY_ABONEMENTS
|
|
? 'border border-amber-200 bg-amber-50 hover:bg-amber-100 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md'
|
|
: 'border border-gray-200 bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<span
|
|
className={`inline-flex h-10 w-10 items-center justify-center rounded-md border ${
|
|
DISPLAY_ABONEMENTS
|
|
? 'bg-amber-100 border-amber-200 group-hover:animate-pulse'
|
|
: 'bg-gray-100 border-gray-300'
|
|
}`}
|
|
>
|
|
<BanknotesIcon className={`h-6 w-6 ${DISPLAY_ABONEMENTS ? 'text-amber-600' : 'text-gray-400'}`} />
|
|
</span>
|
|
<div className="text-left">
|
|
<div className="text-base font-semibold text-amber-900">Coffee Subscription Management</div>
|
|
<div className="text-xs text-amber-700">Plans, billing and renewals</div>
|
|
{!DISPLAY_ABONEMENTS && (
|
|
<p className="mt-1 text-xs text-gray-500 italic">
|
|
This module is currently disabled in the system configuration.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<ArrowRightIcon
|
|
className={`h-5 w-5 ${
|
|
DISPLAY_ABONEMENTS
|
|
? 'text-amber-600 opacity-70 group-hover:opacity-100'
|
|
: 'text-gray-400 opacity-60'
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
{/* Contract Management (unchanged) */}
|
|
<button
|
|
type="button"
|
|
onClick={() => router.push('/admin/contract-management')}
|
|
className="group w-full flex items-center justify-between rounded-lg border border-indigo-200 bg-indigo-50 hover:bg-indigo-100 px-4 py-4 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<span className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-indigo-100 border border-indigo-200 group-hover:animate-pulse">
|
|
<ClipboardDocumentListIcon className="h-6 w-6 text-indigo-600" />
|
|
</span>
|
|
<div className="text-left">
|
|
<div className="text-base font-semibold text-indigo-900">Contract Management</div>
|
|
<div className="text-xs text-indigo-700">Templates, approvals, status</div>
|
|
</div>
|
|
</div>
|
|
<ArrowRightIcon className="h-5 w-5 text-indigo-600 opacity-70 group-hover:opacity-100" />
|
|
</button>
|
|
|
|
{/* User Management (unchanged) */}
|
|
<button
|
|
type="button"
|
|
onClick={() => router.push('/admin/user-management')}
|
|
className="group w-full flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 hover:bg-blue-50 px-4 py-4 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<span className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-blue-100 border border-blue-200 group-hover:animate-pulse">
|
|
<UsersIcon className="h-6 w-6 text-blue-600" />
|
|
</span>
|
|
<div className="text-left">
|
|
<div className="text-base font-semibold text-blue-900">User Management</div>
|
|
<div className="text-xs text-blue-700">Browse, search, and manage all users</div>
|
|
</div>
|
|
</div>
|
|
<ArrowRightIcon className="h-5 w-5 text-blue-600 opacity-70 group-hover:opacity-100" />
|
|
</button>
|
|
|
|
{/* News Management */}
|
|
<button
|
|
type="button"
|
|
disabled={!DISPLAY_NEWS}
|
|
onClick={DISPLAY_NEWS ? () => router.push('/admin/news-management') : undefined}
|
|
className={`group w-full flex items-center justify-between rounded-lg px-4 py-4 ${
|
|
DISPLAY_NEWS
|
|
? 'border border-green-200 bg-green-50 hover:bg-green-100 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md'
|
|
: 'border border-gray-200 bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<span
|
|
className={`inline-flex h-10 w-10 items-center justify-center rounded-md border ${
|
|
DISPLAY_NEWS
|
|
? 'bg-green-100 border-green-200 group-hover:animate-pulse'
|
|
: 'bg-gray-100 border-gray-300'
|
|
}`}
|
|
>
|
|
<ClipboardDocumentListIcon className={`h-6 w-6 ${DISPLAY_NEWS ? 'text-green-600' : 'text-gray-400'}`} />
|
|
</span>
|
|
<div className="text-left">
|
|
<div className="text-base font-semibold text-green-900">News Management</div>
|
|
<div className="text-xs text-green-700">Create and manage news articles</div>
|
|
{!DISPLAY_NEWS && (
|
|
<p className="mt-1 text-xs text-gray-500 italic">
|
|
This module is currently disabled in the system configuration.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<ArrowRightIcon
|
|
className={`h-5 w-5 ${
|
|
DISPLAY_NEWS ? 'text-green-600 opacity-70 group-hover:opacity-100' : 'text-gray-400 opacity-60'
|
|
}`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Server Status & Logs */}
|
|
<div className="rounded-2xl border border-gray-100 bg-white p-8 shadow-lg hover:shadow-xl transition">
|
|
<div className="flex items-start gap-4 mb-6">
|
|
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gray-100">
|
|
<ServerStackIcon className="h-7 w-7 text-gray-700" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|
Server Status & Logs
|
|
</h2>
|
|
<p className="text-sm text-gray-500 mt-0.5">
|
|
System health, resource usage & recent error insights.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-8 lg:grid-cols-3">
|
|
{/* Metrics */}
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<span className={`h-2.5 w-2.5 rounded-full ${serverStats.status === 'Online' ? 'bg-emerald-500' : 'bg-red-500'}`} />
|
|
<p className="text-base">
|
|
<span className="font-semibold">Server Status:</span>{' '}
|
|
<span className={serverStats.status === 'Online' ? 'text-emerald-600 font-medium' : 'text-red-600 font-medium'}>
|
|
{serverStats.status === 'Online' ? 'Server Online' : 'Offline'}
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<div className="text-sm space-y-1 text-gray-600">
|
|
<p><span className="font-medium text-gray-700">Uptime:</span> {serverStats.uptime}</p>
|
|
<p><span className="font-medium text-gray-700">CPU Usage:</span> {serverStats.cpu}</p>
|
|
<p><span className="font-medium text-gray-700">Memory Usage:</span> {serverStats.memory} GB</p>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm text-gray-500">
|
|
<CpuChipIcon className="h-4 w-4" />
|
|
<span>Autoscaled environment (mock)</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Divider */}
|
|
<div className="hidden lg:block border-l border-gray-200" />
|
|
|
|
{/* Logs */}
|
|
<div className="lg:col-span-2">
|
|
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
|
Recent Error Logs
|
|
</h3>
|
|
{serverStats.recentErrors.length === 0 && (
|
|
<p className="text-sm text-gray-500 italic">
|
|
No recent logs.
|
|
</p>
|
|
)}
|
|
{/* Placeholder for future logs list */}
|
|
{/* TODO: Replace with mapped log entries */}
|
|
<div className="mt-6">
|
|
<button
|
|
type="button"
|
|
className="inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-gray-50 hover:bg-gray-100 text-gray-700 text-sm font-medium px-4 py-3 transition"
|
|
// TODO: navigate to logs / monitoring page
|
|
onClick={() => {}}
|
|
>
|
|
View Full Logs
|
|
<ArrowRightIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
)
|
|
} |