From 949c91cf1354e5a62be34bdc9b63c38bc8851f2d Mon Sep 17 00:00:00 2001 From: abrichr Date: Sun, 26 Jul 2026 15:06:46 -0400 Subject: [PATCH] feat: contribute-for-credits program (landing + /contribute + pricing) [early access] Rebuilt on current main after the launch-focus close, addressing the closing objections within what is truthful today: - /contribute now carries an explicit Program status and eligibility section: the contribution path is versioned-terms-gated and off by default until a finalized terms version is active, eligibility requires the named-standard attestation, contributions are curated, and no customer or patient data has been collected through this program. - Contributor leads use a dedicated Netlify form (contributor-program) registered in public/form.html, posting over the durable /form.html lead path, segmented from the generic contact queue. - The landing section is re-homed after TrustSummary in the new homepage IA (commons/flywheel message in the trust cluster) so it does not interrupt the qualification funnel narrative. - The pricing callout is re-applied to the current Pricing component as the chosen Option A callout under the tiers. - Regression tests lock the binding framing: no sell/monetize-your-data language, no per-record price, early-access-only status, form-name parity between runtime submit and Netlify definition, and no em dashes. The Cloud mechanism (consent record, credit ledger, contribution rows) is merged and inert, unreachable until an operator sets CONTRIBUTION_CREDITS_ENABLED and a real CONTRIBUTION_TERMS_VERSION. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM --- components/ContributeSection.js | 79 ++++++ components/ContributorProgramForm.js | 225 +++++++++++++++++ components/Pricing.js | 31 +++ pages/contribute.js | 350 +++++++++++++++++++++++++++ pages/index.js | 11 + public/form.html | 12 + tests/contributeProgram.test.js | 105 ++++++++ 7 files changed, 813 insertions(+) create mode 100644 components/ContributeSection.js create mode 100644 components/ContributorProgramForm.js create mode 100644 pages/contribute.js create mode 100644 tests/contributeProgram.test.js diff --git a/components/ContributeSection.js b/components/ContributeSection.js new file mode 100644 index 00000000..cd30320a --- /dev/null +++ b/components/ContributeSection.js @@ -0,0 +1,79 @@ +import Link from 'next/link' + +// Contribute-for-credits landing SECTION. +// +// Placed after the trust summary (the open-source / trust cluster) because +// this is a commons / flywheel message, not a pricing gimmick: contributions +// strengthen the shared hardening corpus that lowers everyone's +// silent-wrong-effect rate. The full mechanism, expanded guarantees, program +// status, and FAQ live on /contribute. +// +// Framing is binding (see the data-for-credits framing brief): lead with the +// guarantees, never "sell/monetize your data", no per-record price. The +// program is EARLY ACCESS (gated on legal terms), so the copy is "request +// access", never "upload now / available today", and never claims any data has +// been collected. + +const GUARANTEES = [ + 'Sanitized, de-identified derivatives only. Raw recordings never leave your machine.', + 'You approve every byte through hash-bound local review before anything is shared.', + 'Opt-in and off by default. You can stop future contributions at any time.', + 'Every contribution must meet the named de-identification standard in the versioned terms, and your organization attests before sharing.', +] + +export default function ContributeSection() { + return ( +
+
+
+

Strengthen the commons

+ + Early access + +
+

+ Contribute data for credits +

+

+ Every sanitized contribution strengthens the shared + hardening corpus, the commons that lowers everyone's + silent-wrong-effect rate. In return, you earn run credits + that extend your usage allowance. +

+ +
    + {GUARANTEES.map((item) => ( +
  • + + {item} +
  • + ))} +
+ +
+ + Request access to the contributor program + + + Early access. Opt-in. Sanitized derivatives only. + +
+
+
+ ) +} diff --git a/components/ContributorProgramForm.js b/components/ContributorProgramForm.js new file mode 100644 index 00000000..b36fde11 --- /dev/null +++ b/components/ContributorProgramForm.js @@ -0,0 +1,225 @@ +import { useState } from 'react' +import Link from 'next/link' + +import { trackEmailCapture } from 'utils/conversion' + +// Dedicated intake for the contribute-for-credits program. +// +// Posts to the durable Netlify Forms lead path (`/form.html`) under its own +// form name, `contributor-program`, so contributor leads land in their own +// Netlify submissions bucket instead of the generic `contact` queue. The +// hidden form definition lives in public/form.html; Netlify's build-time bots +// read it from there. Registering interest shares only these contact fields; +// it never enables any upload or shares any workflow data. + +const INITIAL_FORM = { + name: '', + email: '', + company: '', + role: '', + workflows: '', + message: '', + botField: '', +} + +export default function ContributorProgramForm() { + const [form, setForm] = useState(INITIAL_FORM) + const [isSubmitting, setIsSubmitting] = useState(false) + const [isSubmitted, setIsSubmitted] = useState(false) + const [error, setError] = useState('') + + const handleChange = (event) => { + const { name, value } = event.target + setForm((prev) => ({ + ...prev, + [name]: value, + })) + } + + const handleSubmit = async (event) => { + event.preventDefault() + setError('') + setIsSubmitting(true) + + try { + const formData = new URLSearchParams() + formData.set('form-name', 'contributor-program') + formData.set('name', form.name) + formData.set('email', form.email) + formData.set('company', form.company) + formData.set('role', form.role) + formData.set('workflows', form.workflows) + formData.set('message', form.message) + formData.set('bot-field', form.botField) + + const response = await fetch('/form.html', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: formData.toString(), + }) + + if (!response.ok) { + throw new Error( + `Form submission failed with status ${response.status}` + ) + } + + // E1 qualified-lead conversion: contributor-program interest + // captured. Carries first-touch utm_* attribution; never any form + // contents. + trackEmailCapture({ location: 'contributor_program_form' }) + setIsSubmitted(true) + } catch (submitError) { + console.error(submitError) + setError( + 'Submission failed. Please email hello@openadapt.ai directly.' + ) + } finally { + setIsSubmitting(false) + } + } + + return ( +
+ {!isSubmitted ? ( +
+ +

+ +

+ +
+ + + + +
+ + + +