export const dynamic = 'force-dynamic'; import { createClient } from '@/lib/supabase/server'; import { redirect } from 'next/navigation'; import { checkIsAdmin, getGlobalApiStats } from '@/services/track-api-usage'; import { BarChart3, TrendingUp, Users, Calendar, AlertCircle } from 'lucide-react'; import Link from 'next/link'; export default async function AdminPage() { const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); console.log('[Admin Page] User:', user?.id, user?.email); if (!user) { console.log('[Admin Page] No user found, redirecting to home'); redirect('/'); } const isAdmin = await checkIsAdmin(user.id); console.log('[Admin Page] Is admin check result:', isAdmin); if (!isAdmin) { console.log('[Admin Page] User is not admin, redirecting to home'); redirect('/'); } console.log('[Admin Page] Access granted, loading dashboard'); // Fetch global API stats const stats = await getGlobalApiStats(); // Fetch recent API usage without join to avoid relationship errors console.log('[Admin Page] Fetching recent API usage...'); const { data: recentUsageRaw, error: recentError } = await supabase .from('api_usage') .select('*') .order('created_at', { ascending: false }) .limit(50); console.log('[Admin Page] Recent usage raw - count:', recentUsageRaw?.length, 'error:', recentError); // Get unique user IDs from recent usage const recentUserIds = Array.from(new Set(recentUsageRaw?.map(u => u.user_id) || [])); // Fetch profiles for these users const { data: recentProfiles } = recentUserIds.length > 0 ? await supabase.from('profiles').select('id, username').in('id', recentUserIds) : { data: [] }; // Combine usage with profiles const recentUsage = recentUsageRaw?.map(usage => ({ ...usage, profiles: recentProfiles?.find(p => p.id === usage.user_id) || { username: 'Unknown' } })) || []; // Fetch per-user statistics const { data: userStatsRaw } = await supabase .from('api_usage') .select('user_id, api_type'); // Group by user const userStatsMap = new Map(); userStatsRaw?.forEach(item => { const current = userStatsMap.get(item.user_id) || { googleSearch: 0, geminiAi: 0, total: 0 }; if (item.api_type === 'google_search') current.googleSearch++; if (item.api_type === 'gemini_ai') current.geminiAi++; current.total++; userStatsMap.set(item.user_id, current); }); // Get user details for top users const topUserIds = Array.from(userStatsMap.entries()) .sort((a, b) => b[1].total - a[1].total) .slice(0, 10) .map(([userId]) => userId); const { data: topUsers } = topUserIds.length > 0 ? await supabase.from('profiles').select('id, username').in('id', topUserIds) : { data: [] }; const topUsersWithStats = topUsers?.map(user => ({ ...user, stats: userStatsMap.get(user.id)! })) || []; return (
{/* Header */}

Admin Dashboard

API Usage Monitoring & Statistics

OCR Logs Manage Plans Manage Tags Manage Users Manage Banners All Bottles All Splits All Tastings Back to App
{/* Global Stats Cards */}
Total Calls
{stats?.totalCalls || 0}
All time
Today
{stats?.todayCalls || 0}
{stats?.todayCalls && stats.todayCalls >= 80 ? ( Limit reached ) : ( `${80 - (stats?.todayCalls || 0)} remaining` )}
Google Search
{stats?.googleSearchCalls || 0}
Whiskybase searches
Gemini AI
{stats?.geminiAiCalls || 0}
Bottle analyses
{/* Top Users */}

Top Users by API Usage

{topUsersWithStats.map((user, index) => (
{index + 1}
{user.username}
{user.stats.googleSearch} searches ยท {user.stats.geminiAi} analyses
{user.stats.total}
))}
{/* Recent API Calls */}

Recent API Calls

Total calls logged: {recentUsage?.length || 0}
{!recentUsage || recentUsage.length === 0 ? (

No API calls recorded yet

{recentError && (

Error: {recentError.message}

)}
) : (
{recentUsage.map((call: any) => ( ))}
Time User API/Provider Model Endpoint Status
{new Date(call.created_at).toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit', day: '2-digit', month: '2-digit' })} {call.profiles?.username || 'Unknown'}
{call.api_type === 'google_search' ? 'Google' : 'AI'} {call.provider && ( via {call.provider} )}
{call.model || '-'}
{call.endpoint}
{call.response_text && (
Response
                                                                {call.response_text}
                                                            
)}
{call.success ? ( OK ) : (
ERR {call.error_message && (
{call.error_message}
)}
)}
)}
); }