feat: Latest updates

This commit is contained in:
Satyam
2026-02-11 16:53:31 +05:30
parent 0da0d6797f
commit 3e0fc5b764
8 changed files with 322 additions and 1178 deletions
+19 -59
View File
@@ -14,24 +14,14 @@ In a competitive roofing market, speed and data are everything. Traditional spre
- **Operational Efficiency**: Automate scheduling and follow-ups with our AI Concierge. - **Operational Efficiency**: Automate scheduling and follow-ups with our AI Concierge.
- **Real-Time Intelligence**: Know the storm risk and roof condition of every property before you knock. - **Real-Time Intelligence**: Know the storm risk and roof condition of every property before you knock.
### 🤖 The "Plano AI Assistant" (Chatbot) ### 🤖 The "Plano AI Concierge" (Upgrade)
Our AI is not just a support bot; it is a context-aware worker that understands your business data. Our AI is now a full-fledged **Sales & Support Engine**:
- **For Guests**: Acts as a "Seasoned Consultant" to triage emergencies (leaks) vs. sales (inspections). "Hook" logic converts casual visitors into leads.
#### For Field Agents (Workers) - **For Employees**: Answers complex queries like *“What is my win rate this month?”* or *“Show me the average home value in this zip code.”*
- **"Smart Querying"**: Ask *“Show me the properties built before 2000”* or *“Which homeowner wants to sell?”*. - **For Customers**: Dynamic logic checks warranty status based on build year and offer personalized maintenance tips.
- **Lead Qualification**: The AI knows which leads are "Hot" and prompts you to prioritize them.
- **Risk Analysis**: Ask *“What is the current storm risk?”* to get a breakdown of wind speeds vs. roof vulnerability.
#### For Customers (Homeowners)
- **Instant Diagnostics**: (Future) Schedule inspections without waiting on hold.
- **Project Updates**: Check the status of their roof repair.
#### For Guests (Website Visitors)
- **Lead Capture**: The AI engages visitors, answers generic questions about roofing services, and encourages them to sign up or request a free estimate.
### 📊 The Data Dashboard ### 📊 The Data Dashboard
A 360-degree view of your territory's health. A 360-degree view of your territory's health.
1. **Golden Leads Scatter Plot**: Visualizes high-value opportunities (High Value + Older Built Year). 1. **Golden Leads Scatter Plot**: Visualizes high-value opportunities (High Value + Older Built Year).
2. **Risk Index Gauge**: A real-time meter combining live wind speed data with the percentage of "Needs Repair" roofs. 2. **Risk Index Gauge**: A real-time meter combining live wind speed data with the percentage of "Needs Repair" roofs.
3. **Revenue Potential**: Bar charts showing which neighborhoods offer the highest ROI based on renovation needs. 3. **Revenue Potential**: Bar charts showing which neighborhoods offer the highest ROI based on renovation needs.
@@ -40,20 +30,25 @@ A 360-degree view of your territory's health.
--- ---
## KEY FEATURES (Latest Updates) ## 🎨 KEY FEATURES (Latest Updates)
### 🎨 "LynkedUp Pro" Branding & UX ### 1. "LynkedUp Pro" Branding & UX
- **Glassmorphism UI**: A premium, dark-mode-first aesthetic with "Spotlight" cards that glow on hover.
- **Smooth Animations**: Global page transitions (Fade/Slide) and silky-smooth collapsible sidebar. - **Smooth Animations**: Global page transitions (Fade/Slide) and silky-smooth collapsible sidebar.
- **Visual Polish**: Glassmorphism everywhere, custom "LynkedUp" loaders, and pulsing interactive elements. - **System-Wide Dark Mode**: Inverted map tiles and high-contrast text for comfortable night viewing.
- **Dark Mode**: Fully supported system-wide dark mode with inverted map tiles for comfortable night viewing.
### 🌍 Advanced Mapping ### 2. Advanced Geospatial Mapping
- **Lead Creation**: Click anywhere on the map to reverse-geocode addresses and create new leads on the fly. - **Interactive Polygons**: Color-coded property footprints (Red=Hot Lead, Blue=Customer).
- **Polygon Visualization**: Color-coded property footprints indicating status (Hot Lead, Customer, etc.). - **Reverse Geocoding**: Click anywhere on the map to create a new property record instantly using OpenStreetMap data.
### 3. Role-Based Access Control (RBAC)
- **Field Agents**: View assigned territories, update lead status, and request schedule changes.
- **Admins**: Global view of all agents, approval workflows, and system settings.
- **Customers**: (Coming Soon) Portal to view project status and request service.
--- ---
## 👥 USER ROLES & DEMO ACCOUNTS ## 👥 USER ROLES & DEMO ACCOUNTS
To explore the platform, use these pre-configured accounts. **Password for ALL accounts is: `password`** To explore the platform, use these pre-configured accounts. **Password for ALL accounts is: `password`**
@@ -61,63 +56,28 @@ To explore the platform, use these pre-configured accounts. **Password for ALL a
*The primary user. Can view maps, access detailed property data, and manage leads.* *The primary user. Can view maps, access detailed property data, and manage leads.*
- **Username/ID**: `FA001` (Frank Agent) - **Username/ID**: `FA001` (Frank Agent)
- **Alternates**: `FA002`, `FA003`, `FA004`, `FA005` - **Alternates**: `FA002`, `FA003`, `FA004`, `FA005`
- **Role Capabilities**:
- Access Dashboard & Widget Data.
- View/Edit Lead Status (Hot, Cold, Customer).
- Use Chatbot with full data context.
### 2. Administrator ### 2. Administrator
*The manager. Sees all agents and global stats.* *The manager. Sees all agents and global stats.*
- **Username/ID**: `ADM01` (Adam Admin) - **Username/ID**: `ADM01` (Adam Admin)
- **Alternates**: `ADM02`, `ADM03` - **Alternates**: `ADM02`, `ADM03`
- **Role Capabilities**:
- All Agent capabilities + User Management (Mock).
### 3. Customer (Homeowner) ### 3. Customer (Homeowner)
*The client. Limited view of their own property.* *The client. Limited view of their own property.*
- **Username**: `alice` - **Username**: `alice`
- **Alternates**: `bob`, `charlie` - **Alternates**: `bob`, `charlie`
- **Role Capabilities**:
- View personal property details.
- Request Service.
--- ---
## 💻 PART 2: DEVELOPER GUIDE ## 💻 PART 2: DEVELOPER GUIDE
### 🛠 Tech Stack & Architecture ### 🛠 Tech Stack & Architecture
- **Frontend**: React 18 (Vite), Tailwind CSS, Glassmorphism UI. - **Frontend**: React 18 (Vite), Tailwind CSS, Glassmorphism UI.
- **State**: `MockStore` (Simulates Backend/DB), `AuthContext` (Session Management). - **State**: `MockStore` (Simulates Backend/DB), `AuthContext` (Session Management).
- **AI Engine**: Groq SDK (`qwen-3-32b`) injected with dynamic system prompts. - **AI Engine**: Groq SDK (`qwen-32b`) injected with dynamic system prompts.
- **Visuals**: `react-leaflet` (Maps), `recharts` (Analytics), `lucide-react` (Icons). - **Visuals**: `react-leaflet` (Maps), `recharts` (Analytics), `lucide-react` (Icons).
### 🧠 Deep Dive: How It Works
#### 1. The "Mock Store" (Data Layer)
Located in `src/data/mockStore.jsx`. It generates ~60 realistic property records using weighted randomization algorithms to create organic-looking data distributions (e.g., 10% bad roofs, random build years).
- **Why?**: To provide a rich, testable dataset without needing a live backend database.
#### 2. AI Context Injection
Located in `src/components/Chatbot.jsx`.
- When a user sends a message, we don't just send their text. We inject a **System Prompt** containing live dashboard metrics.
- **Example Injection**:
```text
SYSTEM: You are a roofing assistant.
CONTEXT:
- 45 Total Properties
- 12 Golden Leads (Top: 123 Maple Dr)
- 8 Hot Leads needing canvassing
```
- This allows the AI to answer specific questions accurately.
#### 3. Leaflet Customization
Located in `src/pages/Maps.jsx`.
- We use custom SVGs overlays on the map.
- **Reverse Geocoding**: Clicking a blank spot calls the OpenStreetMap API to fetch the address, allowing agents to "create" new property records on the fly.
### 🚀 Setup & Contribution ### 🚀 Setup & Contribution
1. **Clone**: `git clone <repo>` 1. **Clone**: `git clone <repo>`
2. **Install**: `pnpm install` 2. **Install**: `pnpm install`
3. **Env**: Create `.env` with `VITE_GROQ_API_KEY` and `VITE_OPENWEATHER_API_KEY`. 3. **Env**: Create `.env` with `VITE_GROQ_API_KEY` and `VITE_OPENWEATHER_API_KEY`.
+39 -1001
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -11,6 +11,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@vercel/analytics": "^1.6.1",
"autoprefixer": "^10.4.23", "autoprefixer": "^10.4.23",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
+210 -90
View File
@@ -14,30 +14,198 @@ const groq = new Groq({
dangerouslyAllowBrowser: true // Allowed for client-side demo dangerouslyAllowBrowser: true // Allowed for client-side demo
}); });
const SYSTEM_PROMPT = ` // --- CONTEXT GENERATORS ---
You are the AI Concierge for "LynkedUpPro", the pinnacle of roofing excellence with over two decades of mastery in the field.
Your mission is to guide clients through our seamless, automated roof damage detection and restoration services with elegance and expertise.
COMPANY HIGHLIGHTS: const BASE_IDENTITY = `
- **Experience**: Over 20 years of unrivaled craftsmanship. You are the AI Concierge for "LynkedUp Pro", the pinnacle of roofing excellence.
- **Special Offer**: The first 3 diagnostic examination visits are completely **COMPLIMENTARY**. Your mission is to assist employees with data-driven insights and guide customers through our services.
- **Efficiency**: We provide detailed estimations in a "jiffy" — swift and precise.
- **Value**: We pride ourselves on delivering superior results that outshine the competition, all while remaining cost-effective.
- **Discounts**: We offer exclusive price reductions for **Veterans and Seniors** as a token of our gratitude.
- **Pricing Policy**: Transparency is our hallmark. While our initial diagnostics are free (first 3 visits), our restoration services are priced with integrity, tailored strictly to the extent of damage and the surface area requiring attention.
- **Contact**: (555) 012-3456
GUIDELINES: COMPANY INFO:
1. **Tone**: Use eloquent, "flowery", and professional language. Be persuasive yet sincere. (e.g., instead of "we fix roofs", say "we restore the integrity of your shelter"). - **Phone**: 866-259-6533
2. **Context Awareness**: Do not overwhelm the user. Gauge their intent. If they ask about price, explain the free exams vs. work costs. If they ask about speed, mention our "estimation in a jiffy". - **Experience**: 20+ years of craftsmanship.
3. **For General Inquiries**: Weave in our 20+ years of heritage and our commitment to excellence. - **Offer**: First 3 diagnostic exams are FREE.
4. **For Callback Requests**: - **Discounts**: 15% off for Veterans & Seniors.
- **IF GUEST**: Gently invite them to Log In or Sign Up to unlock our seamless scheduling experience.
- **IF LOGGED IN**: Request their preferred **Phone Number** and **Time Slot** to arrange a consultation. Extract the **Customer Name** from the conversation context (e.g., if they asked about "Greg Alston"). TONE: Professional, efficient, and data-aware.
- **ACTION**: Once details are confirmed, output this JSON object ONLY at the very end:
:::CALLBACK_REQUEST{"name": "...", "phone": "...", "time": "HH:MM"}:::
`; `;
const getEmployeeContext = (user, meetings, properties) => {
// 1. Agenda (Today + Upcoming)
const today = new Date().toISOString().split('T')[0];
const myMeetings = meetings
.filter(m => m.agentId === user.id)
.sort((a, b) => new Date(a.date) - new Date(b.date));
// Simple segment: Today vs Upcoming
const agendaText = myMeetings.map(m => {
const isToday = m.date === today;
return `- [${m.date} @ ${m.time}] ${m.status.toUpperCase()} with ${m.customerName} (${m.propertyId})${isToday ? ' **(TODAY)**' : ''}
Issue: "${m.issueDescription || 'N/A'}"
Comments: "${m.customerComments || 'None'}"
Outcome: ${m.outcome || 'Pending'}`;
}).join('\n');
// 2. Hot Leads (Top 5)
const hotLeads = properties
.filter(p => p.canvassingStatus === 'Hot Lead')
.slice(0, 5)
.map(p => `- ${p.propertyData.propertyAddress} (Value: $${p.propertyData.currentEstimatedMarketValue.toLocaleString()})`)
.join('\n');
// 3. Golden Leads (Older + High Value)
const goldenLeads = properties
.filter(p => p.propertyData.yearBuilt < 2000 && p.propertyData.currentEstimatedMarketValue > 400000)
.slice(0, 3)
.map(p => `- ${p.propertyData.propertyAddress} (Built: ${p.propertyData.yearBuilt})`)
.join('\n');
// 4. Pending Signatures
const pendingSigs = properties
.filter(p => p.propertyData.pendingSignature)
.map(p => `- ${p.propertyData.propertyAddress} (Owner: ${p.ownerData.fullName})`)
.join('\n');
// 5. Revenue & Performance Snapshot
const myCompleted = meetings.filter(m => m.agentId === user.id && (m.status === 'Converted' || m.status === 'Completed'));
const myWins = meetings.filter(m => m.agentId === user.id && m.status === 'Converted');
const totalRevenue = myWins.reduce((sum, m) => sum + (m.dealValue || 0), 0);
const winRate = myCompleted.length > 0 ? Math.round((myWins.length / myCompleted.length) * 100) : 0;
// 6. Neighborhood Intelligence
const avgValue = Math.round(properties.reduce((sum, p) => sum + p.propertyData.currentEstimatedMarketValue, 0) / properties.length);
const commonRoof = "Architectural Shingle"; // Hardcoded for demo, or could calculate mode
return `
ROLE: FIELD AGENT / ADMIN (${user.name})
YOUR DATA ACCESS:
1. AGENDA:
${agendaText || "No meetings found."}
2. HOT LEADS (Priority):
${hotLeads || "None."}
3. GOLDEN LEADS (High Value / Old):
${goldenLeads || "None."}
4. PENDING SIGNATURES (Follow Up Needed):
${pendingSigs || "None."}
5. MY PERFORMANCE:
- Total Revenue (YTD): $${totalRevenue.toLocaleString()}
- Win Rate: ${winRate}% (${myWins.length} wins / ${myCompleted.length} completed)
6. TERRITORY INSIGHTS:
- Avg Home Value: $${avgValue.toLocaleString()}
- Most Common Roof: ${commonRoof}
INSTRUCTIONS:
- If asked about "agenda", "schedule", or "next appointment", summarize the meetings.
- If asked about "leads", suggest the designated Hot or Golden leads.
- If asked about "performance" or "stats", share your win rate and revenue.
- If asked about "neighborhood" or "territory", share the average value and roof types.
`;
};
const getCustomerContext = (user, meetings, properties) => {
// 1. My Property
// In mock data, customers are linked via 'propertyId' or name matching.
const myProperty = properties.find(p => p.propertyData.propertyId === user.propertyId)
|| properties.find(p => p.ownerData.fullName === user.name);
let propDetails = "Property details not found.";
let preventiveTips = "Standard seasonal maintenance recommended.";
let warrantyStatus = "Standard Manufacturer Warranty (Expired)";
if (myProperty) {
propDetails = `
- Address: ${myProperty.propertyData.propertyAddress}
- Roof Type: ${myProperty.propertyData.roofMaterial}
- Roof Condition: ${myProperty.propertyData.roofCondition}
- Storm Risk Score: ${myProperty.propertyData.neighborhoodRating}/10
- Est. Value: $${myProperty.propertyData.currentEstimatedMarketValue.toLocaleString()}
- Last Inspection: ${myProperty.propertyData.lastInspectionDate || 'Unknown'}
`;
// Dynamic Tips
if (myProperty.propertyData.roofCondition === 'Needs Repair') {
preventiveTips = "URGENT: Schedule an inspection immediately to prevent water damage.";
} else if (myProperty.propertyData.treeCoverage > 50) {
preventiveTips = "Consider trimming overhanging branches to protect shingles.";
}
// Mock Warranty Logic
const yearBuilt = myProperty.propertyData.yearBuilt;
if (yearBuilt > 2015) warrantyStatus = "Active Workmanship Warranty (Valid until 2030)";
}
// 2. History & Upcoming
const myHistory = meetings
.filter(m => m.customerId === user.id || m.customerName === user.name)
.sort((a, b) => new Date(b.date) - new Date(a.date)); // Newest first
const historyText = myHistory.map(m => {
const isFuture = new Date(m.date) > new Date();
return `- [${m.date}] ${isFuture ? 'UPCOMING' : 'COMPLETED'} (${m.status})
Agent: ${m.agentName || 'Assigned Rep'}
Note: ${m.notes}
${m.dealValue ? `Cost: $${m.dealValue}` : ''}`;
}).join('\n');
// 3. Total Spend
const totalSpend = myHistory.reduce((sum, m) => sum + (m.dealValue || 0), 0);
return `
ROLE: HOMEOWNER / CUSTOMER (${user.name})
YOUR DATA:
1. PROPERTY PROFILE:
${propDetails}
2. SERVICE HISTORY:
${historyText || "No records found."}
3. FINANCIALS:
- Total Spend to Date: $${totalSpend.toLocaleString()}
- Warranty Status: ${warrantyStatus}
4. PERSONALIZED ADVICE:
- Tip: ${preventiveTips}
INSTRUCTIONS:
- Answer questions about "my roof", "my appointment", "warranty", or "spending".
- Be polite and reassuring.
`;
};
const getGuestContext = () => {
return `
ROLE: EXPERT ROOFING CONSULTANT (20+ Years Experience)
YOUR KNOWLEDGE BASE (Use this to diagnose, reassure, but EDUCATE ON HIDDEN RISKS):
1. COMMON ISSUES & RISK FACTORS:
- **Active Leak**: "A bucket stops the floor damage, but water is currently saturating your insulation and decking." -> URGENCY: "Mold begins to grow in wet insulation within 24-48 hours. We need to map the moisture path immediately."
- **Missing Shingles**: "It looks minor, but your underlayment is now exposed to UV rays which destroy it in days." -> RISK: "Leaving this can lead to a denied insurance claim later for 'neglect'."
- **Hail Dents**: "These look like small bruises, but they have fractured the fiberglass mat." -> INVISIBLE DAMAGE: "This reduces roof life by 3-5 years. If not documented now, insurance will call it 'wear and tear' later."
- **Granule Loss**: "Your roof is shedding its sunscreen." -> REALITY: "The asphalt is now baking in the Texas sun. This is a leading indicator of roof failure within 12-18 months."
2. COMPLEX ISSUE TRIAGE:
- **Ice Dams**: "Heat loss from your attic is melting snow." -> SOLUTION: "We need to assess your intake vents, not just patch the roof."
- **Sagging**: "This indicates structural fatigue or long-term water intrusion." -> ACTION: "Do not walk on this area. Needs professional stabilization."
3. THE "WHY US" HOOK:
- "Insurance adjusters use specific criteria to approve claims. We know exactly what they look for."
- "A DIY patch often voids the manufacturer warranty. Our repairs are certified."
- "We are in Plano right now—I can have a project manager stop by in 30 minutes to check the moisture levels for free."
INSTRUCTIONS:
- **Tone**: Professional, Authoritative, "Doctor-like".
- **Strategy**: Validate the user's observation -> Explain the *hidden* consequence of waiting -> Offer the *Zero-Risk* solution (Free Inspection).
- **Contact**: "Call 866-259-6533. We can be there today."
`;
};
const Chatbot = () => { const Chatbot = () => {
const { user, isAuthenticated } = useAuth(); const { user, isAuthenticated } = useAuth();
const { addMeeting, meetings, properties } = useMockStore(); const { addMeeting, meetings, properties } = useMockStore();
@@ -47,7 +215,7 @@ const Chatbot = () => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isDemoMode, setIsDemoMode] = useState(false); const [isDemoMode, setIsDemoMode] = useState(false);
const [messages, setMessages] = useState([ const [messages, setMessages] = useState([
{ role: 'assistant', content: 'Hello! Welcome to LynkedUpPro. How can I help you with your roofing needs today?' } { role: 'assistant', content: 'Hello! Welcome to LynkedUp Pro. How can I help you with your roofing needs today?' }
]); ]);
const messagesEndRef = useRef(null); const messagesEndRef = useRef(null);
@@ -65,10 +233,15 @@ const Chatbot = () => {
// Update greeting and context when user changes // Update greeting and context when user changes
useEffect(() => { useEffect(() => {
const greeting = user let greeting = 'Hello! Welcome to LynkedUp Pro. How can I help you with your roofing needs today?';
? `Welcome back to LynkedUpPro, ${user.name}! I see you're logged in. How can I assist you with your roofing projects today?` if (user) {
: 'Hello! Welcome to LynkedUpPro. How can I help you with your roofing needs today?'; greeting = `Welcome back, ${user.name}! `;
if (user.role === 'FIELD_AGENT' || user.role === 'ADMIN') {
greeting += "I have your latest territory data and agenda ready. What do you need?";
} else if (user.role === 'CUSTOMER') {
greeting += "I have your property records open. How can I assist?";
}
}
setMessages([{ role: 'assistant', content: greeting }]); setMessages([{ role: 'assistant', content: greeting }]);
}, [user]); }, [user]);
@@ -85,8 +258,8 @@ const Chatbot = () => {
// ... existing demo logic ... // ... existing demo logic ...
setTimeout(() => { setTimeout(() => {
const responses = [ const responses = [
"I can help with that! At LynkedUpPro, we've been in business for 20+ years. I can't access real-time data in demo mode, but I can help you navigate.", "I can help with that! At LynkedUp Pro, we've been in business for 20+ years. I can't access real-time data in demo mode, but I can help you navigate.",
"We specialize in automated damage detection. Please call us at (555) 012-3456 for a real quote!", "Please call us at 866-259-6533 for a real quote!", // UPDATED PHONE
"Would you like to schedule a callback? Please log in first." "Would you like to schedule a callback? Please log in first."
]; ];
const randomResponse = responses[Math.floor(Math.random() * responses.length)]; const randomResponse = responses[Math.floor(Math.random() * responses.length)];
@@ -98,74 +271,21 @@ const Chatbot = () => {
// --- REAL AI REQUEST --- // --- REAL AI REQUEST ---
// Construct System Context with User Details and DATA // 1. Build Dynamic Context
let systemContext = SYSTEM_PROMPT; let systemContext = BASE_IDENTITY;
if (user) { if (user) {
// 1. Basic User Info if (user.role === 'FIELD_AGENT' || user.role === 'ADMIN') {
systemContext += `\n\nCURRENT USER CONTEXT: systemContext += getEmployeeContext(user, meetings, properties);
- Name: ${user.name} } else if (user.role === 'CUSTOMER') {
- Role: ${user.role} (Type: ${user.type}) systemContext += getCustomerContext(user, meetings, properties);
- ID: ${user.id}
- Email: ${user.email || 'Not provided'}`;
// 2. Inject Meeting/Schedule Data (If Agent or Admin)
const userMeetings = user.role === 'ADMIN'
? meetings // Admins see ALL meetings (simplified for chatbot context, maybe limit to recent?)
: meetings.filter(m => m.agentId === user.id);
if (userMeetings.length > 0) {
const scheduleSummary = userMeetings.map(m =>
`- [${m.status}] ${m.date} @ ${m.time}: ${m.customerName} (Prop: ${m.propertyId})${m.dealValue ? ` | Value: $${m.dealValue.toLocaleString()}` : ''}`
).join('\n');
systemContext += `\n\nUSER'S SCHEDULE / DEALS (Use this to answer questions about meetings, status, and conversions):
${scheduleSummary}`;
} else {
systemContext += `\n\nUSER'S SCHEDULE: No meetings found.`;
} }
} else { } else {
systemContext += `\n\nCURRENT USER CONTEXT: Guest (Not Logged In)`; // GUEST CONTEXT - The "Seasoned Consultant"
} systemContext += getGuestContext();
// --- INJECT DASHBOARD DATA ---
// Calculate real-time metrics to give the AI context about the territory
if (properties && properties.length > 0) {
const totalProps = properties.length;
// 1. Golden Leads
const goldenLeads = properties.filter(p =>
p.propertyData.yearBuilt < 2000 && p.propertyData.currentEstimatedMarketValue > 400000
);
const goldenLeadAddresses = goldenLeads.slice(0, 3).map(p => p.propertyData.propertyAddress).join(', ');
// 2. Hot Leads
const hotLeads = properties.filter(p => p.canvassingStatus === 'Hot Lead');
// 3. Roof Health
const roofCounts = properties.reduce((acc, p) => {
const c = p.propertyData.roofCondition || 'Unknown';
acc[c] = (acc[c] || 0) + 1;
return acc;
}, {});
// 4. Intent
const willingToSell = properties.filter(p => p.ownerData.willingToSellProperty).length;
const willingToRent = properties.filter(p => p.ownerData.willingToRentProperty).length;
// 5. Risk Index (simplified calculation for context)
const vulnerableCount = (roofCounts['Fair'] || 0) + (roofCounts['Needs Repair'] || 0);
const riskPercent = Math.round((vulnerableCount / totalProps) * 100);
systemContext += `\n\nLIVE DASHBOARD INTELLIGENCE (Use this to answer questions about leads and territory status):
- Total Properties in Territory: ${totalProps}
- GOLDEN LEADS (High Value + Older Homes): ${goldenLeads.length} found. (Top examples: ${goldenLeadAddresses}...)
- HOT LEADS (Priority for Canvassing): ${hotLeads.length} properties.
- OWNER INTENT: ${willingToSell} owners willing to sell, ${willingToRent} willing to rent.
- ROOF HEALTH BREAKDOWN: Excellent: ${roofCounts['Excellent'] || 0}, Good: ${roofCounts['Good'] || 0}, Fair: ${roofCounts['Fair'] || 0}, Needs Repair: ${roofCounts['Needs Repair'] || 0}.
- RISK ANALYSIS: ${riskPercent}% of territory has vulnerable roofs.`;
} }
// 2. Call Groq
const chatCompletion = await groq.chat.completions.create({ const chatCompletion = await groq.chat.completions.create({
messages: [ messages: [
{ role: 'system', content: systemContext }, { role: 'system', content: systemContext },
@@ -181,7 +301,7 @@ ${scheduleSummary}`;
// Filter out <think> blocks if present // Filter out <think> blocks if present
responseContent = responseContent.replace(/<think>[\s\S]*?<\/think>/gi, '').trim(); responseContent = responseContent.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
// Check for Function Call Pattern // Check for Function Call Pattern (Legacy Callback)
const callbackMatch = responseContent.match(/:::CALLBACK_REQUEST({.*?}):::/); const callbackMatch = responseContent.match(/:::CALLBACK_REQUEST({.*?}):::/);
if (callbackMatch) { if (callbackMatch) {
const jsonStr = callbackMatch[1]; const jsonStr = callbackMatch[1];
+2 -2
View File
@@ -3,7 +3,7 @@ import { Icons } from './landing/constants';
const services = [ const services = [
{ {
title: "Residential Repair", title: "Commercial and Residential Repair and Diagnostics",
desc: "Leak detection & shingle replacement in record time. We identify micro-fractures invisible to the naked eye.", desc: "Leak detection & shingle replacement in record time. We identify micro-fractures invisible to the naked eye.",
icon: Icons.Home, icon: Icons.Home,
color: "from-blue-400 to-blue-600", color: "from-blue-400 to-blue-600",
@@ -12,7 +12,7 @@ const services = [
// Commercial Development Removed as per request // Commercial Development Removed as per request
{ {
title: "Lead & Data Solutions", title: "Lead & Data Solutions",
desc: "Advanced lead scoring and real-time storm tracking alerts. Know where the damage is before the phone rings.", desc: "Advanced lead scoring and real time storm tracking alerts. Know where the damage is before the phone rings. Powered by intelligent data models and predictive algorithms, we direct your reps and marketing into high-impact zones; where probability, urgency and opportunity align.",
icon: Icons.Chart, icon: Icons.Chart,
color: "from-purple-400 to-indigo-600", color: "from-purple-400 to-indigo-600",
glow: "shadow-purple-500/20" glow: "shadow-purple-500/20"
+37 -5
View File
@@ -329,7 +329,10 @@ function generateProperties() {
latestPurchaseDate: "2015-06-20", latestPurchaseDate: "2015-06-20",
latestSaleListingDate: null, latestSaleListingDate: null,
latestSaleAskingPrice: null, latestSaleAskingPrice: null,
lotPlotSize: isCommercial ? "1.5 Acres" : "0.22 Acres" lotPlotSize: isCommercial ? "1.5 Acres" : "0.22 Acres",
// New Fields for Chatbot
pendingSignature: Math.random() > 0.9, // 10% chance
closedDate: Math.random() > 0.8 ? "2026-01-15" : null // Simple mock for revenue queries
}, },
ownerData: { ownerData: {
ownerId: `OWN-${200 + index}`, ownerId: `OWN-${200 + index}`,
@@ -583,6 +586,22 @@ function generateMockMeetings() {
const hour = 9 + (i % 8); const hour = 9 + (i % 8);
const timeString = `${hour.toString().padStart(2, '0')}:00`; const timeString = `${hour.toString().padStart(2, '0')}:00`;
// New Data Fields for Chatbot Context
const issues = [
"Leaking skylight in master bath",
"Missing shingles after recent storm",
"Water spots on ceiling",
"Gutter detachment on north side",
"General roof inspection requested"
];
const comments = [
"Customer is worried about upcoming rain.",
"Insurance adjuster already visited.",
"Prefer morning appointments.",
"Dog in backyard, please knock front door.",
"Previous contractor did poor work."
];
meetings.push({ meetings.push({
id: `m${meetingIdCounter++}`, id: `m${meetingIdCounter++}`,
agentId: agentId, agentId: agentId,
@@ -593,7 +612,11 @@ function generateMockMeetings() {
status: status, status: status,
dealValue: dealValue, dealValue: dealValue,
notes: status === 'Converted' ? 'Contract signed for full roof replacement.' : notes: status === 'Converted' ? 'Contract signed for full roof replacement.' :
status === 'Rescheduled' ? 'Client verified availability.' : 'Standard consultation.' status === 'Rescheduled' ? 'Client verified availability.' : 'Standard consultation.',
// New Fields
issueDescription: issues[i % issues.length],
customerComments: comments[i % comments.length],
outcome: status === 'Converted' ? 'Signed Contract' : status === 'Completed' ? 'Quote Sent' : 'Pending'
}); });
} }
}); });
@@ -611,7 +634,10 @@ function generateMockMeetings() {
time: '14:00', time: '14:00',
status: 'Scheduled', status: 'Scheduled',
agentName: 'Frank Agent', agentName: 'Frank Agent',
notes: 'Initial Roof Inspection' notes: 'Initial Roof Inspection',
issueDescription: 'Noticed missing shingles after last storm.',
customerComments: 'Gate code is 1234.',
outcome: 'Pending'
}, },
// History // History
{ {
@@ -625,7 +651,10 @@ function generateMockMeetings() {
status: 'Completed', status: 'Completed',
agentName: 'Fiona Field', agentName: 'Fiona Field',
dealValue: 1500, dealValue: 1500,
notes: 'Minor repairs completed. Payment received.' notes: 'Minor repairs completed. Payment received.',
issueDescription: 'Leak in garage.',
customerComments: 'Please call before arriving.',
outcome: 'Repairs Completed'
}, },
{ {
id: `m-alice-3`, id: `m-alice-3`,
@@ -638,7 +667,10 @@ function generateMockMeetings() {
status: 'Completed', status: 'Completed',
agentName: 'Frank Agent', agentName: 'Frank Agent',
dealValue: 0, dealValue: 0,
notes: 'Consultation - Quote provided.' notes: 'Consultation - Quote provided.',
issueDescription: 'General inspection.',
customerComments: '',
outcome: 'Quote Sent'
} }
]; ];
+3
View File
@@ -7,8 +7,11 @@ import { MockStoreProvider } from './data/mockStore'
import { AuthProvider } from './context/AuthContext' import { AuthProvider } from './context/AuthContext'
import { ThemeProvider } from './context/ThemeContext' import { ThemeProvider } from './context/ThemeContext'
import { Analytics } from "@vercel/analytics/react"
ReactDOM.createRoot(document.getElementById('root')).render( ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode> <React.StrictMode>
<Analytics />
<BrowserRouter> <BrowserRouter>
<MockStoreProvider> <MockStoreProvider>
<AuthProvider> <AuthProvider>
+9 -19
View File
@@ -170,7 +170,7 @@ const Landing = () => {
<div className="max-w-7xl mx-auto px-6 relative z-10 text-center" ref={textRef}> <div className="max-w-7xl mx-auto px-6 relative z-10 text-center" ref={textRef}>
<div className="inline-flex items-center px-4 py-1.5 rounded-full bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-500/20 text-[10px] font-bold tracking-widest uppercase mb-8 backdrop-blur-sm shadow-sm text-blue-800 dark:text-blue-300"> <div className="inline-flex items-center px-4 py-1.5 rounded-full bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-500/20 text-[10px] font-bold tracking-widest uppercase mb-8 backdrop-blur-sm shadow-sm text-blue-800 dark:text-blue-300">
<span className="mr-2 text-base">🇺🇸</span> <span className="mr-2 text-base">🇺🇸</span>
Proudly American Made by Americans, For Americans Crafted to Level the Field for Those Who Build America
</div> </div>
<h1 className="text-6xl md:text-9xl font-black tracking-tighter mb-6 leading-[0.9]"> <h1 className="text-6xl md:text-9xl font-black tracking-tighter mb-6 leading-[0.9]">
@@ -205,21 +205,6 @@ const Landing = () => {
</div> </div>
</div> </div>
{/* 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>
<div className="w-8 h-8 rounded-full bg-gray-400 border-2 border-white dark:border-black"></div>
<div className="w-8 h-8 rounded-full bg-gray-500 border-2 border-white dark:border-black"></div>
</div>
<span className="text-xs font-bold">500+ Happy Homes</span>
</div>
<div className="flex items-center space-x-2">
<ShieldCheck size={16} />
<span className="text-xs font-bold">Licensed & Insured</span>
</div>
</div> */}
</div> </div>
</div> </div>
@@ -479,8 +464,8 @@ const Landing = () => {
<div className="grid md:grid-cols-4 gap-12 mb-20"> <div className="grid md:grid-cols-4 gap-12 mb-20">
<div className="col-span-1 md:col-span-2 space-y-6"> <div className="col-span-1 md:col-span-2 space-y-6">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<img src={Logo} alt="LynkedUp Pro" className="w-8 h-8 object-contain opacity-80 grayscale hover:grayscale-0 transition-all" /> <img src={Logo} alt="LynkedUp Pro" className="h-16 w-auto object-contain" />
<span className="text-xl font-bold text-white tracking-tight">LynkedUp Pro</span> {/* Removed: <span className="text-xl font-bold text-white tracking-tight">LynkedUp Pro</span> */}
</div> </div>
<p className="text-sm leading-relaxed max-w-sm text-zinc-500"> <p className="text-sm leading-relaxed max-w-sm text-zinc-500">
Revolutionizing property management and roofing inspections with military-grade precision and AI-driven insights. Built for the modern homeowner. Revolutionizing property management and roofing inspections with military-grade precision and AI-driven insights. Built for the modern homeowner.
@@ -505,9 +490,14 @@ const Landing = () => {
</li> </li>
<li className="flex items-center"> <li className="flex items-center">
<Mail size={16} className="mr-3 text-blue-500 shrink-0" /> <Mail size={16} className="mr-3 text-blue-500 shrink-0" />
<span>info@LynkedUpPro.com</span> <span>Info@LynkedUpPro.com</span>
</li> </li>
</ul> </ul>
<div className="pt-4">
<Link to="/login" className="inline-flex items-center px-6 py-2.5 rounded-full bg-blue-700 hover:bg-blue-600 text-white text-sm font-bold transition-colors shadow-lg shadow-blue-900/20">
Book Demo <ArrowRight size={14} className="ml-2" />
</Link>
</div>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">