mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
<!-- 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>
110 lines
4.0 KiB
JavaScript
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)
|
|
})
|