mobile: Optimizations for Maps Drawer, Legend, and Customer Profile Tabs
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useMockStore } from '../data/mockStore';
|
||||
import { SpotlightCard } from '../components/SpotlightCard';
|
||||
import { User, Calendar, Clock, DollarSign, Save, Loader2, CheckCircle, History, Home, ShieldCheck } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const CustomerProfile = () => {
|
||||
const { user, updateProfile } = useAuth();
|
||||
const { meetings, properties } = useMockStore();
|
||||
const [activeTab, setActiveTab] = useState('details');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Form State
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setFormData({
|
||||
name: user.name || '',
|
||||
email: user.username || '', // Assuming username is email for customers
|
||||
phone: user.phone || '555-0123', // Mock default if missing
|
||||
address: user.address || '123 Maple Ave, Plano, TX' // Mock default
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleSaveProfile = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
updateProfile({
|
||||
name: formData.name,
|
||||
username: formData.email,
|
||||
phone: formData.phone,
|
||||
address: formData.address
|
||||
});
|
||||
|
||||
toast.success("Profile updated successfully!");
|
||||
} catch (error) {
|
||||
toast.error("Failed to update profile.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Filter Meetings
|
||||
const myMeetings = meetings.filter(m =>
|
||||
(m.customerId === user?.id) ||
|
||||
(m.customerName === user?.name) // Fallback for legacy data
|
||||
);
|
||||
|
||||
// Get My Property
|
||||
const myProperty = properties.find(p => p.propertyData.propertyId === user?.propertyId);
|
||||
|
||||
const upcomingVisits = myMeetings.filter(m => {
|
||||
const meetingDate = new Date(m.date);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return meetingDate >= today && m.status !== 'Completed' && m.status !== 'Cancelled';
|
||||
}).sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||
|
||||
const pastVisits = myMeetings.filter(m => {
|
||||
const meetingDate = new Date(m.date);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return meetingDate < today || m.status === 'Completed';
|
||||
}).sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-50 dark:bg-[#09090b] text-zinc-900 dark:text-white p-8 pt-24 transition-colors duration-300">
|
||||
<div className="max-w-4xl mx-auto space-y-8">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center space-x-4 mb-8">
|
||||
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-2xl font-bold text-white shadow-lg">
|
||||
{user?.name?.charAt(0) || 'C'}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-black text-transparent bg-clip-text bg-gradient-to-r from-zinc-900 to-zinc-600 dark:from-white dark:to-white/60">
|
||||
My Profile
|
||||
</h1>
|
||||
<p className="text-zinc-500 dark:text-zinc-400">Manage your account and view visit history.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex space-x-1 border-b border-zinc-200 dark:border-zinc-800 mb-6 overflow-x-auto scrollbar-hide">
|
||||
<button
|
||||
onClick={() => setActiveTab('details')}
|
||||
className={`px-4 md:px-6 py-3 text-xs md:text-sm font-bold uppercase tracking-wider border-b-2 transition-all whitespace-nowrap ${activeTab === 'details'
|
||||
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
||||
: 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
|
||||
}`}
|
||||
>
|
||||
Personal Details
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('property')}
|
||||
className={`px-4 md:px-6 py-3 text-xs md:text-sm font-bold uppercase tracking-wider border-b-2 transition-all whitespace-nowrap ${activeTab === 'property'
|
||||
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
||||
: 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
|
||||
}`}
|
||||
>
|
||||
My Property
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('visits')}
|
||||
className={`px-4 md:px-6 py-3 text-xs md:text-sm font-bold uppercase tracking-wider border-b-2 transition-all whitespace-nowrap ${activeTab === 'visits'
|
||||
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
||||
: 'border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
|
||||
}`}
|
||||
>
|
||||
Visit Management
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
{activeTab === 'details' ? (
|
||||
<SpotlightCard>
|
||||
<form onSubmit={handleSaveProfile} className="p-8 space-y-6">
|
||||
<FormInput label="Full Name" icon={User} value={formData.name} onChange={v => setFormData({ ...formData, name: v })} required />
|
||||
<FormInput label="Email Address" icon={null} prefix="@" type="email" value={formData.email} onChange={v => setFormData({ ...formData, email: v })} required />
|
||||
<FormInput label="Phone Number" value={formData.phone} onChange={v => setFormData({ ...formData, phone: v })} required />
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-xs font-bold uppercase text-zinc-500">Address</label>
|
||||
<textarea
|
||||
value={formData.address}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
className="w-full px-4 py-2 bg-zinc-100 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all h-24 resize-none"
|
||||
placeholder="1234 Main St..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-zinc-200 dark:border-zinc-800 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="flex items-center px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-bold shadow-lg shadow-blue-500/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isLoading ? (
|
||||
<><Loader2 className="animate-spin mr-2" size={18} /> Saving...</>
|
||||
) : (
|
||||
<><Save className="mr-2" size={18} /> Save Changes</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</SpotlightCard>
|
||||
) : activeTab === 'property' ? (
|
||||
<SpotlightCard>
|
||||
<form onSubmit={(e) => { e.preventDefault(); toast.success("Property details updated!"); }} className="p-8 space-y-8">
|
||||
|
||||
{/* Property Basics */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2 pb-4 border-b border-zinc-200 dark:border-white/5 text-blue-600 dark:text-blue-400">
|
||||
<Home size={20} /> <h3 className="text-sm font-black uppercase tracking-widest">Property Basics</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-2">
|
||||
<FormInput label="Address" value="2612 Dunwick Dr, Plano, TX 75023" onChange={() => { }} />
|
||||
</div>
|
||||
<FormInput label="Type" value="Residential" onChange={() => { }} />
|
||||
<FormInput label="Year Built" value="1971" onChange={() => { }} />
|
||||
<FormInput label="Built-Up Area (sqft)" value="2450" onChange={() => { }} />
|
||||
<FormInput label="Lot Size (sqft)" value="8500" onChange={() => { }} />
|
||||
<div className="grid grid-cols-3 gap-4 md:col-span-2">
|
||||
<FormInput label="Beds" value="4" onChange={() => { }} />
|
||||
<FormInput label="Baths" value="3" onChange={() => { }} />
|
||||
<FormInput label="Parking" value="2" onChange={() => { }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Insurance Information */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2 pb-4 border-b border-zinc-200 dark:border-white/5 text-purple-600 dark:text-purple-400">
|
||||
<ShieldCheck size={20} /> <h3 className="text-sm font-black uppercase tracking-widest">Insurance Information</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FormInput label="Insurance Company" value="Farmers" onChange={() => { }} />
|
||||
<FormInput label="Policy Number" value="POL-987654321" onChange={() => { }} />
|
||||
<FormInput label="Claim Filed?" value="No" onChange={() => { }} />
|
||||
<FormInput label="Adjuster Name" value="" placeholder="N/A" onChange={() => { }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-zinc-200 dark:border-zinc-800 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-bold shadow-lg shadow-blue-500/20 transition-all"
|
||||
>
|
||||
<Save className="mr-2" size={18} /> Save Details
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</SpotlightCard>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{/* Upcoming Visits */}
|
||||
<section>
|
||||
<h3 className="text-lg font-bold mb-4 flex items-center text-zinc-800 dark:text-zinc-200">
|
||||
<Calendar className="mr-2 text-blue-500" size={20} /> Upcoming Visits
|
||||
</h3>
|
||||
{upcomingVisits.length > 0 ? (
|
||||
<div className="grid gap-4">
|
||||
{upcomingVisits.map(visit => (
|
||||
<SpotlightCard key={visit.id} className="p-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-12 h-12 rounded-xl bg-blue-50 dark:bg-blue-900/20 flex flex-col items-center justify-center text-blue-600 dark:text-blue-400 border border-blue-100 dark:border-blue-500/20">
|
||||
<span className="text-[10px] uppercase font-bold">{new Date(visit.date).toLocaleString('default', { month: 'short' })}</span>
|
||||
<span className="text-lg font-bold leading-none">{new Date(visit.date).getDate()}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-lg">{visit.agentName || 'Assigned Agent'}</h4>
|
||||
<div className="flex items-center text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<Clock size={14} className="mr-1" /> {visit.time}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="px-3 py-1 bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-300 text-xs font-bold uppercase rounded-full">
|
||||
{visit.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-8 text-center border border-dashed border-zinc-300 dark:border-zinc-700 rounded-xl text-zinc-500">
|
||||
No upcoming visits scheduled.
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Visit History */}
|
||||
<section>
|
||||
<h3 className="text-lg font-bold mb-4 flex items-center text-zinc-800 dark:text-zinc-200">
|
||||
<History className="mr-2 text-zinc-500" size={20} /> Visit History
|
||||
</h3>
|
||||
{pastVisits.length > 0 ? (
|
||||
<div className="grid gap-4">
|
||||
{pastVisits.map(visit => (
|
||||
<SpotlightCard key={visit.id} className="p-6 opacity-80 hover:opacity-100 transition-opacity">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-10 h-10 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-500">
|
||||
<CheckCircle size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-bold">{visit.agentName || 'Agent'}</h4>
|
||||
<span className="text-xs text-zinc-400">• {new Date(visit.date).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400 mt-1">
|
||||
{visit.notes || 'No notes available.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visit.dealValue !== undefined && (
|
||||
<div className="flex items-center space-x-2 bg-emerald-50 dark:bg-emerald-900/10 px-4 py-2 rounded-lg border border-emerald-100 dark:border-emerald-500/20">
|
||||
<DollarSign size={16} className="text-emerald-600 dark:text-emerald-400" />
|
||||
<span className="font-bold text-emerald-700 dark:text-emerald-300">
|
||||
${visit.dealValue.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-8 text-center border border-dashed border-zinc-300 dark:border-zinc-700 rounded-xl text-zinc-500">
|
||||
No history available.
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper Components
|
||||
const ReadOnlyField = ({ label, value }) => (
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-500 tracking-wider">{label}</label>
|
||||
<div className="text-sm font-medium text-zinc-900 dark:text-zinc-200 border-b border-zinc-200 dark:border-white/10 pb-1">
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const FormInput = ({ label, icon: Icon, prefix, value, onChange, type = "text", required }) => (
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-bold uppercase text-zinc-500">{label} {required && <span className="text-red-500">*</span>}</label>
|
||||
<div className="relative">
|
||||
{Icon && <Icon className="absolute left-3 top-3 text-zinc-400" size={16} />}
|
||||
{prefix && <span className="absolute left-3 top-3 text-zinc-400 text-xs">{prefix}</span>}
|
||||
<input
|
||||
type={type}
|
||||
required={required}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className={`w-full ${Icon || prefix ? 'pl-10' : 'px-4'} pr-4 py-2 bg-zinc-100 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CustomerProfile;
|
||||
@@ -409,7 +409,7 @@ const RealWeatherWidget = ({ weather, forecast, loading, error, lastUpdated, onR
|
||||
{/* Dynamic Weather Background Gradient - subtle */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-blue-500/10 dark:from-blue-500/5 to-transparent pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 p-8 flex flex-col justify-between h-full">
|
||||
<div className="relative z-10 p-6 flex flex-col justify-between h-full">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
@@ -423,7 +423,7 @@ const RealWeatherWidget = ({ weather, forecast, loading, error, lastUpdated, onR
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-6xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-b from-zinc-800 to-zinc-400 dark:from-white dark:to-white/50 flex items-start">
|
||||
<h2 className="text-5xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-b from-zinc-800 to-zinc-400 dark:from-white dark:to-white/50 flex items-start">
|
||||
{Math.round(weather.main.temp)}
|
||||
<span className="text-2xl text-zinc-400 dark:text-white/50 mt-2 font-bold tracking-normal">°F</span>
|
||||
</h2>
|
||||
|
||||
+97
-18
@@ -1,11 +1,14 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Chatbot from '../components/Chatbot';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { ArrowRight, CheckCircle2, Home, Star, ShieldCheck, Clock, Award, TrendingDown, Users, MapPin, Phone, Mail, Instagram, Twitter, Youtube } from 'lucide-react';
|
||||
import { useTheme } from '../context/ThemeContext'; // Import useTheme
|
||||
import { ArrowRight, CheckCircle2, Home, Star, ShieldCheck, Clock, Award, TrendingDown, Users, MapPin, Phone, Mail, Instagram, Twitter, Youtube, Sun, Moon, LogOut, LayoutDashboard, User, Menu, X } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { SpotlightCard } from '../components/SpotlightCard';
|
||||
import { ComparisonSlider } from '../components/ComparisonSlider';
|
||||
import Services from '../components/Services';
|
||||
import IntelligenceMap from '../components/IntelligenceMap';
|
||||
import gsap from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import Logo from '../assets/images/LynkedUp_Pro_F_logo_Y.png';
|
||||
@@ -19,9 +22,11 @@ gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const Landing = () => {
|
||||
const { user } = useAuth();
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const heroRef = useRef(null);
|
||||
const textRef = useRef(null);
|
||||
const sectionsRef = useRef([]);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Console Signature (Info level to persist in prod)
|
||||
@@ -69,30 +74,98 @@ const Landing = () => {
|
||||
<nav className="border-b border-zinc-200 dark:border-white/5 backdrop-blur-xl sticky top-0 z-40 bg-white/70 dark:bg-black/50">
|
||||
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<img src={Logo} alt="LynkedUp Pro" className="w-10 h-10 object-contain" />
|
||||
<span className="text-xl font-bold tracking-tight">LynkedUp Pro</span>
|
||||
<img src={Logo} alt="LynkedUp Pro" className="h-16 w-auto object-contain" />
|
||||
{/* <span className="text-xl font-bold tracking-tight">LynkedUp Pro</span> */}
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="p-2 rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors text-zinc-600 dark:text-zinc-400"
|
||||
aria-label="Toggle Theme"
|
||||
>
|
||||
{theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />}
|
||||
</button>
|
||||
|
||||
{!user ? (
|
||||
<Link to="/login" className="px-5 py-2 rounded-full text-xs font-bold uppercase tracking-wider bg-zinc-100 dark:bg-zinc-900 border hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors">
|
||||
Sign In
|
||||
</Link>
|
||||
) : (
|
||||
<Link to={user.role === 'customer' ? '/' : '/emp/fa/dashboard'} className="px-5 py-2 rounded-full text-xs font-bold uppercase tracking-wider bg-black dark:bg-white text-white dark:text-black hover:opacity-80 transition-colors">
|
||||
Dashboard
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
to={user.role === 'CUSTOMER' ? '/portal/profile' : '/emp/fa/dashboard'}
|
||||
className="px-5 py-2 rounded-full text-xs font-bold uppercase tracking-wider bg-black dark:bg-white text-white dark:text-black hover:opacity-80 transition-colors"
|
||||
>
|
||||
{user.role === 'CUSTOMER' ? 'My Profile' : 'Dashboard'}
|
||||
</Link>
|
||||
<button
|
||||
onClick={useAuth().logout}
|
||||
className="p-2 rounded-full hover:bg-red-50 dark:hover:bg-red-900/20 text-zinc-400 hover:text-red-500 transition-colors"
|
||||
title="Sign Out"
|
||||
>
|
||||
<LogOut size={20} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Toggle */}
|
||||
<button
|
||||
className="md:hidden p-2 text-zinc-600 dark:text-zinc-400"
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
>
|
||||
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Nav Dropdown */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden absolute top-20 left-0 right-0 bg-white dark:bg-black border-b border-zinc-200 dark:border-white/5 p-4 flex flex-col space-y-4 shadow-xl animate-in slide-in-from-top-4">
|
||||
<button
|
||||
onClick={() => { toggleTheme(); setIsMobileMenuOpen(false); }}
|
||||
className="flex items-center space-x-3 p-3 rounded-xl bg-zinc-100 dark:bg-zinc-900"
|
||||
>
|
||||
{theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />}
|
||||
<span className="font-bold">{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>
|
||||
</button>
|
||||
|
||||
{!user ? (
|
||||
<Link
|
||||
to="/login"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="w-full text-center py-3 rounded-xl font-bold uppercase tracking-wider bg-blue-600 text-white"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
) : (
|
||||
<>
|
||||
<Link
|
||||
to={user.role === 'CUSTOMER' ? '/portal/profile' : '/emp/fa/dashboard'}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="w-full text-center py-3 rounded-xl font-bold uppercase tracking-wider bg-black dark:bg-white text-white dark:text-black"
|
||||
>
|
||||
{user.role === 'CUSTOMER' ? 'My Profile' : 'Dashboard'}
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => { useAuth().logout(); setIsMobileMenuOpen(false); }}
|
||||
className="w-full text-center py-3 rounded-xl font-bold uppercase tracking-wider bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<div className="relative pt-32 pb-40 overflow-hidden" ref={heroRef}>
|
||||
<div className="relative min-h-[90vh] flex items-center justify-center pt-32 pb-40 overflow-hidden" ref={heroRef}>
|
||||
{/* Background Image with Overlay */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<img src={HeroImg} alt="Modern Roof" className="w-full h-full object-cover opacity-20 dark:opacity-40" />
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-zinc-50/80 to-zinc-50 dark:via-[#050505]/80 dark:to-[#050505]"></div>
|
||||
<img src={HeroImg} alt="Modern Roof" className="w-full h-full object-cover object-[center_35%] scale-110 opacity-20 dark:opacity-40" />
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-zinc-50/50 to-zinc-50 dark:via-[#050505]/50 dark:to-[#050505]"></div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto px-6 relative z-10 text-center" ref={textRef}>
|
||||
@@ -103,7 +176,7 @@ const Landing = () => {
|
||||
|
||||
<h1 className="text-6xl md:text-9xl font-black tracking-tighter mb-6 leading-[0.9]">
|
||||
ROOFING <br />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-b from-blue-700 to-red-600 dark:from-blue-400 dark:to-red-400">REVOLUTIONIZED</span>
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-b from-blue-700 to-red-600 dark:from-blue-400 dark:to-red-400 text-4xl md:text-9xl">REVOLUTIONIZED</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl md:text-2xl text-zinc-500 dark:text-zinc-400 max-w-2xl mx-auto mb-12 font-medium">
|
||||
@@ -114,12 +187,12 @@ const Landing = () => {
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12">
|
||||
<Link to="/login" className="px-8 py-4 rounded-full bg-blue-700 hover:bg-blue-800 text-white font-bold transition-all flex items-center shadow-lg shadow-blue-600/20 hover:scale-105 ring-4 ring-blue-500/10 dark:ring-blue-400/10">
|
||||
Get Free Estimate
|
||||
Schedule Demo
|
||||
<ArrowRight size={18} className="ml-2" />
|
||||
</Link>
|
||||
<button className="px-8 py-4 rounded-full bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 hover:bg-zinc-200 dark:hover:bg-white/10 font-semibold transition-colors">
|
||||
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank" rel="noopener noreferrer" className="px-8 py-4 rounded-full bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 hover:bg-zinc-200 dark:hover:bg-white/10 font-semibold transition-colors">
|
||||
See The Process
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Patriotic Discount Banner */}
|
||||
@@ -133,8 +206,8 @@ const Landing = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Satisfaction Badges */}
|
||||
<div className="mt-12 flex items-center justify-center gap-8 opacity-70 grayscale hover:grayscale-0 transition-all duration-500">
|
||||
{/* Satisfaction Badges REMOVED */}
|
||||
{/* <div className="mt-12 flex items-center justify-center gap-8 opacity-70 grayscale hover:grayscale-0 transition-all duration-500">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex -space-x-2">
|
||||
<div className="w-8 h-8 rounded-full bg-gray-300 border-2 border-white dark:border-black"></div>
|
||||
@@ -147,7 +220,7 @@ const Landing = () => {
|
||||
<ShieldCheck size={16} />
|
||||
<span className="text-xs font-bold">Licensed & Insured</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,7 +290,7 @@ const Landing = () => {
|
||||
<div className="w-12 h-12 rounded-full bg-zinc-900 dark:bg-white text-white dark:text-black flex items-center justify-center font-black text-xl mb-6">03</div>
|
||||
<h3 className="text-3xl font-bold mb-4">The Resolution.</h3>
|
||||
<p className="text-lg text-zinc-500 dark:text-zinc-400 leading-relaxed mb-6">
|
||||
Armed with precise data, our certified ground team executes the repair cheaply and effectively. Homeowners are assured and relaxed, knowing they are getting the best of the best.
|
||||
Generate property assessment reports and repair estimates within minutes of inspection. AI-verified analysis documents visible damage using clear, objective evidence. Reports can be shared with property owners or used in insurance discussions. All findings are based on measurable data, not opinions. Our goal is simple: equip field representatives with the latest technology and verified evidence so they can operate with confidence, gain a competitive edge, and ensure all parties work from the same factual record.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<span className="px-3 py-1 bg-red-100 dark:bg-red-900/20 text-red-600 dark:text-red-400 text-xs font-bold uppercase rounded-md">Cost Efficient</span>
|
||||
@@ -234,6 +307,12 @@ const Landing = () => {
|
||||
<ComparisonSlider />
|
||||
</div>
|
||||
|
||||
{/* NEW SERVICES SECTION */}
|
||||
<Services />
|
||||
|
||||
{/* NEW INTELLIGENCE MAP SECTION */}
|
||||
<IntelligenceMap />
|
||||
|
||||
{/* FACT CHECK & STATS */}
|
||||
<div className="py-24 bg-zinc-900 text-white relative overflow-hidden" ref={addToRefs}>
|
||||
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-10"></div>
|
||||
|
||||
+3
-2
@@ -74,10 +74,11 @@ const Login = () => {
|
||||
const result = login(identifier, password, type);
|
||||
|
||||
if (result.success) {
|
||||
if (type === 'customer') {
|
||||
// Role-based Redirect
|
||||
if (result.role === 'CUSTOMER') {
|
||||
navigate('/');
|
||||
} else {
|
||||
navigate('/emp/fa/dashboard'); // Default for employees
|
||||
navigate('/emp/fa/dashboard');
|
||||
}
|
||||
} else {
|
||||
setError(result.message);
|
||||
|
||||
+171
-43
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useMemo, useEffect } from 'react';
|
||||
import { MapContainer, TileLayer, Polygon, useMapEvents, Marker, Popup } from 'react-leaflet';
|
||||
import { X, Home, DollarSign, User, Info, Edit2, Save, Check, MapPin, Loader2, Plus, AlertCircle } from 'lucide-react';
|
||||
import { X, Home, DollarSign, User, Info, Edit2, Save, Check, MapPin, Loader2, Plus, AlertCircle, ShieldCheck } from 'lucide-react';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { useMockStore } from '../data/mockStore';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
@@ -51,33 +51,53 @@ const MapInteraction = ({ onMapClick }) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const MapLegend = () => (
|
||||
<div className="absolute bottom-6 left-6 z-[1000] bg-white/90 dark:bg-black/90 backdrop-blur-md p-4 rounded-2xl border border-zinc-200 dark:border-white/10 shadow-xl">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-500 mb-3">Map Legend</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500 shadow-sm shadow-red-500/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Hot Lead</span>
|
||||
const MapLegend = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Toggle - Top Right */}
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="md:hidden absolute top-4 right-4 z-[2000] bg-white dark:bg-zinc-900 p-3 rounded-full shadow-xl border border-zinc-200 dark:border-white/10 text-zinc-600 dark:text-zinc-400"
|
||||
>
|
||||
<Info size={20} />
|
||||
</button>
|
||||
|
||||
{/* Legend Content - Below Toggle */}
|
||||
<div className={`absolute top-20 right-4 md:bottom-6 md:right-auto md:top-auto md:left-6 z-[2000] bg-white/95 dark:bg-black/95 backdrop-blur-md p-4 rounded-2xl border border-zinc-200 dark:border-white/10 shadow-xl transition-all duration-300 origin-top-right md:origin-bottom-left
|
||||
${isOpen ? 'scale-100 opacity-100' : 'scale-0 opacity-0 md:scale-100 md:opacity-100'}
|
||||
`}>
|
||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-500 mb-3 flex items-center justify-between">
|
||||
Map Legend
|
||||
<button onClick={() => setIsOpen(false)} className="md:hidden ml-4"><X size={14} /></button>
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500 shadow-sm shadow-red-500/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Hot Lead</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500 shadow-sm shadow-emerald-500/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Customer</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-blue-500 shadow-sm shadow-blue-500/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Renovated</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-violet-400 shadow-sm shadow-violet-400/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Neutral</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-white border-2 border-zinc-300 dark:border-zinc-500 shadow-sm"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Not Interested</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500 shadow-sm shadow-emerald-500/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Customer</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-blue-500 shadow-sm shadow-blue-500/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Renovated</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-violet-400 shadow-sm shadow-violet-400/50"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Neutral</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-white border-2 border-zinc-300 dark:border-zinc-500 shadow-sm"></div>
|
||||
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Not Interested</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Map View
|
||||
const MapView = ({ data, onSelect, onMapCreate }) => {
|
||||
@@ -102,7 +122,6 @@ const MapView = ({ data, onSelect, onMapCreate }) => {
|
||||
/>
|
||||
|
||||
<MapInteraction onMapClick={onMapCreate} />
|
||||
<MapLegend />
|
||||
|
||||
{data.map((item) => {
|
||||
let color = "#a78bfa"; // Violet-400 (New Neutral)
|
||||
@@ -175,6 +194,7 @@ const RenderInput = ({ label, field, type = "text", placeholder, options, min, m
|
||||
const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, loadingAddr }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// Initial State Matching Schema
|
||||
// Initial State Matching Schema
|
||||
const initialFormState = {
|
||||
// Status
|
||||
@@ -224,6 +244,23 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
desiredPrice: '',
|
||||
minPrice: '',
|
||||
|
||||
// 3.0 Insurance Information
|
||||
insurance_company: '',
|
||||
insurance_company_not_listed: false,
|
||||
damage_location: '',
|
||||
date_of_loss: '',
|
||||
claim_filed: 'No',
|
||||
claim_number: '',
|
||||
has_paperwork: 'No',
|
||||
adjuster_name: '',
|
||||
adjuster_phone: '',
|
||||
adjuster_ext: '',
|
||||
adjuster_type: '',
|
||||
adjuster_fax: '',
|
||||
adjuster_email: '',
|
||||
met_with_adjuster: 'No',
|
||||
claim_approved: 'No',
|
||||
|
||||
// Notes
|
||||
notes: ''
|
||||
};
|
||||
@@ -245,7 +282,7 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
propertyType: data.propertyData?.propertyType || 'House',
|
||||
// Correctly map fields from mockStore structure (flat objects) to form state
|
||||
builtUpArea: data.propertyData?.totalBuiltUpArea || '',
|
||||
lotSize: data.propertyData?.lotPlotSize || '', // mockStore might return string "0.22 Acres", assuming input handles text or needs parsing if strictly number
|
||||
lotSize: data.propertyData?.lotPlotSize || '',
|
||||
yearBuilt: data.propertyData?.yearBuilt || '',
|
||||
bedrooms: data.propertyData?.numberOfBedrooms || '',
|
||||
bathrooms: data.propertyData?.numberOfBathrooms || '',
|
||||
@@ -271,6 +308,23 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
desiredPrice: data.ownerData?.desiredSellingPrice || '',
|
||||
minPrice: data.ownerData?.minimumAcceptableSellingPrice || '',
|
||||
ownerAddress: data.ownerData?.mailingAddress || '',
|
||||
|
||||
// Map Insurance Data
|
||||
insurance_company: data.insuranceData?.insurance_company || '',
|
||||
insurance_company_not_listed: data.insuranceData?.insurance_company_not_listed || false,
|
||||
damage_location: data.insuranceData?.damage_location || '',
|
||||
date_of_loss: data.insuranceData?.date_of_loss || '',
|
||||
claim_filed: data.insuranceData?.claim_filed ? 'Yes' : 'No',
|
||||
claim_number: data.insuranceData?.claim_number || '',
|
||||
has_paperwork: data.insuranceData?.has_paperwork ? 'Yes' : 'No',
|
||||
adjuster_name: data.insuranceData?.adjuster_name || '',
|
||||
adjuster_phone: data.insuranceData?.adjuster_phone || '',
|
||||
adjuster_ext: data.insuranceData?.adjuster_ext || '',
|
||||
adjuster_type: data.insuranceData?.adjuster_type || '',
|
||||
adjuster_fax: data.insuranceData?.adjuster_fax || '',
|
||||
adjuster_email: data.insuranceData?.adjuster_email || '',
|
||||
met_with_adjuster: data.insuranceData?.met_with_adjuster ? 'Yes' : 'No',
|
||||
claim_approved: data.insuranceData?.claim_approved ? 'Yes' : 'No',
|
||||
});
|
||||
setIsEditing(false);
|
||||
} else if (newLocation) {
|
||||
@@ -340,7 +394,7 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`fixed top-4 right-4 bottom-4 w-[500px] z-[1000] flex flex-col transition-transform duration-300 ease-out ${isOpen ? 'translate-x-0' : 'translate-x-[calc(100%+2rem)]'}`}>
|
||||
<div className={`fixed z-[1000] flex flex-col transition-transform duration-300 ease-out inset-0 md:inset-auto md:top-4 md:right-4 md:bottom-4 md:w-[500px] ${isOpen ? 'translate-x-0' : 'translate-x-full md:translate-x-[calc(100%+2rem)]'}`}>
|
||||
<SpotlightCard className="h-full flex flex-col overflow-hidden bg-white/95 dark:bg-zinc-900/95 backdrop-blur-3xl border-l border-white/20 shadow-2xl">
|
||||
|
||||
{/* Header */}
|
||||
@@ -375,7 +429,7 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
{isEditing && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] uppercase font-bold text-zinc-500 tracking-widest ml-1">Canvassing Status</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
||||
{["Neutral", "Hot Lead", "Renovated", "Customer", "Not Interested"].map(s => (
|
||||
<button key={s} onClick={() => setFormData({ ...formData, status: s })} className={`px-1 py-2 rounded text-[10px] font-bold border transition-all ${formData.status === s ? 'bg-zinc-900 dark:bg-white text-white dark:text-black' : 'bg-transparent border-zinc-200 dark:border-white/10 text-zinc-500'}`}>{s}</button>
|
||||
))}
|
||||
@@ -388,12 +442,12 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-blue-600 dark:text-blue-400">
|
||||
<Home size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Property Basics</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Property Type" field="propertyType" options={["House", "Apartment", "Condo", "Commercial", "Land"]} />
|
||||
<RenderInput label="Built-Up Area (sqft)" field="builtUpArea" type="number" />
|
||||
<RenderInput label="Lot Size (sqft)" field="lotSize" type="number" />
|
||||
<RenderInput label="Year Built" field="yearBuilt" type="number" />
|
||||
<div className="grid grid-cols-3 gap-2 col-span-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 col-span-1 md:col-span-2">
|
||||
<RenderInput label="Beds" field="bedrooms" type="number" />
|
||||
<RenderInput label="Baths" field="bathrooms" type="number" />
|
||||
<RenderInput label="Parking" field="parkingSpaces" type="number" />
|
||||
@@ -406,7 +460,7 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-emerald-600 dark:text-emerald-400">
|
||||
<DollarSign size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Value & Condition</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Est. Market Value ($)" field="marketValue" type="number" />
|
||||
<RenderInput label="Tax Assessed Value ($)" field="taxValue" type="number" />
|
||||
<RenderInput label="Roof Condition" field="roofCondition" options={["Excellent", "Good", "Fair", "Needs Repair", "Critical"]} />
|
||||
@@ -427,22 +481,22 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
<div className="p-4 bg-purple-50 dark:bg-purple-900/10 rounded-xl space-y-4 border border-purple-100 dark:border-purple-500/20">
|
||||
<h4 className="text-[10px] uppercase font-bold text-purple-600 dark:text-purple-400 border-b border-purple-200 dark:border-purple-500/20 pb-1 mb-2">Tenant Profile</h4>
|
||||
<RenderInput label="Full Name *" field="tenantName" placeholder="Required" />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Phone *" field="tenantPhone" placeholder="Required" />
|
||||
<RenderInput label="Email" field="tenantEmail" />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Occupation" field="tenantOccupation" />
|
||||
<RenderInput label="Employer" field="tenantEmployer" />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Annual Income" field="tenantIncome" />
|
||||
<RenderInput label="Family Status" field="livingStatus" options={["Single", "Family", "Couple", "Roommates"]} />
|
||||
</div>
|
||||
|
||||
<h4 className="text-[10px] uppercase font-bold text-purple-600 dark:text-purple-400 border-b border-purple-200 dark:border-purple-500/20 pb-1 mb-2 mt-4">Lease Details</h4>
|
||||
<RenderInput label="Lease Type" field="leaseType" options={["Residential Standard", "Commercial NNN", "Short Term", "Month-to-Month"]} />
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
<RenderInput label="Signed" field="leaseSigned" type="date" />
|
||||
<RenderInput label="Start" field="leaseStart" type="date" />
|
||||
<RenderInput label="End" field="leaseEnd" type="date" />
|
||||
@@ -459,20 +513,73 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SECTION 4: OWNER INTENT (Conditional) */}
|
||||
{/* SECTION 4: INSURANCE INFORMATION */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-blue-600 dark:text-blue-400">
|
||||
<ShieldCheck size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Insurance Information</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<RenderInput label="Insurance Company" field="insurance_company" options={["State Farm", "Allstate", "Liberty Mutual", "Farmers", "Nationwide", "USAA", "Chubb", "Travelers", "Progressive", "American Family", "Other"]} />
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.insurance_company_not_listed}
|
||||
onChange={(e) => isEditing && setFormData({ ...formData, insurance_company_not_listed: e.target.checked })}
|
||||
disabled={!isEditing}
|
||||
className="rounded border-zinc-300 dark:border-white/10"
|
||||
/>
|
||||
<span className="text-xs text-zinc-500">Company Not Listed</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Claim Filed?" field="claim_filed" options={["Yes", "No"]} />
|
||||
<RenderInput label="Date of Loss" field="date_of_loss" type="date" />
|
||||
</div>
|
||||
|
||||
{formData.claim_filed === 'Yes' && (
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-900/10 rounded-xl space-y-4 border border-blue-100 dark:border-blue-500/20">
|
||||
<RenderInput label="Damage Location" field="damage_location" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Claim Number" field="claim_number" />
|
||||
<RenderInput label="Has Paperwork?" field="has_paperwork" options={["Yes", "No"]} />
|
||||
</div>
|
||||
|
||||
<h4 className="text-[10px] uppercase font-bold text-blue-600 dark:text-blue-400 border-b border-blue-200 dark:border-blue-500/20 pb-1 mb-2 mt-2">Adjuster Details</h4>
|
||||
<RenderInput label="Adjuster Name" field="adjuster_name" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
<RenderInput label="Phone" field="adjuster_phone" />
|
||||
<RenderInput label="Ext" field="adjuster_ext" />
|
||||
<RenderInput label="Fax" field="adjuster_fax" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Email" field="adjuster_email" />
|
||||
<RenderInput label="Type" field="adjuster_type" options={["Staff Adjuster", "Independent Adjuster", "Public Adjuster"]} />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Met Adjuster?" field="met_with_adjuster" options={["Yes", "No"]} />
|
||||
<RenderInput label="Claim Approved?" field="claim_approved" options={["Yes", "No"]} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION 5: OWNER INTENT (Conditional) */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-amber-600 dark:text-amber-400">
|
||||
<AlertCircle size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Recall & Sales Intent</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Willing to Sell?" field="willingToSell" options={["Yes", "No", "Undecided"]} />
|
||||
<RenderInput label="Ownership Type" field="ownershipType" options={["Individual", "Joint", "Corporate", "Trust"]} />
|
||||
</div>
|
||||
|
||||
{formData.willingToSell === 'Yes' ? (
|
||||
<div className="p-4 bg-amber-50 dark:bg-amber-900/10 rounded-xl space-y-4 border border-amber-100 dark:border-amber-500/20">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Desired Price" field="desiredPrice" type="number" />
|
||||
<RenderInput label="Min Price" field="minPrice" type="number" />
|
||||
</div>
|
||||
@@ -488,7 +595,7 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
||||
<User size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Owner Contact</h3>
|
||||
</div>
|
||||
<RenderInput label="Full Name" field="ownerName" />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<RenderInput label="Phone" field="ownerPhone" />
|
||||
<RenderInput label="Email" field="ownerEmail" />
|
||||
</div>
|
||||
@@ -666,6 +773,24 @@ function Maps() {
|
||||
}
|
||||
};
|
||||
|
||||
const newInsuranceData = {
|
||||
insurance_company: formData.insurance_company,
|
||||
insurance_company_not_listed: formData.insurance_company_not_listed,
|
||||
damage_location: formData.damage_location,
|
||||
date_of_loss: formData.date_of_loss,
|
||||
claim_filed: formData.claim_filed === 'Yes',
|
||||
claim_number: formData.claim_number,
|
||||
has_paperwork: formData.has_paperwork === 'Yes',
|
||||
adjuster_name: formData.adjuster_name,
|
||||
adjuster_phone: formData.adjuster_phone,
|
||||
adjuster_ext: formData.adjuster_ext,
|
||||
adjuster_type: formData.adjuster_type,
|
||||
adjuster_fax: formData.adjuster_fax,
|
||||
adjuster_email: formData.adjuster_email,
|
||||
met_with_adjuster: formData.met_with_adjuster === 'Yes',
|
||||
claim_approved: formData.claim_approved === 'Yes',
|
||||
};
|
||||
|
||||
if (selectedPropertyId) {
|
||||
// Update Existing
|
||||
setProperties(prev => prev.map(p => {
|
||||
@@ -678,7 +803,8 @@ function Maps() {
|
||||
...p.propertyData,
|
||||
...newPropertyData,
|
||||
details: { ...p.propertyData.details, ...newPropertyData.details } // Merge to keep existing data not in form if any
|
||||
}
|
||||
},
|
||||
insuranceData: newInsuranceData
|
||||
};
|
||||
}
|
||||
return p;
|
||||
@@ -691,6 +817,7 @@ function Maps() {
|
||||
polygon: [], // We don't have a polygon drawing tool yet, implies point-based or empty
|
||||
canvassingStatus: formData.status,
|
||||
ownerData: newOwnerData,
|
||||
insuranceData: newInsuranceData,
|
||||
propertyData: {
|
||||
...newPropertyData,
|
||||
propertyAddress: newPropertyLocation.address,
|
||||
@@ -732,6 +859,7 @@ function Maps() {
|
||||
onSelect={handlePropertySelect}
|
||||
onMapCreate={handleMapCreate}
|
||||
/>
|
||||
<MapLegend />
|
||||
|
||||
<Drawer
|
||||
isOpen={!!selectedPropertyId || !!newPropertyLocation}
|
||||
|
||||
Reference in New Issue
Block a user