From 205ada99e92e9a4448167af269edfe213fec7af4 Mon Sep 17 00:00:00 2001 From: Satyam <95536056+Satyam-Rastogi@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:22:54 +0530 Subject: [PATCH] =?UTF-8?q?feat(leads):=20Lead=20Creation=20Phase=204=20?= =?UTF-8?q?=E2=80=94=20Property=20section=20with=20address,=20state=20sele?= =?UTF-8?q?ct,=20and=20photo=20upload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LeadPropertySection: Street Address (with pin icon), City/State/ZIP row (flex layout, ZIP auto-strips non-digits to 5 chars, state defaults to TX), Property Type dropdown and Site Photos (Full Form only, animate in/out) - Photo upload: dashed zone with Camera + Image icons, hover highlight in emerald, hidden file input with capture=environment (opens camera on mobile), multiple selection allowed - Thumbnails: 4-col grid, spring scale-in animation, hover overlay with X to remove individual photos - Focus ring accent changed to emerald to match section color identity (blue=contact, emerald=property) - CreateLeadPage: propertyPhotos added to INITIAL_FORM, sectionCompletion.property live (address + city required), LeadPropertySection wired into renderSectionContent --- src/components/leads/LeadPropertySection.jsx | 271 +++++++++++++++++++ src/pages/CreateLeadPage.jsx | 15 +- 2 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 src/components/leads/LeadPropertySection.jsx diff --git a/src/components/leads/LeadPropertySection.jsx b/src/components/leads/LeadPropertySection.jsx new file mode 100644 index 0000000..ece6314 --- /dev/null +++ b/src/components/leads/LeadPropertySection.jsx @@ -0,0 +1,271 @@ +import React, { useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { MapPin, Camera, X, ChevronDown, Image } from 'lucide-react'; +import { useTheme } from '../../context/ThemeContext'; +import { LEAD_FORM_OPTIONS } from '../../data/mockStore'; + +// --------------------------------------------------------------------------- +// Shared field primitives (local) +// --------------------------------------------------------------------------- +function FieldLabel({ children }) { + return ( + + ); +} + +function LeadInput({ label, value, onChange, placeholder, type = 'text', autoComplete, inputMode }) { + const { theme } = useTheme(); + const isDark = theme === 'dark'; + return ( +
+ {label && {label}} + onChange(e.target.value)} + placeholder={placeholder} + autoComplete={autoComplete} + className={`w-full rounded-xl px-4 py-3 text-sm font-medium + outline-none transition-all duration-150 + ${isDark + ? 'bg-zinc-800/60 border border-zinc-700 text-white placeholder-zinc-500 focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20' + : 'bg-white border border-zinc-200 text-zinc-800 placeholder-zinc-400 focus:border-emerald-400 focus:ring-2 focus:ring-emerald-100' + } + `} + /> +
+ ); +} + +function LeadSelect({ label, value, onChange, options, placeholder }) { + const { theme } = useTheme(); + const isDark = theme === 'dark'; + return ( +
+ {label && {label}} +
+ + +
+
+ ); +} + +// Collapse transition shared config +const collapseTransition = { + type: 'spring', + stiffness: 300, + damping: 32, + opacity: { duration: 0.15 }, +}; + +// --------------------------------------------------------------------------- +// Property Section +// --------------------------------------------------------------------------- +export default function LeadPropertySection({ formData, updateField, isQuickCapture }) { + const { theme } = useTheme(); + const isDark = theme === 'dark'; + const fileInputRef = useRef(null); + + const handlePhotoAdd = (e) => { + const files = Array.from(e.target.files || []); + if (!files.length) return; + const newPhotos = files.map(file => ({ + id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, + url: URL.createObjectURL(file), + name: file.name, + })); + updateField('propertyPhotos', [...(formData.propertyPhotos || []), ...newPhotos]); + // Reset input so the same file can be re-added after removal + e.target.value = ''; + }; + + const removePhoto = (id) => { + updateField('propertyPhotos', (formData.propertyPhotos || []).filter(p => p.id !== id)); + }; + + return ( +
+ {/* ---- Street Address ---- */} +
+ Street Address +
+ + updateField('address', e.target.value)} + placeholder="123 Main St" + autoComplete="street-address" + className={`w-full rounded-xl pl-9 pr-4 py-3 text-sm font-medium + outline-none transition-all duration-150 + ${isDark + ? 'bg-zinc-800/60 border border-zinc-700 text-white placeholder-zinc-500 focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20' + : 'bg-white border border-zinc-200 text-zinc-800 placeholder-zinc-400 focus:border-emerald-400 focus:ring-2 focus:ring-emerald-100' + } + `} + /> +
+
+ + {/* ---- City / State / ZIP ---- */} +
+ {/* City — flex-1 */} +
+ updateField('city', v)} + placeholder="Plano" + autoComplete="address-level2" + /> +
+ + {/* State — fixed narrow */} +
+ updateField('state', v)} + options={LEAD_FORM_OPTIONS.usStates} + /> +
+ + {/* ZIP — fixed narrow */} +
+ updateField('zip', v.replace(/\D/g, '').slice(0, 5))} + placeholder="75023" + type="text" + inputMode="numeric" + autoComplete="postal-code" + /> +
+
+ + {/* ---- Property Type + Photo Upload — Full Form only ---- */} + + {!isQuickCapture && ( + +
+ updateField('propertyType', v)} + options={LEAD_FORM_OPTIONS.leadTypes} + placeholder="Residential, Commercial…" + /> +
+ + {/* ---- Photo Upload ---- */} +
+ Site Photos + + {/* Upload zone */} + + + {/* Hidden file input */} + + + {/* Photo thumbnails */} + {(formData.propertyPhotos || []).length > 0 && ( +
+ + {(formData.propertyPhotos || []).map(photo => ( + + {photo.name} + {/* Remove overlay */} + + + ))} + +
+ )} +
+
+ )} +
+
+ ); +} diff --git a/src/pages/CreateLeadPage.jsx b/src/pages/CreateLeadPage.jsx index b438bda..ebad923 100644 --- a/src/pages/CreateLeadPage.jsx +++ b/src/pages/CreateLeadPage.jsx @@ -10,6 +10,7 @@ import { import LeadSectionWrapper from '../components/leads/LeadSectionWrapper'; import LeadJobSection from '../components/leads/LeadJobSection'; import LeadContactSection from '../components/leads/LeadContactSection'; +import LeadPropertySection from '../components/leads/LeadPropertySection'; import gsap from 'gsap'; // --------------------------------------------------------------------------- @@ -91,12 +92,13 @@ const INITIAL_FORM = { lastName: '', phones: [{ id: '1', number: '', type: 'Mobile', isPrimary: true }], emails: [], - // Property (Phase 4) + // Property address: '', city: '', state: 'TX', zip: '', propertyType: '', + propertyPhotos: [], // Insurance (Phase 5) insuranceCompany: '', claimNumber: '', @@ -138,7 +140,7 @@ export default function CreateLeadPage() { formData.lastName && formData.phones.some(p => p.number.replace(/\D/g, '').length >= 10) ), - property: false, // Phase 4 + property: !!(formData.address && formData.city), job: isQuickCapture ? !!formData.leadSource : !!(formData.leadSource && formData.leadType && formData.workType), @@ -193,6 +195,15 @@ export default function CreateLeadPage() { /> ); } + if (section.id === 'property') { + return ( + + ); + } if (section.id === 'job') { return (