@@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
44import { WarningIcon } from "@phosphor-icons/react/ssr"
55import { useMutation } from "@tanstack/react-query"
66import { useTranslations } from "next-intl"
7- import { useMemo } from "react"
7+ import { useMemo , type ReactNode } from "react"
88import { useForm } from "react-hook-form"
99
1010import { SectionTitle } from "@/components/elementary/section-header"
@@ -32,6 +32,12 @@ interface HubSpotSsrFormProps {
3232 readonly onSubmitted ?: ( values : Record < string , unknown > ) => void
3333 /** Optional heading rendered above the form. Omitted when absent. */
3434 readonly title ?: string | null
35+ /**
36+ * Server-rendered content shown in place of the form after a successful
37+ * submission. Takes precedence over HubSpot's `inlineMessage`, which is
38+ * empty for forms built in the HubSpot new editor.
39+ */
40+ readonly thankYouContent ?: ReactNode
3541}
3642
3743interface SubmitPayload {
@@ -41,6 +47,18 @@ interface SubmitPayload {
4147
4248interface SubmitResult {
4349 inlineMessage ?: string
50+ redirectUri ?: string
51+ }
52+
53+ /** Returns the trimmed string, or undefined when empty or not a string. */
54+ function nonEmpty ( value : unknown ) : string | undefined {
55+ if ( typeof value !== "string" ) {
56+ return undefined
57+ }
58+
59+ const trimmed = value . trim ( )
60+
61+ return trimmed . length > 0 ? trimmed : undefined
4462}
4563
4664async function submitHubSpotForm (
@@ -90,6 +108,7 @@ function HubSpotSsrFormInner({
90108 enableRecaptcha,
91109 onSubmitted,
92110 title,
111+ thankYouContent,
93112} : HubSpotSsrFormProps ) {
94113 const t = useTranslations ( "forms" )
95114
@@ -119,10 +138,22 @@ function HubSpotSsrFormInner({
119138 return
120139 }
121140
141+ // HubSpot may answer with a redirect target; honour it first.
142+ const redirectUri = nonEmpty ( data . redirectUri )
143+
144+ if ( redirectUri ) {
145+ window . location . href = redirectUri
146+
147+ return
148+ }
149+
122150 const postSubmitAction = schema . configuration ?. postSubmitAction
123151
124- if ( postSubmitAction ?. type === "redirect_url" && postSubmitAction . value ) {
125- window . location . href = postSubmitAction . value
152+ if (
153+ postSubmitAction ?. type === "redirect_url" &&
154+ nonEmpty ( postSubmitAction . value )
155+ ) {
156+ window . location . href = postSubmitAction . value as string
126157 }
127158 } ,
128159 onError : ( err ) => {
@@ -156,17 +187,35 @@ function HubSpotSsrFormInner({
156187 }
157188
158189 if ( submitMutation . isSuccess && ! onSubmitted ) {
190+ // 1. CMS-managed thank-you block (preferred: works for every HubSpot form,
191+ // including new-editor forms whose inlineMessage comes back empty).
192+ if ( thankYouContent ) {
193+ return < > { thankYouContent } </ >
194+ }
195+
196+ // 2. HubSpot-provided HTML, skipping empty strings so the fallback below
197+ // always applies (`??` alone would render a blank box for "").
159198 const postSubmitAction = schema . configuration ?. postSubmitAction
160199 const message =
161- submitMutation . data . inlineMessage ??
162- postSubmitAction ?. value ??
163- t ( "submissionSuccess" )
200+ nonEmpty ( submitMutation . data . inlineMessage ) ??
201+ ( postSubmitAction ?. type !== "redirect_url"
202+ ? nonEmpty ( postSubmitAction ?. value )
203+ : undefined )
204+
205+ if ( message ) {
206+ return (
207+ < div
208+ className = "text-foreground text-sm leading-relaxed"
209+ dangerouslySetInnerHTML = { { __html : message } }
210+ />
211+ )
212+ }
164213
214+ // 3. Generic, translated fallback.
165215 return (
166- < div
167- className = "text-foreground text-sm leading-relaxed"
168- dangerouslySetInnerHTML = { { __html : message } }
169- />
216+ < p className = "text-foreground text-sm leading-relaxed" role = "status" >
217+ { t ( "submissionSuccess" ) }
218+ </ p >
170219 )
171220 }
172221
0 commit comments