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
+105
View File
@@ -0,0 +1,105 @@
"use client"
import { motion } from "framer-motion"
import {
CreditCard, Mail, Cloud, Zap, BookOpen, MessageSquare,
BarChart3, Archive, FileText, Smartphone, Lock, RefreshCw,
} from "lucide-react"
const TOOLS_ROW1 = [
{ name: "Stripe", icon: CreditCard, color: "text-violet-400", bg: "bg-violet-500/10 border-violet-500/20", desc: "Payments" },
{ name: "Gmail", icon: Mail, color: "text-red-400", bg: "bg-red-500/10 border-red-500/20", desc: "Email" },
{ name: "Google Drive", icon: Cloud, color: "text-blue-400", bg: "bg-blue-500/10 border-blue-500/20", desc: "Storage" },
{ name: "Zapier", icon: Zap, color: "text-orange-400", bg: "bg-orange-500/10 border-orange-500/20", desc: "Automation" },
{ name: "Xero", icon: BarChart3, color: "text-cyan-400", bg: "bg-cyan-500/10 border-cyan-500/20", desc: "Accounting" },
{ name: "Dropbox", icon: Archive, color: "text-blue-300", bg: "bg-blue-400/10 border-blue-400/20", desc: "Documents" },
]
const TOOLS_ROW2 = [
{ name: "Slack", icon: MessageSquare, color: "text-emerald-400", bg: "bg-emerald-500/10 border-emerald-500/20", desc: "Notifications" },
{ name: "Notion", icon: FileText, color: "text-white/70", bg: "bg-white/5 border-white/10", desc: "Notes" },
{ name: "QuickBooks", icon: BookOpen, color: "text-green-400", bg: "bg-green-500/10 border-green-500/20", desc: "Accounting" },
{ name: "WhatsApp", icon: Smartphone, color: "text-emerald-300", bg: "bg-emerald-400/10 border-emerald-400/20", desc: "Reminders" },
{ name: "2FA / Auth", icon: Lock, color: "text-indigo-400", bg: "bg-indigo-500/10 border-indigo-500/20", desc: "Security" },
{ name: "Auto-sync", icon: RefreshCw, color: "text-amber-400", bg: "bg-amber-500/10 border-amber-500/20", desc: "Sync" },
]
function ToolBadge({ name, icon: Icon, color, bg, desc }: typeof TOOLS_ROW1[0]) {
return (
<motion.div
whileHover={{ scale: 1.05, y: -2 }}
transition={{ type: "spring", stiffness: 300 }}
className={`flex shrink-0 items-center gap-3 rounded-xl border px-4 py-3 ${bg} cursor-default mx-3`}
>
<div className={`flex h-8 w-8 items-center justify-center rounded-lg bg-black/20`}>
<Icon className={`h-4 w-4 ${color}`} />
</div>
<div>
<p className="text-xs font-semibold text-white">{name}</p>
<p className="text-[10px] text-white/40">{desc}</p>
</div>
</motion.div>
)
}
export function Integrations() {
const doubled1 = [...TOOLS_ROW1, ...TOOLS_ROW1]
const doubled2 = [...TOOLS_ROW2, ...TOOLS_ROW2]
return (
<section className="py-14 sm:py-20 overflow-hidden">
<div className="mx-auto max-w-7xl px-4 sm:px-6 mb-10 sm:mb-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center"
>
<p className="text-xs font-semibold uppercase tracking-widest text-indigo-400 mb-3">Integrations</p>
<h2 className="text-3xl font-bold text-white sm:text-4xl">Works with tools you already use</h2>
<p className="mt-4 text-white/50 max-w-xl mx-auto">
Property Management Network connects with your existing stack from payments to accounting to communication.
</p>
</motion.div>
</div>
{/* Marquee rows */}
<div className="space-y-4">
{/* Row 1 — left to right */}
<div className="relative flex overflow-hidden">
<div className="pointer-events-none absolute left-0 top-0 z-10 h-full w-32 bg-gradient-to-r from-[#09090b] to-transparent" />
<div className="pointer-events-none absolute right-0 top-0 z-10 h-full w-32 bg-gradient-to-l from-[#09090b] to-transparent" />
<motion.div
animate={{ x: ["0%", "-50%"] }}
transition={{ duration: 22, ease: "linear", repeat: Infinity }}
className="flex"
>
{doubled1.map((t, i) => <ToolBadge key={i} {...t} />)}
</motion.div>
</div>
{/* Row 2 — right to left */}
<div className="relative flex overflow-hidden">
<div className="pointer-events-none absolute left-0 top-0 z-10 h-full w-32 bg-gradient-to-r from-[#09090b] to-transparent" />
<div className="pointer-events-none absolute right-0 top-0 z-10 h-full w-32 bg-gradient-to-l from-[#09090b] to-transparent" />
<motion.div
animate={{ x: ["-50%", "0%"] }}
transition={{ duration: 25, ease: "linear", repeat: Infinity }}
className="flex"
>
{doubled2.map((t, i) => <ToolBadge key={i} {...t} />)}
</motion.div>
</div>
</div>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="mt-8 text-center text-xs text-white/30"
>
+ more integrations via Zapier webhooks
</motion.p>
</section>
)
}