mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
Made the HubSpot component more generic to support different types of forms
This commit is contained in:
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."
|
||||
/>
|
||||
+102
-67
@@ -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">
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user