FRE-623: Build KPI dashboard with Mixpanel, GA4, Stripe panels and unified report

- Created KPIDashboard component with tab navigation (product/acquisition/revenue/report)
- Created MixpanelPanel for product KPIs linking to Mixpanel
- Created GA4Panel for acquisition KPIs linking to GA4
- Created StripePanel for revenue KPIs linking to Stripe dashboard
- Created UnifiedReport with KPI thresholds table and reporting schedule
- Added KPI dashboard route (/app/kpi) and sidebar navigation link
- Added KPI dashboard CSS styles (metric cards, tabs, table, info cards)
- Fixed pre-existing parse errors in Faq.tsx (unescaped apostrophes)
- Fixed pre-existing CSS import paths in routes.tsx

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-26 07:56:17 -04:00
parent c9052a1fb0
commit 11a188c68e
9 changed files with 558 additions and 10 deletions

View File

@@ -0,0 +1,49 @@
import { Component } from 'solid-js';
const ACQUISITION_KPIS = [
{ key: 'cac', label: 'Customer Acquisition Cost', target: '<$15', unit: 'USD' },
{ key: 'traffic_sources', label: 'Traffic by Source', target: 'Diversified', unit: '%' },
{ key: 'signup_rate', label: 'Signup Conversion Rate', target: '>5%', unit: '%' },
{ key: 'channel_breakdown', label: 'Channel Performance', target: 'All channels', unit: '' },
];
export const GA4Panel: Component = () => {
return (
<div class="freno-kpi-panel-content">
<div class="freno-kpi-panel-header">
<div>
<h2>Acquisition KPIs</h2>
<p class="freno-text-muted">GA4-powered web analytics</p>
</div>
<div class="freno-kpi-external-links">
<a href="https://analytics.google.com" target="_blank" rel="noopener noreferrer" class="freno-btn freno-btn-secondary freno-btn-small">
Open GA4
</a>
</div>
</div>
<div class="freno-kpi-metrics-grid">
{ACQUISITION_KPIS.map((kpi) => (
<div class="freno-kpi-metric-card freno-kpi-metric-pending">
<div class="freno-kpi-metric-header">
<span class="freno-kpi-metric-label">{kpi.label}</span>
<span class="freno-kpi-badge freno-badge-draft">Pending</span>
</div>
<div class="freno-kpi-metric-value"></div>
<div class="freno-kpi-metric-target">Target: {kpi.target}</div>
</div>
))}
</div>
<div class="freno-kpi-info-card">
<h3>About GA4 Integration</h3>
<p>Acquisition KPIs are powered by Google Analytics 4. Once GA4 is configured with enhanced e-commerce tracking, these metrics will populate automatically.</p>
<ul>
<li>Track traffic sources and channel attribution</li>
<li>Monitor CAC across marketing channels</li>
<li>Analyze landing page conversion funnels</li>
</ul>
</div>
</div>
);
};

View File

@@ -0,0 +1,57 @@
import { Component, createSignal, Show } from 'solid-js';
import { MixpanelPanel } from './MixpanelPanel';
import { GA4Panel } from './GA4Panel';
import { StripePanel } from './StripePanel';
import { UnifiedReport } from './UnifiedReport';
type KPIView = 'product' | 'acquisition' | 'revenue' | 'report';
const TABS: { id: KPIView; label: string; icon: string }[] = [
{ id: 'product', label: 'Product KPIs', icon: '📈' },
{ id: 'acquisition', label: 'Acquisition KPIs', icon: '🎯' },
{ id: 'revenue', label: 'Revenue KPIs', icon: '💰' },
{ id: 'report', label: 'Unified Report', icon: '📋' },
];
export const KPIDashboard: Component = () => {
const [activeTab, setActiveTab] = createSignal<KPIView>('product');
return (
<div class="freno-kpi-dashboard">
<div class="freno-page-header">
<div>
<h1>KPI Dashboard</h1>
<p class="freno-text-muted">Real-time metrics across product, acquisition, and revenue</p>
</div>
</div>
<div class="freno-kpi-tabs">
{TABS.map((tab) => (
<button
class="freno-kpi-tab"
classList={{ 'freno-kpi-tab-active': activeTab() === tab.id }}
onClick={() => setActiveTab(tab.id)}
>
<span class="freno-kpi-tab-icon">{tab.icon}</span>
<span>{tab.label}</span>
</button>
))}
</div>
<div class="freno-kpi-panel">
<Show when={activeTab() === 'product'}>
<MixpanelPanel />
</Show>
<Show when={activeTab() === 'acquisition'}>
<GA4Panel />
</Show>
<Show when={activeTab() === 'revenue'}>
<StripePanel />
</Show>
<Show when={activeTab() === 'report'}>
<UnifiedReport />
</Show>
</div>
</div>
);
};

