feat: Latest updates
This commit is contained in:
+210
-90
@@ -14,30 +14,198 @@ const groq = new Groq({
|
||||
dangerouslyAllowBrowser: true // Allowed for client-side demo
|
||||
});
|
||||
|
||||
const SYSTEM_PROMPT = `
|
||||
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.
|
||||
// --- CONTEXT GENERATORS ---
|
||||
|
||||
COMPANY HIGHLIGHTS:
|
||||
- **Experience**: Over 20 years of unrivaled craftsmanship.
|
||||
- **Special Offer**: The first 3 diagnostic examination visits are completely **COMPLIMENTARY**.
|
||||
- **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
|
||||
const BASE_IDENTITY = `
|
||||
You are the AI Concierge for "LynkedUp Pro", the pinnacle of roofing excellence.
|
||||
Your mission is to assist employees with data-driven insights and guide customers through our services.
|
||||
|
||||
GUIDELINES:
|
||||
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").
|
||||
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".
|
||||
3. **For General Inquiries**: Weave in our 20+ years of heritage and our commitment to excellence.
|
||||
4. **For Callback Requests**:
|
||||
- **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").
|
||||
- **ACTION**: Once details are confirmed, output this JSON object ONLY at the very end:
|
||||
:::CALLBACK_REQUEST{"name": "...", "phone": "...", "time": "HH:MM"}:::
|
||||
COMPANY INFO:
|
||||
- **Phone**: 866-259-6533
|
||||
- **Experience**: 20+ years of craftsmanship.
|
||||
- **Offer**: First 3 diagnostic exams are FREE.
|
||||
- **Discounts**: 15% off for Veterans & Seniors.
|
||||
|
||||
TONE: Professional, efficient, and data-aware.
|
||||
`;
|
||||
|
||||
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 { user, isAuthenticated } = useAuth();
|
||||
const { addMeeting, meetings, properties } = useMockStore();
|
||||
@@ -47,7 +215,7 @@ const Chatbot = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDemoMode, setIsDemoMode] = useState(false);
|
||||
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);
|
||||
|
||||
@@ -65,10 +233,15 @@ const Chatbot = () => {
|
||||
|
||||
// Update greeting and context when user changes
|
||||
useEffect(() => {
|
||||
const greeting = user
|
||||
? `Welcome back to LynkedUpPro, ${user.name}! I see you're logged in. How can I assist you with your roofing projects today?`
|
||||
: 'Hello! Welcome to LynkedUpPro. How can I help you with your roofing needs today?';
|
||||
|
||||
let greeting = 'Hello! Welcome to LynkedUp Pro. How can I help you with your roofing needs today?';
|
||||
if (user) {
|
||||
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 }]);
|
||||
}, [user]);
|
||||
|
||||
@@ -85,8 +258,8 @@ const Chatbot = () => {
|
||||
// ... existing demo logic ...
|
||||
setTimeout(() => {
|
||||
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.",
|
||||
"We specialize in automated damage detection. Please call us at (555) 012-3456 for a real quote!",
|
||||
"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.",
|
||||
"Please call us at 866-259-6533 for a real quote!", // UPDATED PHONE
|
||||
"Would you like to schedule a callback? Please log in first."
|
||||
];
|
||||
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
|
||||
@@ -98,74 +271,21 @@ const Chatbot = () => {
|
||||
|
||||
// --- REAL AI REQUEST ---
|
||||
|
||||
// Construct System Context with User Details and DATA
|
||||
let systemContext = SYSTEM_PROMPT;
|
||||
// 1. Build Dynamic Context
|
||||
let systemContext = BASE_IDENTITY;
|
||||
|
||||
if (user) {
|
||||
// 1. Basic User Info
|
||||
systemContext += `\n\nCURRENT USER CONTEXT:
|
||||
- Name: ${user.name}
|
||||
- Role: ${user.role} (Type: ${user.type})
|
||||
- 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.`;
|
||||
if (user.role === 'FIELD_AGENT' || user.role === 'ADMIN') {
|
||||
systemContext += getEmployeeContext(user, meetings, properties);
|
||||
} else if (user.role === 'CUSTOMER') {
|
||||
systemContext += getCustomerContext(user, meetings, properties);
|
||||
}
|
||||
} else {
|
||||
systemContext += `\n\nCURRENT USER CONTEXT: Guest (Not Logged In)`;
|
||||
}
|
||||
|
||||
// --- 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.`;
|
||||
// GUEST CONTEXT - The "Seasoned Consultant"
|
||||
systemContext += getGuestContext();
|
||||
}
|
||||
|
||||
// 2. Call Groq
|
||||
const chatCompletion = await groq.chat.completions.create({
|
||||
messages: [
|
||||
{ role: 'system', content: systemContext },
|
||||
@@ -181,7 +301,7 @@ ${scheduleSummary}`;
|
||||
// Filter out <think> blocks if present
|
||||
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({.*?}):::/);
|
||||
if (callbackMatch) {
|
||||
const jsonStr = callbackMatch[1];
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Icons } from './landing/constants';
|
||||
|
||||
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.",
|
||||
icon: Icons.Home,
|
||||
color: "from-blue-400 to-blue-600",
|
||||
@@ -12,7 +12,7 @@ const services = [
|
||||
// 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.",
|
||||
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,
|
||||
color: "from-purple-400 to-indigo-600",
|
||||
glow: "shadow-purple-500/20"
|
||||
|
||||
+37
-5
@@ -329,7 +329,10 @@ function generateProperties() {
|
||||
latestPurchaseDate: "2015-06-20",
|
||||
latestSaleListingDate: 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: {
|
||||
ownerId: `OWN-${200 + index}`,
|
||||
@@ -583,6 +586,22 @@ function generateMockMeetings() {
|
||||
const hour = 9 + (i % 8);
|
||||
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({
|
||||
id: `m${meetingIdCounter++}`,
|
||||
agentId: agentId,
|
||||
@@ -593,7 +612,11 @@ function generateMockMeetings() {
|
||||
status: status,
|
||||
dealValue: dealValue,
|
||||
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',
|
||||
status: 'Scheduled',
|
||||
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
|
||||
{
|
||||
@@ -625,7 +651,10 @@ function generateMockMeetings() {
|
||||
status: 'Completed',
|
||||
agentName: 'Fiona Field',
|
||||
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`,
|
||||
@@ -638,7 +667,10 @@ function generateMockMeetings() {
|
||||
status: 'Completed',
|
||||
agentName: 'Frank Agent',
|
||||
dealValue: 0,
|
||||
notes: 'Consultation - Quote provided.'
|
||||
notes: 'Consultation - Quote provided.',
|
||||
issueDescription: 'General inspection.',
|
||||
customerComments: '',
|
||||
outcome: 'Quote Sent'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -7,8 +7,11 @@ import { MockStoreProvider } from './data/mockStore'
|
||||
import { AuthProvider } from './context/AuthContext'
|
||||
import { ThemeProvider } from './context/ThemeContext'
|
||||
|
||||
import { Analytics } from "@vercel/analytics/react"
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<Analytics />
|
||||
<BrowserRouter>
|
||||
<MockStoreProvider>
|
||||
<AuthProvider>
|
||||
|
||||
+9
-19
@@ -170,7 +170,7 @@ const Landing = () => {
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<h1 className="text-6xl md:text-9xl font-black tracking-tighter mb-6 leading-[0.9]">
|
||||
@@ -205,21 +205,6 @@ const Landing = () => {
|
||||
</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>
|
||||
|
||||
@@ -479,8 +464,8 @@ const Landing = () => {
|
||||
<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="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" />
|
||||
<span className="text-xl font-bold text-white tracking-tight">LynkedUp Pro</span>
|
||||
<img src={Logo} alt="LynkedUp Pro" className="h-16 w-auto object-contain" />
|
||||
{/* Removed: <span className="text-xl font-bold text-white tracking-tight">LynkedUp Pro</span> */}
|
||||
</div>
|
||||
<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.
|
||||
@@ -505,9 +490,14 @@ const Landing = () => {
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<Mail size={16} className="mr-3 text-blue-500 shrink-0" />
|
||||
<span>info@LynkedUpPro.com</span>
|
||||
<span>Info@LynkedUpPro.com</span>
|
||||
</li>
|
||||
</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 className="space-y-4">
|
||||
|
||||
Reference in New Issue
Block a user