From 5a6db9c20fea1671494563f2c2c8910630092dbb Mon Sep 17 00:00:00 2001 From: WorkRoot Agent Date: Wed, 17 Jun 2026 20:33:14 +0530 Subject: [PATCH] feat(homepage): expand inline lead form to name/email/phone/type/message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the homepage InlineLeadForm from a single combined "email or phone" contact field to an explicit 5-field layout — name, email (type=email), phone (type=tel), project type and an optional message — so each field gets the correct mobile keyboard and leads carry a real email + phone separately. - Required fields kept to name + email + project type for a low-friction mobile flow; phone and message are optional. - Email uses type=email + inputmode=email; phone uses type=tel + inputmode=tel for the numeric keypad. Message is an optional textarea; when blank the client synthesizes a >=10 char summary so the /api/contact validator passes. - Still posts to the same /api/contact endpoint tagged source=homepage-inline, delivering leads exactly the same way the /contact page does (no page nav). - Update inline-lead-form.spec.ts to the new field set and keyboards. Co-Authored-By: Claude Opus 4.8 --- src/components/InlineLeadForm.astro | 242 +++++++++++++++++++--------- src/pages/index.astro | 8 +- tests/inline-lead-form.spec.ts | 46 ++++-- 3 files changed, 200 insertions(+), 96 deletions(-) diff --git a/src/components/InlineLeadForm.astro b/src/components/InlineLeadForm.astro index ebb35e5..2c66ac3 100644 --- a/src/components/InlineLeadForm.astro +++ b/src/components/InlineLeadForm.astro @@ -1,20 +1,25 @@ --- -// InlineLeadForm — Three-field lead capture rendered inline on the homepage. +// InlineLeadForm — Compact lead capture rendered inline on the homepage. // -// The goal is to cut the friction step of routing every CTA through /contact. -// Visitors who reach the bottom of the homepage can submit name + contact -// (email OR phone) + project type without a navigation. The submission reuses -// /api/contact under the hood and is tagged `source: 'homepage-inline'` so the -// backend / analytics layer can segment these leads cleanly. +// Goal: let visitors who reach the bottom of the homepage submit a project +// enquiry without the friction of navigating to /contact. The submission +// reuses the SAME /api/contact endpoint as the full contact page and is tagged +// `source: 'homepage-inline'` so the backend / analytics layer can segment +// these leads cleanly (it also routes them to the `[Project Inquiry]` admin +// subject prefix). // -// Why a single "contact" field instead of separate email + phone: -// - 3 fields keeps the form psychologically tiny and dramatically lifts -// completion. Real-world conversion testing repeatedly shows 3 → 5+ fields -// is the biggest single-step drop-off on B2B lead forms. -// - We sniff the value on submit. If it has an "@" we treat it as email. -// Otherwise we treat it as a phone and synthesize a placeholder email to -// satisfy the backend's strict email validation. The phone is also sent -// through, so the admin email body shows the real reachable value. +// Fields (kept to 5, mobile-first): +// 1. Full name — required +// 2. Email — required, type="email" (email keyboard on mobile) +// 3. Phone — optional, type="tel" (numeric keypad on mobile) +// 4. Project type — required, + + +
= 7; + } + function validate() { var ok = true; @@ -322,14 +390,21 @@ showError(nameError, false); } - var contactVal = (contactInput.value || '').trim(); - var isEmail = EMAIL_REGEX.test(contactVal); - var isPhone = PHONE_REGEX.test(contactVal) && digitsOnly(contactVal).length >= 7; - if (!contactVal || (!isEmail && !isPhone)) { - showError(contactError, true); + var emailVal = (emailInput.value || '').trim(); + if (!EMAIL_REGEX.test(emailVal)) { + showError(emailError, true); ok = false; } else { - showError(contactError, false); + showError(emailError, false); + } + + // Phone is optional — only validate format when something was typed. + var phoneVal = (phoneInput.value || '').trim(); + if (phoneVal && !phoneIsValid(phoneVal)) { + showError(phoneError, true); + ok = false; + } else { + showError(phoneError, false); } var projectVal = projectTypeInput.value || ''; @@ -340,13 +415,24 @@ showError(projectTypeError, false); } + // Message is optional — only enforce the min length when something was typed. + var messageVal = (messageInput.value || '').trim(); + if (messageVal && messageVal.length < 10) { + showError(messageError, true); + ok = false; + } else { + showError(messageError, false); + } + return ok; } - // Live-validate on input/change so the error UI doesn't linger after fix. + // Live-validate on blur/change so the error UI clears once a field is fixed. nameInput.addEventListener('blur', validate); - contactInput.addEventListener('blur', validate); + emailInput.addEventListener('blur', validate); + phoneInput.addEventListener('blur', validate); projectTypeInput.addEventListener('change', validate); + messageInput.addEventListener('blur', validate); // Map the user-friendly project type label to the backend subject code so // the existing /api/contact validator passes. Anything unmapped → 'other'. @@ -359,41 +445,39 @@ }; function buildPayload() { - var contactVal = (contactInput.value || '').trim(); - var isEmail = EMAIL_REGEX.test(contactVal); var nameVal = (nameInput.value || '').trim(); + var emailVal = (emailInput.value || '').trim(); + var phoneVal = (phoneInput.value || '').trim(); var projectType = projectTypeInput.value || 'Other'; var subject = SUBJECT_BY_PROJECT_TYPE[projectType] || 'other'; + var messageVal = (messageInput.value || '').trim(); - var email, phone; - if (isEmail) { - email = contactVal; - phone = undefined; + // The backend requires a message of at least 10 characters. When the + // visitor leaves the (optional) details blank, synthesize a concise + // summary so the submission still goes through and the admin email + // carries the useful context. + var message; + if (messageVal.length >= 10) { + message = messageVal; } else { - // Treat as phone. Synthesize a stable placeholder email so the - // backend's strict email validator passes — the admin notification - // surfaces the real phone in both Phone: and Message: fields so the - // team always has a real way to reach the lead. - var digits = digitsOnly(contactVal) || 'unknown'; - email = 'lead-' + digits + '@homepage.workroot.in'; - phone = contactVal; + message = + 'New project enquiry from the homepage form.\n\n' + + 'Project type: ' + projectType + '\n' + + 'Name: ' + nameVal + + (phoneVal ? '\nPhone: ' + phoneVal : '') + + (messageVal ? '\n\nNote: ' + messageVal : ''); } - var message = - 'New inline lead from the homepage.\n\n' + - 'Project type: ' + projectType + '\n' + - 'Preferred contact: ' + contactVal; - var payload = { name: nameVal, - email: email, + email: emailVal, subject: subject, message: message, source: 'homepage-inline', projectType: projectType, website: (websiteInput && websiteInput.value) || '', }; - if (phone) payload.phone = phone; + if (phoneVal) payload.phone = phoneVal; return payload; } diff --git a/src/pages/index.astro b/src/pages/index.astro index e2d5fd1..c51e56e 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -744,9 +744,11 @@ const techStack = [ + submit a compact enquiry (name, email, phone, project type, message) + without leaving the homepage. Required fields are kept to name + email + + project type for a low-friction mobile flow. Submits to the same + /api/contact endpoint and is tagged `source: 'homepage-inline'` so the + lead can be segmented downstream. -->