From cbadde74580e20cb4641d675d7201335a12e2dba Mon Sep 17 00:00:00 2001 From: Prashant Sridharan Date: Wed, 25 Mar 2026 19:28:16 +0000 Subject: [PATCH] Made the HubSpot component more generic to support different types of forms --- ...-02-25-enterprise-innovation-with-bolt.mdx | 5 +- ...ency-webinar-ai-prototyping-production.mdx | 5 +- ...WebinarContactForm.tsx => HubSpotForm.tsx} | 169 +++++++++++------- apps/www/pages/events/[slug].tsx | 4 +- 4 files changed, 112 insertions(+), 71 deletions(-) rename apps/www/components/Events/{WebinarContactForm.tsx => HubSpotForm.tsx} (50%) diff --git a/apps/www/_events/2026-02-25-enterprise-innovation-with-bolt.mdx b/apps/www/_events/2026-02-25-enterprise-innovation-with-bolt.mdx index 7f9d4075823..2d07957893d 100644 --- a/apps/www/_events/2026-02-25-enterprise-innovation-with-bolt.mdx +++ b/apps/www/_events/2026-02-25-enterprise-innovation-with-bolt.mdx @@ -61,4 +61,7 @@ We hope you enjoy the recording! Ready to let your team build with AI coding tools on a production-grade backend? Whether you're exploring how non-technical teams can ship safely or looking to connect Bolt with your existing Supabase project, reach out and we'll connect you with an engineer to scope your project and set up the right guardrails. - + diff --git a/apps/www/_events/2026-03-19-agency-webinar-ai-prototyping-production.mdx b/apps/www/_events/2026-03-19-agency-webinar-ai-prototyping-production.mdx index 73e3306ea65..f3b36c4181f 100644 --- a/apps/www/_events/2026-03-19-agency-webinar-ai-prototyping-production.mdx +++ b/apps/www/_events/2026-03-19-agency-webinar-ai-prototyping-production.mdx @@ -58,4 +58,7 @@ We hope you enjoy the recording! Want to ship client projects faster without sacrificing production safety? Whether you're setting up RLS policies, designing schemas for AI-assisted development, or planning a client handoff, reach out and we'll connect you with an engineer to help you scope your next build on Supabase. - + diff --git a/apps/www/components/Events/WebinarContactForm.tsx b/apps/www/components/Events/HubSpotForm.tsx similarity index 50% rename from apps/www/components/Events/WebinarContactForm.tsx rename to apps/www/components/Events/HubSpotForm.tsx index 4bf0f2ed586..2f02304d5b0 100644 --- a/apps/www/components/Events/WebinarContactForm.tsx +++ b/apps/www/components/Events/HubSpotForm.tsx @@ -4,7 +4,27 @@ import { Button, Input_Shadcn_, TextArea_Shadcn_ } from 'ui' type SubmitState = 'idle' | 'loading' | 'success' | 'error' -const FIELD_MAP: Record = { +type FormFieldType = 'text' | 'email' | 'textarea' + +type FormField = { + type: FormFieldType + name: string + label: string + placeholder?: string + required?: boolean + half?: boolean + rows?: number +} + +const DEFAULT_FIELDS: FormField[] = [ + { type: 'text', name: 'first_name', label: 'First Name', placeholder: 'First Name', required: true, half: true }, + { type: 'text', name: 'last_name', label: 'Last Name', placeholder: 'Last Name', required: true, half: true }, + { type: 'email', name: 'email_address', label: 'Email', placeholder: 'Work email', required: true }, + { type: 'text', name: 'company_name', label: 'Company', placeholder: 'Company name' }, + { type: 'textarea', name: 'message', label: 'Tell us about your project', placeholder: 'I want to build...' }, +] + +const DEFAULT_FIELD_MAP: Record = { first_name: 'firstname', last_name: 'lastname', email_address: 'email', @@ -12,27 +32,58 @@ const FIELD_MAP: Record = { message: 'what_are_you_currently_working_on_', } -const CONSENT_TEXT = +const DEFAULT_CONSENT = 'By submitting this form, I confirm that I have read and understood the Privacy Policy.' -type WebinarContactFormProps = { +type HubSpotFormProps = { formId: string + fields?: FormField[] + fieldMap?: Record + submitLabel?: string successMessage?: string successRedirect?: string + consent?: string } -export default function WebinarContactForm({ +function renderField(field: FormField, value: string, onChange: (v: string) => void) { + switch (field.type) { + case 'textarea': + return ( + onChange(e.target.value)} + /> + ) + case 'text': + case 'email': + default: + return ( + onChange(e.target.value)} + /> + ) + } +} + +export default function HubSpotForm({ formId, + fields = DEFAULT_FIELDS, + fieldMap = DEFAULT_FIELD_MAP, + submitLabel = 'Get in touch', successMessage = "We've received your details and will be in touch soon.", successRedirect, -}: WebinarContactFormProps) { - const [values, setValues] = useState({ - first_name: '', - last_name: '', - email_address: '', - company_name: '', - message: '', - }) + consent = DEFAULT_CONSENT, +}: HubSpotFormProps) { + const [values, setValues] = useState>(() => + Object.fromEntries(fields.map((f) => [f.name, ''])) + ) const [submitState, setSubmitState] = useState('idle') const [errorMessages, setErrorMessages] = useState([]) @@ -52,8 +103,8 @@ export default function WebinarContactForm({ body: JSON.stringify({ formGuid: formId, fields: values, - fieldMap: FIELD_MAP, - consent: CONSENT_TEXT, + fieldMap, + consent, pageName: document.title, }), }) @@ -85,64 +136,48 @@ export default function WebinarContactForm({ ) } + // Group fields into rows: half-width fields pair up, full-width fields get their own row + const rows: FormField[][] = [] + let pendingHalf: FormField | null = null + + for (const field of fields) { + if (field.half) { + if (pendingHalf) { + rows.push([pendingHalf, field]) + pendingHalf = null + } else { + pendingHalf = field + } + } else { + if (pendingHalf) { + rows.push([pendingHalf]) + pendingHalf = null + } + rows.push([field]) + } + } + if (pendingHalf) { + rows.push([pendingHalf]) + } + return (
-
-
- - handleChange('first_name', e.target.value)} - /> + {rows.map((row, rowIndex) => ( +
1 ? 'grid grid-cols-1 sm:grid-cols-2 gap-4' : undefined} + > + {row.map((field) => ( +
+ + {renderField(field, values[field.name] ?? '', (v) => handleChange(field.name, v))} +
+ ))}
-
- - handleChange('last_name', e.target.value)} - /> -
-
- -
- - handleChange('email_address', e.target.value)} - /> -
- -
- - handleChange('company_name', e.target.value)} - /> -
- -
- - handleChange('message', e.target.value)} - /> -
+ ))} {submitState === 'error' && errorMessages.length > 0 && (
@@ -157,7 +192,7 @@ export default function WebinarContactForm({

diff --git a/apps/www/pages/events/[slug].tsx b/apps/www/pages/events/[slug].tsx index 353368de84e..3d3756d4b4c 100644 --- a/apps/www/pages/events/[slug].tsx +++ b/apps/www/pages/events/[slug].tsx @@ -25,7 +25,7 @@ import utc from 'dayjs/plugin/utc' import { Button, Image } from 'ui' import ShareArticleActions from '~/components/Blog/ShareArticleActions' -import WebinarContactForm from '~/components/Events/WebinarContactForm' +import HubSpotForm from '~/components/Events/HubSpotForm' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' @@ -390,7 +390,7 @@ const EventPage = ({ event }: InferGetStaticPropsType) => {...content} components={{ ...mdxComponents(), - WebinarContactForm, + HubSpotForm, }} />