Files
OpenFrame/app/admin/page.tsx
T

181 lines
8.0 KiB
TypeScript

import { Metadata } from 'next';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';
import { getCachedBunnyStorageStats, getCachedTotalStorage } from '@/lib/admin-stats';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Users, Folder, Video, MessageSquare, Mic, HardDrive, Image as ImageIcon, Film, MessageSquareQuote, Star } from 'lucide-react';
export const metadata: Metadata = {
title: 'Admin Dashboard | OpenFrame',
description: 'Admin overview dashboard',
};
function formatBytes(bytes: number, decimals = 2) {
if (bytes < 0) return 'Error Fetching';
if (!+bytes) return '0 Bytes';
const k = 1000;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
export default async function AdminDashboardPage() {
const session = await auth();
if (!session?.user?.isAdmin) {
redirect('/');
}
const userFeedbackDelegate = (db as unknown as {
userFeedback?: { count: (args?: unknown) => Promise<number> };
}).userFeedback;
// 1. Database Stats
const [
totalUsers,
totalProjects,
totalVideos,
totalComments,
totalVoiceComments,
totalImageComments,
] = await Promise.all([
db.user.count(),
db.project.count(),
db.video.count(),
db.comment.count(),
db.comment.count({
where: { voiceUrl: { not: null } },
}),
db.comment.count({
where: { imageUrl: { not: null } },
}),
]);
let totalFeedback = 0;
let totalReviews = 0;
if (userFeedbackDelegate) {
try {
[totalFeedback, totalReviews] = await Promise.all([
userFeedbackDelegate.count({
where: { type: 'FEEDBACK' },
}),
userFeedbackDelegate.count({
where: { type: 'REVIEW' },
}),
]);
} catch (error) {
console.error('Failed to fetch feedback stats:', error);
}
}
// 2. Storage Stats (Cached)
const [totalStorageBytes, bunnyStorageStats] = await Promise.all([
getCachedTotalStorage(),
getCachedBunnyStorageStats(),
]);
return (
<div className="flex-1 space-y-4 px-4 md:px-8">
<div className="flex items-center justify-between space-y-2">
<h2 className="text-3xl font-bold tracking-tight">Dashboard Overview</h2>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Users</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalUsers}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Workspaces & Projects</CardTitle>
<Folder className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalProjects}</div>
<p className="text-xs text-muted-foreground">
Total active projects on the platform
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Videos</CardTitle>
<Video className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalVideos}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Comments</CardTitle>
<MessageSquare className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalComments}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Voice Recordings</CardTitle>
<Mic className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalVoiceComments}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Image Attachments</CardTitle>
<ImageIcon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalImageComments}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Feedback Submissions</CardTitle>
<MessageSquareQuote className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalFeedback}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Review Submissions</CardTitle>
<Star className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{totalReviews}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Cloudflare R2 Storage</CardTitle>
<HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{formatBytes(totalStorageBytes)}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Bunny Stream Storage</CardTitle>
<Film className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{formatBytes(bunnyStorageStats.totalBytes)}</div>
</CardContent>
</Card>
</div>
</div>
);
}