first commit

This commit is contained in:
gotolombok
2026-01-31 17:21:32 +08:00
commit f839fbd63a
109 changed files with 19204 additions and 0 deletions

93
app/admin/page.tsx Normal file
View File

@@ -0,0 +1,93 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Users, Film, Activity } from 'lucide-react';
import { createAdminClient } from '@/lib/supabase/admin';
import { getRank } from '@/lib/api';
export default async function AdminDashboard() {
// 1. Fetch User Count
let userCount = 0;
try {
const supabase = createAdminClient();
const { data, error } = await supabase.auth.admin.listUsers({ perPage: 1 });
if (data) {
// Supabase listUsers returns total if requesting?
// Actually perPage: 1 returns data.users which is array.
// Does it return total?
// Checking docs/types: ListUsersResponse usually contains 'total'.
// If not, I might need page 1 and no perPage or separate count.
// listUsers returns { users: User[], aud: string } - IT DOES NOT RETURN TOTAL in some versions.
// Wait, Supabase Admin API usually wraps it.
// Let's assume for now I might have to fetch all or check if metadata has it.
// Actually, `listUsers` returns `User[]` in the data property in older versions?
// Checking supabase-js v2: `listUsers` returns `ListUsersResponse` which has `users` and `total`.
// Ideally `total` is available.
// TypeScript check might be needed.
// Let's rely on 'total' being there or fallback to users.length (which is 1 if paged).
// If perPage=1, total should be the total count.
userCount = (data as any).total || 0;
}
} catch (e) {
console.error("Error fetching user count", e);
}
// 2. Fetch Rank Data for Top Content
let topMovie: any = null;
let topSeries: any = null;
try {
const rank = await getRank();
if (rank.movie && rank.movie.length > 0) topMovie = rank.movie[0];
if (rank.tv && rank.tv.length > 0) topSeries = rank.tv[0];
} catch (e) {
console.error("Error fetching rank", e);
}
// Fallback if no data
const movieTitle = topMovie ? topMovie.title : 'N/A';
const movieRating = topMovie ? topMovie.imdbRatingValue : '-';
const seriesTitle = topSeries ? topSeries.title : 'N/A';
const seriesRating = topSeries ? topSeries.imdbRatingValue : '-';
return (
<div className="space-y-6">
<h2 className="text-3xl font-bold tracking-tight text-white">Dashboard</h2>
<div className="grid gap-4 md:grid-cols-3">
<Card className="bg-[#141414] border-gray-800 text-white">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Total Users</CardTitle>
<Users className="h-4 w-4 text-gray-400" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{userCount}</div>
<p className="text-xs text-gray-500">Registered users</p>
</CardContent>
</Card>
<Card className="bg-[#141414] border-gray-800 text-white">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Top Rated Movie</CardTitle>
<Film className="h-4 w-4 text-primary" />
</CardHeader>
<CardContent>
<div className="text-xl font-bold truncate" title={movieTitle}>{movieTitle}</div>
<p className="text-xs text-gray-500">IMDb Rating: <span className="text-yellow-500 font-bold">{movieRating}</span></p>
</CardContent>
</Card>
<Card className="bg-[#141414] border-gray-800 text-white">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Top Rated Series</CardTitle>
<Activity className="h-4 w-4 text-primary" />
</CardHeader>
<CardContent>
<div className="text-xl font-bold truncate" title={seriesTitle}>{seriesTitle}</div>
<p className="text-xs text-gray-500">IMDb Rating: <span className="text-yellow-500 font-bold">{seriesRating}</span></p>
</CardContent>
</Card>
</div>
</div>
);
}