feat(leads): Lead Creation Phase 3 — Contact section with dynamic phone and email rows

- LeadContactSection: First/Last name grid, dynamic phone list, dynamic email list (Full Form only), all with spring height animations and LayoutGroup for coordinated layout shifts
- PhoneEntryRow: phone input with (555) 000-0000 mask, type selector (Mobile/Home/Work/Other), amber star primary toggle, red X remove — type+star hidden in Quick Capture mode
- EmailEntryRow: email input, type selector, star primary toggle, remove button
- Add Phone / Add Email buttons animate in when switching to Full Form; rows slide in/out with spring height collapse
- CreateLeadPage: sectionCompletion.contact now live (firstName + lastName + 10-digit phone), progress bar switched to section-completion-based percentage (completed sections / visible sections)
This commit is contained in:
Satyam
2026-03-13 16:18:51 +05:30
parent a2e4fc9039
commit d8868f67f1
4 changed files with 468 additions and 23 deletions
+32 -23
View File
@@ -9,6 +9,7 @@ import {
} from 'lucide-react';
import LeadSectionWrapper from '../components/leads/LeadSectionWrapper';
import LeadJobSection from '../components/leads/LeadJobSection';
import LeadContactSection from '../components/leads/LeadContactSection';
import gsap from 'gsap';
// ---------------------------------------------------------------------------
@@ -85,10 +86,10 @@ const INITIAL_FORM = {
tradeType: '',
urgency: 'standard',
notes: '',
// Contact (Phase 3)
// Contact
firstName: '',
lastName: '',
phones: [],
phones: [{ id: '1', number: '', type: 'Mobile', isPrimary: true }],
emails: [],
// Property (Phase 4)
address: '',
@@ -130,14 +131,26 @@ export default function CreateLeadPage() {
const progressBarRef = useRef(null);
// Derived: which fields must be filled for this mode
const requiredFields = isQuickCapture
? ['leadSource']
: ['leadSource', 'leadType', 'workType'];
// Section completion map
const sectionCompletion = {
contact: !!(
formData.firstName &&
formData.lastName &&
formData.phones.some(p => p.number.replace(/\D/g, '').length >= 10)
),
property: false, // Phase 4
job: isQuickCapture
? !!formData.leadSource
: !!(formData.leadSource && formData.leadType && formData.workType),
insurance: false, // Phase 5
assignment: false, // Phase 6
};
const filledCount = requiredFields.filter(f => formData[f]).length;
const progress = requiredFields.length > 0
? Math.round((filledCount / requiredFields.length) * 100)
// Progress = % of visible sections that are complete
const visibleSectionIds = (isQuickCapture ? SECTIONS.filter(s => s.quickCapture) : SECTIONS).map(s => s.id);
const completedCount = visibleSectionIds.filter(id => sectionCompletion[id]).length;
const progress = visibleSectionIds.length > 0
? Math.round((completedCount / visibleSectionIds.length) * 100)
: 0;
// GSAP animates the progress bar whenever progress changes
@@ -151,17 +164,6 @@ export default function CreateLeadPage() {
}
}, [progress]);
// Section completion map — grows as phases add fields
const sectionCompletion = {
contact: false, // Phase 3
property: false, // Phase 4
job: isQuickCapture
? !!formData.leadSource
: !!(formData.leadSource && formData.leadType && formData.workType),
insurance: false, // Phase 5
assignment: false, // Phase 6
};
const updateField = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
@@ -170,9 +172,7 @@ export default function CreateLeadPage() {
setOpenSections(prev => ({ ...prev, [id]: !prev[id] }));
};
const visibleSections = isQuickCapture
? SECTIONS.filter(s => s.quickCapture)
: SECTIONS;
const visibleSections = isQuickCapture ? SECTIONS.filter(s => s.quickCapture) : SECTIONS;
const modeSwitchTo = (quick) => {
if (quick === isQuickCapture) return;
@@ -184,6 +184,15 @@ export default function CreateLeadPage() {
// Render the content for a given section (placeholder until its phase is built)
const renderSectionContent = (section) => {
if (section.id === 'contact') {
return (
<LeadContactSection
formData={formData}
updateField={updateField}
isQuickCapture={isQuickCapture}
/>
);
}
if (section.id === 'job') {
return (
<LeadJobSection