feat(contact): CRO improvements — align services dropdown, value-prop H1, response promise above form, tighter message field
E2E Test Suite / Form Interaction Tests (push) Failing after 10m53s
E2E Test Suite / Security Header Tests (push) Failing after 10m39s
E2E Test Suite / Mobile Device Tests (push) Failing after 11m31s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 12m23s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m16s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m8s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 15m0s
Ping Search Engines / Notify Search Engines (push) Failing after 14m47s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m30s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Test Report Summary (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Failing after 14m58s
Deploy to Production / Build & Verify (push) Failing after 19m22s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Deploy to Render (push) Failing after 14m57s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m5s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
E2E Test Suite / Form Interaction Tests (push) Failing after 10m53s
E2E Test Suite / Security Header Tests (push) Failing after 10m39s
E2E Test Suite / Mobile Device Tests (push) Failing after 11m31s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 12m23s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m16s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m8s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 15m0s
Ping Search Engines / Notify Search Engines (push) Failing after 14m47s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m30s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Test Report Summary (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Failing after 14m58s
Deploy to Production / Build & Verify (push) Failing after 19m22s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Deploy to Render (push) Failing after 14m57s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m5s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
Addresses contact-page audit points 22–27: - Confirmed no '200+ companies' credibility claim; canonical stat set retained - Service dropdown now matches real services, led by ERP/Enterprise + Government IT; removed Cloud Services and AI & ML, added UI/UX Design - Backend allowlist + labels updated to accept 'ui-ux' and reflect real service names - H1 paired with value prop: free 1-page proposal in 24 hours - '< 24h response' promise badge moved above the form - Clarified contradictory hours to 'We reply within 24 business hours, Mon–Fri' - Reduced message textarea height (rows 5→4) and max length (5000→2000) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0b8c2163f3
commit
721d683eb9
@@ -35,8 +35,8 @@ Both endpoints include server-side validation, rate limiting, spam protection, a
|
||||
- `name` — required, 2–100 characters
|
||||
- `email` — required, valid format, max 254 chars
|
||||
- `phone` — optional; if present, must match phone regex
|
||||
- `subject` — required, allowlist enum (web-development, mobile-development, cloud-services, ai-ml, consulting, support, other)
|
||||
- `message` — required, 10–5000 characters
|
||||
- `subject` — required, allowlist enum (erp-solutions, government-systems, web-development, mobile-development, ui-ux, consulting, other; legacy values cloud-services, ai-ml, support still accepted by the API but no longer offered in the contact-page dropdown)
|
||||
- `message` — required, 10–2000 characters
|
||||
- `website` — honeypot, must be empty
|
||||
|
||||
**Rate limit:** 5 requests per IP per hour
|
||||
|
||||
@@ -46,6 +46,7 @@ const PHONE_REGEX = /^[\+]?[(]?[0-9]{1,3}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[
|
||||
const VALID_SUBJECTS = [
|
||||
'web-development',
|
||||
'mobile-development',
|
||||
'ui-ux',
|
||||
'cloud-services',
|
||||
'ai-ml',
|
||||
'consulting',
|
||||
@@ -312,12 +313,13 @@ async function sendContactEmail(data: ContactFormData): Promise<SendResult | voi
|
||||
const subjectLabels: Record<string, string> = {
|
||||
'web-development': 'Web Development',
|
||||
'mobile-development': 'Mobile Development',
|
||||
'ui-ux': 'UI/UX Design',
|
||||
'cloud-services': 'Cloud Services',
|
||||
'ai-ml': 'AI & Machine Learning',
|
||||
'consulting': 'IT Consulting',
|
||||
'consulting': 'IT Consulting / DevOps',
|
||||
'support': 'Technical Support',
|
||||
'erp-solutions': 'ERP / Custom Software',
|
||||
'government-systems': 'Government / GeM',
|
||||
'erp-solutions': 'ERP / Enterprise Systems',
|
||||
'government-systems': 'Government IT',
|
||||
'other': 'Other',
|
||||
};
|
||||
|
||||
|
||||
+23
-14
@@ -34,7 +34,7 @@ const contactInfo = [
|
||||
{
|
||||
icon: 'clock',
|
||||
title: 'Business Hours',
|
||||
details: ['Mon – Fri: 10:00 AM – 6:00 PM', 'Sat – Sun: Closed'],
|
||||
details: ['Mon – Fri: 10:00 AM – 6:00 PM', 'We reply within 24 business hours, Mon – Fri.'],
|
||||
link: null,
|
||||
linkLabel: null,
|
||||
color: 'violet',
|
||||
@@ -125,7 +125,7 @@ const trustStats = [
|
||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-300 to-primary-500">Hear From You</span>
|
||||
</h1>
|
||||
<p class="text-xl text-secondary-300 max-w-2xl mx-auto reveal-on-scroll reveal-delay-2">
|
||||
Whether you have a project in mind, a question about our services, or just want to explore the possibilities — our team is ready to help.
|
||||
Tell us about your project — get a free 1-page proposal in 24 hours.
|
||||
</p>
|
||||
|
||||
<!-- Trust stats strip -->
|
||||
@@ -151,8 +151,17 @@ const trustStats = [
|
||||
<div class="bg-white dark:bg-secondary-800 rounded-2xl shadow-card border border-secondary-100 dark:border-secondary-700 p-8 lg:p-10">
|
||||
<!-- Form Header -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold text-secondary-900 dark:text-secondary-100 mb-2">Send Us a Message</h2>
|
||||
<p class="text-secondary-500 dark:text-secondary-400 text-sm">Fill out the form below and we'll respond within 24 hours.</p>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-2">
|
||||
<h2 class="text-2xl font-bold text-secondary-900 dark:text-secondary-100">Send Us a Message</h2>
|
||||
<!-- Response promise — shown ABOVE the form so the commitment is visible before users invest time -->
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 text-emerald-700 dark:text-emerald-300 text-xs font-semibold whitespace-nowrap">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
< 24h response
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-secondary-500 dark:text-secondary-400 text-sm">Fill out the form below and we'll reply within 24 business hours, Mon – Fri.</p>
|
||||
</div>
|
||||
|
||||
<form id="contact-form" class="space-y-5" novalidate>
|
||||
@@ -325,12 +334,12 @@ const trustStats = [
|
||||
class="form-input w-full pl-10 pr-8 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 appearance-none cursor-pointer"
|
||||
>
|
||||
<option value="">Select a service...</option>
|
||||
<option value="erp-solutions">ERP / Enterprise Systems</option>
|
||||
<option value="government-systems">Government IT</option>
|
||||
<option value="web-development">Web Development</option>
|
||||
<option value="mobile-development">Mobile Development</option>
|
||||
<option value="cloud-services">Cloud Services</option>
|
||||
<option value="ai-ml">AI & Machine Learning</option>
|
||||
<option value="consulting">IT Consulting</option>
|
||||
<option value="support">Technical Support</option>
|
||||
<option value="ui-ux">UI/UX Design</option>
|
||||
<option value="consulting">IT Consulting / DevOps</option>
|
||||
<option value="other">Other / General Inquiry</option>
|
||||
</select>
|
||||
<div class="valid-check absolute inset-y-0 right-9 flex items-center hidden" aria-label="Valid">
|
||||
@@ -379,7 +388,7 @@ const trustStats = [
|
||||
Your Message <span class="text-red-500" aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</label>
|
||||
<span id="char-count" class="text-xs text-secondary-400" aria-hidden="true">0 / 5000</span>
|
||||
<span id="char-count" class="text-xs text-secondary-400" aria-hidden="true">0 / 2000</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<textarea
|
||||
@@ -387,8 +396,8 @@ const trustStats = [
|
||||
name="message"
|
||||
required
|
||||
minlength="10"
|
||||
maxlength="5000"
|
||||
rows="5"
|
||||
maxlength="2000"
|
||||
rows="4"
|
||||
aria-required="true"
|
||||
aria-describedby="message-error message-hint"
|
||||
class="form-input w-full px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 resize-none"
|
||||
@@ -895,8 +904,8 @@ const trustStats = [
|
||||
if (messageField && charCount) {
|
||||
messageField.addEventListener('input', () => {
|
||||
const len = messageField.value.length;
|
||||
charCount.textContent = `${len} / 5000`;
|
||||
charCount.className = len > 4500 ? 'text-xs text-red-500' : 'text-xs text-secondary-400';
|
||||
charCount.textContent = `${len} / 2000`;
|
||||
charCount.className = len > 1800 ? 'text-xs text-red-500' : 'text-xs text-secondary-400';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1111,7 +1120,7 @@ const trustStats = [
|
||||
form.querySelectorAll('.valid-check').forEach((el) => el.classList.add('hidden'));
|
||||
form.querySelectorAll('.invalid-check').forEach((el) => el.classList.add('hidden'));
|
||||
form.querySelectorAll('[data-error]').forEach((el) => el.classList.add('hidden'));
|
||||
if (charCount) charCount.textContent = '0 / 5000';
|
||||
if (charCount) charCount.textContent = '0 / 2000';
|
||||
budgetOptions.forEach((o) => o.classList.remove('selected'));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user