Skip to content

Commit 3cc0ab8

Browse files
authored
Fix custom success page (#287)
* Fix custom success page * Update src/partials/feedback-forms.hbs * Strip trailing slash to avoid netlify redirect issues * Remove duplicate * Add feedback messages closer to the clicked thumbs
1 parent 897b00f commit 3cc0ab8

File tree

7 files changed

+111
-76
lines changed

7 files changed

+111
-76
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict'
2+
3+
module.exports = (url) => {
4+
if (typeof url !== 'string') return url
5+
// leave “/” alone, but drop any single trailing slash
6+
return url.length > 1 && url.endsWith('/')
7+
? url.slice(0, -1)
8+
: url
9+
}

src/partials/article.hbs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<article class="doc {{#if page.attributes.no-toc}}no-toc{{/if}}">
2-
{{> feedback-success}}
32
{{#if (ne page.attributes.role 'home')}}
43
{{> breadcrumbs}}
54
{{/if}}

src/partials/feedback-footer.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
</a>
2121
</div>
2222
</section>
23+
{{> feedback-success}}
2324
<script>
2425
var modal = document.getElementById("contributors-modal");
2526
Lines changed: 96 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<script>
2-
;
3-
(() => {
2+
;(function() {
43
const SELECTORS = {
54
form: '#feedbackForm',
65
thumbs: '.thumb',
@@ -10,111 +9,135 @@
109
submitButton: '#submitButton',
1110
captchaHint: '#captchaHint',
1211
captchaField: 'textarea[name="g-recaptcha-response"]',
13-
toast: '#feedback-toast',
14-
modalOverlay: '#modal-overlay',
12+
successMessage: '#feedback-toast',
13+
successMessageToc: '#feedback-toast-thumbs-toc',
1514
thumbsToc: '#thumbs-toc'
1615
};
1716
1817
const POLL_INTERVAL = 300; // ms between captcha checks
19-
const POLL_TIMEOUT = 5 * 60 * 1000; // stop polling after 5 minutes
18+
const POLL_TIMEOUT = 5 * 60 * 1000; // ms to give up on captcha polling
2019
const SCROLL_HIDE_OFFSET = 700; // px before bottom to hide thumbs
2120
2221
let positive = null;
22+
let fromToc = false; // track if last click was in TOC
2323
2424
document.addEventListener('DOMContentLoaded', () => {
25-
const form = document.querySelector(SELECTORS.form);
26-
if (!form) return;
27-
const thumbs = document.querySelectorAll(SELECTORS.thumbs);
28-
const details = document.querySelector(SELECTORS.feedbackDetails);
29-
const options = document.querySelector(SELECTORS.feedbackOptions);
30-
const prompt = document.querySelector(SELECTORS.feedbackPrompt);
25+
const form = document.querySelector(SELECTORS.form);
3126
const submitBtn = document.querySelector(SELECTORS.submitButton);
32-
const hint = document.querySelector(SELECTORS.captchaHint);
33-
const toast = document.querySelector(SELECTORS.toast);
34-
35-
// Hide thumbs when scrolling to the bottom
36-
document.addEventListener(
37-
'scroll',
38-
event => requestAnimationFrame(() => {
39-
const toc = document.querySelector(SELECTORS.thumbsToc);
40-
if (!toc) return;
41-
const docH = document.body.scrollHeight;
42-
const scrollPos = window.scrollY + window.innerHeight;
43-
toc.style.display = (scrollPos + SCROLL_HIDE_OFFSET > docH) ? 'none' : 'block';
44-
}),
45-
{ passive: true }
46-
);
27+
const hint = document.querySelector(SELECTORS.captchaHint);
28+
const successMsg = document.querySelector(SELECTORS.successMessage);
29+
const successMsgToc = document.querySelector(SELECTORS.successMessageToc);
30+
if (!form) return;
4731
48-
thumbs.forEach(thumb => {
32+
// Show form on thumbs click
33+
document.querySelectorAll(SELECTORS.thumbs).forEach(thumb => {
4934
thumb.addEventListener('click', () => {
5035
positive = thumb.id.includes('up');
36+
fromToc = !!thumb.closest(SELECTORS.thumbsToc);
5137
form.elements['positiveFeedback'].value = positive;
52-
53-
// Safely build radio options
5438
const items = positive
55-
? [ 'Solved my problem', 'Easy to understand', 'Other' ]
56-
: [ 'Not helpful', 'Too complex', 'Other' ];
57-
options.innerHTML = items.map((txt, i) =>
58-
`<label><input type="radio" name="feedback" value="${txt}"${i===0? ' checked':''}> ${txt}</label>`
59-
).join('');
60-
61-
prompt.textContent = positive
62-
? 'Let us know what we do well:'
63-
: 'Let us know what could be improved:';
64-
65-
details.classList.remove('hidden');
39+
? ['Solved my problem','Easy to understand','Other']
40+
: ['Not helpful','Too complex','Other'];
41+
document.querySelector(SELECTORS.feedbackOptions).innerHTML =
42+
items.map((txt,i) =>
43+
`<label><input type=\"radio\" name=\"feedback\" value=\"${txt}\"${i===0?' checked':''}> ${txt}</label>`
44+
).join('');
45+
document.querySelector(SELECTORS.feedbackPrompt).textContent =
46+
positive ? 'Let us know what we do well:' : 'Let us know what could be improved:';
47+
document.querySelector(SELECTORS.feedbackDetails).classList.remove('hidden');
48+
form.hidden = false;
6649
form.classList.remove('hidden');
6750
});
6851
});
6952
70-
form.addEventListener('submit', () => {
71-
const version = form.dataset.version || '';
72-
const beta = form.dataset.beta === 'true';
73-
form.elements['url'].value = window.location.href;
74-
form.elements['positiveFeedback'].value = positive;
75-
form.elements['version'].value = version;
76-
form.elements['beta'].value = beta;
77-
form.elements['date'].value = new Date().toISOString();
78-
form.elements['navigator'].value = `${navigator.userAgent}, ${navigator.language}`;
79-
// Store feedback type
80-
sessionStorage.setItem('feedbackType', positive ? 'positive' : 'negative');
81-
});
82-
83-
window.closeForm = event => {
84-
event.preventDefault();
85-
form.classList.add('hidden');
86-
};
53+
// Hide thumbs near bottom
54+
document.addEventListener('scroll', () => requestAnimationFrame(() => {
55+
const toc = document.querySelector(SELECTORS.thumbsToc);
56+
if (!toc) return;
57+
const docH = document.body.scrollHeight;
58+
const scrollPos = window.scrollY + window.innerHeight;
59+
toc.style.display = (scrollPos + SCROLL_HIDE_OFFSET > docH) ? 'none' : 'block';
60+
}), { passive: true });
8761
62+
// Poll reCAPTCHA token
8863
if (submitBtn && hint) {
8964
submitBtn.disabled = true;
9065
hint.style.display = 'block';
9166
const start = Date.now();
92-
// Enable the submit button when the reCAPTCHA token is available
93-
// https://answers.netlify.com/t/recaptcha-integration-in-forms-configuration/6181/3
94-
const intervalId = setInterval(() => {
67+
const checkId = setInterval(() => {
9568
const captcha = document.querySelector(SELECTORS.captchaField);
96-
if (captcha && captcha.value.trim() !== '') {
69+
if (captcha && captcha.value.trim()) {
9770
submitBtn.disabled = false;
9871
hint.style.display = 'none';
99-
clearInterval(intervalId);
72+
clearInterval(checkId);
10073
}
10174
if (Date.now() - start > POLL_TIMEOUT) {
102-
clearInterval(intervalId);
103-
console.warn('reCAPTCHA token polling timed out');
75+
clearInterval(checkId);
76+
console.warn('reCAPTCHA polling timed out');
10477
}
10578
}, POLL_INTERVAL);
10679
}
10780
108-
if (toast && window.location.search.includes('success')) {
109-
const modal = document.querySelector(SELECTORS.form);
110-
modal.classList.add('hidden');
111-
toast.classList.remove('hidden');
112-
setTimeout(() => toast.classList.add('hidden'), 5000);
81+
// AJAX submit with URL-encoded body
82+
form.addEventListener('submit', async e => {
83+
e.preventDefault();
84+
submitBtn.disabled = true;
11385
114-
const url = new URL(window.location.href);
115-
url.searchParams.delete('success');
116-
window.history.replaceState({}, document.title, url);
117-
}
86+
// Copy selected radio into hidden feedback input
87+
const checked = Array.from(
88+
form.querySelectorAll('input[name="feedback"]:checked')
89+
).map(el => el.value);
90+
// pick the first non-empty
91+
const first = checked.find(v => v.trim() !== '') || '';
92+
form.elements['feedback'].value = first;
93+
94+
// Populate hidden metadata
95+
form.elements['url'].value = window.location.href;
96+
form.elements['positiveFeedback'].value = positive;
97+
form.elements['version'].value = form.dataset.version || '';
98+
form.elements['beta'].value = form.dataset.beta === 'true';
99+
form.elements['date'].value = new Date().toISOString();
100+
form.elements['navigator'].value = `${navigator.userAgent}, ${navigator.language}`;
101+
sessionStorage.setItem('feedbackType', positive ? 'positive' : 'negative');
102+
103+
// Build URL-encoded body per Netlify AJAX requirements
104+
const formData = new FormData(form);
105+
const body = new URLSearchParams();
106+
for (const [key, value] of formData.entries()) {
107+
body.append(key, value);
108+
}
109+
110+
try {
111+
const resp = await fetch(window.location.pathname, {
112+
method: 'POST',
113+
headers: {
114+
'Content-Type': 'application/x-www-form-urlencoded',
115+
'Accept': 'application/json'
116+
},
117+
body: body.toString()
118+
});
119+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
120+
// On success, show custom message
121+
form.hidden = true;
122+
if (successMsg) successMsg.classList.add('hidden');
123+
if (successMsgToc) successMsgToc.classList.add('hidden');
124+
const toastToShow = fromToc ? successMsgToc : successMsg;
125+
if (toastToShow) {
126+
toastToShow.classList.remove('hidden');
127+
setTimeout(() => toastToShow.classList.add('hidden'), 5000);
128+
}
129+
} catch (err) {
130+
console.error(err);
131+
alert('Submission failed—please try again later.');
132+
} finally {
133+
submitBtn.disabled = false;
134+
}
135+
});
136+
137+
window.closeForm = ev => {
138+
ev.preventDefault();
139+
form.classList.add('hidden');
140+
};
118141
});
119142
})();
120143
</script>

src/partials/feedback-forms.hbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<form id="feedbackForm" class="hidden" name="feedbackForm" method="POST" netlify-honeypot="bot-field" data-netlify="true" data-netlify-recaptcha="true" action="?success=true">
1+
<form id="feedbackForm" class="hidden" name="feedbackForm" method="POST" netlify-honeypot="bot-field" data-netlify="true" data-netlify-recaptcha="true">
2+
<input type="hidden" name="form-name" value="feedbackForm" />
23

34
<!-- Honeypot -->
45
<p class="hidden">
@@ -11,6 +12,7 @@
1112
<input type="hidden" name="positiveFeedback">
1213
<input type="hidden" name="beta">
1314
<input type="hidden" name="date">
15+
<input type="radio" name="feedback" value="" hidden>
1416
<input type="hidden" name="navigator">
1517

1618
<div class="feedback-modal">

src/partials/feedback-success.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
<div id="feedback-toast" class="feedback-toast hidden" role="status" aria-live="polite">
1+
<div id="feedback-toast{{#if id}}-{{id}}{{/if}}" class="feedback-toast hidden" role="status" aria-live="polite">
22
🎉 Thanks for your feedback!
33
</div>

src/partials/toc.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
</ul>
1313
</div>
1414
{{> thumbs id='thumbs-toc'}}
15+
{{> feedback-success id='thumbs-toc'}}
1516
</aside>
1617
{{/if}}

0 commit comments

Comments
 (0)