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>
106 lines
4.9 KiB
TypeScript
106 lines
4.9 KiB
TypeScript
"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>
|
|
)
|
|
}
|