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:
co-authored by
Claude Opus 4.8
parent
969d5d4c8a
commit
c9968531e4
@@ -0,0 +1,115 @@
|
||||
import Link from "next/link"
|
||||
import { LEGAL, LEGAL_PAGES } from "@/lib/legal"
|
||||
|
||||
/**
|
||||
* Shared shell for every legal page. Renders a consistent header (title,
|
||||
* effective/updated dates), the body, and a cross-link nav to sibling policies.
|
||||
* `pt-32` clears the fixed marketing navbar.
|
||||
*/
|
||||
export function LegalPage({
|
||||
title,
|
||||
subtitle,
|
||||
updated,
|
||||
children,
|
||||
}: {
|
||||
title: string
|
||||
subtitle?: string
|
||||
/** Optional per-page override; defaults to the global LEGAL.lastUpdated. */
|
||||
updated?: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-[#09090b] text-white min-h-screen">
|
||||
<div className="mx-auto max-w-3xl px-6 pt-32 pb-24">
|
||||
<nav className="mb-8 text-xs text-white/30">
|
||||
<Link href="/" className="hover:text-white/60 transition">Home</Link>
|
||||
<span className="mx-1.5">/</span>
|
||||
<span className="text-white/50">{title}</span>
|
||||
</nav>
|
||||
|
||||
<header className="mb-10 border-b border-white/[0.06] pb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-3">{title}</h1>
|
||||
{subtitle && <p className="text-sm text-white/50 leading-relaxed">{subtitle}</p>}
|
||||
<p className="mt-4 text-xs text-white/30">
|
||||
Effective date: {LEGAL.effectiveDate} · Last updated: {updated ?? LEGAL.lastUpdated}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="space-y-9">{children}</div>
|
||||
|
||||
<PolicyNav current={title} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** A titled section. Pass plain text or JSX children (paragraphs, lists, etc.). */
|
||||
export function Section({
|
||||
id,
|
||||
heading,
|
||||
children,
|
||||
}: {
|
||||
id?: string
|
||||
heading: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<section id={id} className="scroll-mt-32">
|
||||
<h2 className="text-lg font-semibold text-white mb-3">{heading}</h2>
|
||||
<div className="space-y-3 text-sm text-white/50 leading-relaxed [&_a]:text-indigo-300 [&_a:hover]:text-indigo-200 [&_strong]:text-white/80 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:space-y-1.5 [&_li]:marker:text-white/30">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/** Highlighted note (e.g. a disclaimer or important caveat). */
|
||||
export function Callout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="rounded-xl border border-amber-500/20 bg-amber-500/[0.06] px-4 py-3.5 text-sm text-amber-100/80 leading-relaxed">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Standard "questions / contact us" block for the bottom of a policy. */
|
||||
export function LegalContact({
|
||||
email = LEGAL.contactEmail,
|
||||
children,
|
||||
}: {
|
||||
email?: string
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Section heading="Contact us">
|
||||
<p>
|
||||
{children ?? "If you have questions about this policy, contact us at "}
|
||||
<a href={`mailto:${email}`}>{email}</a>.
|
||||
</p>
|
||||
<p className="text-white/30 text-xs">
|
||||
{LEGAL.entity}
|
||||
{LEGAL.address && !LEGAL.address.startsWith("[") ? ` · ${LEGAL.address}` : ""}
|
||||
</p>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
/** Cross-links to the other legal documents. */
|
||||
function PolicyNav({ current }: { current: string }) {
|
||||
return (
|
||||
<nav className="mt-14 border-t border-white/[0.06] pt-8">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-white/30 mb-4">
|
||||
More policies
|
||||
</p>
|
||||
<ul className="grid grid-cols-2 gap-x-6 gap-y-2.5 sm:grid-cols-3">
|
||||
{LEGAL_PAGES.filter((p) => p.label !== current).map((p) => (
|
||||
<li key={p.href}>
|
||||
<Link href={p.href} className="text-sm text-white/50 hover:text-white transition">
|
||||
{p.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user