diff --git a/.github/workflows/www-partner-form-sync-check.yml b/.github/workflows/www-partner-form-sync-check.yml
new file mode 100644
index 00000000000..4af0843b4c1
--- /dev/null
+++ b/.github/workflows/www-partner-form-sync-check.yml
@@ -0,0 +1,60 @@
+name: Partner intake form / HubSpot sync check
+
+# Deliberately its own workflow, path-filtered to only the files this check
+# actually concerns — NOT part of www-tests.yml's `pnpm test` run, which
+# gates every apps/www PR. A HubSpot-side change surfaced by the scheduled
+# www-partner-form-sync.yml bot PR (which touches only the generated
+# snapshot) should fail *this* check, not block unrelated apps/www work.
+on:
+ pull_request:
+ branches: ['master']
+ paths:
+ - 'apps/www/components/Partners/PartnerIntakeForm.fields.ts'
+ - 'apps/www/components/Partners/PartnerIntakeForm.sync.test.ts'
+ - 'apps/www/components/Partners/__generated__/partner-intake-form.hubspot.json'
+ - 'apps/www/lib/staticFormCrm.ts'
+ - 'apps/www/vitest.sync.config.ts'
+ - 'apps/www/package.json'
+ - '.github/workflows/www-partner-form-sync-check.yml'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+env:
+ CI: true
+
+jobs:
+ check:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ apps/www
+ packages
+ supabase
+ patches
+
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
+ name: Install pnpm
+ with:
+ run_install: false
+
+ - name: Use Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'pnpm'
+
+ - name: Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: Run partner intake form / HubSpot sync check
+ run: pnpm run test:partner-form-sync
+ working-directory: ./apps/www
diff --git a/.github/workflows/www-partner-form-sync.yml b/.github/workflows/www-partner-form-sync.yml
new file mode 100644
index 00000000000..df3c793201b
--- /dev/null
+++ b/.github/workflows/www-partner-form-sync.yml
@@ -0,0 +1,78 @@
+name: Sync partner intake form from HubSpot
+
+on:
+ schedule:
+ # Run at 00:00 UTC every Monday
+ - cron: '0 0 * * 1'
+ workflow_dispatch:
+
+permissions:
+ pull-requests: write
+ contents: write
+
+jobs:
+ sync-partner-form:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+ ref: ${{ github.ref }}
+ sparse-checkout: |
+ apps/www
+ packages
+ patches
+
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
+ name: Install pnpm
+ with:
+ run_install: false
+
+ - name: Use Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'pnpm'
+
+ - name: Install deps
+ run: pnpm install --frozen-lockfile
+
+ - name: Fetch the live HubSpot form and regenerate the snapshot
+ run: pnpm run sync:partner-form
+ working-directory: ./apps/www
+
+ - name: Generate token
+ id: app-token
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
+ with:
+ client-id: ${{ vars.GH_AUTOFIX_APP_CLIENT_ID }}
+ private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}
+ permission-pull-requests: write
+ permission-contents: write
+
+ # Only opens/updates a PR when the regenerated snapshot actually
+ # differs from what's committed — i.e. only when the live HubSpot form
+ # has changed since the last sync. The PR's existence is the drift
+ # alert: PartnerIntakeForm.sync.test.ts (run by the normal
+ # www-tests.yml suite on this PR) fails until a human reconciles
+ # PartnerIntakeForm.tsx / staticFormCrm.ts with the new snapshot.
+ - name: Create pull request
+ uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ commit-message: 'chore(www): sync partner intake form snapshot from HubSpot'
+ title: 'chore(www): partner intake form has changed in HubSpot'
+ body: |
+ The live "Become a Partner" HubSpot form no longer matches the
+ checked-in snapshot at
+ `apps/www/components/Partners/__generated__/partner-intake-form.hubspot.json`.
+
+ `PartnerIntakeForm.sync.test.ts` will fail on this PR until
+ `apps/www/components/Partners/PartnerIntakeForm.fields.ts` and
+ `apps/www/lib/staticFormCrm.ts` are updated by hand to match —
+ see the comment at the top of that test for what it checks.
+ branch: 'gha/auto-sync-partner-intake-form'
+ base: 'master'
+ add-paths: |
+ apps/www/components/Partners/__generated__/partner-intake-form.hubspot.json
diff --git a/.prettierignore b/.prettierignore
index 41a171fe018..d7aecae95d3 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -14,6 +14,8 @@ apps/www/public/images/*
apps/www/public/changelog.md
apps/www/public/changelog/*.md
apps/www/.generated/*
+# Written by apps/www/scripts/syncPartnerIntakeForm.mjs
+apps/www/components/Partners/__generated__
apps/docs/**/generated/*
apps/docs/examples/*
examples/slack-clone/nextjs-slack-clone/full-schema.sql
diff --git a/apps/www/app/partners/PartnersContent.tsx b/apps/www/app/partners/PartnersContent.tsx
new file mode 100644
index 00000000000..912f419b7c5
--- /dev/null
+++ b/apps/www/app/partners/PartnersContent.tsx
@@ -0,0 +1,315 @@
+'use client'
+
+import { ArrowRight, Check } from 'lucide-react'
+import Image from 'next/image'
+import Link from 'next/link'
+import ReactMarkdown from 'react-markdown'
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button } from 'ui'
+
+import BackgroundPattern from '../../components/Partners/BackgroundPattern'
+import DefaultLayout from '@/components/Layouts/Default'
+import SectionContainer from '@/components/Layouts/SectionContainer'
+import SectionHeading from '@/components/Layouts/SectionHeading'
+import Panel from '@/components/Panel'
+import BecomeAPartner from '@/components/Partners/BecomeAPartner'
+import ProductHeaderCentered from '@/components/Sections/ProductHeaderCentered'
+import pageData, { type PARTNER_TIER } from '@/data/partners'
+
+type FeaturedPartner = { slug: string; title: string; logo: string }
+
+interface Props {
+ featuredPartners: FeaturedPartner[]
+}
+
+const FaqList = ({ items }: { items: { question: string; answer: string }[] }) => {
+ return (
+ {reason.description}
+ {tier.description}
+ {step.description}
+ {pageData.featuredPartners.description}
+ {reason.title}
+ {tier.title}
+
+
+
+ {pageData.benefits.items.map((item: any) => (
+
+
+ {pageData.howToApply.steps.map((step, i) => (
+
+ {step.title}
+
+ {pageData.featuredPartners.title}
+
+
+ {pageData.integrationOptions.options.map((option) => (
+
+
+
- Discover how technology and consulting partners are already working with Supabase. -
-- Use your favorite tools with Supabase. -
-}
- {item.description}
-- Supabase lets you build a third-party app that can control organizations or - projects programmatically. -
-{field.description}
+ )} ++ {submitState === 'success' ? 'Thank you! We’ve received your submission.' : ''} +
+ ) + if (submitState === 'success') { return (Thank you!
-- {successMessage ?? "We've received your submission and will be in touch soon."} -
+Thank you!
+{children}
, + a: ({ href, children }) => ( + + {children} + + ), + }} + > + {successMessage ?? "We've received your submission and will be in touch soon."} +