diff --git a/templates/saas/nextjs-monolith/app/analytics/page.tsx b/templates/saas/nextjs-monolith/app/analytics/page.tsx
index 502bf63..c9650fa 100644
--- a/templates/saas/nextjs-monolith/app/analytics/page.tsx
+++ b/templates/saas/nextjs-monolith/app/analytics/page.tsx
@@ -1,27 +1,70 @@
+import SparkLine from '../../components/SparkLine';
+import LineChart from '../../components/LineChart';
+import DonutChart from '../../components/DonutChart';
+import BarChart from '../../components/BarChart';
+
export default function AnalyticsPage() {
+ const summaryMetrics = [
+ { label: 'Total Page Views', value: '284,920', change: '+18.3%', positive: true, sparkData: [180, 210, 195, 230, 255, 270, 285] },
+ { label: 'Unique Visitors', value: '11,847', change: '+9.7%', positive: true, sparkData: [7.2, 8.1, 8.8, 9.5, 10.2, 11.0, 11.8] },
+ { label: 'Conversion Rate', value: '7.1%', change: '-0.4%', positive: false, sparkData: [7.8, 7.6, 7.5, 7.3, 7.2, 7.0, 7.1], color: '#ef4444' },
+ ];
+
+ const trafficData = [
+ { label: '1', value: 8200 },
+ { label: '2', value: 7800 },
+ { label: '3', value: 9100 },
+ { label: '4', value: 8600 },
+ { label: '5', value: 9400 },
+ { label: '6', value: 10200 },
+ { label: '7', value: 9800 },
+ { label: '8', value: 8900 },
+ { label: '9', value: 9600 },
+ { label: '10', value: 10800 },
+ { label: '11', value: 11200 },
+ { label: '12', value: 10500 },
+ { label: '13', value: 9900 },
+ { label: '14', value: 10100 },
+ { label: '15', value: 11400 },
+ { label: '16', value: 12100 },
+ { label: '17', value: 11800 },
+ { label: '18', value: 10900 },
+ { label: '19', value: 11500 },
+ { label: '20', value: 12400 },
+ { label: '21', value: 11900 },
+ { label: '22', value: 10600 },
+ { label: '23', value: 11100 },
+ { label: '24', value: 12800 },
+ { label: '25', value: 13200 },
+ { label: '26', value: 12600 },
+ { label: '27', value: 11800 },
+ { label: '28', value: 12300 },
+ { label: '29', value: 13500 },
+ { label: '30', value: 14100 },
+ ];
+
const trafficSources = [
- { source: 'Organic Search', visitors: 4521, percentage: 38, color: 'bg-blue-500' },
- { source: 'Direct', visitors: 2847, percentage: 24, color: 'bg-green-500' },
- { source: 'Social Media', visitors: 1923, percentage: 16, color: 'bg-purple-500' },
- { source: 'Referral', visitors: 1456, percentage: 12, color: 'bg-orange-500' },
- { source: 'Email', visitors: 1100, percentage: 10, color: 'bg-pink-500' },
+ { label: 'Organic', value: 38, color: 'var(--primary)' },
+ { label: 'Direct', value: 24, color: '#10b981' },
+ { label: 'Social', value: 16, color: '#8b5cf6' },
+ { label: 'Referral', value: 12, color: '#f59e0b' },
+ { label: 'Email', value: 10, color: '#ec4899' },
];
- const pageViews = [
- { page: '/dashboard', views: 12450, avgTime: '3m 24s', bounceRate: '18%' },
- { page: '/analytics', views: 8920, avgTime: '5m 12s', bounceRate: '12%' },
- { page: '/users', views: 6340, avgTime: '2m 48s', bounceRate: '24%' },
- { page: '/billing', views: 4210, avgTime: '4m 06s', bounceRate: '15%' },
- { page: '/settings', views: 3180, avgTime: '1m 52s', bounceRate: '32%' },
- { page: '/integrations', views: 2890, avgTime: '6m 30s', bounceRate: '8%' },
+ const topPages = [
+ { label: 'Dashboard', value: 12450 },
+ { label: 'Analytics', value: 8920 },
+ { label: 'Users', value: 6340 },
+ { label: 'Billing', value: 4210 },
+ { label: 'Settings', value: 3180 },
];
const conversionFunnel = [
- { stage: 'Visitors', count: 45000, width: '100%' },
- { stage: 'Sign Ups', count: 12800, width: '72%' },
- { stage: 'Activated', count: 8400, width: '48%' },
- { stage: 'Subscribed', count: 3200, width: '28%' },
- { stage: 'Enterprise', count: 420, width: '12%' },
+ { stage: 'Visitors', count: 45000, percentage: 100 },
+ { stage: 'Sign Ups', count: 12800, percentage: 72 },
+ { stage: 'Activated', count: 8400, percentage: 48 },
+ { stage: 'Subscribed', count: 3200, percentage: 28 },
+ { stage: 'Enterprise', count: 420, percentage: 12 },
];
return (
@@ -38,94 +81,74 @@ export default function AnalyticsPage() {
- {/* Summary Row */}
+ {/* Summary Metrics */}
-
-
Total Page Views
-
284,920
-
+18.3% vs last period
-
-
-
Unique Visitors
-
11,847
-
+9.7% vs last period
-
-
-
Conversion Rate
-
7.1%
-
-0.4% vs last period
+ {summaryMetrics.map((metric) => (
+
+
+
+
{metric.label}
+
{metric.value}
+
+
+
+
+ {metric.change} vs last period
+
+
+ ))}
+
+
+ {/* Traffic Over Time */}
+
+
Traffic Over Time
+
+
+
+
Total Views (30d)
+
284,920
+
+
+
Daily Average
+
9,497
+
+
+ {/* Two-column: Donut + Bar */}
- {/* Traffic Sources */}
Traffic Sources
-
- {trafficSources.map((source) => (
-
-
- {source.source}
- {source.visitors.toLocaleString()} ({source.percentage}%)
-
-
-
- ))}
-
+
-
- {/* Conversion Funnel */}
-
Conversion Funnel
-
- {conversionFunnel.map((stage) => (
-
-
{stage.stage}
-
-
- {stage.count.toLocaleString()}
-
-
-
- ))}
-
+
Top Pages
+
- {/* Top Pages */}
-
-
-
Top Pages
-
-
-
-
-
- | Page |
- Views |
- Avg. Time |
- Bounce Rate |
-
-
-
- {pageViews.map((page) => (
-
- | {page.page} |
- {page.views.toLocaleString()} |
- {page.avgTime} |
- {page.bounceRate} |
-
- ))}
-
-
+ {/* Conversion Funnel */}
+
+
Conversion Funnel
+
+ {conversionFunnel.map((stage) => (
+
+
{stage.stage}
+
+
+ {stage.count.toLocaleString()}
+
+
+
{stage.percentage}%
+
+ ))}
diff --git a/templates/saas/nextjs-monolith/app/page.tsx b/templates/saas/nextjs-monolith/app/page.tsx
index 72c8189..66df1ff 100644
--- a/templates/saas/nextjs-monolith/app/page.tsx
+++ b/templates/saas/nextjs-monolith/app/page.tsx
@@ -1,18 +1,45 @@
+import SparkLine from '../components/SparkLine';
+import LineChart from '../components/LineChart';
+import DonutChart from '../components/DonutChart';
+import BarChart from '../components/BarChart';
+
export default function Dashboard() {
const metrics = [
- { label: 'Monthly Revenue', value: '$48,295', change: '+12.5%', positive: true },
- { label: 'Active Users', value: '12,847', change: '+8.2%', positive: true },
- { label: 'Churn Rate', value: '2.4%', change: '-0.3%', positive: true },
- { label: 'Avg. Session', value: '4m 32s', change: '-12s', positive: false },
+ { label: 'Monthly Revenue', value: '$48,295', change: '+12.5%', positive: true, sparkData: [32, 35, 38, 41, 44, 48] },
+ { label: 'Active Users', value: '12,847', change: '+8.2%', positive: true, sparkData: [8, 9, 9.8, 10.5, 11.3, 12.8] },
+ { label: 'Churn Rate', value: '2.4%', change: '-0.3%', positive: true, sparkData: [3.2, 2.9, 2.8, 2.7, 2.5, 2.4], color: '#ef4444' },
+ { label: 'Avg. Session', value: '4m 32s', change: '-12s', positive: false, sparkData: [4.1, 4.3, 4.5, 4.4, 4.6, 4.5] },
+ ];
+
+ const revenueData = [
+ { label: 'Jan', value: 28400 },
+ { label: 'Feb', value: 31200 },
+ { label: 'Mar', value: 29800 },
+ { label: 'Apr', value: 35600 },
+ { label: 'May', value: 33100 },
+ { label: 'Jun', value: 38500 },
+ { label: 'Jul', value: 36200 },
+ { label: 'Aug', value: 41000 },
+ { label: 'Sep', value: 39400 },
+ { label: 'Oct', value: 44200 },
+ { label: 'Nov', value: 46100 },
+ { label: 'Dec', value: 48295 },
];
- const chartData = [
- { month: 'Jan', revenue: 32000, users: 8400 },
- { month: 'Feb', revenue: 35000, users: 9200 },
- { month: 'Mar', revenue: 38500, users: 9800 },
- { month: 'Apr', revenue: 41000, users: 10500 },
- { month: 'May', revenue: 44200, users: 11300 },
- { month: 'Jun', revenue: 48295, users: 12847 },
+ const revenueBySource = [
+ { label: 'Direct', value: 38, color: 'var(--primary)' },
+ { label: 'Organic', value: 28, color: '#10b981' },
+ { label: 'Referral', value: 18, color: '#f59e0b' },
+ { label: 'Social', value: 16, color: '#8b5cf6' },
+ ];
+
+ const monthlyUsers = [
+ { label: 'Jul', value: 8400 },
+ { label: 'Aug', value: 9200 },
+ { label: 'Sep', value: 9800 },
+ { label: 'Oct', value: 10500 },
+ { label: 'Nov', value: 11300 },
+ { label: 'Dec', value: 12847 },
];
const recentTransactions = [
@@ -23,8 +50,6 @@ export default function Dashboard() {
{ id: 'TXN-005', customer: 'BigCo Ltd', plan: 'Enterprise', amount: '$2,400', status: 'Failed', date: '2 days ago' },
];
- const maxRevenue = Math.max(...chartData.map((d) => d.revenue));
-
return (
{/* Header */}
@@ -41,9 +66,14 @@ export default function Dashboard() {
{metrics.map((metric) => (
-
{metric.label}
-
{metric.value}
-
+
+
+
{metric.label}
+
{metric.value}
+
+
+
+
{metric.change} from last month
@@ -52,30 +82,40 @@ export default function Dashboard() {
{/* Revenue Chart */}
-
Revenue Overview
-
- {chartData.map((point) => (
-
- ))}
+
+
Revenue Overview
+
+
+
+
+
+
+
-
Total Revenue (6mo)
-
$238,995
+
Total Revenue (12mo)
+
$451,795
Growth Rate
-
+50.9%
+
+70.1%
+ {/* Two-column: Donut + Bar */}
+
+
+
Revenue by Source
+
+
+
+
Monthly Active Users
+
+
+
+
{/* Recent Transactions */}
diff --git a/templates/saas/nextjs-monolith/components/BarChart.tsx b/templates/saas/nextjs-monolith/components/BarChart.tsx
new file mode 100644
index 0000000..07b7f4f
--- /dev/null
+++ b/templates/saas/nextjs-monolith/components/BarChart.tsx
@@ -0,0 +1,36 @@
+interface BarChartProps {
+ data: { label: string; value: number }[];
+ height?: number;
+}
+
+export default function BarChart({ data, height = 200 }: BarChartProps) {
+ const max = Math.max(...data.map((d) => d.value));
+
+ return (
+
+
+ {data.map((item) => {
+ const barHeight = (item.value / max) * 100;
+ return (
+
+
+ {item.value >= 1000 ? `${(item.value / 1000).toFixed(1)}k` : item.value}
+
+
+
+ );
+ })}
+
+
+ {data.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+}
diff --git a/templates/saas/nextjs-monolith/components/DonutChart.tsx b/templates/saas/nextjs-monolith/components/DonutChart.tsx
new file mode 100644
index 0000000..2fe0426
--- /dev/null
+++ b/templates/saas/nextjs-monolith/components/DonutChart.tsx
@@ -0,0 +1,87 @@
+interface DonutChartProps {
+ segments: { label: string; value: number; color: string }[];
+ size?: number;
+}
+
+export default function DonutChart({ segments, size = 180 }: DonutChartProps) {
+ const total = segments.reduce((sum, s) => sum + s.value, 0);
+ const strokeWidth = size * 0.18;
+ const radius = (size - strokeWidth) / 2;
+ const circumference = 2 * Math.PI * radius;
+ const center = size / 2;
+
+ let cumulativeOffset = 0;
+
+ const segmentArcs = segments.map((segment) => {
+ const percentage = segment.value / total;
+ const dashLength = circumference * percentage;
+ const dashGap = circumference - dashLength;
+ const offset = -cumulativeOffset;
+ cumulativeOffset += dashLength;
+
+ return {
+ ...segment,
+ percentage,
+ dashArray: `${dashLength} ${dashGap}`,
+ dashOffset: offset,
+ };
+ });
+
+ return (
+
+
+
+ {/* Center text */}
+
+ {total}
+ Total
+
+
+
+ {/* Legend */}
+
+ {segments.map((seg) => (
+
+
+ {seg.label}
+
+ {Math.round((seg.value / total) * 100)}%
+
+
+ ))}
+
+
+ );
+}
diff --git a/templates/saas/nextjs-monolith/components/LineChart.tsx b/templates/saas/nextjs-monolith/components/LineChart.tsx
new file mode 100644
index 0000000..15a8610
--- /dev/null
+++ b/templates/saas/nextjs-monolith/components/LineChart.tsx
@@ -0,0 +1,122 @@
+interface LineChartProps {
+ data: { label: string; value: number }[];
+ height?: number;
+}
+
+export default function LineChart({ data, height = 200 }: LineChartProps) {
+ const max = Math.max(...data.map((d) => d.value));
+ const min = Math.min(...data.map((d) => d.value));
+ const range = max - min || 1;
+
+ const padding = { top: 20, right: 20, bottom: 30, left: 10 };
+ const chartWidth = 800;
+ const chartHeight = height;
+ const innerWidth = chartWidth - padding.left - padding.right;
+ const innerHeight = chartHeight - padding.top - padding.bottom;
+
+ const points = data.map((d, i) => {
+ const x = padding.left + (i / (data.length - 1)) * innerWidth;
+ const y = padding.top + innerHeight - ((d.value - min) / range) * innerHeight;
+ return { x, y };
+ });
+
+ const polylinePoints = points.map((p) => `${p.x},${p.y}`).join(' ');
+
+ const areaPath = [
+ `M ${points[0].x},${padding.top + innerHeight}`,
+ `L ${points[0].x},${points[0].y}`,
+ ...points.slice(1).map((p) => `L ${p.x},${p.y}`),
+ `L ${points[points.length - 1].x},${padding.top + innerHeight}`,
+ 'Z',
+ ].join(' ');
+
+ const gridLines = 4;
+ const gridValues = Array.from({ length: gridLines }, (_, i) => {
+ const value = min + (range / (gridLines - 1)) * i;
+ const y = padding.top + innerHeight - ((value - min) / range) * innerHeight;
+ return { value, y };
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/templates/saas/nextjs-monolith/components/SparkLine.tsx b/templates/saas/nextjs-monolith/components/SparkLine.tsx
new file mode 100644
index 0000000..0f8fd2b
--- /dev/null
+++ b/templates/saas/nextjs-monolith/components/SparkLine.tsx
@@ -0,0 +1,30 @@
+interface SparkLineProps {
+ data: number[];
+ color?: string;
+ width?: number;
+ height?: number;
+}
+
+export default function SparkLine({ data, color = 'var(--primary)', width = 80, height = 24 }: SparkLineProps) {
+ const max = Math.max(...data);
+ const min = Math.min(...data);
+ const range = max - min || 1;
+ const points = data.map((val, i) => {
+ const x = (i / (data.length - 1)) * width;
+ const y = height - ((val - min) / range) * (height - 4) - 2;
+ return `${x},${y}`;
+ }).join(' ');
+
+ return (
+
+ );
+}