Files
supabase/apps/www/scripts/syncPartnerIntakeForm.mjs
51a167d910 feat(www): partners landing page (#47874)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added a redesigned Partners page with partnership options, benefits,
application steps, FAQs, integration resources, and featured partners.
- Added an on-page partner application form with questions tailored to
the selected partnership type.
- Added a confirmation message with next steps and a link to the OAuth
integration guide.
- **Bug Fixes**
- Updated partner application links to open the form on the Partners
page.
  - Added support for checkbox-group fields in forms.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alan Daniel <stylesshjs@gmail.com>
Co-authored-by: Alex Hall <alex.hall@supabase.io>
Co-authored-by: Dion Zeneli <101271736+Dionysos288@users.noreply.github.com>
2026-09-25 15:53:32 +02:00

110 lines
4.0 KiB
JavaScript

// @ts-check
/**
* Fetches the live "Become a Partner" HubSpot form schema and writes a
* normalized snapshot to __generated__/partner-intake-form.hubspot.json.
*
* HubSpot's own embeddable-form widget (and share.hsforms.com links) loads
* the form definition from a public, keyless JSON(P) endpoint — the same one
* used here. `portalId`/`formGuid` aren't secrets: they're recoverable from
* any public share link or the embed page's own source, so it's safe to
* check them into the repo. They should match the (secret-adjacent, but not
* actually secret) HUBSPOT_PORTAL_ID / HUBSPOT_PARTNER_INTAKE_FORM_GUID env
* vars used at submission time in apps/www/lib/staticFormCrm.ts.
*
* This snapshot is NOT rendered directly — PartnerIntakeForm.tsx stays
* hand-authored for curated copy/layout. PartnerIntakeForm.sync.test.ts
* cross-checks the hand-authored form against this file so drift between
* the two fails CI instead of silently breaking submissions. Re-run this
* script (`pnpm run sync:partner-form`) whenever that test starts failing
* after a snapshot update, and reconcile PartnerIntakeForm.tsx / staticFormCrm.ts
* by hand.
*/
import { promises as fs } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const OUT_DIR = join(__dirname, '..', 'components', 'Partners', '__generated__')
const OUT_FILE = join(OUT_DIR, 'partner-intake-form.hubspot.json')
// Decoded from the share link https://share.hsforms.com/1ZSo-7Y0GRfuTdvWusOP13Abvo3m
const PORTAL_ID = process.env.HUBSPOT_PORTAL_ID || '19953346'
const FORM_GUID =
process.env.HUBSPOT_PARTNER_INTAKE_FORM_GUID || '652a3eed-8d06-45fb-9376-f5aeb0e3f5dc'
async function fetchFormSchema(portalId, formGuid) {
const url = `https://forms.hubspot.com/embed/v3/form/${portalId}/${formGuid}?callback=cb`
const res = await fetch(url, { headers: { 'User-Agent': 'supabase-www-build' } })
if (!res.ok) throw new Error(`GET ${url} → ${res.status}`)
const body = await res.text()
const jsonpMatch = body.match(/^cb\((.*)\)$/s)
if (!jsonpMatch)
throw new Error(`Unexpected response shape from HubSpot embed endpoint: ${body.slice(0, 200)}`)
const data = JSON.parse(jsonpMatch[1])
if (!data.form) throw new Error(`HubSpot response missing "form": ${body.slice(0, 200)}`)
return data.form
}
function normalizeField(field, dependsOn) {
const normalized = {
name: field.name,
label: field.label,
fieldType: field.fieldType,
required: Boolean(field.required),
options: (field.options ?? []).map((o) => ({ label: o.label, value: o.value })),
}
if (field.description) normalized.description = field.description
if (dependsOn) normalized.dependsOn = dependsOn
return normalized
}
/**
* Flattens `formFieldGroups[].fields[]` plus every nested
* `dependentFieldFilters[].dependentFormField` into one flat list, recording
* which field/values a dependent field is conditional on.
*/
function flattenFields(form) {
const fields = []
for (const group of form.formFieldGroups ?? []) {
for (const field of group.fields ?? []) {
fields.push(normalizeField(field))
for (const dep of field.dependentFieldFilters ?? []) {
const trigger = dep.filters?.[0]
fields.push(
normalizeField(dep.dependentFormField, {
field: field.name,
values: trigger?.strValues ?? [],
})
)
}
}
}
return fields
}
async function main() {
console.log(`Fetching HubSpot form ${FORM_GUID} (portal ${PORTAL_ID})`)
const form = await fetchFormSchema(PORTAL_ID, FORM_GUID)
const snapshot = {
portalId: form.portalId,
formGuid: form.guid,
submitText: form.submitText,
fields: flattenFields(form),
}
await fs.mkdir(OUT_DIR, { recursive: true })
await fs.writeFile(OUT_FILE, JSON.stringify(snapshot, null, 2) + '\n')
console.log(`Wrote ${snapshot.fields.length} fields → ${OUT_FILE}`)
}
main().catch((err) => {
console.error(err)
process.exit(1)
})