/** * Upstream Command Center — Sales & System Pages * Loaded BEFORE app.jsx so components are available for NAV_SECTIONS. * * Pages: OverviewPage, ActivityLogPage, AnalyticsPage * Placeholder pages: ProspectFeedPage, SequencesPage, DealBoardPage, * ReplyInboxPage, ContactsPage, SegmentsPage */ // --------------------------------------------------------------------------- // Helpers (apiFetch defined in app.jsx but this loads first — use window.fetch) // --------------------------------------------------------------------------- function salesApiFetch(path, options = {}) { const base = window.__UPSTREAM_API_BASE || ""; return fetch(base + path, { ...options, credentials: "include", headers: { ...options.headers, "Content-Type": "application/json" }, }); } function relativeTime(isoStr) { if (!isoStr) return ""; const diff = Date.now() - new Date(isoStr).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return "just now"; if (mins < 60) return mins + "m ago"; const hrs = Math.floor(mins / 60); if (hrs < 24) return hrs + "h ago"; const days = Math.floor(hrs / 24); return days + "d ago"; } // --------------------------------------------------------------------------- // OverviewPage — unified metrics from both engines // --------------------------------------------------------------------------- function OverviewPage() { const [dashboard, setDashboard] = React.useState(null); const [analytics, setAnalytics] = React.useState(null); const [activity, setActivity] = React.useState(null); const [deals, setDeals] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); React.useEffect(() => { Promise.all([ salesApiFetch("/api/dashboard").then(r => r.ok ? r.json() : null).catch(() => null), salesApiFetch("/api/analytics").then(r => r.ok ? r.json() : null).catch(() => null), salesApiFetch("/api/activity-log").then(r => r.ok ? r.json() : null).catch(() => null), salesApiFetch("/api/sales/deals").then(r => r.ok ? r.json() : null).catch(() => null), ]).then(([dash, anal, act, dl]) => { setDashboard(dash); setAnalytics(anal); setActivity(act); setDeals(dl); setLoading(false); }).catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) return
Loading overview...
; if (error) return
{error}
; const subscribers = analytics?.funnel?.find(s => s.stage === "subscribers")?.count || 0; const prospects = analytics?.funnel?.find(s => s.stage === "prospects")?.count || 0; const dealCount = deals?.summary ? Object.values(deals.summary).reduce((sum, n) => sum + n, 0) : 0; const repliesToday = activity?.summary?.today?.reply_received || 0; const tracks = dashboard?.tracks || []; const salesPaused = deals?.paused || false; const sequencesActive = deals?.summary?.sequencing || 0; const todaySummary = activity?.summary?.today || {}; return (

Overview

Unified command center — content + sales engines

{subscribers}
Subscribers
{prospects}
Active Prospects
{dealCount}
Deals in Pipeline
{repliesToday}
Replies Today
Content Engine
{tracks.length > 0 ? tracks.map(t => (
{t.name} {t.status}
)) : (
No track data available
)}
Sales Engine
Status {salesPaused ? "paused" : "active"}
Active Sequences {sequencesActive}
{analytics?.costs && (
Monthly Cost ${(analytics.costs.total_this_month || 0).toFixed(2)}
)}
{Object.keys(todaySummary).length > 0 && (
Today
{Object.entries(todaySummary).map(([type, count], i) => ( {i > 0 ? ", " : ""} {count} {type.replace(/_/g, " ")} ))}
)}
); } // --------------------------------------------------------------------------- // ActivityLogPage — reverse-chronological system actions // --------------------------------------------------------------------------- const ACTIVITY_TYPE_COLORS = { email_sent: "#2D7DD2", reply_received: "#4CAF7D", prospect_discovered: "#9B59B6", stage_change: "#E67E22", note: "#6B7A8D", meeting_booked: "#C9A84C", }; const ACTIVITY_FILTERS = [ { key: "all", label: "All" }, { key: "email_sent", label: "Emails" }, { key: "reply_received", label: "Replies" }, { key: "prospect_discovered", label: "Discoveries" }, { key: "stage_change", label: "Stage Changes" }, ]; function ActivityLogPage() { const [entries, setEntries] = React.useState([]); const [summary, setSummary] = React.useState({}); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [filter, setFilter] = React.useState("all"); const [expandedIdx, setExpandedIdx] = React.useState(null); React.useEffect(() => { salesApiFetch("/api/activity-log") .then(r => { if (!r.ok) throw new Error("Failed to load activity log"); return r.json(); }) .then(data => { setEntries(data.entries || []); setSummary(data.summary?.today || {}); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) return
Loading activity log...
; if (error) return
{error}
; const filtered = filter === "all" ? entries : entries.filter(e => e.type === filter); const summaryParts = Object.entries(summary); return (

Activity Log

System actions from the last 7 days

{summaryParts.length > 0 && (
Today:{" "} {summaryParts.map(([type, count], i) => ( {i > 0 ? ", " : ""} {count} {type.replace(/_/g, " ")} ))}
)}
{ACTIVITY_FILTERS.map(f => ( ))}
{filtered.length === 0 ? (
No activity entries found
) : (
{filtered.map((entry, idx) => { const dotColor = ACTIVITY_TYPE_COLORS[entry.type] || "#6B7A8D"; const isExpanded = expandedIdx === idx; return (
setExpandedIdx(isExpanded ? null : idx)} >
{relativeTime(entry.at)}
{entry.contact_name && ( {entry.contact_name} )} {entry.contact_name && " — "} {entry.type.replace(/_/g, " ")}
{isExpanded && entry.detail && (
{entry.detail}
)}
); })}
)}
); } // --------------------------------------------------------------------------- // AnalyticsPage — funnel, attribution, costs // --------------------------------------------------------------------------- function AnalyticsPage() { const [data, setData] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [sortCol, setSortCol] = React.useState("prospects"); const [sortAsc, setSortAsc] = React.useState(false); React.useEffect(() => { salesApiFetch("/api/analytics") .then(r => { if (!r.ok) throw new Error("Failed to load analytics"); return r.json(); }) .then(d => { setData(d); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) return
Loading analytics...
; if (error) return
{error}
; const funnel = data?.funnel || []; const maxCount = Math.max(...funnel.map(s => s.count), 1); const FUNNEL_COLORS = [ "#2D7DD2", "#3A8FD6", "#47A1DA", "#54B3DE", "#5FC4D6", "#4CAF7D", "#3FA06A", ]; const attribution = data?.attribution || []; const sortedAttribution = [...attribution].sort((a, b) => { const va = a[sortCol] ?? 0; const vb = b[sortCol] ?? 0; return sortAsc ? va - vb : vb - va; }); function handleSort(col) { if (col === sortCol) { setSortAsc(!sortAsc); } else { setSortCol(col); setSortAsc(false); } } const sortIndicator = (col) => { if (col !== sortCol) return ""; return sortAsc ? " \u25B2" : " \u25BC"; }; const costs = data?.costs || {}; const services = costs.services || []; const budgetPct = costs.budget_cap > 0 ? Math.min((costs.total_this_month / costs.budget_cap) * 100, 100) : 0; const budgetColor = budgetPct > 90 ? "var(--danger)" : budgetPct > 70 ? "var(--cta-gold)" : "var(--data-positive)"; return (

Analytics

Funnel performance, content attribution, and cost tracking

Conversion Funnel
{funnel.map((stage, idx) => (
{stage.stage.replace(/_/g, " ")}
{stage.count}
{idx < funnel.length - 1 && funnel[idx].count > 0 && (
{funnel[idx].count} → {funnel[idx + 1].count} {" "}({(funnel[idx + 1].count / funnel[idx].count * 100).toFixed(1)}%)
)} ))}
Content Attribution
{sortedAttribution.length === 0 ? (
No attribution data yet
) : ( {sortedAttribution.map(row => ( ))}
Article handleSort("prospects")}> Prospects{sortIndicator("prospects")} handleSort("replies")}> Replies{sortIndicator("replies")} handleSort("meetings")}> Meetings{sortIndicator("meetings")} handleSort("revenue")}> Revenue{sortIndicator("revenue")}
{row.slug} {row.prospects} {row.replies} {row.meetings} ${(row.revenue || 0).toFixed(0)}
)}
Cost Breakdown
{services.map(svc => ( ))}
Service This Month Last Month Delta
{svc.service} ${(svc.this_month || 0).toFixed(2)} ${(svc.last_month || 0).toFixed(2)} 0 ? "text-negative" : svc.delta < 0 ? "text-positive" : ""}> {svc.delta > 0 ? "+" : ""}{(svc.delta || 0).toFixed(2)}
Total ${(costs.total_this_month || 0).toFixed(2)} ${(costs.total_last_month || 0).toFixed(2)} 0 ? "text-negative" : "text-positive"}> {(costs.total_this_month - costs.total_last_month) > 0 ? "+" : ""} {((costs.total_this_month || 0) - (costs.total_last_month || 0)).toFixed(2)}
{costs.budget_cap > 0 && (
Budget: ${(costs.total_this_month || 0).toFixed(2)} / ${(costs.budget_cap || 0).toFixed(2)}
)} {costs.cac != null && (
Customer Acquisition Cost: ${(costs.cac || 0).toFixed(2)}
)}
{/* Flywheel Metrics */} {data?.flywheel && (
Flywheel Metrics
{data.flywheel.content_sourced_prospects || 0}
Content-Sourced Prospects
{data.flywheel.outbound_subscriber_rate || 0}%
Outbound-to-Subscriber Rate
{data.flywheel.hot_leads_with_content || 0}
Hot Leads with Content Data
{data.flywheel.contacts_with_content_data || 0}
Contacts with Content Interests
{/* Pillar demand from reply signals */} {data.flywheel.pillar_demand && Object.keys(data.flywheel.pillar_demand).length > 0 && (
Reply Topic Demand
{Object.entries(data.flywheel.pillar_demand) .sort((a, b) => b[1] - a[1]) .map(([pillar, count]) => ( {pillar.replace(/_/g, ' ')}: {count} ))}
)}
)}
); } // --------------------------------------------------------------------------- // Placeholder pages — replaced by plans 09-03 and 09-04 // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // ProspectCard — compact row with tier/layer badges, score, actions // --------------------------------------------------------------------------- function ProspectCard({ prospect, activeTab, expanded, onToggle, onAction }) { const tierColors = { S: "tier-S", A: "tier-A", B: "tier-B", C: "tier-C" }; const layerColors = { relationship: "layer-relationship", operator: "layer-operator", bdr: "layer-bdr", newsletter: "layer-newsletter", }; const [acting, setActing] = React.useState(false); function handleAction(e, action) { e.stopPropagation(); if (acting) return; setActing(true); onAction(prospect.id, action).finally(() => setActing(false)); } const interactions = prospect.interactions || []; const tags = prospect.tags || []; return (
{prospect.tier || "?"} { e.stopPropagation(); window.location.hash = "#contact/" + prospect.id; }} >{prospect.name || "Unknown"} {prospect.title || ""}{prospect.title && prospect.company ? " @ " : ""}{prospect.company || ""} {prospect.layer || ""} {prospect.total_score || 0}/100 {prospect.pain_summary || ""}
{activeTab === "pending" && ( <> )} {activeTab === "approved" && Approved} {activeTab === "skipped" && Skipped}
{expanded && (
Research Dossier
{prospect.pain_summary || "No pain signals recorded."}
Scores
Fit: {prospect.fit_score || 0}/40
Pain: {prospect.pain_score || 0}/40
Access: {prospect.access_score || 0}/20
Total: {prospect.total_score || 0}/100
{tags.length > 0 && (
Tags
{tags.map(t => {t})}
)}
Recent Interactions
{interactions.length === 0 ? (
No interactions yet
) : (
{interactions.slice(0, 5).map((ix, i) => (
{ix.type ? ix.type.replace(/_/g, " ") : "action"} {ix.at ? " — " + relativeTime(ix.at) : ""}
{ix.detail || ""}
))}
)}
)}
); } // --------------------------------------------------------------------------- // ProspectFeedPage — filterable prospect action queue // --------------------------------------------------------------------------- function ProspectFeedPage() { const [prospects, setProspects] = React.useState([]); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [activeTab, setActiveTab] = React.useState("pending"); const [expandedId, setExpandedId] = React.useState(null); const [page, setPage] = React.useState(1); const [totalPages, setTotalPages] = React.useState(1); function fetchProspects(tab, pg) { setLoading(true); setError(null); const status = tab === "all" ? "" : "&status=" + tab; salesApiFetch("/api/sales/prospects?page=" + pg + "&per_page=20" + status) .then(r => { if (!r.ok) throw new Error("Failed to load prospects"); return r.json(); }) .then(data => { setProspects(data.prospects || []); setTotalPages(data.pages || 1); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); } React.useEffect(() => { fetchProspects(activeTab, page); }, [activeTab, page]); function handleTabChange(tab) { setActiveTab(tab); setPage(1); setExpandedId(null); } function handleAction(prospectId, action) { const body = action === "approve" ? { action: "approve" } : action === "skip" ? { action: "skip" } : { action: "pause" }; return salesApiFetch("/api/sales/prospects/" + prospectId, { method: "PUT", body: JSON.stringify(body), }).then(r => { if (!r.ok) throw new Error("Action failed"); if (activeTab === "pending") { setProspects(prev => prev.filter(p => p.id !== prospectId)); } else { fetchProspects(activeTab, page); } }).catch(err => { console.error("Prospect action error:", err); }); } const tabs = [ { key: "pending", label: "Pending" }, { key: "approved", label: "Approved" }, { key: "skipped", label: "Skipped" }, { key: "all", label: "All" }, ]; return (

