From d801f54600eab0f9459e9c6e5170fa178402627c Mon Sep 17 00:00:00 2001
From: Satyam <95536056+Satyam-Rastogi@users.noreply.github.com>
Date: Sat, 14 Mar 2026 23:43:08 +0530
Subject: [PATCH] =?UTF-8?q?feat(leads):=20Phase=205=20&=206=20=E2=80=94=20?=
=?UTF-8?q?Insurance=20and=20Assignment=20sections?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 5 — Insurance (purple #8B5CF6):
- Insurance Company custom select, Claim Number + Claim Status (2-col grid)
- Adjuster Name + Adjuster Phone (2-col grid, flex-wrapper with Phone icon + mask)
- Policy Number full-width input, all focus rings use purple accent
Phase 6 — Assignment (cyan #06B6D4):
- Role-aware rep selector: Field Agents see self-assign toggle, Admins/Owners
see scrollable list of all field agents with Unassigned option
- Priority pill selector (Low/Medium/High) with Framer Motion layoutId slide
- Follow-up Date: flex-wrapper with Calendar icon + color-scheme fix
- sectionCompletion wired for both sections
Fix: replace Framer Motion variant inheritance with explicit initial/animate on
each section motion.div — inherited variants from already-animated parent caused
Insurance and Assignment to render stuck at opacity:0 when added dynamically
Author: Satyam Rastogi
---
.../leads/LeadAssignmentSection.jsx | 235 ++++++++++++++++++
src/components/leads/LeadInsuranceSection.jsx | 138 ++++++++++
src/pages/CreateLeadPage.jsx | 66 ++---
3 files changed, 399 insertions(+), 40 deletions(-)
create mode 100644 src/components/leads/LeadAssignmentSection.jsx
create mode 100644 src/components/leads/LeadInsuranceSection.jsx
diff --git a/src/components/leads/LeadAssignmentSection.jsx b/src/components/leads/LeadAssignmentSection.jsx
new file mode 100644
index 0000000..79be5db
--- /dev/null
+++ b/src/components/leads/LeadAssignmentSection.jsx
@@ -0,0 +1,235 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { Calendar } from 'lucide-react';
+import { useMockStore } from '../../data/mockStore';
+import { useTheme } from '../../context/ThemeContext';
+
+// ---------------------------------------------------------------------------
+// Shared field primitives
+// ---------------------------------------------------------------------------
+function FieldLabel({ children }) {
+ return (
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Priority pill selector — Low / Medium / High
+// ---------------------------------------------------------------------------
+const PRIORITY_OPTIONS = [
+ { value: 'low', label: 'Low', darkColor: '#71717a', lightColor: '#71717a' },
+ { value: 'medium', label: 'Medium', darkColor: '#06B6D4', lightColor: '#0891b2' },
+ { value: 'high', label: 'High', darkColor: '#F59E0B', lightColor: '#d97706' },
+];
+
+function PriorityPillSelector({ value, onChange }) {
+ const { theme } = useTheme();
+ const isDark = theme === 'dark';
+
+ return (
+
+ {PRIORITY_OPTIONS.map(opt => {
+ const isSelected = value === opt.value;
+ return (
+
+ );
+ })}
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Rep selector — role-aware
+// Agents: only their own name (self-assign toggle)
+// Admin/Owner: scrollable list of all field agents
+// ---------------------------------------------------------------------------
+function RepSelector({ value, onChange, currentUser, agents }) {
+ const { theme } = useTheme();
+ const isDark = theme === 'dark';
+ const isAgent = currentUser?.role === 'FIELD_AGENT';
+
+ if (isAgent) {
+ // Simplified self-assign for field agents
+ const isSelf = value === currentUser.id;
+ return (
+
+ );
+ }
+
+ // Admin / Owner — scrollable agent list
+ return (
+
+
+ {/* Unassigned option */}
+
+
+ {agents.map(agent => {
+ const isSelected = value === agent.id;
+ const initials = agent.name.split(' ').map(n => n[0]).join('').slice(0, 2);
+ return (
+
+ );
+ })}
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Assignment Section — Phase 6 (Full Form only)
+// ---------------------------------------------------------------------------
+export default function LeadAssignmentSection({ formData, updateField, currentUser }) {
+ const { users } = useMockStore();
+ const agents = users.filter(u => u.role === 'FIELD_AGENT');
+
+ return (
+
+ {/* Assign Rep */}
+
+ Assign Rep
+ updateField('assignedTo', v)}
+ currentUser={currentUser}
+ agents={agents}
+ />
+
+
+ {/* Priority */}
+
+
Priority
+
updateField('priority', v)}
+ />
+
+
+ {/* Follow-up Date */}
+
+
Follow-up Date
+
+
+ updateField('followUpDate', e.target.value)}
+ className="flex-1 py-3 pl-3 pr-4 text-sm font-medium
+ outline-none bg-transparent
+ text-zinc-800 dark:text-white
+ [color-scheme:light] dark:[color-scheme:dark]"
+ />
+
+
+
+ );
+}
diff --git a/src/components/leads/LeadInsuranceSection.jsx b/src/components/leads/LeadInsuranceSection.jsx
new file mode 100644
index 0000000..9cbd63b
--- /dev/null
+++ b/src/components/leads/LeadInsuranceSection.jsx
@@ -0,0 +1,138 @@
+import React from 'react';
+import { Phone } from 'lucide-react';
+import { LEAD_FORM_OPTIONS } from '../../data/mockStore';
+import LeadCustomSelect from './LeadCustomSelect';
+
+// ---------------------------------------------------------------------------
+// Shared field primitives
+// ---------------------------------------------------------------------------
+function FieldLabel({ children }) {
+ return (
+
+ );
+}
+
+function LeadInput({ label, value, onChange, placeholder, type = 'text' }) {
+ return (
+
+ {label}
+ onChange(e.target.value)}
+ placeholder={placeholder}
+ className="w-full rounded-xl px-4 py-3 text-sm font-medium
+ outline-none transition-all duration-150
+ bg-white dark:bg-zinc-800/60
+ border border-zinc-200 dark:border-zinc-700
+ text-zinc-800 dark:text-white
+ placeholder-zinc-400 dark:placeholder-zinc-500
+ focus:border-purple-400 dark:focus:border-purple-500
+ focus:ring-2 focus:ring-purple-100 dark:focus:ring-purple-500/20"
+ />
+
+ );
+}
+
+// Phone input with (xxx) xxx-xxxx mask and purple focus ring
+function AdjusterPhoneInput({ label, value, onChange }) {
+ const handleChange = (e) => {
+ const digits = e.target.value.replace(/\D/g, '').slice(0, 10);
+ let formatted = digits;
+ if (digits.length >= 7) {
+ formatted = `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
+ } else if (digits.length >= 4) {
+ formatted = `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
+ } else if (digits.length >= 1) {
+ formatted = `(${digits}`;
+ }
+ onChange(formatted);
+ };
+
+ return (
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Insurance Section — Phase 5 (Full Form only)
+// ---------------------------------------------------------------------------
+export default function LeadInsuranceSection({ formData, updateField }) {
+ return (
+
+ {/* Row 1: Insurance Company (full width) */}
+
updateField('insuranceCompany', v)}
+ options={LEAD_FORM_OPTIONS.insuranceCompanies}
+ placeholder="Select carrier…"
+ accent="purple"
+ />
+
+ {/* Row 2: Claim Number + Claim Status */}
+
+ updateField('claimNumber', v)}
+ placeholder="e.g. CLM-2026-00482"
+ />
+ updateField('claimStatus', v)}
+ options={LEAD_FORM_OPTIONS.claimStatuses}
+ placeholder="Select status…"
+ accent="purple"
+ />
+
+
+ {/* Row 3: Adjuster Name + Adjuster Phone */}
+
+
updateField('adjusterName', v)}
+ placeholder="Full name"
+ />
+ updateField('adjusterPhone', v)}
+ />
+
+
+ {/* Row 4: Policy Number (full width) */}
+ updateField('policyNumber', v)}
+ placeholder="e.g. POL-7734892-A"
+ />
+
+ );
+}
diff --git a/src/pages/CreateLeadPage.jsx b/src/pages/CreateLeadPage.jsx
index 5d46fc1..12374e5 100644
--- a/src/pages/CreateLeadPage.jsx
+++ b/src/pages/CreateLeadPage.jsx
@@ -11,6 +11,8 @@ 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 LeadInsuranceSection from '../components/leads/LeadInsuranceSection';
+import LeadAssignmentSection from '../components/leads/LeadAssignmentSection';
import gsap from 'gsap';
// ---------------------------------------------------------------------------
@@ -59,23 +61,6 @@ const SECTIONS = [
},
];
-// ---------------------------------------------------------------------------
-// Animation variants
-// ---------------------------------------------------------------------------
-const containerVariants = {
- hidden: {},
- visible: { transition: { staggerChildren: 0.07 } },
-};
-
-const sectionVariants = {
- hidden: { opacity: 0, y: 22 },
- visible: {
- opacity: 1,
- y: 0,
- transition: { type: 'spring', stiffness: 280, damping: 28 },
- },
-};
-
// ---------------------------------------------------------------------------
// Initial form state
// ---------------------------------------------------------------------------
@@ -144,8 +129,8 @@ export default function CreateLeadPage() {
job: isQuickCapture
? !!formData.leadSource
: !!(formData.leadSource && formData.leadType && formData.workType),
- insurance: false, // Phase 5
- assignment: false, // Phase 6
+ insurance: !!(formData.insuranceCompany && formData.claimStatus),
+ assignment: !!(formData.assignedTo && formData.followUpDate),
};
// Progress = % of visible sections that are complete
@@ -213,20 +198,24 @@ export default function CreateLeadPage() {
/>
);
}
- // Placeholder for sections not yet built
- return (
-
-
-
- {section.description}
-
-
- );
+ );
+ }
+ if (section.id === 'assignment') {
+ return (
+
+ );
+ }
+ return null;
};
return (
@@ -325,17 +314,14 @@ export default function CreateLeadPage() {
{/* --- Sections --- */}
-
+
{visibleSections.map(section => (
))}
-
+
{/* ----------------------------------------------------------------