import { useState, useEffect, useCallback, useRef } from "react"; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Legend, AreaChart, Area } from "recharts"; const STORAGE_KEY_DAILY = "wb_daily_v2"; const STORAGE_KEY_LEADS = "wb_leads_v2"; const STORAGE_KEY_SCRIPTS = "wb_scripts_v2"; const STORAGE_KEY_SETTINGS = "wb_settings_v2"; const STORAGE_KEY_CLIENTS = "wb_clients_v2"; const STORAGE_KEY_CALLS = "wb_calls_v2"; const STATUS_COLORS = { New: "#64748b", Contacted: "#3b82f6", Replied: "#8b5cf6", Interested: "#f59e0b", Booked: "#10b981", Closed: "#22c55e", Lost: "#ef4444" }; const TEMP_COLORS = { Cold: "#64748b", Warm: "#f59e0b", Hot: "#ef4444" }; const genId = () => Math.random().toString(36).slice(2, 10); const today = () => new Date().toISOString().split("T")[0]; const fmt = (n, d = 1) => isNaN(n) ? "0" : Number(n).toFixed(d); const pct = (a, b) => b > 0 ? fmt((a / b) * 100) : "0.0"; const defaultScripts = [ { id: genId(), name: "Script A — SOP v1.0", content: "Hey [Name], do you get most of your cleaning jobs through referrals or are you running anything online to bring in new homeowners?", active: true, stages: [ { title: "Stage 1 — The Opener", body: `Send this to every carpet/home cleaner you find on Facebook:\n\n"Hey [Name], do you get most of your cleaning jobs through referrals or are you running anything online to bring in new homeowners?"\n\n✅ Why This Works: It's a question, not a pitch. Zero resistance. Forces them to think about their lead source. Either answer opens the door for you.` }, { title: "Stage 2 — Handling Their Reply", body: `"Mostly referrals" → Yeah thats how most cleaners run it. Problem is referrals are unpredictable — some months are great, others are dead. Are you getting a consistent flow right now or is it hit or miss?\n\n"Little bit of both" → Nice. Is the online side actually bringing in consistent leads or still kind of hit or miss?\n\n"We run ads / do social media" → Nice, whats been working for you? Most cleaners I talk to say ads feel like gambling — spend money and hope something sticks.\n\n"Rn just trying to build clientele" → Thats exactly what I help with. I run Facebook ads for cleaning businesses that bring in direct homeowner leads. You only pay if you're satisfied — no risk. Want me to show you how it works?\n\n"We're pretty busy / fully booked" → Thats great. I work with cleaners who want to stay consistently booked even in slow season. If things ever slow down, I'd be happy to show you what I do. Good luck!\n\nShort/one-word reply → I'll keep it straight — I help cleaning businesses get homeowner leads through Facebook ads. No retainer, you only pay if satisfied. Worth a 2 min chat?\n\nNo reply → Wait 2 days then: "Hey [Name], just wanted to follow up - I help cleaning businesses get consistent homeowner leads on Facebook. Happy to show you how it works if you're open to it."` }, { title: "Stage 3 — Pain Qualification", body: `If they admitted inconsistency:\n"Yeah that's the biggest problem I see with cleaners - it's either feast or famine. Is slow season hitting you right now or is it more of a general inconsistency thing?"\n\nIf they said social media but no real results:\n"Makes sense. Organic posting builds trust but rarely brings in leads on demand - you're waiting for people to find you. Are you getting actual inquiries from it or more just likes and follows?"\n\nIf they're running ads but struggling:\n"What kind of results are you seeing? Cost per lead, bookings from it? I ask because most cleaners I talk to say they're spending but not really converting homeowners."\n\n🎯 What You're Listening For: Any sign of inconsistency, slow months, or wasted ad spend. Frustration with their current method. Openness to something new.` }, { title: "Stage 4 — The Pitch", body: `Only pitch AFTER they've shown pain or interest. Never before.\n\nStandard pitch:\n"So what I do is run Facebook ads specifically for cleaning businesses - targeting homeowners in your area who are actively looking for cleaning services. You get direct leads, not just clicks. And the deal is simple: you only pay if you're actually satisfied with the results. No retainer, no contracts. Want me to walk you through how it works?"\n\nIf hesitant about ads:\n"Totally get it - most cleaners have had bad experiences with agencies that take money and disappear. That's why I only work on a pay-if-satisfied basis. No results, you don't pay. Worst case you get a free look at what's possible."\n\nIf they ask 'what do you charge?':\n"Depends on what you need - but the model is simple. You only pay if you're satisfied with the leads you get. No upfront fees. Want me to show you the setup first so you can decide if it even makes sense for your business?"\n\n⚠️ Pitch Rules: Always mention 'pay only if satisfied'. Talk outcomes: leads, homeowners, bookings. End every pitch with a question.` }, { title: "Stage 5 — Moving to Close", body: `"Sure, show me" → Perfect. I'll walk you through it right here - no call needed unless you want one. Here's basically how it works: [explain your process in 3-4 sentences]. Does this sound like something that would work for your business?\n\n"Sounds interesting, tell me more" → So the way it works: I set up Facebook ads targeting homeowners in your area looking for cleaning services. The leads come directly to you - name, number, ready to book. I handle everything on the ad side. You focus on closing the jobs. And again - pay only if you're happy with what you're getting. Want to give it a shot?\n\n"I'll think about it" → No rush at all. Just so you know - I only take on 2-3 clients per area so I can focus on results. If you want, I can check if your area is still open when you're ready.\n\n"Yes, let's do it" → Awesome. Let me get a few quick details from you so I can get started - what city/area are you targeting, and what's the best number to reach you on?` }, { title: "Stage 6 — Objections", body: `"I don't have budget right now" → That's exactly why the model works - you don't pay anything upfront. You only pay after you're satisfied with the leads. Zero risk to try it.\n\n"I tried ads before and it didn't work" → That's the most common thing I hear. Most agencies run generic ads and disappear. I only work with cleaning businesses and I'm accountable for results - you don't pay if it doesn't work.\n\n"I'm not sure about Facebook ads" → Totally fair. Facebook is still the best platform for reaching local homeowners - that's just where they are. I can show you what the ads look like before anything goes live.\n\n"How do I know you're legit?" → Fair question. I'm happy to show you examples of what I've done and walk you through exactly how it works before you commit to anything. No pressure.\n\n"I'm too busy right now" → No problem at all. I'll check back in when things slow down. What months are usually slower for you?\n\n"Send me more info" → Happy to. What's the main thing you want to know - how the leads come in, what the ads look like, or how the pay model works?` }, { title: "Follow-Up Sequence", body: `Day 2 (no reply to opener): "Hey [Name], just wanted to follow up on my last message. I help cleaning businesses get homeowner leads on Facebook - pay only if satisfied. Worth a quick chat?"\n\nDay 5 (said 'I'll think about it'): "Hey [Name], just checking back in. Still have your area open if you're interested in giving it a shot."\n\nDay 10 (went cold mid-convo): "Hey [Name], I know things get busy. Still happy to show you how the lead system works whenever you're ready."\n\nDay 30 (long cold prospect): "Hey [Name], just reaching out again as we're taking on new cleaning clients this month. Is now a better time to talk?"` }, { title: "🔥 Ignore Breaker Messages", body: `After opener no reply:\n"Hey [Name], I'll be straight with you — I help carpet & home cleaning businesses get 20 to 30 homeowner leads every month through Facebook ads + an AI follow-up system that works 24/7. You don't touch the ads. You don't chase leads. You just show up and do the job. And you don't pay a single penny until you're actually making money from it. If that sounds worth 2 minutes — I can show you exactly how it works for your area. Just say the word."\n\nAfter opener reply if they don't reply back:\n"Hey [Name], I know things get busy so I'll keep it short. When we last spoke you mentioned [referrals / trying online — swap based on their reply]. A lot of cleaners I work with were in the same spot — before we got them a steady pipeline of 20 to 30 ready-to-book homeowners every month. The setup is simple — Facebook ads targeted to your area, AI handles the follow-up, you only pay once you're actually making money from the leads. Still have your area open. Worth picking back up where we left off?"` } ] }, { id: genId(), name: "Script B — SOP v2.0", content: "Hey [Name] — quick thing I noticed. Most cleaning businesses I've been working with lately are getting leads for around $8–$15 each using Facebook ads. I checked your page and it doesn't look like you're running anything right now — are you mainly relying on referrals or testing anything paid?", active: true, stages: [ { title: "Stage 1 — Opener (Authority-First)", body: `"Hey [Name] — quick thing I noticed.\n\nMost cleaning businesses I've been working with lately are getting leads for around $8–$15 each using Facebook ads.\n\nI checked your page and it doesn't look like you're running anything right now — are you mainly relying on referrals or testing anything paid?"\n\n✅ Why This Works: Leads with insight and authority (specific numbers). Shows you've done your homework. Opens with value before asking anything.` }, { title: "Stage 2 — Handling Replies", body: `"Referrals" → Yeah that's how most start — but it gets unpredictable fast. Some weeks are full, others dead. Are you getting consistent jobs right now or is it up and down?\n\n"We run ads" → Nice — what kind of cost per lead are you seeing right now? Most I talk to are spending but not actually converting into jobs.\n\n"Trying to grow" → Makes sense — that's exactly where ads help most. Are you actively trying to bring in more jobs right now or just building slowly?` }, { title: "Stage 3 — Pain Qualification", body: `"Yeah that's the biggest issue I see — cleaners stuck in that feast-or-famine cycle.\n\nIf you could lock in a consistent flow of jobs every month, would that be something you'd want to fix?"\n\n🎯 You're listening for: Any frustration with inconsistency. A 'yes' or 'definitely' to your question. Openness to change.` }, { title: "Stage 4 — The Pitch", body: `"So what we do is simple.\n\nWe run Facebook ads targeting homeowners in your area who are actively looking for cleaning.\n\nLeads come directly to you — name + number — ready to book.\n\nAnd there's no upfront cost — you only pay if you're actually getting results.\n\nIf I could show you how this would work specifically for your area, would you be open to it?"` }, { title: "Stage 5 — Close", body: `"Perfect — I'll keep it simple.\n\nI'll show you:\n- What the ads look like\n- How leads come in\n- What results to expect\n\nIf it makes sense, we move forward. If not, no worries."\n\n🎯 Goal: Remove all friction. Make the yes feel easy and the no feel safe.` }, { title: "Follow-Up Sequence", body: `Day 2: "Hey [Name], just following up — still open to seeing how this could bring in a few extra jobs this month?"\n\nDay 5: "Hey — quick one, still have your area open if you're interested."\n\nDay 10: "Hey [Name], I know things get busy — happy to show you whenever timing's better."` }, { title: "🔥 Ignore Breaker", body: `"Hey [Name] — I'll keep it straight.\n\nWe help cleaning businesses generate 20–30 jobs per month using Facebook ads.\n\nNo upfront cost. You only pay if it works.\n\nIf that's even slightly interesting, I can show you how it works for your area."` } ] }, ]; const defaultSettings = { userName: "Ayan", partnerName: "Rohan", niches: ["Cleaning", "Construction"], services: ["Ads", "Website"], targetDMs: 90, dealValue: 700, currency: "$", }; const TABS = ["Dashboard", "Daily Log", "Lead CRM", "Cold Calls", "Scripts", "Pipeline", "Reports", "Settings"]; const parseJSONSafe = (value, fallback) => { if (typeof value !== "string" || !value.trim()) return fallback; try { return JSON.parse(value); } catch { return fallback; } }; const getLeadsFromLocal = () => { if (typeof window === "undefined" || !window.localStorage) return []; return parseJSONSafe(window.localStorage.getItem(STORAGE_KEY_LEADS), []); }; const saveLeadsToLocal = (data) => { if (typeof window === "undefined" || !window.localStorage) return; try { window.localStorage.setItem(STORAGE_KEY_LEADS, JSON.stringify(data)); } catch {} }; export default function WebbotixCRM() { const [tab, setTab] = useState("Dashboard"); const [daily, setDaily] = useState([]); const [leads, setLeads] = useState([]); const [scripts, setScripts] = useState(defaultScripts); const [clients, setClients] = useState([]); const [calls, setCalls] = useState([]); const [settings, setSettings] = useState(defaultSettings); const [loading, setLoading] = useState(true); const [saveIndicator, setSaveIndicator] = useState(""); const [rangeFilter, setRangeFilter] = useState(30); const showSaved = (msg = "Saved") => { setSaveIndicator(msg); setTimeout(() => setSaveIndicator(""), 2000); }; const loadAll = useCallback(async () => { try { const [d, l, s, st, cl, ca] = await Promise.allSettled([ window.storage.get(STORAGE_KEY_DAILY, true), window.storage.get(STORAGE_KEY_LEADS, true), window.storage.get(STORAGE_KEY_SCRIPTS, true), window.storage.get(STORAGE_KEY_SETTINGS, true), window.storage.get(STORAGE_KEY_CLIENTS, true), window.storage.get(STORAGE_KEY_CALLS, true), ]); if (d.status === "fulfilled" && d.value) setDaily(JSON.parse(d.value.value)); if (l.status === "fulfilled" && l.value) { const cloudLeads = parseJSONSafe(l.value.value, null); if (Array.isArray(cloudLeads)) { setLeads(cloudLeads); saveLeadsToLocal(cloudLeads); } else { setLeads(getLeadsFromLocal()); } } else { setLeads(getLeadsFromLocal()); } if (s.status === "fulfilled" && s.value) setScripts(JSON.parse(s.value.value)); if (st.status === "fulfilled" && st.value) setSettings(JSON.parse(st.value.value)); if (cl.status === "fulfilled" && cl.value) setClients(JSON.parse(cl.value.value)); if (ca.status === "fulfilled" && ca.value) setCalls(JSON.parse(ca.value.value)); } catch (e) {} setLoading(false); }, []); useEffect(() => { loadAll(); }, []); const saveDaily = async (data) => { setDaily(data); try { await window.storage.set(STORAGE_KEY_DAILY, JSON.stringify(data), true); showSaved(); } catch (e) {} }; const saveLeads = async (data) => { setLeads(data); saveLeadsToLocal(data); try { await window.storage.set(STORAGE_KEY_LEADS, JSON.stringify(data), true); showSaved(); } catch (e) { showSaved(); } }; const saveScripts = async (data) => { setScripts(data); try { await window.storage.set(STORAGE_KEY_SCRIPTS, JSON.stringify(data), true); showSaved(); } catch (e) {} }; const saveSettings = async (data) => { setSettings(data); try { await window.storage.set(STORAGE_KEY_SETTINGS, JSON.stringify(data), true); showSaved(); } catch (e) {} }; const saveClients = async (data) => { setClients(data); try { await window.storage.set(STORAGE_KEY_CLIENTS, JSON.stringify(data), true); showSaved(); } catch (e) {} }; const saveCalls = async (data) => { setCalls(data); try { await window.storage.set(STORAGE_KEY_CALLS, JSON.stringify(data), true); showSaved(); } catch (e) {} }; const [quickAddOpen, setQuickAddOpen] = useState(false); const [quickLead, setQuickLead] = useState(null); const blankLead = () => ({ id: genId(), name: "", businessName: "", niche: settings.niches?.[0] || "Cleaning", city: "", platform: "Instagram", dateContacted: today(), status: "New", leadScore: 5, temp: "Cold", lastMessage: "", nextFollowUp: "", notes: "", scriptUsed: "", facebookLink: "", profileNo: "", phoneNumber: "", service: settings.services?.[0] || "Ads", fee: settings.dealValue || 700 }); const openQuickAdd = () => { setQuickLead(blankLead()); setQuickAddOpen(true); }; const saveQuickLead = () => { if (quickLead) { saveLeads([...leads, quickLead]); setQuickAddOpen(false); showSaved("Lead added! ✓"); } }; const filteredDaily = daily .slice() .sort((a, b) => a.date.localeCompare(b.date)) .slice(-rangeFilter); const chartData = filteredDaily.map(d => ({ date: d.date.slice(5), DMs: d.dms, SMS: d.sms || 0, Emails: d.emails || 0, "Reply Rate": d.dms > 0 ? parseFloat(pct(d.replies, d.dms)) : 0, "Interest Rate": d.dms > 0 ? parseFloat(pct(d.interested, d.dms)) : 0, "Close Rate": d.dms > 0 ? parseFloat(pct(d.closed, d.dms)) : 0, Revenue: (d.closed || 0) * (settings.dealValue || 700), })); const totalDMs = filteredDaily.reduce((s, d) => s + (d.dms || 0), 0); const totalReplies = filteredDaily.reduce((s, d) => s + (d.replies || 0), 0); const totalInterested = filteredDaily.reduce((s, d) => s + (d.interested || 0), 0); const totalClosed = filteredDaily.reduce((s, d) => s + (d.closed || 0), 0); const totalRevenue = totalClosed * (settings.dealValue || 500); const hotLeads = leads.filter(l => l.temp === "Hot" && l.status !== "Closed" && l.status !== "Lost"); const followUps = leads.filter(l => l.nextFollowUp && l.nextFollowUp <= today() && l.status !== "Closed" && l.status !== "Lost"); if (loading) return (

Loading Webbotix CRM...

); return (
{/* HEADER */}
W
Webbotix CRM LIVE
{saveIndicator && ✓ {saveIndicator}} {hotLeads.length > 0 && 🔥 {hotLeads.length} Hot Leads} {followUps.length > 0 && ⏰ {followUps.length} Follow-ups}
{settings.userName?.[0] || "A"}
{settings.userName}
{tab === "Dashboard" && } {tab === "Daily Log" && } {tab === "Lead CRM" && } {tab === "Cold Calls" && } {tab === "Scripts" && } {tab === "Pipeline" && } {tab === "Reports" && } {tab === "Settings" && }
{/* ⚡ Floating Quick-Add Lead Button */} {/* Quick-Add Modal */} {quickAddOpen && quickLead && (

⚡ Quick Add Lead

Capture fast — fill details later in Lead CRM

setQuickLead(p => ({...p, name: e.target.value}))} placeholder="John Smith" /> setQuickLead(p => ({...p, businessName: e.target.value}))} placeholder="Smith Cleaning Co." /> setQuickLead(p => ({...p, phoneNumber: e.target.value}))} placeholder="+1 234 567 8900" /> setQuickLead(p => ({...p, nextFollowUp: e.target.value}))} />