feat(sales): add /contact-sales form + landing page CTA

Public Talk-to-Sales surface and a "See pricing" hero CTA on the marketing
landing page. Phase 2 Task 43 of self-serve signup.

- frontend/src/api/sales.ts: salesApi.createLead -> POST /sales-leads.
- ContactSalesPage at /contact-sales (public, gated by self_serve_enabled
  with a 404-style fallback). Form fields: name, work email, company,
  team size (1-2 / 3-5 / 6-10 / 11-25 / 26+), and an optional
  "what brought you here?" textarea -> message. Submit button disabled
  while in flight to block duplicate submissions.
- Confirmation surface replaces the form on success. Calendly block is
  hidden when VITE_CALENDLY_URL is unset.
- detectSource(): 'pricing_page' if document.referrer contains '/pricing',
  else 'landing_page'. Server emits the canonical PostHog
  talk_to_sales_form_submitted event with this source.
- LandingPage: new "See pricing" hero CTA gated by useAppConfig().
  self_serve_enabled.
- frontend/.env.example + Dockerfile: VITE_CALENDLY_URL ARG/ENV.
- Tests: ContactSalesPage submit/confirmation, Calendly hide-when-unset,
  in-flight de-dup, 404 when self-serve off; LandingPage CTA on/off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 23:31:56 -04:00
parent 67fae91087
commit db2478dd89
9 changed files with 673 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { Link } from 'react-router-dom'
import { PageMeta } from '@/components/common/PageMeta'
import { useAppConfig } from '@/hooks/useAppConfig'
import '@/styles/landing.css'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
@@ -29,6 +30,7 @@ const FAQ_ITEMS = [
]
export default function LandingPage() {
const appConfig = useAppConfig()
const [navScrolled, setNavScrolled] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [betaEmail, setBetaEmail] = useState('')
@@ -174,6 +176,15 @@ export default function LandingPage() {
</p>
<div className="landing-hero-actions">
<Link to="/register" className="landing-btn-hero-primary">Start Free</Link>
{appConfig.self_serve_enabled && (
<Link
to="/pricing"
className="landing-btn-hero-secondary"
data-testid="landing-see-pricing"
>
See pricing
</Link>
)}
<a href="#how-it-works" className="landing-btn-hero-secondary">See How It Works</a>
</div>
<p className="landing-hero-credibility">