feat(storm-intel): Phase 2 — storm-sourced lead generation flow
When a canvasser taps "Create Lead from Zone" in the Storm Intel detail drawer, router state carries the storm context into CreateLeadPage: - CreateLeadPage reads stormSource from location.state; initialises the form with city=Plano, state=TX, leadSource=Door Knock, urgency=high for significant/severe storms, and a pre-filled notes field with zone name, hail size, and date - Amber storm banner renders below the page header showing zone name, hail size, and a note that urgency was auto-set; dismissible with X - stormSource is saved on the lead object via the existing addLead spread - LeadsListPage LeadCard shows an amber "Storm Zone" badge when lead.stormSource exists - KanbanCard footer shows a compact amber "Storm" label for storm-sourced leads
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTheme } from '../context/ThemeContext';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useMockStore } from '../data/mockStore';
|
||||
@@ -8,7 +8,7 @@ import { toast } from 'sonner';
|
||||
import { useRegisterAddress } from '../hooks/useHailRecon';
|
||||
import {
|
||||
User, MapPin, Briefcase, Shield, Users,
|
||||
ChevronLeft, Zap, FileText, Save, AlertCircle,
|
||||
ChevronLeft, Zap, FileText, Save, AlertCircle, CloudLightning, X,
|
||||
} from 'lucide-react';
|
||||
import LeadSectionWrapper from '../components/leads/LeadSectionWrapper';
|
||||
import LeadJobSection from '../components/leads/LeadJobSection';
|
||||
@@ -113,8 +113,13 @@ export default function CreateLeadPage() {
|
||||
const registerAddress = useRegisterAddress();
|
||||
const isDark = theme === 'dark';
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [validationError, setValidationError] = useState('');
|
||||
|
||||
// Storm zone context — passed from StormIntelPage via router state
|
||||
const stormSource = location.state?.stormSource ?? null;
|
||||
const [stormBannerDismissed, setStormBannerDismissed] = useState(false);
|
||||
|
||||
const [isQuickCapture, setIsQuickCapture] = useState(true);
|
||||
const [openSections, setOpenSections] = useState({
|
||||
contact: true,
|
||||
@@ -123,7 +128,24 @@ export default function CreateLeadPage() {
|
||||
insurance: false,
|
||||
assignment: false,
|
||||
});
|
||||
const [formData, setFormData] = useState(INITIAL_FORM);
|
||||
|
||||
// Pre-fill form when arriving from Storm Intel
|
||||
const [formData, setFormData] = useState(() => {
|
||||
if (!stormSource) return INITIAL_FORM;
|
||||
const urgency = (stormSource.severity === 'severe' || stormSource.severity === 'significant') ? 'high' : 'standard';
|
||||
const stormDate = stormSource.date
|
||||
? new Date(stormSource.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
||||
: '';
|
||||
return {
|
||||
...INITIAL_FORM,
|
||||
city: 'Plano',
|
||||
state: 'TX',
|
||||
leadSource: 'Door Knock',
|
||||
urgency,
|
||||
notes: `Storm zone canvass — ${stormSource.areaName}.${stormSource.maxHailSize ? ` ${stormSource.maxHailSize}" hail` : ''}${stormDate ? ` on ${stormDate}` : ''}.`,
|
||||
stormSource,
|
||||
};
|
||||
});
|
||||
|
||||
const progressBarRef = useRef(null);
|
||||
|
||||
@@ -400,6 +422,52 @@ export default function CreateLeadPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- Storm zone banner --- */}
|
||||
<AnimatePresence>
|
||||
{stormSource && !stormBannerDismissed && (
|
||||
<motion.div
|
||||
key="storm-banner"
|
||||
initial={{ opacity: 0, y: -8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -8 }}
|
||||
transition={{ duration: 0.22 }}
|
||||
className="mb-5 rounded-xl border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/8 overflow-hidden"
|
||||
>
|
||||
<div className="flex items-start gap-3 px-4 py-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-amber-500/15 dark:bg-amber-500/20 flex items-center justify-center shrink-0 mt-0.5">
|
||||
<CloudLightning size={16} className="text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[12px] font-black uppercase tracking-wider text-amber-700 dark:text-amber-400 mb-0.5">
|
||||
Storm Zone Lead
|
||||
</p>
|
||||
<p className="text-[13px] font-semibold text-zinc-800 dark:text-zinc-200">
|
||||
{stormSource.areaName}
|
||||
{stormSource.maxHailSize && (
|
||||
<span className="ml-2 text-[11px] font-bold text-amber-600 dark:text-amber-400">
|
||||
{stormSource.maxHailSize}" hail
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-[11px] text-zinc-500 dark:text-zinc-400 mt-0.5">
|
||||
Lead source, urgency, and notes pre-filled from storm data.
|
||||
{(stormSource.severity === 'severe' || stormSource.severity === 'significant') && (
|
||||
<span className="ml-1 text-amber-600 dark:text-amber-400 font-semibold">Urgency set to High.</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStormBannerDismissed(true)}
|
||||
className="p-1 rounded-lg text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 hover:bg-amber-100 dark:hover:bg-amber-500/10 transition-colors shrink-0"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* --- Sections --- */}
|
||||
<div className="space-y-3 mb-8">
|
||||
<AnimatePresence mode="popLayout">
|
||||
|
||||
Reference in New Issue
Block a user