Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening

Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-07-02 13:42:34 -04:00
co-authored by Claude Opus 4.8
parent 969d5d4c8a
commit c9968531e4
282 changed files with 41530 additions and 4013 deletions
+29 -71
View File
@@ -1,54 +1,39 @@
"use client"
import { motion } from "framer-motion"
import {
CreditCard, Mail, Cloud, Zap, BookOpen, MessageSquare,
BarChart3, Archive, FileText, Smartphone, Lock, RefreshCw,
} from "lucide-react"
import { CreditCard, LogIn } 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" },
// Only integrations that genuinely exist in the product are listed here.
// - Stripe: real payment processing (checkout + rent collection).
// - Google: real sign-in via OAuth (Better Auth Google provider).
// We deliberately do NOT advertise connectors we haven't built.
const TOOLS = [
{ name: "Stripe", icon: CreditCard, color: "text-violet-400", bg: "bg-violet-500/10 border-violet-500/20", desc: "Payments & rent collection" },
{ name: "Google", icon: LogIn, color: "text-blue-400", bg: "bg-blue-500/10 border-blue-500/20", desc: "Sign in with Google (OAuth)" },
]
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]) {
function ToolBadge({ name, icon: Icon, color, bg, desc }: typeof TOOLS[0]) {
return (
<motion.div
whileHover={{ scale: 1.05, y: -2 }}
whileHover={{ scale: 1.03, 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`}
className={`flex items-center gap-3 rounded-xl border px-5 py-4 ${bg} cursor-default`}
>
<div className={`flex h-8 w-8 items-center justify-center rounded-lg bg-black/20`}>
<Icon className={`h-4 w-4 ${color}`} />
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-black/20">
<Icon className={`h-5 w-5 ${color}`} />
</div>
<div>
<p className="text-xs font-semibold text-white">{name}</p>
<p className="text-[10px] text-white/40">{desc}</p>
<p className="text-sm font-semibold text-white">{name}</p>
<p className="text-xs 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">
<div className="mx-auto max-w-3xl px-4 sm:px-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@@ -56,50 +41,23 @@ export function Integrations() {
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>
<h2 className="text-3xl font-bold text-white sm:text-4xl">
Works with the 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.
Take rent payments with Stripe and let landlords sign in with Google no extra setup, no fragile connectors.
</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mt-10 grid gap-4 sm:grid-cols-2 max-w-xl mx-auto"
>
{TOOLS.map((t) => <ToolBadge key={t.name} {...t} />)}
</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>
)
}