682 lines
21 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Burton Method — GHL Dashboard</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-card: #16161f;
--bg-card-hover: #1c1c28;
--border: #2a2a3a;
--text-primary: #e8e8f0;
--text-secondary: #8888a0;
--text-muted: #5a5a70;
--blue: #3b82f6;
--blue-dim: rgba(59,130,246,0.15);
--green: #10b981;
--green-dim: rgba(16,185,129,0.15);
--amber: #f59e0b;
--amber-dim: rgba(245,158,11,0.15);
--rose: #f43f5e;
--rose-dim: rgba(244,63,94,0.15);
--purple: #8b5cf6;
--purple-dim: rgba(139,92,246,0.15);
--teal: #14b8a6;
--teal-dim: rgba(20,184,166,0.15);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
.dashboard {
max-width: 1400px;
margin: 0 auto;
padding: 32px 24px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border);
}
.header h1 {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, var(--blue), var(--purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header .subtitle {
color: var(--text-secondary);
font-size: 14px;
margin-top: 4px;
}
.header .live-badge {
display: flex;
align-items: center;
gap: 8px;
background: var(--green-dim);
color: var(--green);
padding: 8px 16px;
border-radius: 100px;
font-size: 13px;
font-weight: 600;
}
.header .live-dot {
width: 8px;
height: 8px;
background: var(--green);
border-radius: 50%;
animation: pulse 2s ease infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
transition: all 0.2s ease;
}
.stat-card:hover {
background: var(--bg-card-hover);
transform: translateY(-2px);
}
.stat-label {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-value {
font-size: 36px;
font-weight: 800;
line-height: 1;
}
.stat-change {
font-size: 13px;
margin-top: 8px;
display: flex;
align-items: center;
gap: 4px;
}
.stat-change.up { color: var(--green); }
.stat-change.down { color: var(--rose); }
.stat-change.neutral { color: var(--text-muted); }
/* Section */
.section {
margin-bottom: 32px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.section-title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
}
.section-badge {
font-size: 12px;
padding: 4px 12px;
border-radius: 100px;
font-weight: 600;
}
/* Two-column layout */
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.two-col { grid-template-columns: 1fr; }
}
/* Pipeline */
.pipeline {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
}
.pipeline-stage {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid var(--border);
}
.pipeline-stage:last-child { border-bottom: none; }
.pipeline-stage-name {
font-weight: 600;
font-size: 15px;
}
.pipeline-stage-count {
font-size: 24px;
font-weight: 800;
color: var(--blue);
}
/* Workflow list */
.workflow-list {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
max-height: 500px;
overflow-y: auto;
}
.workflow-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid rgba(42,42,58,0.5);
}
.workflow-item:last-child { border-bottom: none; }
.workflow-name {
font-size: 14px;
color: var(--text-primary);
flex: 1;
margin-right: 12px;
}
.workflow-status {
font-size: 11px;
padding: 4px 10px;
border-radius: 100px;
font-weight: 600;
white-space: nowrap;
}
.workflow-status.published {
background: var(--green-dim);
color: var(--green);
}
.workflow-status.draft {
background: var(--amber-dim);
color: var(--amber);
}
/* Source bars */
.source-bar-container {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
}
.source-bar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.source-bar:last-child { margin-bottom: 0; }
.source-label {
font-size: 13px;
color: var(--text-secondary);
width: 180px;
flex-shrink: 0;
}
.source-track {
flex: 1;
height: 28px;
background: var(--bg-primary);
border-radius: 8px;
overflow: hidden;
position: relative;
}
.source-fill {
height: 100%;
border-radius: 8px;
transition: width 1s ease;
display: flex;
align-items: center;
padding-left: 10px;
font-size: 12px;
font-weight: 700;
color: white;
}
/* Tags cloud */
.tags-container {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
}
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag-chip {
padding: 6px 14px;
border-radius: 100px;
font-size: 12px;
font-weight: 600;
border: 1px solid var(--border);
background: var(--bg-secondary);
color: var(--text-secondary);
transition: all 0.2s;
}
.tag-chip:hover {
background: var(--purple-dim);
color: var(--purple);
border-color: var(--purple);
}
/* Recent contacts */
.contacts-table {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
overflow-x: auto;
}
.contacts-table table {
width: 100%;
border-collapse: collapse;
}
.contacts-table th {
text-align: left;
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
}
.contacts-table td {
padding: 12px;
font-size: 14px;
border-bottom: 1px solid rgba(42,42,58,0.3);
}
.contacts-table tr:last-child td { border-bottom: none; }
.contacts-table .email {
color: var(--blue);
font-size: 13px;
}
.contacts-table .date {
color: var(--text-muted);
font-size: 13px;
}
.contacts-table .tag-sm {
display: inline-block;
padding: 2px 8px;
border-radius: 100px;
font-size: 11px;
background: var(--blue-dim);
color: var(--blue);
margin: 2px;
}
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
// ---- DATA (pulled from GHL API on Feb 9, 2026) ----
const DATA = {
totalContacts: 5085,
contactsLast7d: 2,
contactsLast30d: 13,
contactsByMonth: [
{ month: '2026-02', count: 2 },
{ month: '2026-01', count: 13 },
{ month: '2025-12', count: 85 },
],
pipeline: {
name: 'Sales',
stages: [
{ name: 'New Lead', count: 0 },
{ name: 'Lead Showed to Training', count: 1 },
],
totalOpps: 1,
totalValue: 0,
},
sources: [
{ name: 'Facebook', count: 373 },
{ name: 'Unknown', count: 15 },
{ name: 'Burt Method Opt In', count: 6 },
{ name: 'Burton Method Official', count: 4 },
{ name: 'Burton Method Colour (Haseeb)', count: 2 },
],
workflows: {
total: 120,
published: 61,
draft: 59,
list: [
{ name: 'Grant Access to Course', status: 'published' },
{ name: 'Email for new leads', status: 'published' },
{ name: 'Send message after grant access to Burt Method Free', status: 'published' },
{ name: 'Logged into membership area', status: 'published' },
{ name: 'Delete Discord Invite (7 day)', status: 'published' },
{ name: 'Create Discord Invite Link', status: 'published' },
{ name: 'Invite to Discord Community', status: 'published' },
{ name: 'Joined Discord, stop follow up', status: 'published' },
{ name: 'Welcome to Burton Method grant access', status: 'published' },
{ name: 'Send login details + intro to course & community', status: 'published' },
{ name: 'Subscriber needs to join Discord', status: 'published' },
{ name: 'Subscription is overdue', status: 'published' },
{ name: 'Send drill set data to Make/CloseBot', status: 'published' },
{ name: 'ALERT: Paid Tutoring session with Brett booked', status: 'published' },
{ name: 'ALERT: Brett for new free 1 on 1', status: 'published' },
{ name: 'Webinar Promo', status: 'published' },
{ name: 'Support Confirmation', status: 'published' },
{ name: 'Responded lead', status: 'published' },
{ name: 'Send Support Message Response to ClickUp', status: 'published' },
{ name: 'New Trial → Remove role (live server)', status: 'published' },
{ name: 'Payment Made, remove from late pay reminders', status: 'published' },
{ name: 'Add Stripe customer ID', status: 'published' },
{ name: 'Add Stripe direct link', status: 'published' },
{ name: 'Got $1 course add tag', status: 'published' },
{ name: 'Drill set done / invite to tutoring', status: 'published' },
{ name: 'Cancel / Revoke Role', status: 'published' },
{ name: 'SaaS Application Notify & First Steps', status: 'draft' },
{ name: 'Last chance offer', status: 'draft' },
{ name: 'SaaS Application Step 1 - New', status: 'draft' },
{ name: 'Training Confirmation + Reminder', status: 'draft' },
{ name: '3 day reminder before remove role', status: 'draft' },
]
},
tags: {
total: 160,
topTags: [
'campaign #1', 'google ads', 'lead has replied',
'first step order form filled', 'free training registrant',
'do not promote', 'new subscriber', 'joined discord',
'needs to join discord', 'trial', 'paid tutoring',
'burt method free', 'webinar registered', 'support',
]
},
calendar: {
name: "Bradley's LSAT Consultations",
eventsLast30d: 0,
slotDuration: 30,
type: 'Round Robin'
},
recentContacts: [
{ name: 'Rohit Ghorai', email: 'rohitghorai78@gmail.com', source: 'Facebook', date: '2026-02-07', tags: ['campaign #1', 'google ads'] },
{ name: 'Recent Lead #2', email: '***@***.com', source: 'Facebook', date: '2026-02-03', tags: ['campaign #1'] },
{ name: 'Jan Lead Batch', email: 'Multiple (13)', source: 'Mixed', date: '2026-01', tags: ['campaign #1', 'google ads'] },
],
locationId: 'DZEpRd43MxUJKdtrev9t',
lastUpdated: new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }),
};
function StatCard({ label, value, change, changeType, color }) {
return (
<div className="stat-card">
<div className="stat-label">{label}</div>
<div className="stat-value" style={{ color: color || 'var(--text-primary)' }}>{value}</div>
{change && <div className={`stat-change ${changeType || 'neutral'}`}>{change}</div>}
</div>
);
}
function SourceBar({ name, count, max, color }) {
const pct = Math.max((count / max) * 100, 2);
return (
<div className="source-bar">
<div className="source-label">{name}</div>
<div className="source-track">
<div className="source-fill" style={{ width: `${pct}%`, background: color || 'var(--blue)' }}>
{count}
</div>
</div>
</div>
);
}
function App() {
const d = DATA;
const maxSource = Math.max(...d.sources.map(s => s.count));
const sourceColors = ['var(--blue)', 'var(--text-muted)', 'var(--amber)', 'var(--purple)', 'var(--teal)'];
return (
<div className="dashboard">
<div className="header">
<div>
<h1>Burton Method GHL Dashboard</h1>
<div className="subtitle">Go High Level · Location: {d.locationId} · Updated: {d.lastUpdated}</div>
</div>
<div className="live-badge">
<div className="live-dot"></div>
Live Data
</div>
</div>
{/* Top-level Stats */}
<div className="stats-grid">
<StatCard label="Total Contacts" value={d.totalContacts.toLocaleString()} change="5,085 in database" changeType="neutral" color="var(--blue)" />
<StatCard label="Last 7 Days" value={d.contactsLast7d} change={d.contactsLast7d > 5 ? "▲ Growing" : "▼ Low volume"} changeType={d.contactsLast7d > 5 ? "up" : "down"} color="var(--rose)" />
<StatCard label="Last 30 Days" value={d.contactsLast30d} change="13 new contacts" changeType="neutral" color="var(--amber)" />
<StatCard label="Workflows" value={d.workflows.total} change={`${d.workflows.published} active · ${d.workflows.draft} draft`} changeType="neutral" color="var(--green)" />
<StatCard label="Pipeline Opps" value={d.pipeline.totalOpps} change="Sales pipeline" changeType="neutral" color="var(--purple)" />
<StatCard label="Tags" value={d.tags.total} change="160 unique tags" changeType="neutral" color="var(--teal)" />
</div>
{/* Pipeline + Calendar */}
<div className="two-col" style={{ marginBottom: 24 }}>
<div className="section">
<div className="section-header">
<div className="section-title">Sales Pipeline</div>
<span className="section-badge" style={{ background: 'var(--purple-dim)', color: 'var(--purple)' }}>
{d.pipeline.totalOpps} opportunity
</span>
</div>
<div className="pipeline">
{d.pipeline.stages.map((s, i) => (
<div className="pipeline-stage" key={i}>
<span className="pipeline-stage-name">{s.name}</span>
<span className="pipeline-stage-count">{s.count}</span>
</div>
))}
<div className="pipeline-stage" style={{ borderTop: '1px solid var(--border)', marginTop: 8, paddingTop: 16 }}>
<span className="pipeline-stage-name" style={{ color: 'var(--text-secondary)' }}>Total Value</span>
<span className="pipeline-stage-count" style={{ color: 'var(--green)' }}>${d.pipeline.totalValue.toLocaleString()}</span>
</div>
</div>
</div>
<div className="section">
<div className="section-header">
<div className="section-title">Lead Sources (Last 500)</div>
</div>
<div className="source-bar-container">
{d.sources.map((s, i) => (
<SourceBar key={i} name={s.name} count={s.count} max={maxSource} color={sourceColors[i]} />
))}
</div>
</div>
</div>
{/* Monthly Contacts */}
<div className="section">
<div className="section-header">
<div className="section-title">Contacts by Month</div>
</div>
<div className="source-bar-container">
{d.contactsByMonth.map((m, i) => (
<SourceBar key={i} name={m.month} count={m.count} max={Math.max(...d.contactsByMonth.map(x => x.count))}
color={i === 0 ? 'var(--blue)' : i === 1 ? 'var(--teal)' : 'var(--purple)'} />
))}
<div style={{ marginTop: 16, color: 'var(--text-muted)', fontSize: 13 }}>
Dec 2025 had 85 new contacts. Jan dropped to 13. Feb has only 2 so far. Lead volume declining.
</div>
</div>
</div>
{/* Workflows + Tags */}
<div className="two-col">
<div className="section">
<div className="section-header">
<div className="section-title">Workflows</div>
<span className="section-badge" style={{ background: 'var(--green-dim)', color: 'var(--green)' }}>
{d.workflows.published} published
</span>
</div>
<div className="workflow-list">
{d.workflows.list.map((w, i) => (
<div className="workflow-item" key={i}>
<span className="workflow-name">{w.name}</span>
<span className={`workflow-status ${w.status}`}>{w.status}</span>
</div>
))}
</div>
</div>
<div className="section">
<div className="section-header">
<div className="section-title">Top Tags</div>
<span className="section-badge" style={{ background: 'var(--teal-dim)', color: 'var(--teal)' }}>
{d.tags.total} total
</span>
</div>
<div className="tags-container">
<div className="tag-cloud">
{d.tags.topTags.map((t, i) => (
<span className="tag-chip" key={i}>{t}</span>
))}
</div>
<div style={{ marginTop: 24, padding: '16px', background: 'var(--bg-primary)', borderRadius: 12 }}>
<div style={{ fontSize: 13, color: 'var(--text-muted)', marginBottom: 8 }}>Calendar</div>
<div style={{ fontSize: 15, fontWeight: 600 }}>{d.calendar.name}</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)', marginTop: 4 }}>
{d.calendar.type} · {d.calendar.slotDuration} min slots · {d.calendar.eventsLast30d} events (30d)
</div>
</div>
</div>
</div>
</div>
{/* Key Insights */}
<div className="section" style={{ marginTop: 8 }}>
<div className="section-header">
<div className="section-title"> Key Insights</div>
</div>
<div style={{ background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 16, padding: 24 }}>
<div style={{ display: 'grid', gap: 16 }}>
<div style={{ padding: 16, background: 'var(--rose-dim)', borderRadius: 12, borderLeft: '3px solid var(--rose)' }}>
<strong style={{ color: 'var(--rose)' }}> Lead Volume Declining</strong>
<p style={{ color: 'var(--text-secondary)', fontSize: 14, marginTop: 4 }}>
Dec: 85 contacts Jan: 13 Feb: 2 (so far). 85% drop month-over-month. Need to revive lead gen campaigns.
</p>
</div>
<div style={{ padding: 16, background: 'var(--amber-dim)', borderRadius: 12, borderLeft: '3px solid var(--amber)' }}>
<strong style={{ color: 'var(--amber)' }}> Pipeline Nearly Empty</strong>
<p style={{ color: 'var(--text-secondary)', fontSize: 14, marginTop: 4 }}>
Only 1 opportunity in pipeline, $0 value. Sales funnel needs attention.
</p>
</div>
<div style={{ padding: 16, background: 'var(--blue-dim)', borderRadius: 12, borderLeft: '3px solid var(--blue)' }}>
<strong style={{ color: 'var(--blue)' }}>📊 Facebook Dominates (93%)</strong>
<p style={{ color: 'var(--text-secondary)', fontSize: 14, marginTop: 4 }}>
373 of 400 tracked contacts from Facebook. Heavy platform dependency diversify to Google, organic, partnerships.
</p>
</div>
<div style={{ padding: 16, background: 'var(--green-dim)', borderRadius: 12, borderLeft: '3px solid var(--green)' }}>
<strong style={{ color: 'var(--green)' }}> Automation Machine</strong>
<p style={{ color: 'var(--text-secondary)', fontSize: 14, marginTop: 4 }}>
120 workflows (61 active). Onboarding, Discord, Stripe, support, drill sets all automated. Backend is solid.
</p>
</div>
<div style={{ padding: 16, background: 'var(--purple-dim)', borderRadius: 12, borderLeft: '3px solid var(--purple)' }}>
<strong style={{ color: 'var(--purple)' }}>📅 Zero Calendar Bookings (30d)</strong>
<p style={{ color: 'var(--text-secondary)', fontSize: 14, marginTop: 4 }}>
Bradley's LSAT Consultations: 0 events in last 30 days. Booking funnel may be broken or not promoted.
</p>
</div>
</div>
</div>
</div>
<div style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: 12, marginTop: 32, paddingTop: 16, borderTop: '1px solid var(--border)' }}>
Powered by Buba · Data from GHL API v2 · Location {d.locationId}
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>