Made the HubSpot component more generic to support different types of forms

This commit is contained in:
Prashant Sridharan committed 2026-03-25 19:28:16 +00:00
1 parent 68a30d3216
commit cbadde7458
4 files changed
+112 -71

No files matched your search

@@ -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.
<WebinarContactForm formId="b110ee65-7caf-4ad1-bbf3-c02c2b5b0550" successMessage="Thanks for reaching out. One of our engineers will be in touch to help you get your team building on Supabase." />
<HubSpotForm
formId="b110ee65-7caf-4ad1-bbf3-c02c2b5b0550"
successMessage="Thanks for reaching out. One of our engineers will be in touch to help you get your team building on Supabase."
/>
@@ -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.
<WebinarContactForm formId="a8276214-6883-4aeb-85d3-8bae81b9f149" successMessage="Thanks for reaching out. One of our engineers will be in touch to help you scope your next client project on Supabase." />
<HubSpotForm
formId="a8276214-6883-4aeb-85d3-8bae81b9f149"
successMessage="Thanks for reaching out. One of our engineers will be in touch to help you scope your next client project on Supabase."
/>
@@ -4,7 +4,27 @@ import { Button, Input_Shadcn_, TextArea_Shadcn_ } from 'ui'
type SubmitState = 'idle' | 'loading' | 'success' | 'error'
const FIELD_MAP: Record<string, string> = {
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<string, string> = {
first_name: 'firstname',
last_name: 'lastname',
email_address: 'email',
@@ -12,27 +32,58 @@ const FIELD_MAP: Record<string, string> = {
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<string, string>
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 (
<TextArea_Shadcn_
placeholder={field.placeholder}
required={field.required}
rows={field.rows ?? 4}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
)
case 'text':
case 'email':
default:
return (
<Input_Shadcn_
type={field.type}
placeholder={field.placeholder}
required={field.required}
value={value}
onChange={(e) => 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<Record<string, string>>(() =>
Object.fromEntries(fields.map((f) => [f.name, '']))
)
const [submitState, setSubmitState] = useState<SubmitState>('idle')
const [errorMessages, setErrorMessages] = useState<string[]>([])
@@ -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 (
<form
onSubmit={handleSubmit}
className="border border-muted rounded-2xl p-6 sm:p-8 flex flex-col gap-6 not-prose"
>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="flex flex-col gap-2">
<label className="text-sm text-foreground font-medium">First Name</label>
<Input_Shadcn_
type="text"
placeholder="First Name"
required
value={values.first_name}
onChange={(e) => handleChange('first_name', e.target.value)}
/>
{rows.map((row, rowIndex) => (
<div
key={rowIndex}
className={row.length > 1 ? 'grid grid-cols-1 sm:grid-cols-2 gap-4' : undefined}
>
{row.map((field) => (
<div key={field.name} className="flex flex-col gap-2">
<label className="text-sm text-foreground font-medium">{field.label}</label>
{renderField(field, values[field.name] ?? '', (v) => handleChange(field.name, v))}
</div>
))}
</div>
<div className="flex flex-col gap-2">
<label className="text-sm text-foreground font-medium">Last Name</label>
<Input_Shadcn_
type="text"
placeholder="Last Name"
required
value={values.last_name}
onChange={(e) => handleChange('last_name', e.target.value)}
/>
</div>
</div>
<div className="flex flex-col gap-2">
<label className="text-sm text-foreground font-medium">Email</label>
<Input_Shadcn_
type="email"
placeholder="Work email"
required
value={values.email_address}
onChange={(e) => handleChange('email_address', e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<label className="text-sm text-foreground font-medium">Company</label>
<Input_Shadcn_
type="text"
placeholder="Company name"
value={values.company_name}
onChange={(e) => handleChange('company_name', e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<label className="text-sm text-foreground font-medium">Tell us about your project</label>
<TextArea_Shadcn_
placeholder="I want to build..."
rows={4}
value={values.message}
onChange={(e) => handleChange('message', e.target.value)}
/>
</div>
))}
{submitState === 'error' && errorMessages.length > 0 && (
<div className="flex flex-col gap-1">
@@ -157,7 +192,7 @@ export default function WebinarContactForm({
<hr className="border-muted" />
<Button htmlType="submit" type="primary" size="large" block loading={submitState === 'loading'}>
Get in touch
{submitLabel}
</Button>
<p className="text-xs text-foreground-lighter leading-relaxed">
+2 -2
View File
@@ -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<typeof getStaticProps>) =>
{...content}
components={{
...mdxComponents(),
WebinarContactForm,
HubSpotForm,
}}
/>
</div>