// apps/admin/src/app/(admin)/users/page.tsx // D3RO Console — Users list import { Box } from '@mui/material' import { C, FONT, panelSx, tableSx, filterBtnSx, statusBadgeSx } from '@/lib/console-theme' import { getSupabaseServerClient } from '@/lib/supabase-server' import Link from 'next/link' const PAGE_SIZE = 20 interface UserRow { id: string name: string | null tier: string role: string created_at: string subscription_status: string | null payment_provider: string | null } async function loadUsers(page: number, search: string, tierFilter: string): Promise<{ users: UserRow[]; total: number }> { const supabase = await getSupabaseServerClient() const from = page * PAGE_SIZE const to = from + PAGE_SIZE - 1 const profileQuery = supabase .from('profiles') .select('id, name, tier, created_at', { count: 'exact' }) .order('created_at', { ascending: false }) .range(from, to) if (search) profileQuery.ilike('name', `%${search}%`) if (tierFilter && tierFilter !== 'all') profileQuery.eq('tier', tierFilter as 'free' | 'pro' | 'pro_plus') const { data: rawProfiles, count } = await profileQuery const profiles = (rawProfiles ?? []) as Array> const userIds = profiles.map((p) => p.id as string) const { data: rawSubs } = userIds.length > 0 ? await supabase.from('subscriptions').select('user_id, status, payment_provider').in('user_id', userIds) : { data: [] } const subMap = new Map( ((rawSubs ?? []) as Array>).map((s) => [s.user_id as string, s]) ) const { data: rawRoles } = userIds.length > 0 ? await supabase.from('profiles').select('id, role').in('id', userIds) : { data: [] } const roleMap = new Map( ((rawRoles ?? []) as Array>).map((r) => [r.id as string, (r.role as string) ?? 'user']) ) const users: UserRow[] = profiles.map((row) => { const sub = subMap.get(row.id as string) return { id: row.id as string, name: row.name as string | null, tier: (row.tier as string) ?? 'free', role: roleMap.get(row.id as string) ?? 'user', created_at: row.created_at as string, subscription_status: (sub?.status as string) ?? null, payment_provider: (sub?.payment_provider as string) ?? null, } }) return { users, total: count ?? 0 } } interface PageProps { searchParams: Promise<{ page?: string; search?: string; tier?: string }> } export default async function AdminUsersPage({ searchParams }: PageProps): Promise { const params = await searchParams const page = parseInt(params.page ?? '0', 10) const search = params.search ?? '' const tierFilter = params.tier ?? 'all' const { users, total } = await loadUsers(page, search, tierFilter) const totalPages = Math.ceil(total / PAGE_SIZE) const roleColor = (r: string): string => r === 'super_admin' ? C.purple400 : r === 'admin' ? C.green400 : r === 'manager' ? C.orange400 : C.dim return ( <> {/* Header */} Users {total} TOTAL {['all', 'free', 'pro', 'pro_plus'].map((t) => ( {t === 'all' ? 'ALL' : t === 'pro_plus' ? 'PRO+' : t.toUpperCase()} ))} {/* Table */} Name Tier Role Status Provider Joined {users.map((u) => ( {u.name ?? u.id.substring(0, 8)} {u.tier === 'pro_plus' ? 'PRO+' : u.tier.toUpperCase()} {u.role.toUpperCase()} {u.subscription_status ? ( {u.subscription_status.toUpperCase()} ) : ( - )} {u.payment_provider ?? '-'} {new Date(u.created_at).toLocaleDateString()} ))} {users.length === 0 && ( No users found )} {/* Pagination */} {totalPages > 1 && ( {Array.from({ length: Math.min(totalPages, 10) }, (_, i) => ( {i + 1} ))} )} ) }