Prospect Feed

Review and approve prospects for outreach sequences

{tabs.map(t => ( ))}
{loading &&
Loading prospects...
} {error &&
{error}
} {!loading && !error && prospects.length === 0 && (
No {activeTab === "all" ? "" : activeTab + " "}prospects found
)} {!loading && !error && prospects.length > 0 && (
{prospects.map(p => ( setExpandedId(expandedId === p.id ? null : p.id)} onAction={handleAction} /> ))}
)} {!loading && totalPages > 1 && (
Page {page} of {totalPages}
)}
); } // --------------------------------------------------------------------------- // SequencesPage — contacts grouped by layer with delivery timeline // --------------------------------------------------------------------------- function SequenceContactCard({ contact }) { const layerColors = { relationship: "layer-relationship", operator: "layer-operator", bdr: "layer-bdr", newsletter: "layer-newsletter", }; function getStatusClass(contact) { if (contact.sequence_paused) return "status-paused"; if (!contact.sequence_next_send) return "status-on-track"; const nextSend = new Date(contact.sequence_next_send); return nextSend < new Date() ? "status-overdue" : "status-on-track"; } function formatNextSend(isoStr) { if (!isoStr) return "Not scheduled"; const d = new Date(isoStr); const now = new Date(); const diffMs = d - now; const diffDays = Math.ceil(diffMs / 86400000); if (diffDays < 0) return Math.abs(diffDays) + "d overdue"; if (diffDays === 0) return "Today"; if (diffDays === 1) return "Tomorrow"; return "In " + diffDays + "d"; } return (
{contact.name || "Unknown"} {contact.company || ""} {contact.layer || ""} Touch {contact.sequence_touch || "?"} of {contact.sequence_total || "?"} {formatNextSend(contact.sequence_next_send)} {contact.last_interaction && ( {contact.last_interaction.detail || contact.last_interaction.type || ""} )}
); } function SequencesPage() { const [sequences, setSequences] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); React.useEffect(() => { salesApiFetch("/api/sales/sequences") .then(r => { if (!r.ok) throw new Error("Failed to load sequences"); return r.json(); }) .then(data => { setSequences(data); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) return
Loading sequences...
; if (error) return
{error}
; const layers = ["relationship", "operator", "bdr"]; const layerLabels = { relationship: "Relationship", operator: "Operator", bdr: "BDR" }; const seqData = sequences?.sequences || {}; const totalActive = sequences?.total_active || 0; return (

Active Sequences

Contacts currently in outreach sequences

{totalActive} active sequence{totalActive !== 1 ? "s" : ""} across {layers.length} layers
{layers.map(layer => { const contacts = seqData[layer] || []; return (

{layerLabels[layer]}

{contacts.length}
{contacts.length === 0 ? (
No active {layer} sequences
) : (
{contacts.map(c => )}
)}
); })}
); } // --------------------------------------------------------------------------- // DealCard — compact Kanban card with inline expand // --------------------------------------------------------------------------- const DEAL_STAGES = ["discovery", "qualified", "meeting", "proposal", "closed_won", "closed_lost"]; const DEAL_STAGE_LABELS = { discovery: "Discovery", qualified: "Qualified", meeting: "Meeting", proposal: "Proposal", closed_won: "Closed Won", closed_lost: "Closed Lost", }; function DealCard({ deal, expanded, onToggle, onStageChange, onNoteSave }) { const layerColors = { relationship: "layer-relationship", operator: "layer-operator", bdr: "layer-bdr", newsletter: "layer-newsletter", }; const [noteText, setNoteText] = React.useState(deal.notes || ""); const [noteDirty, setNoteDirty] = React.useState(false); const [saving, setSaving] = React.useState(false); function handleNoteBlur() { if (!noteDirty) return; setSaving(true); onNoteSave(deal.id, noteText).finally(() => { setSaving(false); setNoteDirty(false); }); } function handleStageChange(e) { const newStage = e.target.value; if (newStage && newStage !== deal.stage) { onStageChange(deal.id, newStage); } } return (
{ e.stopPropagation(); window.location.hash = "#contact/" + deal.contact_id; }} >{deal.contact_name || "Unknown"}
{deal.contact_company || ""}
{deal.layer || ""} {deal.days_in_stage || 0}d
{deal.last_interaction && (
{deal.last_interaction}
)}
{expanded && (
Stage
{deal.stage_history && deal.stage_history.length > 0 && (
Stage History
{deal.stage_history.slice(-3).map((sh, i) => (
{DEAL_STAGE_LABELS[sh.stage] || sh.stage} {sh.at ? " — " + relativeTime(sh.at) : ""}
))}
)}
Notes {saving && (saving...)}