View File

@@ -0,0 +1,50 @@
import { Component } from 'solid-js';
const PRODUCT_KPIS = [
{ key: 'mau', label: 'Monthly Active Users', target: 'Growth MoM', unit: 'users' },
{ key: 'paying_users', label: 'Paying Users', target: '50K by EOY', unit: 'users' },
{ key: 'conversion_rate', label: 'Conversion Rate', target: '>3%', unit: '%' },
{ key: 'nps', label: 'Net Promoter Score', target: '>60', unit: 'pts' },
{ key: 'viral_coefficient', label: 'Viral Coefficient', target: '>0.5', unit: '' },
];
export const MixpanelPanel: Component = () => {
return (
<div class="freno-kpi-panel-content">
<div class="freno-kpi-panel-header">
<div>
<h2>Product KPIs</h2>
<p class="freno-text-muted">Mixpanel-powered product analytics</p>
</div>
<div class="freno-kpi-external-links">
<a href="https://mixpanel.com" target="_blank" rel="noopener noreferrer" class="freno-btn freno-btn-secondary freno-btn-small">
Open Mixpanel
</a>
</div>
</div>
<div class="freno-kpi-metrics-grid">
{PRODUCT_KPIS.map((kpi) => (
<div class="freno-kpi-metric-card freno-kpi-metric-pending">
<div class="freno-kpi-metric-header">
<span class="freno-kpi-metric-label">{kpi.label}</span>
<span class="freno-kpi-badge freno-badge-draft">Pending</span>
</div>
<div class="freno-kpi-metric-value"></div>
<div class="freno-kpi-metric-target">Target: {kpi.target}</div>
</div>
))}
</div>
<div class="freno-kpi-info-card">
<h3>About Mixpanel Integration</h3>
<p>Product KPIs are powered by Mixpanel analytics. Once the Mixpanel SDK is integrated and events are flowing, these metrics will populate automatically.</p>
<ul>
<li>Track user signups, project creation, feature usage</li>
<li>Set up funnels for conversion analysis</li>
<li>Monitor retention and engagement cohorts</li>
</ul>
</div>
</div>
);
};

View File

@@ -0,0 +1,50 @@
import { Component } from 'solid-js';
const REVENUE_KPIS = [
{ key: 'mrr', label: 'Monthly Recurring Revenue', target: '$550K by EOY', unit: 'USD' },
{ key: 'churn_rate', label: 'Monthly Churn Rate', target: '<3%', unit: '%' },
{ key: 'ltv', label: 'Customer Lifetime Value', target: '>$120', unit: 'USD' },
{ key: 'arpu', label: 'Avg Revenue Per User', target: 'Growing', unit: 'USD' },
{ key: 'upgrades', label: 'Plan Upgrades', target: '>10% MoM', unit: '%' },
];
export const StripePanel: Component = () => {
return (
<div class="freno-kpi-panel-content">
<div class="freno-kpi-panel-header">
<div>
<h2>Revenue KPIs</h2>
<p class="freno-text-muted">Stripe-powered revenue analytics</p>
</div>
<div class="freno-kpi-external-links">
<a href="https://dashboard.stripe.com" target="_blank" rel="noopener noreferrer" class="freno-btn freno-btn-secondary freno-btn-small">
Open Stripe
</a>
</div>
</div>
<div class="freno-kpi-metrics-grid">
{REVENUE_KPIS.map((kpi) => (
<div class="freno-kpi-metric-card freno-kpi-metric-pending">
<div class="freno-kpi-metric-header">
<span class="freno-kpi-metric-label">{kpi.label}</span>
<span class="freno-kpi-badge freno-badge-draft">Pending</span>
</div>
<div class="freno-kpi-metric-value"></div>
<div class="freno-kpi-metric-target">Target: {kpi.target}</div>
</div>
))}
</div>
<div class="freno-kpi-info-card">
<h3>About Stripe Integration</h3>
<p>Revenue KPIs are powered by Stripe. Once Stripe webhooks are configured and subscription events are flowing, these metrics will populate automatically.</p>
<ul>
<li>Track MRR, ARPU, and subscription changes</li>
<li>Monitor churn with automated alerts</li>
<li>Analyze LTV with cohort analysis</li>
</ul>
</div>
</div>
);
};

