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>
This commit is contained in:
Leon Serfaty
2026-06-23 20:36:07 -04:00
co-authored by Claude Opus 4.8
commit 857b9a7811
291 changed files with 38996 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
"use client"
import { formatCurrency } from "@/lib/utils"
interface MonthData {
month: string
label: string
amount: number
}
interface RevenueChartProps {
data: MonthData[]
thisMonth: number
pending: number
}
export function RevenueChart({ data, thisMonth, pending }: RevenueChartProps) {
const max = Math.max(...data.map((d) => d.amount), 1)
const total = data.reduce((sum, d) => sum + d.amount, 0)
const currentMonthKey = new Date().toISOString().slice(0, 7)
const lastEntry = data[data.length - 1]
return (
<div className="rounded-2xl border border-white/[0.06] bg-[#16161f] p-5 h-full">
{/* Header */}
<div className="flex items-start justify-between mb-6">
<div>
<p className="text-xs font-medium uppercase tracking-wider text-white/40 mb-1">Revenue Overview</p>
<p className="text-2xl font-bold text-white tabular-nums">{formatCurrency(thisMonth)}</p>
<p className="text-xs text-white/40 mt-0.5">
Collected this month
{pending > 0 && <span className="text-amber-400 ml-1.5">· {formatCurrency(pending)} pending</span>}
</p>
</div>
<div className="flex items-center gap-4 text-xs text-white/40">
<div className="flex items-center gap-1.5">
<div className="h-2 w-2 rounded-full bg-indigo-500" />
Collected
</div>
</div>
</div>
{/* Bar chart */}
<div className="flex items-end justify-between gap-2 h-28">
{data.map((d) => {
const isCurrentMonth = d.month === currentMonthKey
const heightPct = max > 0 ? (d.amount / max) * 100 : 0
const minHeight = d.amount > 0 ? 8 : 4
return (
<div key={d.month} className="group flex flex-col items-center gap-1.5 flex-1">
{/* Tooltip */}
<div className="opacity-0 group-hover:opacity-100 transition-opacity text-[10px] text-white/70 font-medium whitespace-nowrap -mt-6 absolute">
{formatCurrency(d.amount)}
</div>
{/* Bar */}
<div className="relative w-full flex items-end" style={{ height: "100px" }}>
<div
className={`w-full rounded-t-lg transition-all duration-500 ${
isCurrentMonth
? "bg-gradient-to-t from-indigo-600 to-indigo-400"
: d.amount > 0
? "bg-white/[0.12] group-hover:bg-white/[0.2]"
: "bg-white/[0.04]"
}`}
style={{ height: `${Math.max(heightPct, minHeight)}%` }}
/>
</div>
{/* Label */}
<span className={`text-[10px] font-medium ${isCurrentMonth ? "text-indigo-400" : "text-white/30"}`}>
{d.label}
</span>
</div>
)
})}
</div>
{/* Summary row */}
<div className="mt-5 pt-4 border-t border-white/[0.06] grid grid-cols-3 gap-4">
<div>
<p className="text-[10px] text-white/30 uppercase tracking-wider mb-1">6-mo total</p>
<p className="text-sm font-bold text-white tabular-nums">{formatCurrency(total)}</p>
</div>
<div>
<p className="text-[10px] text-white/30 uppercase tracking-wider mb-1">Monthly avg</p>
<p className="text-sm font-bold text-white tabular-nums">{formatCurrency(Math.round(total / 6))}</p>
</div>
<div>
<p className="text-[10px] text-white/30 uppercase tracking-wider mb-1">Best month</p>
<p className="text-sm font-bold text-white tabular-nums">{formatCurrency(max === 1 ? 0 : max)}</p>
</div>
</div>
</div>
)
}