From f0728a90705c68a141bb807a9d5de6ce6c93bc92 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Sun, 12 Jul 2026 13:46:29 -0700 Subject: [PATCH] fix(builder): emit ContactForm successMessage as a data attribute (D1b) successMessage was collected in props (and FormStylePanel already had a live "Success Message" input for it) but toHtml never emitted it anywhere. The form-sender relay delivers success via a full-page 303 redirect to thankYouUrl or a hosted thanks.php page -- there is no in-page JS today that could reveal an inline success element. Emit it as data-whp-success-message (escaped) on the
: forward-compatible for a future AJAX submission mode, without implying a live mechanism that doesn't exist yet. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../forms/ContactForm.toHtml.test.ts | 22 +++++++++++++++++++ craft/src/components/forms/ContactForm.tsx | 9 +++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/craft/src/components/forms/ContactForm.toHtml.test.ts b/craft/src/components/forms/ContactForm.toHtml.test.ts index 36e48f3..e64dd22 100644 --- a/craft/src/components/forms/ContactForm.toHtml.test.ts +++ b/craft/src/components/forms/ContactForm.toHtml.test.ts @@ -41,3 +41,25 @@ describe('ContactForm.toHtml relay wiring', () => { expect(html).toContain('Name'); }); }); + +describe('ContactForm.toHtml successMessage', () => { + // The published form-sender relay (form-sender/app/submit.php) delivers + // success via a full-page 303 redirect to thankYouUrl or a hosted + // thanks.php page -- there is no in-page JS to reveal an inline success + // element. So successMessage is emitted as a forward-compatible data + // attribute for a future AJAX/JS submission mode, not a live DOM element. + test('with successMessage set: emits it as an escaped data attribute on the form', () => { + const { html } = toHtml({ successMessage: "We'll be in touch!", fields: [] }, ''); + expect(html).toContain('data-whp-success-message="We'll be in touch!"'); + }); + + test('without successMessage: no data attribute emitted', () => { + const { html } = toHtml({ fields: [] }, ''); + expect(html).not.toContain('data-whp-success-message'); + }); + + test('escapes attribute-breakout attempts in successMessage', () => { + const { html } = toHtml({ successMessage: 'x" onerror="alert(1)', fields: [] }, ''); + expect(html).not.toContain('onerror="alert(1)"'); + }); +}); diff --git a/craft/src/components/forms/ContactForm.tsx b/craft/src/components/forms/ContactForm.tsx index f03aef5..7295b63 100644 --- a/craft/src/components/forms/ContactForm.tsx +++ b/craft/src/components/forms/ContactForm.tsx @@ -211,8 +211,15 @@ ContactForm.craft = { const { marker, actionAttr, honeypot } = relayFormWiring(props.recipientEmail, props.thankYouUrl, props.formAction); + // The form-sender relay delivers success via a full-page 303 redirect + // (to thankYouUrl or a hosted thanks.php page) -- there is no in-page JS + // that reveals an inline success element today. Emit successMessage as a + // forward-compatible data attribute so a future AJAX/JS submission mode + // can read it, without implying a live mechanism that doesn't exist yet. + const successAttr = props.successMessage ? ` data-whp-success-message="${escapeAttr(props.successMessage)}"` : ''; + return { - html: `${marker} + html: `${marker} ${honeypot ? ` ${honeypot}\n` : ''}${fieldsHtml ? ` ${fieldsHtml}\n` : ''} `, };