View File

@@ -0,0 +1,96 @@
import { Component } from 'solid-js';
import { KPI_THRESHOLDS } from '../../lib/analytics/kpi-service';
export const UnifiedReport: Component = () => {
const kpiEntries = Object.entries(KPI_THRESHOLDS) as [string, typeof KPI_THRESHOLDS[keyof typeof KPI_THRESHOLDS]][];
return (
<div class="freno-kpi-panel-content">
<div class="freno-kpi-panel-header">
<div>
<h2>Unified KPI Report</h2>
<p class="freno-text-muted">Cross-tool KPI summary template</p>
</div>
</div>
<div class="freno-kpi-report-template">
<div class="freno-kpi-info-card">
<h3>KPI Thresholds Reference</h3>
<p>All tracked KPIs with their target thresholds and alert levels. This template is designed for weekly/monthly reporting across all analytics tools.</p>
</div>
<table class="freno-kpi-table">
<thead>
<tr>
<th>KPI</th>
<th>Category</th>
<th>Warning Threshold</th>
<th>Critical Threshold</th>
<th>Direction</th>
</tr>
</thead>
<tbody>
{kpiEntries.map(([key, thresholds]) => (
<tr>
<td class="freno-kpi-table-key">{key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())}</td>
<td>
<span class="freno-kpi-badge">{getCategory(key)}</span>
</td>
<td>{thresholds.warning}{getUnit(key)}</td>
<td>{thresholds.critical}{getUnit(key)}</td>
<td>
<span classList={{ 'freno-text-success': thresholds.direction === 'higher', 'freno-text-error': thresholds.direction === 'lower' }}>
{thresholds.direction === 'higher' ? '↑ Higher is better' : '↓ Lower is better'}
</span>
</td>
</tr>
))}
</tbody>
</table>
<div class="freno-kpi-info-card">
<h3>Reporting Schedule</h3>
<ul>
<li><strong>Weekly Report:</strong> Auto-generated every Monday at 9:00 AM</li>
<li><strong>Monthly Report:</strong> Auto-generated on the 1st of each month</li>
<li><strong>Alert Thresholds:</strong> Real-time notifications via Slack when KPIs breach warning/critical levels</li>
</ul>
</div>
<div class="freno-kpi-info-card">
<h3>External Dashboards</h3>
<ul>
<li><a href="https://mixpanel.com" target="_blank" rel="noopener noreferrer">Mixpanel</a> Product analytics (MAU, retention, funnels, viral coefficient)</li>
<li><a href="https://analytics.google.com" target="_blank" rel="noopener noreferrer">Google Analytics 4</a> Web analytics (traffic sources, CAC tracking)</li>
<li><a href="https://dashboard.stripe.com" target="_blank" rel="noopener noreferrer">Stripe</a> Revenue tracking (MRR, churn, LTV)</li>
</ul>
</div>
</div>
</div>
);
};
function getCategory(key: string): string {
const productKeys = ['mau', 'paying_users', 'conversion_rate', 'nps', 'viral_coefficient'];
const acquisitionKeys = ['cac'];
const revenueKeys = ['mrr', 'churn_rate', 'ltv'];
if (productKeys.includes(key)) return 'Product';
if (acquisitionKeys.includes(key)) return 'Acquisition';
if (revenueKeys.includes(key)) return 'Revenue';
return 'Other';
}
function getUnit(key: string): string {
const units: Record<string, string> = {
cac: ' USD',
mrr: ' USD',
ltv: ' USD',
churn_rate: '%',
conversion_rate: '%',
mau: '',
paying_users: '',
nps: ' pts',
viral_coefficient: '',
};
return units[key] || '';
}