"use client"; import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; import discordLogo from "@/public/icon/discord.svg"; import { format, parseISO } from "date-fns"; import { Cpu, Download, Terminal, TrendingUp, Users } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import Papa from "papaparse"; import { useCallback, useEffect, useState } from "react"; import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, XAxis, YAxis, } from "recharts"; import Navbar from "../_components/navbar"; interface AnalyticsData { date: string; cli_version: string; node_version: string; platform: string; backend: string; database: string; orm: string; dbSetup: string; auth: string; api: string; packageManager: string; frontend0: string; frontend1: string; examples0: string; examples1: string; addons: string[]; git: string; install: string; } const timeSeriesConfig = { projects: { label: "Projects Created", color: "hsl(var(--chart-1))", }, } satisfies ChartConfig; const platformConfig = { darwin: { label: "macOS", color: "hsl(var(--chart-1))", }, linux: { label: "Linux", color: "hsl(var(--chart-2))", }, win32: { label: "Windows", color: "hsl(var(--chart-3))", }, } satisfies ChartConfig; const packageManagerConfig = { npm: { label: "npm", color: "hsl(var(--chart-1))", }, pnpm: { label: "pnpm", color: "hsl(var(--chart-2))", }, bun: { label: "bun", color: "hsl(var(--chart-3))", }, yarn: { label: "yarn", color: "hsl(var(--chart-4))", }, unknown: { label: "Unknown", color: "hsl(var(--chart-5))", }, } satisfies ChartConfig; const backendConfig = { hono: { label: "Hono", color: "hsl(var(--chart-1))", }, express: { label: "Express", color: "hsl(var(--chart-2))", }, fastify: { label: "Fastify", color: "hsl(var(--chart-3))", }, next: { label: "Next.js", color: "hsl(var(--chart-4))", }, elysia: { label: "Elysia", color: "hsl(var(--chart-5))", }, convex: { label: "Convex", color: "hsl(var(--chart-6))", }, none: { label: "None", color: "hsl(var(--chart-7))", }, } satisfies ChartConfig; const databaseConfig = { sqlite: { label: "SQLite", color: "hsl(var(--chart-1))", }, postgres: { label: "PostgreSQL", color: "hsl(var(--chart-2))", }, mysql: { label: "MySQL", color: "hsl(var(--chart-3))", }, mongodb: { label: "MongoDB", color: "hsl(var(--chart-4))", }, none: { label: "None", color: "hsl(var(--chart-6))", }, } satisfies ChartConfig; const ormConfig = { drizzle: { label: "Drizzle", color: "hsl(var(--chart-1))", }, prisma: { label: "Prisma", color: "hsl(var(--chart-2))", }, mongoose: { label: "Mongoose", color: "hsl(var(--chart-3))", }, none: { label: "None", color: "hsl(var(--chart-6))", }, } satisfies ChartConfig; const dbSetupConfig = { turso: { label: "Turso", color: "hsl(var(--chart-1))", }, "prisma-postgres": { label: "Prisma + PostgreSQL", color: "hsl(var(--chart-2))", }, "mongodb-atlas": { label: "MongoDB Atlas", color: "hsl(var(--chart-3))", }, neon: { label: "Neon", color: "hsl(var(--chart-4))", }, supabase: { label: "Supabase", color: "hsl(var(--chart-5))", }, none: { label: "None", color: "hsl(var(--chart-6))", }, } satisfies ChartConfig; const apiConfig = { trpc: { label: "tRPC", color: "hsl(var(--chart-1))", }, orpc: { label: "oRPC", color: "hsl(var(--chart-2))", }, none: { label: "None", color: "hsl(var(--chart-6))", }, } satisfies ChartConfig; const frontendConfig = { "react-router": { label: "React Router", color: "hsl(var(--chart-1))", }, "tanstack-router": { label: "TanStack Router", color: "hsl(var(--chart-2))", }, "tanstack-start": { label: "TanStack Start", color: "hsl(var(--chart-3))", }, next: { label: "Next.js", color: "hsl(var(--chart-4))", }, nuxt: { label: "Nuxt", color: "hsl(var(--chart-5))", }, "native-nativewind": { label: "Native NativeWind", color: "hsl(var(--chart-6))", }, "native-unistyles": { label: "Native Unistyles", color: "hsl(var(--chart-7))", }, svelte: { label: "Svelte", color: "hsl(var(--chart-3))", }, solid: { label: "Solid", color: "hsl(var(--chart-4))", }, none: { label: "None", color: "hsl(var(--chart-6))", }, } satisfies ChartConfig; const nodeVersionConfig = { "18": { label: "Node.js 18", color: "hsl(var(--chart-1))", }, "20": { label: "Node.js 20", color: "hsl(var(--chart-2))", }, "22": { label: "Node.js 22", color: "hsl(var(--chart-3))", }, "16": { label: "Node.js 16", color: "hsl(var(--chart-4))", }, other: { label: "Other", color: "hsl(var(--chart-5))", }, } satisfies ChartConfig; const cliVersionConfig = { latest: { label: "Latest", color: "hsl(var(--chart-1))", }, outdated: { label: "Outdated", color: "hsl(var(--chart-5))", }, } satisfies ChartConfig; const authConfig = { enabled: { label: "Enabled", color: "hsl(var(--chart-1))", }, disabled: { label: "Disabled", color: "hsl(var(--chart-5))", }, } satisfies ChartConfig; const gitConfig = { enabled: { label: "Git Initialized", color: "hsl(var(--chart-1))", }, disabled: { label: "No Git", color: "hsl(var(--chart-5))", }, } satisfies ChartConfig; const installConfig = { enabled: { label: "Auto Install", color: "hsl(var(--chart-1))", }, disabled: { label: "Skip Install", color: "hsl(var(--chart-5))", }, } satisfies ChartConfig; const examplesConfig = { todo: { label: "Todo App", color: "hsl(var(--chart-1))", }, ai: { label: "AI Example", color: "hsl(var(--chart-2))", }, none: { label: "No Examples", color: "hsl(var(--chart-6))", }, } satisfies ChartConfig; const addonsConfig = { pwa: { label: "PWA", color: "hsl(var(--chart-1))", }, biome: { label: "Biome", color: "hsl(var(--chart-2))", }, tauri: { label: "Tauri", color: "hsl(var(--chart-3))", }, husky: { label: "Husky", color: "hsl(var(--chart-4))", }, starlight: { label: "Starlight", color: "hsl(var(--chart-5))", }, turborepo: { label: "Turborepo", color: "hsl(var(--chart-6))", }, none: { label: "No Addons", color: "hsl(var(--chart-7))", }, } satisfies ChartConfig; export default function AnalyticsPage() { const [data, setData] = useState([]); const [lastUpdated, setLastUpdated] = useState(null); const [loadingLastUpdated, setLoadingLastUpdated] = useState(true); const loadCSVData = useCallback(async () => { try { const response = await fetch("/export.csv"); const csvText = await response.text(); Papa.parse(csvText, { header: true, complete: (results) => { try { const parsedData = (results.data as Record[]) .map((row) => { const timestamp = row["*.timestamp"] || new Date().toISOString(); const date = timestamp.includes("T") ? timestamp.split("T")[0] : timestamp.split(" ")[0]; const addons = [ row["*.properties.addons.0"], row["*.properties.addons.1"], row["*.properties.addons.2"], row["*.properties.addons.3"], row["*.properties.addons.4"], row["*.properties.addons.5"], ].filter(Boolean); return { date, cli_version: row["*.properties.cli_version"] || "unknown", node_version: row["*.properties.node_version"] || "unknown", platform: row["*.properties.platform"] || "unknown", backend: row["*.properties.backend"] || "none", database: row["*.properties.database"] || "none", orm: row["*.properties.orm"] || "none", dbSetup: row["*.properties.dbSetup"] || "none", auth: row["*.properties.auth"] === "True" ? "enabled" : "disabled", api: row["*.properties.api"] || "none", packageManager: row["*.properties.packageManager"] || "unknown", frontend0: row["*.properties.frontend.0"] || "", frontend1: row["*.properties.frontend.1"] || "", examples0: row["*.properties.examples.0"] || "", examples1: row["*.properties.examples.1"] || "", addons, git: row["*.properties.git"] === "True" ? "enabled" : "disabled", install: row["*.properties.install"] === "True" ? "enabled" : "disabled", }; }) .filter((item): item is AnalyticsData => Boolean(item.date && item.platform !== "unknown"), ); if (parsedData.length > 0) { setData(parsedData); console.log(`Loaded ${parsedData.length} records from CSV`); } } catch (error: unknown) { console.error("Error parsing CSV:", error); } }, error: (error: unknown) => { console.error("Papa Parse error:", error); }, }); } catch (error: unknown) { console.error("Error loading CSV:", error); } }, []); const fetchLastUpdated = useCallback(async () => { try { const response = await fetch("/export.csv"); const csvText = await response.text(); const lines = csvText.split("\n"); const timestampColumn = lines[0] .split(",") .findIndex((header) => header.includes("timestamp")); if (timestampColumn !== -1) { const timestamps = lines .slice(1) .filter((line) => line.trim()) .map((line) => { const columns = line.split(","); return columns[timestampColumn]?.replace(/"/g, ""); }) .filter(Boolean) .map((timestamp) => new Date(timestamp)) .filter((date) => !Number.isNaN(date.getTime())); if (timestamps.length > 0) { const mostRecentDate = new Date( Math.max(...timestamps.map((d) => d.getTime())), ); setLastUpdated( mostRecentDate.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", timeZone: "UTC", }), ); } else { setLastUpdated("NO_DATA_FOUND"); } } else { setLastUpdated("TIMESTAMP_COLUMN_NOT_FOUND"); } } catch (error) { console.error("Error fetching last updated date:", error); setLastUpdated("ERROR_PARSING_CSV"); } finally { setLoadingLastUpdated(false); } }, []); useEffect(() => { loadCSVData(); fetchLastUpdated(); }, [loadCSVData, fetchLastUpdated]); const getPlatformData = () => { const platformCounts = data.reduce( (acc, item) => { const platform = item.platform; acc[platform] = (acc[platform] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(platformCounts).map(([name, value]) => ({ name, value, })); }; const getPackageManagerData = () => { const packageManagerCounts = data.reduce( (acc, item) => { const pm = item.packageManager; acc[pm] = (acc[pm] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(packageManagerCounts).map(([name, value]) => ({ name, value, })); }; const getBackendData = () => { const backendCounts = data.reduce( (acc, item) => { const backend = item.backend || "none"; acc[backend] = (acc[backend] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(backendCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getDatabaseData = () => { const databaseCounts = data.reduce( (acc, item) => { const database = item.database || "none"; acc[database] = (acc[database] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(databaseCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getORMData = () => { const ormCounts = data.reduce( (acc, item) => { const orm = item.orm || "none"; acc[orm] = (acc[orm] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(ormCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getDBSetupData = () => { const dbSetupCounts = data.reduce( (acc, item) => { const dbSetup = item.dbSetup || "none"; acc[dbSetup] = (acc[dbSetup] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(dbSetupCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getAPIData = () => { const apiCounts = data.reduce( (acc, item) => { const api = item.api || "none"; acc[api] = (acc[api] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(apiCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getFrontendData = () => { const frontendCounts = data.reduce( (acc, item) => { const frontend = item.frontend0 || item.frontend1 || "none"; if (frontend && frontend !== "none") { acc[frontend] = (acc[frontend] || 0) + 1; } return acc; }, {} as Record, ); return Object.entries(frontendCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getTimeSeriesData = () => { if (data.length === 0) return []; const dates = data .map((item) => item.date) .filter(Boolean) .sort(); if (dates.length === 0) return []; const startDate = new Date(dates[0]); const endDate = new Date(dates[dates.length - 1]); const today = new Date(); const actualEndDate = endDate > today ? today : endDate; const daysDiff = Math.ceil( (actualEndDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24), ); const maxDays = 60; let finalStartDate = startDate; if (daysDiff > maxDays) { finalStartDate = new Date( actualEndDate.getTime() - maxDays * 24 * 60 * 60 * 1000, ); } const dateRange = []; const currentDate = new Date(finalStartDate); while (currentDate <= actualEndDate) { dateRange.push(format(currentDate, "yyyy-MM-dd")); currentDate.setDate(currentDate.getDate() + 1); } const dailyCounts = data.reduce( (acc, item) => { acc[item.date] = (acc[item.date] || 0) + 1; return acc; }, {} as Record, ); return dateRange.map((date) => ({ date, displayDate: format(parseISO(date), "MMM dd"), count: dailyCounts[date] || 0, })); }; const getNodeVersionData = () => { const versionCounts = data.reduce( (acc, item) => { const version = item.node_version.replace(/^v/, "").split(".")[0]; acc[version] = (acc[version] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(versionCounts) .map(([version, count]) => ({ version, count, })) .sort((a, b) => b.count - a.count) .slice(0, 5); }; const getCLIVersionData = () => { const versionCounts = data.reduce( (acc, item) => { const version = item.cli_version || "unknown"; acc[version] = (acc[version] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(versionCounts) .map(([version, count]) => ({ version, count, })) .sort((a, b) => b.count - a.count) .slice(0, 8); }; const getAuthData = () => { const authCounts = data.reduce( (acc, item) => { const auth = item.auth || "disabled"; acc[auth] = (acc[auth] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(authCounts).map(([name, value]) => ({ name, value, })); }; const getGitData = () => { const gitCounts = data.reduce( (acc, item) => { const git = item.git || "disabled"; acc[git] = (acc[git] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(gitCounts).map(([name, value]) => ({ name, value, })); }; const getInstallData = () => { const installCounts = data.reduce( (acc, item) => { const install = item.install || "disabled"; acc[install] = (acc[install] || 0) + 1; return acc; }, {} as Record, ); return Object.entries(installCounts).map(([name, value]) => ({ name, value, })); }; const getExamplesData = () => { const exampleCounts = data.reduce( (acc, item) => { const examples = [item.examples0, item.examples1].filter(Boolean); if (examples.length === 0) { acc.none = (acc.none || 0) + 1; } else { for (const example of examples) { acc[example] = (acc[example] || 0) + 1; } } return acc; }, {} as Record, ); return Object.entries(exampleCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const getAddonsData = () => { const addonCounts = data.reduce( (acc, item) => { if (!item.addons || item.addons.length === 0) { acc.none = (acc.none || 0) + 1; } else { for (const addon of item.addons) { if (addon) { acc[addon] = (acc[addon] || 0) + 1; } } } return acc; }, {} as Record, ); return Object.entries(addonCounts) .map(([name, value]) => ({ name, value, })) .sort((a, b) => b.value - a.value); }; const totalProjects = data.length; const getAvgProjectsPerDay = () => { if (data.length === 0) return 0; const dates = data.map((item) => item.date).filter(Boolean); if (dates.length === 0) return 0; const uniqueDates = new Set(dates); const daysCovered = uniqueDates.size; return daysCovered > 0 ? totalProjects / daysCovered : 0; }; const avgProjectsPerDay = getAvgProjectsPerDay(); const authEnabledPercent = totalProjects > 0 ? Math.round( (data.filter((d) => d.auth === "enabled").length / totalProjects) * 100, ) : 0; const frontendData = getFrontendData(); const backendData = getBackendData(); const mostPopularFrontend = frontendData.length > 0 ? frontendData[0].name : "None"; const mostPopularBackend = backendData.length > 0 ? backendData[0].name : "None"; return (
ANALYTICS_DASHBOARD.EXE
[{totalProjects} PROJECTS_ANALYZED]
$ # Analytics from Better-T-Stack CLI usage data
$ # Uses PostHog - no personal info tracked, runs on each project creation
$ # Source:{" "} analytics.ts {" | "} export.csv
$ # Last updated:{" "} {loadingLastUpdated ? "CHECKING..." : lastUpdated || "UNKNOWN"}
discord
DISCORD_NOTIFICATIONS.IRC

Join for LIVE project creation alerts

JOIN
SYSTEM_METRICS.LOG
TOTAL_PROJECTS
{totalProjects.toLocaleString()}

$ ./create-better-t-stack executions

TOP_FRONTEND
{mostPopularFrontend}

$ most_selected_frontend.sh

TOP_BACKEND
{mostPopularBackend}

$ most_selected_backend.sh

TOP_ORM
{getORMData().length > 0 ? getORMData()[0].name : "None"}

$ most_selected_orm.sh

TOP_API
{getAPIData().length > 0 ? getAPIData()[0].name : "None"}

$ most_selected_api.sh

AUTH_ADOPTION
{authEnabledPercent}%

$ auth_enabled_percentage.sh

TOP_PKG_MGR
{getPackageManagerData().length > 0 ? getPackageManagerData()[0].name : "npm"}

$ most_used_package_manager.sh

AVG_DAILY
{avgProjectsPerDay.toFixed(1)}

$ average_projects_per_day.sh

TIMELINE_ANALYSIS.CHARTS
PROJECT_TIMELINE.CHART

# Daily project creation timeline from actual data

} labelFormatter={(value, payload) => { const date = payload?.[0]?.payload?.date; return date ? format(parseISO(date), "MMM dd, yyyy") : value; }} />
PLATFORM_DISTRIBUTION.PIE

# Operating system distribution

} /> `${name} ${(percent * 100).toFixed(0)}%` } outerRadius={80} fill="var(--color-platform)" dataKey="value" > {getPlatformData().map((entry) => ( ))} } />
STACK_CONFIGURATION.DB
[CORE_COMPONENTS]
FRONTEND_FRAMEWORKS.BAR

# Frontend framework and meta-framework usage (Step 1)

} /> {getFrontendData().map((entry) => ( ))}
BACKEND_FRAMEWORKS.BAR

# Backend framework distribution (Step 2)

} /> {getBackendData().map((entry) => ( ))}
DATABASE_DISTRIBUTION.BAR

# Database technology distribution (Step 4)

} /> {getDatabaseData().map((entry) => ( ))}
ORM_DISTRIBUTION.BAR

# ORM/Database layer distribution (Step 5)

} /> {getORMData().map((entry) => ( ))}
DATABASE_HOSTING.BAR

# Database hosting service preferences (Step 6)

} /> {getDBSetupData().map((entry) => ( ))}
API_LAYER.PIE

# API layer technology distribution (Step 7)

} /> {getAPIData().map((entry) => ( ))} } />
AUTH_ADOPTION.PIE

# Authentication implementation rate (Step 8)

} /> `${name} ${(percent * 100).toFixed(0)}%` } > {getAuthData().map((entry) => ( ))} } />
ADDONS_USAGE.BAR

# Additional features and tooling adoption (Step 9)

} /> {getAddonsData().map((entry) => ( ))}
EXAMPLES_USAGE.BAR

# Example applications included in projects (Step 10)

} /> {getExamplesData().map((entry) => ( ))}
DEV_ENVIRONMENT.CONFIG
[TOOLING_PREFERENCES]
GIT_INITIALIZATION.PIE

# Git repository initialization rate (Step 11)

} /> `${name} ${(percent * 100).toFixed(0)}%` } > {getGitData().map((entry) => ( ))} } />
PACKAGE_MANAGER.BAR

# Package manager usage distribution (Step 12)

} /> {getPackageManagerData().map((entry) => ( ))}
INSTALL_PREFERENCE.PIE

# Automatic dependency installation preference (Step 13)

} /> `${name} ${(percent * 100).toFixed(0)}%` } > {getInstallData().map((entry) => ( ))} } />
NODE_VERSIONS.BAR

# Node.js version distribution (major versions)

} />
CLI_VERSIONS.BAR

# CLI version distribution across project creations

} />
); }