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
+36
View File
@@ -0,0 +1,36 @@
/* Leaflet map styling for the dark app shell. */
/* Our inline-SVG pin uses a divIcon; strip Leaflet's default white box. */
.leaflet-div-icon.pmn-map-pin {
background: transparent;
border: 0;
}
/* OSM tiles are light — tone them to match the dark UI. Only the tile pane is
filtered, so markers, popups and controls keep their real colors. */
.pmn-map-dark .leaflet-tile-pane {
filter: invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9);
}
/* Popups on the dark surface. */
.pmn-map-dark .leaflet-popup-content-wrapper {
background: #16161f;
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.75rem;
}
.pmn-map-dark .leaflet-popup-tip {
background: #16161f;
}
.pmn-map-dark .leaflet-popup-content a {
color: #818cf8;
}
/* Attribution + controls readability on dark. */
.pmn-map-dark .leaflet-control-attribution {
background: rgba(9, 9, 11, 0.7);
color: rgba(255, 255, 255, 0.4);
}
.pmn-map-dark .leaflet-control-attribution a {
color: rgba(255, 255, 255, 0.6);
}
+103
View File
@@ -0,0 +1,103 @@
"use client"
import { useEffect, useRef } from "react"
import type * as Leaflet from "leaflet"
import "leaflet/dist/leaflet.css"
import "./property-map.css"
export type MapMarker = {
id: string
name: string
lat: number
lng: number
subtitle?: string
href?: string
}
// Indigo pin as an inline SVG divIcon — avoids Leaflet's default marker image
// assets (which break under bundlers) and matches the app accent.
const PIN_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="28" height="28" fill="#6366f1" stroke="#0b0b12" stroke-width="1.2"><path d="M12 0.5C6.9 0.5 2.8 4.6 2.8 9.7c0 6.6 8.2 13.9 8.6 14.2a0.9 0.9 0 0 0 1.2 0c0.4-0.3 8.6-7.6 8.6-14.2C21.2 4.6 17.1 0.5 12 0.5z"/><circle cx="12" cy="9.7" r="3.3" fill="#0b0b12"/></svg>`
function escapeHtml(s: string): string {
return s.replace(/[&<>"']/g, (c) =>
({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c] as string
)
}
/**
* Leaflet map (OpenStreetMap tiles) rendering property markers. Client-only:
* Leaflet touches `window`, so it's dynamically imported inside an effect.
*/
export function PropertyMap({
markers,
className = "h-72 w-full",
zoom = 15,
}: {
markers: MapMarker[]
className?: string
zoom?: number
}) {
const containerRef = useRef<HTMLDivElement>(null)
const mapRef = useRef<Leaflet.Map | null>(null)
useEffect(() => {
let cancelled = false
void (async () => {
const mod = await import("leaflet")
const L = ((mod as unknown as { default?: typeof Leaflet }).default ?? mod) as typeof Leaflet
if (cancelled || !containerRef.current || mapRef.current) return
const map = L.map(containerRef.current, {
scrollWheelZoom: false,
zoomControl: true,
})
mapRef.current = map
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 19,
}).addTo(map)
const icon = L.divIcon({
html: PIN_SVG,
className: "pmn-map-pin",
iconSize: [28, 28],
iconAnchor: [14, 28],
popupAnchor: [0, -26],
})
const latlngs: [number, number][] = []
for (const m of markers) {
if (!Number.isFinite(m.lat) || !Number.isFinite(m.lng)) continue
const marker = L.marker([m.lat, m.lng], { icon }).addTo(map)
const link = m.href
? `<div style="margin-top:4px"><a href="${escapeHtml(m.href)}">View details →</a></div>`
: ""
const sub = m.subtitle ? `<div style="color:#9ca3af">${escapeHtml(m.subtitle)}</div>` : ""
marker.bindPopup(`<div style="font-weight:600">${escapeHtml(m.name)}</div>${sub}${link}`)
latlngs.push([m.lat, m.lng])
}
if (latlngs.length === 1) {
map.setView(latlngs[0], zoom)
} else if (latlngs.length > 1) {
map.fitBounds(latlngs, { padding: [40, 40] })
} else {
map.setView([39.8283, -98.5795], 4) // continental US fallback
}
// Tiles can render blank if the container sized after init.
setTimeout(() => map.invalidateSize(), 0)
})()
return () => {
cancelled = true
mapRef.current?.remove()
mapRef.current = null
}
}, [markers, zoom])
return <div ref={containerRef} className={`pmn-map-dark ${className}`} />
}