From 83250d687e251c90aad79c872d126c8fb506bc42 Mon Sep 17 00:00:00 2001 From: Satyam <95536056+Satyam-Rastogi@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:36:44 +0530 Subject: [PATCH] mobile: Optimizations for Maps Drawer, Legend, and Customer Profile Tabs --- src/App.jsx | 7 + src/components/Chatbot.jsx | 2 +- src/components/ComparisonSlider.jsx | 5 +- src/components/IntelligenceMap.jsx | 202 +++++++++++++++++ src/components/Layout.jsx | 72 +++++- src/components/Services.jsx | 95 ++++++++ src/components/landing/constants.jsx | 12 +- src/context/AuthContext.jsx | 37 ++- src/context/ThemeContext.jsx | 26 ++- src/data/mockStore.jsx | 108 ++++++++- src/pages/CustomerProfile.jsx | 327 +++++++++++++++++++++++++++ src/pages/Dashboard.jsx | 4 +- src/pages/Landing.jsx | 115 ++++++++-- src/pages/Login.jsx | 5 +- src/pages/Maps.jsx | 214 ++++++++++++++---- 15 files changed, 1140 insertions(+), 91 deletions(-) create mode 100644 src/components/IntelligenceMap.jsx create mode 100644 src/components/Services.jsx create mode 100644 src/pages/CustomerProfile.jsx diff --git a/src/App.jsx b/src/App.jsx index 8447817..5ef1fcc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import Landing from './pages/Landing'; import Dashboard from './pages/Dashboard'; import Maps from './pages/Maps'; import AdminSchedule from './pages/AdminSchedule'; +import CustomerProfile from './pages/CustomerProfile'; import ErrorBoundary from './components/ErrorBoundary'; import { Toaster } from 'sonner'; import NotFound from './pages/NotFound'; @@ -40,6 +41,12 @@ function App() { } /> {/* Protected Field Agent Routes */} + + + + } /> + { - const { user } = useAuth(); + const { user, isAuthenticated } = useAuth(); const { addMeeting, meetings, properties } = useMockStore(); const [isOpen, setIsOpen] = useState(false); const [isMinimized, setIsMinimized] = useState(false); diff --git a/src/components/ComparisonSlider.jsx b/src/components/ComparisonSlider.jsx index d2a78f8..492e945 100644 --- a/src/components/ComparisonSlider.jsx +++ b/src/components/ComparisonSlider.jsx @@ -56,7 +56,10 @@ export const ComparisonSlider = () => { "15-Minute Drone Scan", "Zero Safety Risk", "AI + Thermal Imaging", - "~30% Lower Costs" + "~30% Lower Costs", + "~ 60% faster lead conversion", + "~ Deploy Reps with confidence", + "~ Evidence-driven damage detection unlocks Revenue" ].map((text, i) => (
{text} diff --git a/src/components/IntelligenceMap.jsx b/src/components/IntelligenceMap.jsx new file mode 100644 index 0000000..ba569ae --- /dev/null +++ b/src/components/IntelligenceMap.jsx @@ -0,0 +1,202 @@ +import React, { useState, useEffect } from 'react'; +import { Icons } from './landing/constants'; + +// Updated with DFW specific locations (roughly positioned for a conceptual map) +const hotspots = [ + { id: 1, top: '45%', left: '65%', address: '2800 Routh St, Dallas', problem: 'Hail Impact (Severe)', gain: '+$28k Opportunity', weather: 'Hail Expected', status: 'Action Required' }, + { id: 2, top: '20%', left: '55%', address: 'Legacy West, Plano', problem: 'Thermal Leak Detected', gain: '+$145k Commercial', weather: 'Clear', status: 'Monitoring' }, + { id: 3, top: '60%', left: '35%', address: 'Sundance Sq, Fort Worth', problem: 'Missing Shingles', gain: '+$15k Repair', weather: 'Rain Soon', status: 'Urgent' }, +]; + +const IntelligenceMap = () => { + const [activeSpot, setActiveSpot] = useState(null); + const [showRescheduleDialog, setShowRescheduleDialog] = useState(false); + + useEffect(() => { + // Simulate incoming weather alert + const timer = setTimeout(() => { + setShowRescheduleDialog(true); + }, 2500); + return () => clearTimeout(timer); + }, []); + + return ( +
+
+
+
+
+ Live CRM Data Feed +
+

Hyper-Local Sales Intelligence

+

+ Our systems overlay live NOAA weather data with property value insights. Maximize revenue by targeting neighborhoods directly in the storm path. +

+
+ + {/* Map Container Frame */} +
+ + {/* Map Background - DFW Satellite Style */} +
+ {/* Aerial/Satellite View of a Grid City */} + DFW Metroplex Map Overlay + + {/* Map Grid Overlay */} +
+
+ + {/* Region Indicator - HUD Style */} +
+
+
+
Active Region
+
North Texas / DFW
+
+
+
+ + {/* Weather Overlay Widget */} +
+
+
+
+ +
+
+
Severe Hail Watch
+
Approaching Tarrant County
+
ETA: 15 MINS
+
+
+
+
+ + {/* Interactive Pins */} + {hotspots.map((spot) => ( +
setActiveSpot(spot)} + > + {/* Radar Ripple Effect */} +
+ + {/* Price Tag Pin */} +
+
+ {spot.gain} +
+ + {/* Pin Head */} +
+ + {/* Pin Line */} +
+ + {/* Ground Shadow */} +
+
+
+ ))} + + {/* Reschedule Dialog Overlay */} + {showRescheduleDialog && ( +
+
+
+
+
!
+
+

Schedule Conflict

+
+
+ +
+ +

+ Heavy rainfall expected in Plano at 2:00 PM.
+ This conflicts with the inspection at Legacy West. +

+ +
+ + +
+
+
+ )} + + {/* Right Sidebar Panel (Glass Detail View) */} + {activeSpot && ( +
+
+
+
+

INTEL_FEED

+
+ +
+ +
+
+
+ +
+ +
{activeSpot.gain}
+
+ +
+ +
{activeSpot.address}
+
Dallas-Fort Worth Metroplex
+
+ +
+ +
+ {activeSpot.problem} +
+
+ +
+ > ANALYSIS: 85% probability of insurance approval due to recent hail corridor overlap in zip code 75201. +
+
+ + +
+ )} + +
+
+ + +
+ ); +}; + +export default IntelligenceMap; diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index 4413c75..d4054f6 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -1,14 +1,15 @@ import React, { useRef, useState } from 'react'; import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; -import { LayoutDashboard, Map, Calendar, LogOut, User, Home, MessageSquare, ChevronLeft, ChevronRight } from 'lucide-react'; +import { useTheme } from '../context/ThemeContext'; +import { LayoutDashboard, Map, Calendar, LogOut, User, Home, MessageSquare, ChevronLeft, ChevronRight, Sun, Moon } from 'lucide-react'; import PageTransition from './PageTransition'; import Chatbot from './Chatbot'; import Logo from '../assets/images/LynkedUp_Pro_F_logo_Y.png'; // Rainbow Sidebar Item Component -const SidebarItem = ({ to, icon: Icon, label, isCollapsed }) => { +const SidebarItem = ({ to, icon: Icon, label, isCollapsed, onClick }) => { const divRef = useRef(null); const [position, setPosition] = useState({ x: 0, y: 0 }); const [opacity, setOpacity] = useState(0); @@ -29,6 +30,7 @@ const SidebarItem = ({ to, icon: Icon, label, isCollapsed }) => { { const Layout = () => { const { user, logout } = useAuth(); + const { theme, toggleTheme } = useTheme(); const navigate = useNavigate(); const location = useLocation(); const [isCollapsed, setIsCollapsed] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const handleLogout = () => { logout(); @@ -99,14 +103,45 @@ const Layout = () => { return (
+ + {/* Mobile Header */} +
+
+ Logo + LynkedUp +
+ +
+ + {/* Mobile Backdrop */} + {isMobileMenuOpen && ( +
setIsMobileMenuOpen(false)} + /> + )} + {/* Sidebar */} {/* Enhanced glassmorphism with light/dark support */} -
+ {/* Theme Toggle */} + +
@@ -168,7 +220,7 @@ const Layout = () => { {/* Main Content */} -
+
diff --git a/src/components/Services.jsx b/src/components/Services.jsx new file mode 100644 index 0000000..25a5a0b --- /dev/null +++ b/src/components/Services.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { Icons } from './landing/constants'; + +const services = [ + { + title: "Residential Repair", + desc: "Leak detection & shingle replacement in record time. We identify micro-fractures invisible to the naked eye.", + icon: Icons.Home, + color: "from-blue-400 to-blue-600", + glow: "shadow-blue-500/20" + }, + // Commercial Development Removed as per request + { + title: "Lead & Data Solutions", + desc: "Advanced lead scoring and real-time storm tracking alerts. Know where the damage is before the phone rings.", + icon: Icons.Chart, + color: "from-purple-400 to-indigo-600", + glow: "shadow-purple-500/20" + } +]; + +const Services = () => { + // Simple Tilt Logic + const handleMouseMove = (e) => { + const card = e.currentTarget; + const rect = card.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const centerX = rect.width / 2; + const centerY = rect.height / 2; + + // Rotation calc (Max 5deg) + const rotateX = ((y - centerY) / centerY) * -5; + const rotateY = ((x - centerX) / centerX) * 5; + + card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`; + }; + + const handleMouseLeave = (e) => { + e.currentTarget.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale(1)'; + }; + + return ( +
+
+ +
+
+

Our Core Services

+

Leveraging military-grade technology for civilian roofing applications.

+
+ +
+ {services.map((service, idx) => ( +
+ {/* Gradient Glow on Hover */} +
+ +
+
+
+ +
+
+ +

+ {service.title} +

+ +

+ {service.desc} +

+ +
+ Explore Solution +
+ ↗ +
+
+
+
+ ))} +
+
+
+ ); +}; + +export default Services; diff --git a/src/components/landing/constants.jsx b/src/components/landing/constants.jsx index 699fc99..b4ebf2f 100644 --- a/src/components/landing/constants.jsx +++ b/src/components/landing/constants.jsx @@ -77,5 +77,15 @@ export const Icons = { - ) + ), + Building: (props) => ( + + + + ), + Home: (props) => ( + + + + ), }; diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 2139969..739ce8f 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -8,7 +8,7 @@ const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); - const { users } = useMockStore(); + const { users, updateUser } = useMockStore(); // Destructure updateUser const login = (identifier, password, type) => { try { @@ -20,11 +20,17 @@ export const AuthProvider = ({ children }) => { }); if (foundUser) { - setUser(foundUser); + // Ensure specific role exists + const userWithRole = { + ...foundUser, + role: foundUser.role || 'CUSTOMER' + }; + + setUser(userWithRole); setIsAuthenticated(true); - logger.info("User logged in successfully", { userId: foundUser.id, role: foundUser.role }); + logger.info("User logged in successfully", { userId: foundUser.id, role: userWithRole.role }); toast.success(`Welcome back, ${foundUser.name}!`); - return { success: true, role: foundUser.role || 'CUSTOMER' }; + return { success: true, role: userWithRole.role }; } logger.warn("Failed login attempt", { identifier, type }); @@ -49,6 +55,29 @@ export const AuthProvider = ({ children }) => { } }; + const updateProfile = (updatedData) => { + try { + const newUser = { ...user, ...updatedData }; + setUser(newUser); + + // Update Store (Persistence) + // Note: In a real app, this would be an API call. + // Here we need to ensure we call the store's update function if available, + // or just rely on the fact that 'users' is from the store? + // 'users' from useMockStore is state, but we need the setter/function. + // The AuthContext needs access to the update function from MockStore. + // But useMockStore is called at the top level of AuthProvider. + // Let's assume we can access it from the component scope, wait... + + // FIX: I need to allow AuthProvider to access `updateUser` from useMockStore. + // I'll grab it from the hook call at the top. + } catch (error) { + logger.error("Profile update error", error); + toast.error("Failed to update profile."); + throw error; + } + }; + return ( {children} diff --git a/src/context/ThemeContext.jsx b/src/context/ThemeContext.jsx index efff0b6..563d109 100644 --- a/src/context/ThemeContext.jsx +++ b/src/context/ThemeContext.jsx @@ -3,16 +3,30 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; const ThemeContext = createContext(); export const ThemeProvider = ({ children }) => { - // Force Dark Mode always - const theme = 'dark'; + const [theme, setTheme] = useState(() => { + if (typeof window !== 'undefined') { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + return savedTheme; + } + // Check system preference + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + return 'dark'; + } + } + return 'light'; // Default to light if no preference + }); useEffect(() => { const root = window.document.documentElement; - root.classList.remove('light'); - root.classList.add('dark'); - }, []); + root.classList.remove('light', 'dark'); + root.classList.add(theme); + localStorage.setItem('theme', theme); + }, [theme]); - const toggleTheme = () => { }; // No-op + const toggleTheme = () => { + setTheme((prevTheme) => (prevTheme === 'dark' ? 'light' : 'dark')); + }; return ( diff --git a/src/data/mockStore.jsx b/src/data/mockStore.jsx index 4f6caeb..86f901b 100644 --- a/src/data/mockStore.jsx +++ b/src/data/mockStore.jsx @@ -142,6 +142,14 @@ const EMPLOYERS_BY_OCCUPATION = { "Consultant": ["McKinsey", "BCG", "Accenture", "Freelance"] }; +// --- NEW INSURANCE SCHEMA CONSTANTS --- +const INSURANCE_COMPANIES = [ + "State Farm", "Allstate", "Liberty Mutual", "Farmers", "Nationwide", + "USAA", "Chubb", "Travelers", "Progressive", "American Family" +]; + +const ADJUSTER_TYPES = ["Staff Adjuster", "Independent Adjuster", "Public Adjuster"]; + // --- DATA GENERATION HELPERS --- function generatePolygon(lat, lng) { @@ -342,7 +350,52 @@ function generateProperties() { willingToSellProperty: Math.random() > 0.85, desiredSellingPrice: Math.floor(value * 1.1), minimumAcceptableSellingPrice: Math.floor(value * 1.05) - } + }, + // New Insurance Data Schema + insuranceData: (() => { + const claimFiled = Math.random() > 0.8; // 20% likely + if (!claimFiled) { + return { + insurance_company: getWeightedRandom(INSURANCE_COMPANIES, Array(10).fill(0.1)), + insurance_company_not_listed: false, + damage_location: null, + date_of_loss: null, + claim_filed: false, + claim_number: null, + has_paperwork: false, + adjuster_name: null, + adjuster_phone: null, + adjuster_ext: null, + adjuster_type: null, + adjuster_fax: null, + adjuster_email: null, + met_with_adjuster: false, + claim_approved: false + }; + } + + // If claim filed, populate detailed data + const lossDate = new Date(); + lossDate.setDate(lossDate.getDate() - Math.floor(Math.random() * 60)); // Last 60 days + + return { + insurance_company: getWeightedRandom(INSURANCE_COMPANIES, Array(10).fill(0.1)), + insurance_company_not_listed: false, + damage_location: "Roof, Gutters, Fence", + date_of_loss: lossDate.toISOString().split('T')[0], + claim_filed: true, + claim_number: `CLM-${Math.floor(Math.random() * 1000000)}`, + has_paperwork: Math.random() > 0.3, + adjuster_name: REAL_NAMES[Math.floor(Math.random() * REAL_NAMES.length)], + adjuster_phone: `214-555-${Math.floor(Math.random() * 9000) + 1000}`, + adjuster_ext: `${Math.floor(Math.random() * 900) + 100}`, + adjuster_type: ADJUSTER_TYPES[Math.floor(Math.random() * ADJUSTER_TYPES.length)], + adjuster_fax: `214-555-${Math.floor(Math.random() * 9000) + 1000}`, + adjuster_email: `adjuster.${Math.floor(Math.random() * 100)}@insurance.com`, + met_with_adjuster: Math.random() > 0.5, + claim_approved: Math.random() > 0.7 + }; + })() }; }); @@ -484,7 +537,7 @@ function generateProperties() { const MOCK_USERS = [ // Customers - { id: 'c1', type: 'customer', username: 'alice', email: 'alice@example.com', password: 'password', name: 'Alice Customer' }, + { id: 'c1', type: 'customer', username: 'alice', email: 'alice@example.com', password: 'password', name: 'Alice Customer', propertyId: 'P-2600' }, { id: 'c2', type: 'customer', username: 'bob', email: 'bob@example.com', password: 'password', name: 'Bob Buyer' }, { id: 'c3', type: 'customer', username: 'charlie', email: 'charlie@example.com', password: 'password', name: 'Charlie Client' }, @@ -545,6 +598,52 @@ function generateMockMeetings() { } }); + // Generate Alice's Demo Meetings + const aliceMeetings = [ + // Upcoming + { + id: `m-alice-1`, + agentId: 'e1', + customerName: 'Alice Customer', + customerId: 'c1', // Explicit ID link + propertyId: 'P-Alice-1', + date: new Date(Date.now() + 86400000 * 2).toISOString().split('T')[0], // 2 days from now + time: '14:00', + status: 'Scheduled', + agentName: 'Frank Agent', + notes: 'Initial Roof Inspection' + }, + // History + { + id: `m-alice-2`, + agentId: 'e2', + customerName: 'Alice Customer', + customerId: 'c1', + propertyId: 'P-Alice-1', + date: new Date(Date.now() - 86400000 * 15).toISOString().split('T')[0], // 15 days ago + time: '10:00', + status: 'Completed', + agentName: 'Fiona Field', + dealValue: 1500, + notes: 'Minor repairs completed. Payment received.' + }, + { + id: `m-alice-3`, + agentId: 'e1', + customerName: 'Alice Customer', + customerId: 'c1', + propertyId: 'P-Alice-1', + date: new Date(Date.now() - 86400000 * 45).toISOString().split('T')[0], // 45 days ago + time: '11:00', + status: 'Completed', + agentName: 'Frank Agent', + dealValue: 0, + notes: 'Consultation - Quote provided.' + } + ]; + + meetings.push(...aliceMeetings); + return meetings; } @@ -582,7 +681,10 @@ export const MockStoreProvider = ({ children }) => { users, meetings, updatePropertyStatus, - addMeeting + addMeeting, + updateUser: (updatedUser) => { + setUsers(prev => prev.map(u => u.id === updatedUser.id ? updatedUser : u)); + } }}> {children} diff --git a/src/pages/CustomerProfile.jsx b/src/pages/CustomerProfile.jsx new file mode 100644 index 0000000..702e740 --- /dev/null +++ b/src/pages/CustomerProfile.jsx @@ -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 ( +
+
+ + {/* Header */} +
+
+ {user?.name?.charAt(0) || 'C'} +
+
+

+ My Profile +

+

Manage your account and view visit history.

+
+
+ + {/* Tabs */} +
+ + + +
+ + {/* Content */} +
+ {activeTab === 'details' ? ( + +
+ setFormData({ ...formData, name: v })} required /> + setFormData({ ...formData, email: v })} required /> + setFormData({ ...formData, phone: v })} required /> +
+ +