feat: update contact form for instant response UX with loading states and clear feedback
This commit is contained in:
+23
-3
@@ -437,7 +437,7 @@ const trustStats = [
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-2xl font-bold text-secondary-900 dark:text-secondary-100 mb-2">Message Sent!</h3>
|
<h3 class="text-2xl font-bold text-secondary-900 dark:text-secondary-100 mb-2">Message Sent!</h3>
|
||||||
<p class="text-secondary-600 dark:text-secondary-400 mb-6">Thank you for reaching out. We'll get back to you within 24 hours.</p>
|
<p class="text-secondary-600 dark:text-secondary-400 mb-6">Message received! We will get back to you shortly.</p>
|
||||||
<button
|
<button
|
||||||
id="send-another"
|
id="send-another"
|
||||||
class="inline-flex items-center gap-2 text-primary hover:text-primary-700 font-medium text-sm transition-colors"
|
class="inline-flex items-center gap-2 text-primary hover:text-primary-700 font-medium text-sm transition-colors"
|
||||||
@@ -1022,10 +1022,18 @@ const trustStats = [
|
|||||||
let success = false;
|
let success = false;
|
||||||
let errorMessage = 'Something went wrong. Please try again.';
|
let errorMessage = 'Something went wrong. Please try again.';
|
||||||
|
|
||||||
|
// Track when loading started for minimum display time
|
||||||
|
const loadingStart = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Set up a 10-second timeout using AbortController
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 10_000);
|
||||||
|
|
||||||
const response = await fetch('/api/contact', {
|
const response = await fetch('/api/contact', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
signal: controller.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: (form.elements.namedItem('name') as HTMLInputElement)?.value.trim(),
|
name: (form.elements.namedItem('name') as HTMLInputElement)?.value.trim(),
|
||||||
email: (form.elements.namedItem('email') as HTMLInputElement)?.value.trim(),
|
email: (form.elements.namedItem('email') as HTMLInputElement)?.value.trim(),
|
||||||
@@ -1036,6 +1044,8 @@ const trustStats = [
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
const data = await response.json() as { success: boolean; message?: string; error?: string };
|
const data = await response.json() as { success: boolean; message?: string; error?: string };
|
||||||
|
|
||||||
if (response.ok && data.success) {
|
if (response.ok && data.success) {
|
||||||
@@ -1043,9 +1053,19 @@ const trustStats = [
|
|||||||
} else {
|
} else {
|
||||||
errorMessage = data.error ?? 'Failed to send message. Please try again.';
|
errorMessage = data.error ?? 'Failed to send message. Please try again.';
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
if (err instanceof DOMException && err.name === 'AbortError') {
|
||||||
|
errorMessage = 'Request timed out. Please check your connection and try again.';
|
||||||
|
} else {
|
||||||
errorMessage = 'Network error. Please check your connection and try again.';
|
errorMessage = 'Network error. Please check your connection and try again.';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure loading state is shown for at least 300ms to avoid jarring flash
|
||||||
|
const elapsed = Date.now() - loadingStart;
|
||||||
|
if (elapsed < 300) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300 - elapsed));
|
||||||
|
}
|
||||||
|
|
||||||
// Track analytics
|
// Track analytics
|
||||||
const subject = (form.elements.namedItem('subject') as HTMLSelectElement)?.value;
|
const subject = (form.elements.namedItem('subject') as HTMLSelectElement)?.value;
|
||||||
@@ -1062,7 +1082,7 @@ const trustStats = [
|
|||||||
// Show success state inline
|
// Show success state inline
|
||||||
form.classList.add('hidden');
|
form.classList.add('hidden');
|
||||||
if (successState) successState.classList.remove('hidden');
|
if (successState) successState.classList.remove('hidden');
|
||||||
showToast("Message sent! We'll get back to you within 24 hours.", 'success');
|
showToast('Message received! We will get back to you shortly.', 'success');
|
||||||
} else {
|
} else {
|
||||||
showToast(errorMessage, 'error');
|
showToast(errorMessage, 'error');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user