Enhance ContactForm and CookieBanner components for improved accessibility and user feedback
- Added CSRF token handling in the ContactForm for enhanced security. - Introduced a feedback div for displaying form submission results instead of alerts. - Updated the CookieBanner to include ARIA roles and improved focus management for better accessibility. - Refactored manual review email handling to escape HTML special characters, enhancing security.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<form id="contact-form">
|
||||
<input type="hidden" name="csrf_token" id="csrf_token" />
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" id="email" required aria-describedby="email-help" />
|
||||
<div id="email-help" class="sr-only">Enter your email address</div>
|
||||
@@ -8,7 +9,9 @@
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
|
||||
<div id="spam-warning" style="display:none;">
|
||||
<div id="form-feedback" role="alert" aria-live="assertive"></div>
|
||||
|
||||
<div id="spam-warning" style="display:none;" tabindex="-1">
|
||||
<p>
|
||||
Your message was detected as spam and was not sent.<br>
|
||||
If you believe this is a mistake, you can request a manual review.
|
||||
@@ -16,7 +19,7 @@
|
||||
<form id="manual-review-form">
|
||||
<label for="manual-email">Email</label>
|
||||
<input type="email" id="manual-email" readonly aria-describedby="manual-email-help" />
|
||||
<div id="manual-email-help" class="sr-only">Re-enter your email address for confirmation</div>
|
||||
<div id="manual-email-help" class="sr-only">Your email address.</div>
|
||||
<label for="manual-justification">Why is this not spam? (optional)</label>
|
||||
<textarea id="manual-justification" placeholder="Why is this not spam? (optional)" aria-describedby="manual-justification-help"></textarea>
|
||||
<div id="manual-justification-help" class="sr-only">Explain why your message is legitimate</div>
|
||||
@@ -27,8 +30,25 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function fetchCsrfToken() {
|
||||
try {
|
||||
const response = await fetch('/api/contact?csrf=true');
|
||||
const data = await response.json();
|
||||
const csrfTokenInput = document.getElementById('csrf_token');
|
||||
if (csrfTokenInput) {
|
||||
(csrfTokenInput as HTMLInputElement).value = data.csrfToken;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching CSRF token:', error);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', fetchCsrfToken);
|
||||
|
||||
const contactForm = document.getElementById('contact-form');
|
||||
if (contactForm) {
|
||||
const feedbackDiv = document.getElementById('form-feedback');
|
||||
|
||||
if (contactForm && feedbackDiv) {
|
||||
contactForm.onsubmit = async function (e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this as HTMLFormElement);
|
||||
@@ -38,7 +58,7 @@ if (contactForm) {
|
||||
data = await res.json();
|
||||
console.log('Contact API response:', data);
|
||||
} catch (_err) {
|
||||
alert('Unexpected server response.');
|
||||
feedbackDiv.textContent = 'Unexpected server response.';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,14 +71,15 @@ if (contactForm) {
|
||||
spamWarning.style.display = 'block';
|
||||
manualEmail.value = String(formData.get('email'));
|
||||
manualToken.value = data.token;
|
||||
spamWarning.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
alert(data.error || 'There was an error sending your message.');
|
||||
feedbackDiv.textContent = data.error || 'There was an error sending your message.';
|
||||
} else {
|
||||
alert('Your message was sent successfully!');
|
||||
feedbackDiv.textContent = 'Your message was sent successfully!';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -9,9 +9,14 @@ const t = getTranslation(lang);
|
||||
id="cookie-banner"
|
||||
class="fixed bottom-0 left-0 right-0 z-50 p-4 content-backdrop shadow-lg transform transition-transform duration-300 translate-y-full"
|
||||
style="display: none;"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="cookie-banner-title"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="container mx-auto max-w-6xl flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div class="text-sm text-gray-800 dark:text-gray-200 font-medium">
|
||||
<h2 id="cookie-banner-title" class="sr-only">Cookie Consent</h2>
|
||||
<p>
|
||||
{t.cookies.message}
|
||||
<a href={`/${lang}/privacy#cookie-usage`} class="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
@@ -83,6 +88,7 @@ const t = getTranslation(lang);
|
||||
// Show the banner with a slight delay for better UX
|
||||
setTimeout(() => {
|
||||
cookieBanner.classList.remove('translate-y-full');
|
||||
cookieBanner.focus();
|
||||
}, 500);
|
||||
|
||||
// Handle accept button click
|
||||
|
Reference in New Issue
Block a user