Skip to content

Commit 6cce858

Browse files
fix: fix submit email button on what's new page (#1042)
Fern's MDX renderer strips JSX event handlers (onSubmit, onClick), so the subscribe form's validation and submission logic was never attached to the DOM. The form fell through to a native HTML POST that silently redirected back to the same page with no user feedback. Move all form logic to custom.js using plain DOM event listeners, replace inline styles with CSS classes, and add a feedback message div for success/error states. Closes PRO-2451 Co-authored-by: betzlermeow <257694564+betzlermeow@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ec21c32 commit 6cce858

4 files changed

Lines changed: 361 additions & 63 deletions

File tree

fern/assets/styles.css

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,105 @@ html.dark button[data-highlighted] .fern-api-property-meta {
282282
/* Fix: Make subtitle white on Simulations pages in dark mode */
283283
:is(.dark) [id*="simulations"] .prose-p\:text-\(color\:--grayscale-a11\) :where(p):not(:where([class~=not-prose],[class~=not-prose] *)) {
284284
color: var(--grayscale-12) !important;
285+
}
286+
287+
/* Subscribe form on What's New page */
288+
.subscribe-form-row {
289+
display: flex;
290+
gap: 0.5rem;
291+
}
292+
293+
.subscribe-form-input {
294+
border: 1px solid #e2e8f0;
295+
border-radius: 0.375rem;
296+
padding: 0.5rem 1rem;
297+
width: 100%;
298+
font-size: 0.875rem;
299+
outline: none;
300+
transition: border-color 0.2s ease-in-out;
301+
color: #1f2937;
302+
background-color: #fff;
303+
}
304+
305+
.subscribe-form-input:focus {
306+
border-color: #4f46e5;
307+
box-shadow: 0 0 0 1px #4f46e5;
308+
}
309+
310+
.subscribe-form-button {
311+
background-color: #37aa9d;
312+
color: white;
313+
font-weight: 500;
314+
padding: 0.5rem 1rem;
315+
border-radius: 0.375rem;
316+
border: none;
317+
cursor: pointer;
318+
transition: all 0.2s ease-in-out;
319+
white-space: nowrap;
320+
}
321+
322+
.subscribe-form-button:hover {
323+
background-color: #2e8b7d;
324+
transform: translateY(-1px);
325+
}
326+
327+
.subscribe-form-button:active {
328+
transform: translateY(0);
329+
}
330+
331+
.subscribe-form-button:disabled {
332+
opacity: 0.7;
333+
cursor: not-allowed;
334+
transform: none;
335+
}
336+
337+
.subscribe-form-message {
338+
margin-top: 0.5rem;
339+
font-size: 0.875rem;
340+
padding: 0.5rem 0.75rem;
341+
border-radius: 0.375rem;
342+
}
343+
344+
.subscribe-form-message.success {
345+
color: #065f46;
346+
background-color: #d1fae5;
347+
}
348+
349+
.subscribe-form-message.error {
350+
color: #991b1b;
351+
background-color: #fee2e2;
352+
}
353+
354+
:is(.dark) .subscribe-form-input {
355+
background-color: #374151;
356+
border-color: #4b5563;
357+
color: #f3f4f6;
358+
}
359+
360+
:is(.dark) .subscribe-form-input::placeholder {
361+
color: #9ca3af;
362+
}
363+
364+
:is(.dark) .subscribe-form-input:focus {
365+
border-color: #6366f1;
366+
box-shadow: 0 0 0 1px #6366f1;
367+
}
368+
369+
:is(.dark) .subscribe-form-button {
370+
background-color: #94ffd2;
371+
color: #1f2937;
372+
}
373+
374+
:is(.dark) .subscribe-form-button:hover {
375+
background-color: #7cd9b0;
376+
}
377+
378+
:is(.dark) .subscribe-form-message.success {
379+
color: #a7f3d0;
380+
background-color: #064e3b;
381+
}
382+
383+
:is(.dark) .subscribe-form-message.error {
384+
color: #fca5a5;
385+
background-color: #7f1d1d;
285386
}

fern/changelog/overview.mdx

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
slug: whats-new
33
---
44
<Card
5-
title={<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', color: 'inherit' }} onClick={() => document.querySelector('input[type="email"]').focus()}>Subscribe to the latest product updates</div>}
5+
title="Subscribe to the latest product updates"
66
icon="envelope"
77
iconType="solid"
88
>
@@ -11,80 +11,24 @@ slug: whats-new
1111
action="https://customerioforms.com/forms/submit_action?site_id=5f95a74ff6539f0bc48f&form_id=01jk7tf2khhf5satn62531qe25&success_url=https://docs.vapi.ai/whats-new"
1212
className="subscribe-form"
1313
style={{margin: '1rem 0'}}
14-
onSubmit={(e) => {
15-
const emailInput = document.getElementById('email_input');
16-
const emailValue = emailInput.value;
17-
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
18-
if (!emailPattern.test(emailValue)) {
19-
e.preventDefault();
20-
alert('Please enter a valid email address.');
21-
}
22-
}}
2314
>
24-
<div className="flex gap-2">
15+
<div className="subscribe-form-row">
2516
<label htmlFor="email_input" style={{ display: 'none' }}>E-mail address</label>
2617
<input
2718
id="email_input"
19+
className="subscribe-form-input"
2820
type="email"
2921
name="email"
3022
placeholder="Enter your email"
3123
required
32-
style={{
33-
border: '1px solid #e2e8f0',
34-
borderRadius: '0.375rem',
35-
padding: '0.5rem 1rem',
36-
width: '100%',
37-
fontSize: '0.875rem',
38-
outline: 'none',
39-
transition: 'border-color 0.2s ease-in-out',
40-
color: '#1f2937',
41-
':focus': {
42-
borderColor: '#4f46e5',
43-
boxShadow: '0 0 0 1px #4f46e5'
44-
},
45-
'@media (prefers-color-scheme: dark)': {
46-
backgroundColor: '#374151',
47-
borderColor: '#4b5563',
48-
color: '#f3f4f6',
49-
'::placeholder': {
50-
color: '#9ca3af'
51-
},
52-
':focus': {
53-
borderColor: '#6366f1',
54-
boxShadow: '0 0 0 1px #6366f1'
55-
}
56-
}
57-
}}
5824
/>
5925
<button
6026
type="submit"
61-
style={{
62-
backgroundColor: '#37aa9d',
63-
color: 'white',
64-
fontWeight: 500,
65-
padding: '0.5rem 1rem',
66-
borderRadius: '0.375rem',
67-
border: 'none',
68-
cursor: 'pointer',
69-
transition: 'all 0.2s ease-in-out',
70-
':hover': {
71-
backgroundColor: '#2e8b7d',
72-
transform: 'translateY(-1px)'
73-
},
74-
':active': {
75-
transform: 'translateY(0)'
76-
},
77-
'@media (prefers-color-scheme: dark)': {
78-
backgroundColor: '#94ffd2',
79-
color: '#1f2937',
80-
':hover': {
81-
backgroundColor: '#7cd9b0'
82-
}
83-
}
84-
}}
27+
className="subscribe-form-button"
8528
>
8629
Submit
8730
</button>
8831
</div>
32+
<div className="subscribe-form-message" style={{ display: 'none' }}></div>
8933
</form>
9034
</Card>

fern/custom.js

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,116 @@ function initializeHubSpot() {
109109
document.getElementsByTagName('head')[0].appendChild(hubSpotScript);
110110
}
111111

112+
function initializeSubscribeForm() {
113+
// Fern's MDX renderer strips JSX event handlers (onSubmit, onClick), so the
114+
// form's validation and submission logic must be attached from plain JS.
115+
// Without this, the form falls through to a native HTML POST that silently
116+
// redirects back to the same page with no user feedback.
117+
118+
var form = document.querySelector('form.subscribe-form');
119+
if (!form) {
120+
return;
121+
}
122+
123+
// Avoid attaching the handler twice on SPA navigations
124+
if (form.dataset.enhanced === 'true') {
125+
return;
126+
}
127+
form.dataset.enhanced = 'true';
128+
129+
form.addEventListener('submit', function (e) {
130+
e.preventDefault();
131+
132+
var emailInput = form.querySelector('input[name="email"]');
133+
var submitBtn = form.querySelector('button[type="submit"]');
134+
var messageDiv = form.querySelector('.subscribe-form-message');
135+
136+
if (!emailInput || !submitBtn) {
137+
return;
138+
}
139+
140+
var email = emailInput.value.trim();
141+
var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
142+
143+
if (!emailPattern.test(email)) {
144+
if (messageDiv) {
145+
messageDiv.textContent = 'Please enter a valid email address.';
146+
messageDiv.className = 'subscribe-form-message error';
147+
messageDiv.style.display = 'block';
148+
}
149+
return;
150+
}
151+
152+
// Hide any previous message and disable the button while submitting
153+
if (messageDiv) {
154+
messageDiv.style.display = 'none';
155+
}
156+
submitBtn.disabled = true;
157+
var originalText = submitBtn.textContent;
158+
submitBtn.textContent = 'Submitting...';
159+
160+
var formAction = form.getAttribute('action');
161+
var formData = new FormData();
162+
formData.append('email', email);
163+
164+
fetch(formAction, {
165+
method: 'POST',
166+
body: formData,
167+
redirect: 'manual',
168+
})
169+
.then(function (response) {
170+
// Customer.io returns 302 on success which becomes an opaque redirect
171+
// with redirect:'manual'. Both 302 and opaque (type 0) indicate success.
172+
if (response.ok || response.status === 302 || response.status === 0 || response.type === 'opaqueredirect') {
173+
if (messageDiv) {
174+
messageDiv.textContent = 'Thanks for subscribing! You will receive product updates at ' + email + '.';
175+
messageDiv.className = 'subscribe-form-message success';
176+
messageDiv.style.display = 'block';
177+
}
178+
emailInput.value = '';
179+
} else {
180+
throw new Error('Unexpected response: ' + response.status);
181+
}
182+
})
183+
.catch(function () {
184+
if (messageDiv) {
185+
messageDiv.textContent = 'Something went wrong. Please try again.';
186+
messageDiv.className = 'subscribe-form-message error';
187+
messageDiv.style.display = 'block';
188+
}
189+
})
190+
.finally(function () {
191+
submitBtn.disabled = false;
192+
submitBtn.textContent = originalText;
193+
});
194+
});
195+
}
196+
112197
function initializeAll() {
113198
initializeHockeyStack();
114199
initializeReo();
115200
initializeHubSpot();
116201
configurePostHog();
202+
initializeSubscribeForm();
117203
if (ENABLE_VOICE_WIDGET) {
118204
injectVapiWidget();
119205
}
120206
}
121207

208+
// Fern uses client-side routing, so the form may appear after the initial page
209+
// load. Re-attach the handler whenever the DOM changes on the whats-new page.
210+
var subscribeFormObserver = new MutationObserver(function () {
211+
if (window.location.pathname.indexOf('whats-new') !== -1) {
212+
initializeSubscribeForm();
213+
}
214+
});
215+
122216
if (document.readyState === 'loading') {
123-
document.addEventListener('DOMContentLoaded', initializeAll);
217+
document.addEventListener('DOMContentLoaded', function () {
218+
initializeAll();
219+
subscribeFormObserver.observe(document.body, { childList: true, subtree: true });
220+
});
124221
} else {
125222
initializeAll();
126-
}
223+
subscribeFormObserver.observe(document.body, { childList: true, subtree: true });
224+
}

0 commit comments

Comments
 (0)