Files
Leon SerfatyandClaude Opus 4.8 857b9a7811 Initial import: property management SaaS + security hardening + admin dashboard
Property Management Network — Next.js 16 (App Router), Better Auth,
Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend.

Includes:
- Security hardening: access-control/IDOR fixes, TLS-by-default DB layer,
  constant-time cron auth, strict security headers, atomic AI quota gating,
  HTML/email output encoding, demo-backdoor disabled in production.
- Superadmin dashboard at /admin (overview/MRR, server-paginated users with
  ban/impersonate/plan/delete, billing, platform activity + admin audit log,
  AI usage, system health) via the Better Auth admin plugin.
- Seed/migration utility scripts under scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:36:07 -04:00

143 lines
4.7 KiB
TypeScript

import {
getAdminOverviewStats,
getSignupsTrend,
getAtRiskSubscriptions,
} from "@/lib/db/admin-queries"
import { StatsCard } from "@/components/dashboard/stats-card"
import { PlanDonut, SignupsBars } from "@/components/admin/admin-charts"
import { getPlanLabel } from "@/lib/stripe/plans"
import { formatCurrency } from "@/lib/utils"
import type { Plan } from "@/types"
import {
DollarSign, TrendingUp, Users, Activity, CreditCard,
UserPlus, Building2, Home, Banknote, Brain, AlertTriangle,
} from "lucide-react"
export const dynamic = "force-dynamic"
const STATUS_LABELS: Record<string, string> = {
past_due: "Past due",
unpaid: "Unpaid",
incomplete: "Incomplete",
}
export default async function AdminOverviewPage() {
const [stats, signupsTrend, atRisk] = await Promise.all([
getAdminOverviewStats(),
getSignupsTrend(6),
getAtRiskSubscriptions(),
])
return (
<div className="min-h-full">
{/* Heading */}
<div className="mb-6">
<h1 className="text-xl font-bold text-white">Platform Overview</h1>
<p className="text-sm text-white/40 mt-0.5">Key metrics across all accounts</p>
</div>
{/* KPI grid */}
<div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-3 sm:gap-4 mb-6">
<StatsCard
label="MRR"
value={formatCurrency(stats.mrr)}
sub="Recurring monthly"
icon={DollarSign}
variant="success"
/>
<StatsCard
label="ARR"
value={formatCurrency(stats.arr)}
sub="Annualized run-rate"
icon={TrendingUp}
variant="success"
/>
<StatsCard
label="Total Users"
value={stats.totalUsers}
sub={`${stats.paidUsers} paid · ${stats.freeUsers} free`}
icon={Users}
/>
<StatsCard
label="Active (30d)"
value={stats.activeUsers30d}
sub="Recently active"
icon={Activity}
/>
<StatsCard
label="Paid Users"
value={stats.paidUsers}
sub={`${stats.freeUsers} free`}
icon={CreditCard}
variant="success"
/>
<StatsCard
label="New Signups"
value={stats.newSignupsThisMonth}
sub="This month"
icon={UserPlus}
/>
<StatsCard
label="Total Properties"
value={stats.totalProperties}
sub={`${stats.totalUnits} unit${stats.totalUnits !== 1 ? "s" : ""}`}
icon={Building2}
/>
<StatsCard
label="Total Tenants"
value={stats.totalTenants}
sub="Across platform"
icon={Home}
/>
<StatsCard
label="Rent Collected"
value={formatCurrency(stats.rentCollectedThisMonth)}
sub="This month"
icon={Banknote}
variant="success"
/>
<StatsCard
label="AI Calls"
value={stats.aiCallsThisMonth}
sub="This month"
icon={Brain}
/>
</div>
{/* Charts row */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-5 mb-6">
<PlanDonut data={stats.planDistribution} />
<SignupsBars data={signupsTrend} />
</div>
{/* At-risk subscriptions */}
{atRisk.length > 0 && (
<div className="rounded-2xl border border-white/[0.06] bg-[#16161f] overflow-hidden">
<div className="flex items-center gap-2 border-b border-white/[0.06] px-5 py-4">
<AlertTriangle className="h-4 w-4 text-rose-400 shrink-0" />
<h2 className="text-sm font-semibold text-white">At-risk subscriptions</h2>
<span className="flex h-5 min-w-5 items-center justify-center rounded-full bg-rose-500/20 px-1.5 text-[10px] font-bold text-rose-400">
{atRisk.length}
</span>
</div>
<div className="divide-y divide-white/[0.04]">
{atRisk.slice(0, 8).map((s) => (
<div key={s.id} className="flex items-center justify-between px-5 py-3 hover:bg-white/[0.02] transition-colors">
<div className="min-w-0">
<p className="text-sm font-medium text-white truncate">{s.email}</p>
<p className="text-xs text-white/40 truncate">
{s.full_name || "—"} · {getPlanLabel(s.plan as Plan)}
</p>
</div>
<span className="ml-3 shrink-0 rounded-full border border-rose-500/20 bg-rose-500/10 px-2.5 py-1 text-[10px] font-semibold text-rose-400">
{STATUS_LABELS[s.subscription_status ?? ""] ?? s.subscription_status ?? "Unknown"}
</span>
</div>
))}
</div>
</div>
)}
</div>
)
}