feat(leads): Lead Creation Phase 1 — page shell, section framework, mock data
- CreateLeadPage.jsx: full page shell with Quick Capture / Full Form toggle, Barlow Condensed header, breadcrumb nav, GSAP progress bar stub, mobile sticky save footer - LeadSectionWrapper.jsx: collapsible section component with Framer Motion height animation, spring chevron rotation, completion dot, dual light/dark header design matching GlassPanel language - 5 sections wired up: Contact (blue), Property (emerald), Job Details (amber), Insurance (violet), Assignment (cyan) — Quick Capture shows first 3, Full Form shows all 5 - Section indicator pills in header dim gracefully when hidden in Quick Capture mode - LEAD_FORM_OPTIONS added to mockStore: lead types, sources, work types, trade types, urgency/priority levels, insurance companies, claim statuses, phone/email types, US states - Route /emp/fa/leads/new added (FIELD_AGENT, ADMIN, OWNER) - New Lead nav item added to sidebar for all 3 roles
This commit is contained in:
@@ -29,6 +29,7 @@ import VendorDashboard from './pages/vendor/VendorDashboard';
|
|||||||
import VendorOrders from './pages/vendor/VendorOrders';
|
import VendorOrders from './pages/vendor/VendorOrders';
|
||||||
import ContractorDashboard from './pages/contractor/ContractorDashboard';
|
import ContractorDashboard from './pages/contractor/ContractorDashboard';
|
||||||
import SubContractorDashboard from './pages/subcontractor/SubContractorDashboard';
|
import SubContractorDashboard from './pages/subcontractor/SubContractorDashboard';
|
||||||
|
import CreateLeadPage from './pages/CreateLeadPage';
|
||||||
|
|
||||||
// ... (existing imports)
|
// ... (existing imports)
|
||||||
const ProtectedRoute = ({ children, allowedRoles }) => {
|
const ProtectedRoute = ({ children, allowedRoles }) => {
|
||||||
@@ -106,6 +107,14 @@ function App() {
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/emp/fa/leads/new"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute allowedRoles={['FIELD_AGENT', 'ADMIN', 'OWNER']}>
|
||||||
|
<CreateLeadPage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
{/* Owner Routes */}
|
{/* Owner Routes */}
|
||||||
<Route path="/owner/snapshot" element={
|
<Route path="/owner/snapshot" element={
|
||||||
<ProtectedRoute allowedRoles={['OWNER']}>
|
<ProtectedRoute allowedRoles={['OWNER']}>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTheme } from '../context/ThemeContext';
|
|||||||
import {
|
import {
|
||||||
LayoutDashboard, Map, Calendar, LogOut, User, Home, MessageSquare,
|
LayoutDashboard, Map, Calendar, LogOut, User, Home, MessageSquare,
|
||||||
ChevronLeft, ChevronRight, Sun, Moon, Trophy, Users, Briefcase,
|
ChevronLeft, ChevronRight, Sun, Moon, Trophy, Users, Briefcase,
|
||||||
FileText, Menu, X, Calculator
|
FileText, Menu, X, Calculator, PlusCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import PageTransition from './PageTransition';
|
import PageTransition from './PageTransition';
|
||||||
@@ -137,10 +137,11 @@ const Layout = () => {
|
|||||||
{ to: "/owner/snapshot", icon: LayoutDashboard, label: "Owners Box" },
|
{ to: "/owner/snapshot", icon: LayoutDashboard, label: "Owners Box" },
|
||||||
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
|
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
|
||||||
{ to: "/owner/projects", icon: Briefcase, label: "Projects" },
|
{ to: "/owner/projects", icon: Briefcase, label: "Projects" },
|
||||||
|
{ to: "/emp/fa/leads/new", icon: PlusCircle, label: "New Lead" },
|
||||||
{ to: "/owner/maps", icon: Map, label: "Territory Map" },
|
{ to: "/owner/maps", icon: Map, label: "Territory Map" },
|
||||||
{
|
{
|
||||||
to: "/owner/pro-canvas",
|
to: "/owner/pro-canvas",
|
||||||
icon: LayoutDashboard, // Placeholder icon, maybe change later
|
icon: LayoutDashboard,
|
||||||
label: <span><span className="text-[#fda913]">Pro</span>Canvas</span>
|
label: <span><span className="text-[#fda913]">Pro</span>Canvas</span>
|
||||||
},
|
},
|
||||||
{ to: "/owner/estimate", icon: Calculator, label: "Estimate Builder" },
|
{ to: "/owner/estimate", icon: Calculator, label: "Estimate Builder" },
|
||||||
@@ -153,6 +154,7 @@ const Layout = () => {
|
|||||||
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
|
{ to: "/admin/dashboard", icon: LayoutDashboard, label: "Dashboard" },
|
||||||
{ to: "/admin/schedule", icon: Calendar, label: "Schedule" },
|
{ to: "/admin/schedule", icon: Calendar, label: "Schedule" },
|
||||||
{ to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" },
|
{ to: "/admin/leaderboard", icon: Trophy, label: "Leaderboard" },
|
||||||
|
{ to: "/emp/fa/leads/new", icon: PlusCircle, label: "New Lead" },
|
||||||
{ to: "/admin/maps", icon: Map, label: "Territory Map" },
|
{ to: "/admin/maps", icon: Map, label: "Territory Map" },
|
||||||
{
|
{
|
||||||
to: "/admin/pro-canvas",
|
to: "/admin/pro-canvas",
|
||||||
@@ -179,6 +181,7 @@ const Layout = () => {
|
|||||||
return [
|
return [
|
||||||
{ to: "/emp/fa/dashboard", icon: LayoutDashboard, label: "Dashboard" },
|
{ to: "/emp/fa/dashboard", icon: LayoutDashboard, label: "Dashboard" },
|
||||||
{ to: "/emp/fa/maps", icon: Map, label: "My Map" },
|
{ to: "/emp/fa/maps", icon: Map, label: "My Map" },
|
||||||
|
{ to: "/emp/fa/leads/new", icon: PlusCircle, label: "New Lead" },
|
||||||
{
|
{
|
||||||
to: "/emp/fa/pro-canvas",
|
to: "/emp/fa/pro-canvas",
|
||||||
icon: LayoutDashboard,
|
icon: LayoutDashboard,
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { ChevronDown, CheckCircle2 } from 'lucide-react';
|
||||||
|
import { useTheme } from '../../context/ThemeContext';
|
||||||
|
|
||||||
|
export default function LeadSectionWrapper({
|
||||||
|
title,
|
||||||
|
icon: Icon,
|
||||||
|
accentColor,
|
||||||
|
isOpen,
|
||||||
|
onToggle,
|
||||||
|
children,
|
||||||
|
isComplete = false,
|
||||||
|
}) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`rounded-2xl overflow-hidden border transition-shadow duration-200
|
||||||
|
${isDark
|
||||||
|
? 'bg-zinc-900/70 backdrop-blur-xl border-white/[0.08] shadow-[0_8px_32px_rgba(0,0,0,0.3)]'
|
||||||
|
: 'bg-white border-zinc-200/60 shadow-md hover:shadow-lg'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* --- Header Button --- */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onToggle}
|
||||||
|
className={`w-full flex items-center justify-between px-5 py-3.5 transition-opacity duration-150 hover:opacity-90 active:opacity-75
|
||||||
|
${isDark ? 'border-b border-white/[0.08]' : 'rounded-t-2xl'}
|
||||||
|
`}
|
||||||
|
style={isDark ? { backgroundColor: 'rgba(39,39,42,0.8)' } : { backgroundColor: accentColor }}
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
>
|
||||||
|
{/* Left: icon + title */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{Icon && (
|
||||||
|
<Icon
|
||||||
|
size={17}
|
||||||
|
className={isDark ? '' : 'text-white'}
|
||||||
|
style={isDark ? { color: accentColor } : { opacity: 0.92 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<h3 className={`font-['Barlow_Condensed'] font-black uppercase tracking-widest text-sm
|
||||||
|
${isDark ? 'text-[#E4E4E5]' : 'text-white'}
|
||||||
|
`}>
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: completion indicator + chevron */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<AnimatePresence>
|
||||||
|
{isComplete && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
exit={{ scale: 0, opacity: 0 }}
|
||||||
|
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
|
||||||
|
>
|
||||||
|
<CheckCircle2
|
||||||
|
size={15}
|
||||||
|
className={isDark ? '' : 'text-white/90'}
|
||||||
|
style={isDark ? { color: accentColor } : {}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ rotate: isOpen ? 180 : 0 }}
|
||||||
|
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
|
||||||
|
>
|
||||||
|
<ChevronDown
|
||||||
|
size={16}
|
||||||
|
className={isDark ? 'text-zinc-400' : 'text-white/80'}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* --- Collapsible Content --- */}
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{isOpen && (
|
||||||
|
<motion.div
|
||||||
|
key="content"
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
transition={{ type: 'spring', stiffness: 300, damping: 32, opacity: { duration: 0.15 } }}
|
||||||
|
className="overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="p-5 pt-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2839,3 +2839,93 @@ export const MockStoreProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useMockStore = () => useContext(MockStoreContext);
|
export const useMockStore = () => useContext(MockStoreContext);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Lead Creation Form Options
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
export const LEAD_FORM_OPTIONS = {
|
||||||
|
leadTypes: [
|
||||||
|
'Residential',
|
||||||
|
'Commercial',
|
||||||
|
'Multi-Family',
|
||||||
|
'Industrial',
|
||||||
|
'HOA / Community',
|
||||||
|
],
|
||||||
|
leadSources: [
|
||||||
|
'Door Knock',
|
||||||
|
'Referral',
|
||||||
|
'Storm Chase',
|
||||||
|
'Online / Web',
|
||||||
|
'Mailer / Postcard',
|
||||||
|
'Sign Call',
|
||||||
|
'Insurance Agent Referral',
|
||||||
|
'Repeat Customer',
|
||||||
|
'Social Media',
|
||||||
|
'Other',
|
||||||
|
],
|
||||||
|
workTypes: [
|
||||||
|
'Roof Replacement',
|
||||||
|
'Roof Repair',
|
||||||
|
'Gutters',
|
||||||
|
'Siding',
|
||||||
|
'Windows',
|
||||||
|
'Solar',
|
||||||
|
'Inspection Only',
|
||||||
|
'Emergency Tarping',
|
||||||
|
'Other',
|
||||||
|
],
|
||||||
|
tradeTypes: [
|
||||||
|
'Roofing',
|
||||||
|
'Gutter',
|
||||||
|
'Siding',
|
||||||
|
'Windows',
|
||||||
|
'Solar',
|
||||||
|
'General Contractor',
|
||||||
|
],
|
||||||
|
urgencyLevels: [
|
||||||
|
{ label: 'Standard', value: 'standard', color: '#6B7280' },
|
||||||
|
{ label: 'High', value: 'high', color: '#F59E0B' },
|
||||||
|
{ label: 'Emergency', value: 'emergency', color: '#EF4444' },
|
||||||
|
],
|
||||||
|
priorityLevels: [
|
||||||
|
{ label: 'Low', value: 'low', color: '#6B7280' },
|
||||||
|
{ label: 'Medium', value: 'medium', color: '#3B82F6' },
|
||||||
|
{ label: 'High', value: 'high', color: '#F59E0B' },
|
||||||
|
{ label: 'Critical', value: 'critical', color: '#EF4444' },
|
||||||
|
],
|
||||||
|
insuranceCompanies: [
|
||||||
|
'State Farm',
|
||||||
|
'Allstate',
|
||||||
|
'USAA',
|
||||||
|
'Farmers Insurance',
|
||||||
|
'Liberty Mutual',
|
||||||
|
'Progressive',
|
||||||
|
'Nationwide',
|
||||||
|
'American Family',
|
||||||
|
'Travelers',
|
||||||
|
'GEICO',
|
||||||
|
'Chubb',
|
||||||
|
'Erie Insurance',
|
||||||
|
'Auto-Owners',
|
||||||
|
'Other',
|
||||||
|
],
|
||||||
|
claimStatuses: [
|
||||||
|
'Not Filed',
|
||||||
|
'Filed – Pending Adjuster',
|
||||||
|
'Adjuster Scheduled',
|
||||||
|
'Adjuster Completed',
|
||||||
|
'Approved',
|
||||||
|
'Denied',
|
||||||
|
'Supplement In Progress',
|
||||||
|
'Closed',
|
||||||
|
],
|
||||||
|
phoneTypes: ['Mobile', 'Home', 'Work', 'Other'],
|
||||||
|
emailTypes: ['Personal', 'Work', 'Other'],
|
||||||
|
usStates: [
|
||||||
|
'AL','AK','AZ','AR','CA','CO','CT','DE','FL','GA',
|
||||||
|
'HI','ID','IL','IN','IA','KS','KY','LA','ME','MD',
|
||||||
|
'MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ',
|
||||||
|
'NM','NY','NC','ND','OH','OK','OR','PA','RI','SC',
|
||||||
|
'SD','TN','TX','UT','VT','VA','WA','WV','WI','WY',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useTheme } from '../context/ThemeContext';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import {
|
||||||
|
User, MapPin, Briefcase, Shield, Users,
|
||||||
|
ChevronLeft, Zap, FileText, Save,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import LeadSectionWrapper from '../components/leads/LeadSectionWrapper';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Section definitions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const SECTIONS = [
|
||||||
|
{
|
||||||
|
id: 'contact',
|
||||||
|
title: 'Contact',
|
||||||
|
icon: User,
|
||||||
|
accentColor: '#3B82F6',
|
||||||
|
quickCapture: true,
|
||||||
|
description: 'Name, phone numbers, and email addresses',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'property',
|
||||||
|
title: 'Property',
|
||||||
|
icon: MapPin,
|
||||||
|
accentColor: '#10B981',
|
||||||
|
quickCapture: true,
|
||||||
|
description: 'Property address, type, and site photos',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'job',
|
||||||
|
title: 'Job Details',
|
||||||
|
icon: Briefcase,
|
||||||
|
accentColor: '#F59E0B',
|
||||||
|
quickCapture: true,
|
||||||
|
description: 'Lead source, work type, urgency, and notes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'insurance',
|
||||||
|
title: 'Insurance',
|
||||||
|
icon: Shield,
|
||||||
|
accentColor: '#8B5CF6',
|
||||||
|
quickCapture: false,
|
||||||
|
description: 'Carrier, claim number, adjuster, and policy details',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'assignment',
|
||||||
|
title: 'Assignment',
|
||||||
|
icon: Users,
|
||||||
|
accentColor: '#06B6D4',
|
||||||
|
quickCapture: false,
|
||||||
|
description: 'Assign a rep, set priority, and schedule follow-up',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Component
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
export default function CreateLeadPage() {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [isQuickCapture, setIsQuickCapture] = useState(true);
|
||||||
|
const [openSections, setOpenSections] = useState({
|
||||||
|
contact: true,
|
||||||
|
property: false,
|
||||||
|
job: false,
|
||||||
|
insurance: false,
|
||||||
|
assignment: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const progressBarRef = useRef(null);
|
||||||
|
|
||||||
|
// Animate progress bar on mount (Phase 1: stays at 0)
|
||||||
|
useEffect(() => {
|
||||||
|
if (progressBarRef.current) {
|
||||||
|
gsap.fromTo(
|
||||||
|
progressBarRef.current,
|
||||||
|
{ scaleX: 0, transformOrigin: 'left' },
|
||||||
|
{ scaleX: 0, duration: 0.6, ease: 'power2.out' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleSection = (id) => {
|
||||||
|
setOpenSections(prev => ({ ...prev, [id]: !prev[id] }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const visibleSections = isQuickCapture
|
||||||
|
? SECTIONS.filter(s => s.quickCapture)
|
||||||
|
: SECTIONS;
|
||||||
|
|
||||||
|
const modeSwitchTo = (quick) => {
|
||||||
|
if (quick === isQuickCapture) return;
|
||||||
|
setIsQuickCapture(quick);
|
||||||
|
// Close insurance + assignment when switching to quick capture
|
||||||
|
if (quick) {
|
||||||
|
setOpenSections(prev => ({ ...prev, insurance: false, assignment: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`min-h-screen transition-colors duration-300
|
||||||
|
${isDark ? 'bg-[#09090b]' : 'bg-zinc-100/80'}
|
||||||
|
`}>
|
||||||
|
{/* Progress bar — thin accent line at top */}
|
||||||
|
<div className={`h-[3px] w-full ${isDark ? 'bg-zinc-800' : 'bg-zinc-200'}`}>
|
||||||
|
<div
|
||||||
|
ref={progressBarRef}
|
||||||
|
className="h-full bg-blue-500 transition-all duration-500 ease-out"
|
||||||
|
style={{ width: '0%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ----------------------------------------------------------------
|
||||||
|
Page content
|
||||||
|
---------------------------------------------------------------- */}
|
||||||
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-6 lg:py-10 pb-28 md:pb-10">
|
||||||
|
|
||||||
|
{/* --- Page Header --- */}
|
||||||
|
<div className="mb-7">
|
||||||
|
{/* Back breadcrumb */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="flex items-center gap-1 text-xs uppercase tracking-widest font-bold text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 mb-3 transition-colors"
|
||||||
|
>
|
||||||
|
<ChevronLeft size={13} strokeWidth={2.5} />
|
||||||
|
Leads
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
{/* Title */}
|
||||||
|
<div>
|
||||||
|
<h1 className="font-['Barlow_Condensed'] font-black uppercase tracking-widest text-4xl leading-none text-zinc-900 dark:text-white">
|
||||||
|
New Lead
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-zinc-500 dark:text-zinc-400 mt-1.5">
|
||||||
|
{isQuickCapture
|
||||||
|
? 'Essential fields only — fastest capture at the door.'
|
||||||
|
: 'Full lead profile with insurance and assignment details.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Capture / Full Form toggle */}
|
||||||
|
<div className={`flex items-center p-1 rounded-xl gap-0.5 shrink-0
|
||||||
|
${isDark ? 'bg-zinc-800' : 'bg-zinc-200/80'}
|
||||||
|
`}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => modeSwitchTo(true)}
|
||||||
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-[11px] font-bold uppercase tracking-wider transition-all duration-200
|
||||||
|
${isQuickCapture
|
||||||
|
? 'bg-white dark:bg-zinc-700 text-zinc-900 dark:text-white shadow-sm'
|
||||||
|
: 'text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Zap size={11} strokeWidth={2.5} />
|
||||||
|
Quick
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => modeSwitchTo(false)}
|
||||||
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-[11px] font-bold uppercase tracking-wider transition-all duration-200
|
||||||
|
${!isQuickCapture
|
||||||
|
? 'bg-white dark:bg-zinc-700 text-zinc-900 dark:text-white shadow-sm'
|
||||||
|
: 'text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FileText size={11} strokeWidth={2.5} />
|
||||||
|
Full Form
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mode indicator pills */}
|
||||||
|
<div className="flex items-center gap-2 mt-4 flex-wrap">
|
||||||
|
{SECTIONS.map(s => (
|
||||||
|
<div
|
||||||
|
key={s.id}
|
||||||
|
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider transition-all duration-300
|
||||||
|
${(!s.quickCapture && isQuickCapture)
|
||||||
|
? 'opacity-30 bg-zinc-200 dark:bg-zinc-800 text-zinc-400'
|
||||||
|
: 'opacity-100'
|
||||||
|
}`}
|
||||||
|
style={(!s.quickCapture && isQuickCapture)
|
||||||
|
? {}
|
||||||
|
: { backgroundColor: s.accentColor + '22', color: s.accentColor }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<s.icon size={9} strokeWidth={2.5} />
|
||||||
|
{s.title}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- Sections --- */}
|
||||||
|
<motion.div
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="space-y-3"
|
||||||
|
>
|
||||||
|
<AnimatePresence mode="popLayout">
|
||||||
|
{visibleSections.map(section => (
|
||||||
|
<motion.div
|
||||||
|
key={section.id}
|
||||||
|
variants={sectionVariants}
|
||||||
|
layout
|
||||||
|
exit={{
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.97,
|
||||||
|
y: -6,
|
||||||
|
transition: { duration: 0.18, ease: 'easeIn' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LeadSectionWrapper
|
||||||
|
title={section.title}
|
||||||
|
icon={section.icon}
|
||||||
|
accentColor={section.accentColor}
|
||||||
|
isOpen={openSections[section.id]}
|
||||||
|
onToggle={() => toggleSection(section.id)}
|
||||||
|
>
|
||||||
|
{/* Placeholder — fields added in Phase 2+ */}
|
||||||
|
<div className={`rounded-xl border-2 border-dashed px-6 py-8 flex flex-col items-center justify-center gap-2 text-center
|
||||||
|
${isDark ? 'border-zinc-700' : 'border-zinc-200'}
|
||||||
|
`}>
|
||||||
|
<section.icon
|
||||||
|
size={28}
|
||||||
|
style={{ color: section.accentColor, opacity: 0.4 }}
|
||||||
|
/>
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-widest text-zinc-400">
|
||||||
|
{section.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</LeadSectionWrapper>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ----------------------------------------------------------------
|
||||||
|
Mobile sticky save footer
|
||||||
|
---------------------------------------------------------------- */}
|
||||||
|
<div className={`fixed bottom-0 left-0 right-0 md:hidden px-4 py-3
|
||||||
|
border-t backdrop-blur-md
|
||||||
|
${isDark
|
||||||
|
? 'bg-zinc-900/90 border-white/10'
|
||||||
|
: 'bg-white/90 border-zinc-200'
|
||||||
|
}
|
||||||
|
`}>
|
||||||
|
<div className="flex items-center gap-3 max-w-3xl mx-auto">
|
||||||
|
{/* Mode toggle — compact repeat for mobile */}
|
||||||
|
<div className={`flex items-center p-0.5 rounded-lg gap-0.5 shrink-0
|
||||||
|
${isDark ? 'bg-zinc-800' : 'bg-zinc-100'}
|
||||||
|
`}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => modeSwitchTo(true)}
|
||||||
|
className={`p-2 rounded-md transition-all duration-200
|
||||||
|
${isQuickCapture
|
||||||
|
? 'bg-white dark:bg-zinc-700 shadow-sm text-zinc-900 dark:text-white'
|
||||||
|
: 'text-zinc-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Zap size={14} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => modeSwitchTo(false)}
|
||||||
|
className={`p-2 rounded-md transition-all duration-200
|
||||||
|
${!isQuickCapture
|
||||||
|
? 'bg-white dark:bg-zinc-700 shadow-sm text-zinc-900 dark:text-white'
|
||||||
|
: 'text-zinc-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FileText size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Save button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-bold text-sm uppercase tracking-widest text-white transition-all active:scale-[0.97]"
|
||||||
|
style={{ backgroundColor: '#3B82F6' }}
|
||||||
|
>
|
||||||
|
<Save size={15} />
|
||||||
|
Save Lead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user