From bcd553cce161d8bd51852f524c116698a0cbaa94 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Tue, 5 Aug 2025 09:40:15 +0530 Subject: [PATCH] refactor(web): organize home page in separate components --- apps/web/scripts/generate-analytics.ts | 32 +- .../(home)/_components/command-section.tsx | 120 ++++++ .../app/(home)/_components/hero-section.tsx | 61 +++ .../app/(home)/_components/stats-section.tsx | 227 ++++++++++ apps/web/src/app/(home)/page.tsx | 392 +----------------- 5 files changed, 433 insertions(+), 399 deletions(-) create mode 100644 apps/web/src/app/(home)/_components/command-section.tsx create mode 100644 apps/web/src/app/(home)/_components/hero-section.tsx create mode 100644 apps/web/src/app/(home)/_components/stats-section.tsx diff --git a/apps/web/scripts/generate-analytics.ts b/apps/web/scripts/generate-analytics.ts index 1cc62e3..1708e63 100644 --- a/apps/web/scripts/generate-analytics.ts +++ b/apps/web/scripts/generate-analytics.ts @@ -1,5 +1,5 @@ import { execSync } from "node:child_process"; -import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import Papa from "papaparse"; @@ -450,27 +450,23 @@ async function generateAnalyticsData() { lastUpdated: lastUpdated, }; - // Write minimal file to public folder - const publicDir = join(process.cwd(), "public"); - if (!existsSync(publicDir)) { - mkdirSync(publicDir, { recursive: true }); - } - const minimalFilePath = join(publicDir, "analytics-minimal.json"); - writeFileSync( - minimalFilePath, - JSON.stringify(minimalAnalyticsData, null, 2), - ); - console.log("📤 Uploading to Cloudflare R2..."); const tempDir = mkdtempSync(join(tmpdir(), "analytics-")); const tempFilePath = join(tempDir, "analytics-data.json"); + const minimalTempFilePath = join(tempDir, "analytics-minimal.json"); writeFileSync(tempFilePath, JSON.stringify(analyticsData, null, 2)); + writeFileSync( + minimalTempFilePath, + JSON.stringify(minimalAnalyticsData, null, 2), + ); const BUCKET_NAME = "bucket"; const key = "analytics-data.json"; + const minimalKey = "analytics-minimal.json"; const cmd = `npx wrangler r2 object put "${BUCKET_NAME}/${key}" --file="${tempFilePath}" --remote`; + const minimalCmd = `npx wrangler r2 object put "${BUCKET_NAME}/${minimalKey}" --file="${minimalTempFilePath}" --remote`; console.log(`Uploading ${tempFilePath} to r2://${BUCKET_NAME}/${key} ...`); try { @@ -480,11 +476,21 @@ async function generateAnalyticsData() { throw err; } + console.log( + `Uploading ${minimalTempFilePath} to r2://${BUCKET_NAME}/${minimalKey} ...`, + ); + try { + execSync(minimalCmd, { stdio: "inherit" }); + } catch (err) { + console.error("Failed to upload minimal analytics data:", err); + throw err; + } + console.log( `✅ Generated optimized analytics data with ${totalRecords} records`, ); console.log( - "📁 Created minimal analytics file: public/analytics-minimal.json", + "📁 Uploaded minimal analytics file to R2: bucket/analytics-minimal.json", ); console.log("📤 Uploaded to R2 bucket: bucket/analytics-data.json"); console.log(`🕒 Last data update: ${lastUpdated}`); diff --git a/apps/web/src/app/(home)/_components/command-section.tsx b/apps/web/src/app/(home)/_components/command-section.tsx new file mode 100644 index 0000000..0934b49 --- /dev/null +++ b/apps/web/src/app/(home)/_components/command-section.tsx @@ -0,0 +1,120 @@ +import { Check, ChevronDown, ChevronRight, Copy, Terminal } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { cn } from "@/lib/utils"; +import PackageIcon from "./icons"; + +export default function CommandSection() { + const [copiedCommand, setCopiedCommand] = useState(null); + const [selectedPM, setSelectedPM] = useState<"npm" | "pnpm" | "bun">("bun"); + + const commands = { + npm: "npx create-better-t-stack@latest", + pnpm: "pnpm create better-t-stack@latest", + bun: "bun create better-t-stack@latest", + }; + + const copyCommand = (command: string, packageManager: string) => { + navigator.clipboard.writeText(command); + setCopiedCommand(packageManager); + setTimeout(() => setCopiedCommand(null), 2000); + }; + + return ( +
+
+
+
+ + CLI_COMMAND +
+ + + + + + {(["bun", "pnpm", "npm"] as const).map((pm) => ( + setSelectedPM(pm)} + className={cn( + "flex items-center gap-2", + selectedPM === pm && "bg-accent text-background", + )} + > + + {pm.toUpperCase()} + {selectedPM === pm && ( + + )} + + ))} + + +
+ +
+
+
+ $ + {commands[selectedPM]} +
+ +
+
+
+ + +
+
+
+ + STACK_BUILDER +
+
+ INTERACTIVE +
+
+ +
+
+
+ + + Interactive configuration wizard + +
+
+ START +
+
+
+
+ +
+ ); +} diff --git a/apps/web/src/app/(home)/_components/hero-section.tsx b/apps/web/src/app/(home)/_components/hero-section.tsx new file mode 100644 index 0000000..bd84350 --- /dev/null +++ b/apps/web/src/app/(home)/_components/hero-section.tsx @@ -0,0 +1,61 @@ +import NpmPackage from "./npm-package"; + +export default function HeroSection() { + return ( + <> +
+
+
+						{`
+██████╗  ██████╗ ██╗     ██╗
+██╔══██╗██╔═══██╗██║     ██║
+██████╔╝██║   ██║██║     ██║
+██╔══██╗██║   ██║██║     ██║
+██║  ██║╚██████╔╝███████╗███████╗
+╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚══════╝`}
+					
+ +
+						{`
+██╗   ██╗ ██████╗ ██╗   ██╗██████╗
+╚██╗ ██╔╝██╔═══██╗██║   ██║██╔══██╗
+ ╚████╔╝ ██║   ██║██║   ██║██████╔╝
+  ╚██╔╝  ██║   ██║██║   ██║██╔══██╗
+   ██║   ╚██████╔╝╚██████╔╝██║  ██║
+   ╚═╝    ╚═════╝  ╚═════╝ ╚═╝  ╚═╝`}
+					
+ +
+						{`
+ ██████╗ ██╗    ██╗███╗   ██╗
+██╔═══██╗██║    ██║████╗  ██║
+██║   ██║██║ █╗ ██║██╔██╗ ██║
+██║   ██║██║███╗██║██║╚██╗██║
+╚██████╔╝╚███╔███╔╝██║ ╚████║
+ ╚═════╝  ╚══╝╚══╝ ╚═╝  ╚═══╝`}
+					
+ +
+						{`
+███████╗████████╗ █████╗  ██████╗██╗  ██╗
+██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
+███████╗   ██║   ███████║██║     █████╔╝
+╚════██║   ██║   ██╔══██║██║     ██╔═██╗
+███████║   ██║   ██║  ██║╚██████╗██║  ██╗
+╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝`}
+					
+
+
+ +
+

+ Modern CLI for scaffolding end-to-end type-safe TypeScript projects +

+

+ Production-ready • Customizable • Best practices included +

+ +
+ + ); +} diff --git a/apps/web/src/app/(home)/_components/stats-section.tsx b/apps/web/src/app/(home)/_components/stats-section.tsx new file mode 100644 index 0000000..809be39 --- /dev/null +++ b/apps/web/src/app/(home)/_components/stats-section.tsx @@ -0,0 +1,227 @@ +import { api } from "@better-t-stack/backend/convex/_generated/api"; +import { useNpmDownloadCounter } from "@erquhart/convex-oss-stats/react"; +import NumberFlow, { continuous } from "@number-flow/react"; +import { useQuery } from "convex/react"; +import { + BarChart3, + Github, + Package, + Star, + Terminal, + TrendingUp, + Users, +} from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; + +export default function StatsSection() { + const [analyticsData, setAnalyticsData] = useState<{ + totalProjects: number; + avgProjectsPerDay: string; + lastUpdated: string | null; + } | null>(null); + + useEffect(() => { + const fetchAnalytics = async () => { + try { + const response = await fetch( + "https://r2.amanv.dev/analytics-minimal.json", + ); + if (response.ok) { + const data = await response.json(); + setAnalyticsData(data); + } + } catch (error) { + console.error("Failed to fetch analytics data:", error); + } + }; + + fetchAnalytics(); + }, []); + + const githubRepo = useQuery(api.stats.getGithubRepo, { + name: "AmanVarshney01/create-better-t-stack", + }); + const npmPackages = useQuery(api.stats.getNpmPackages, { + names: ["create-better-t-stack"], + }); + + const liveNpmDownloadCount = useNpmDownloadCounter(npmPackages); + + return ( +
+ +
+
+ + + CLI_ANALYTICS.JSON + +
+ +
+
+ + + Total Projects + + +
+ +
+ + + Avg/Day + + + {analyticsData?.avgProjectsPerDay || "—"} + +
+ +
+
+ + Last Updated + + + {analyticsData?.lastUpdated || + new Date().toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} + +
+
+
+
+ + + +
+
+ + + GITHUB_REPO.GIT + +
+ +
+
+ + + Stars + + +
+ +
+ + + Contributors + + + {githubRepo?.contributorCount || "—"} + +
+ +
+
+ + Repository + + + AmanVarshney01/create-better-t-stack + +
+
+
+
+ + + +
+
+ + + NPM_PACKAGE.JS + +
+ +
+
+ + + Downloads + + +
+ +
+ + + Avg/Day + + + {npmPackages?.dayOfWeekAverages + ? Math.round( + npmPackages.dayOfWeekAverages.reduce((a, b) => a + b, 0) / + 7, + ) + : "—"} + +
+ +
+
+ Package + + create-better-t-stack + +
+
+
+
+ +
+ ); +} diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx index 7cc3bc9..7557d7c 100644 --- a/apps/web/src/app/(home)/page.tsx +++ b/apps/web/src/app/(home)/page.tsx @@ -1,398 +1,18 @@ "use client"; -import { api } from "@better-t-stack/backend/convex/_generated/api"; -import { useNpmDownloadCounter } from "@erquhart/convex-oss-stats/react"; -import NumberFlow, { continuous } from "@number-flow/react"; -import { useQuery } from "convex/react"; -import { - BarChart3, - Check, - ChevronDown, - ChevronRight, - Copy, - Github, - Package, - Star, - Terminal, - TrendingUp, - Users, -} from "lucide-react"; -import Link from "next/link"; -import { useState } from "react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { cn } from "@/lib/utils"; -import analyticsData from "@/public/analytics-minimal.json"; +import CommandSection from "./_components/command-section"; import Footer from "./_components/footer"; -import PackageIcon from "./_components/icons"; -import NpmPackage from "./_components/npm-package"; +import HeroSection from "./_components/hero-section"; import SponsorsSection from "./_components/sponsors-section"; +import StatsSection from "./_components/stats-section"; import Testimonials from "./_components/testimonials"; export default function HomePage() { - const [copiedCommand, setCopiedCommand] = useState(null); - const [selectedPM, setSelectedPM] = useState<"npm" | "pnpm" | "bun">("bun"); - - const commands = { - npm: "npx create-better-t-stack@latest", - pnpm: "pnpm create better-t-stack@latest", - bun: "bun create better-t-stack@latest", - }; - - const copyCommand = (command: string, packageManager: string) => { - navigator.clipboard.writeText(command); - setCopiedCommand(packageManager); - setTimeout(() => setCopiedCommand(null), 2000); - }; - - const githubRepo = useQuery(api.stats.getGithubRepo, { - name: "AmanVarshney01/create-better-t-stack", - }); - const npmPackages = useQuery(api.stats.getNpmPackages, { - names: ["create-better-t-stack"], - }); - - const liveNpmDownloadCount = useNpmDownloadCounter(npmPackages); - return (
-
-
-
-							{`
-██████╗  ██████╗ ██╗     ██╗
-██╔══██╗██╔═══██╗██║     ██║
-██████╔╝██║   ██║██║     ██║
-██╔══██╗██║   ██║██║     ██║
-██║  ██║╚██████╔╝███████╗███████╗
-╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚══════╝`}
-						
- -
-							{`
-██╗   ██╗ ██████╗ ██╗   ██╗██████╗
-╚██╗ ██╔╝██╔═══██╗██║   ██║██╔══██╗
- ╚████╔╝ ██║   ██║██║   ██║██████╔╝
-  ╚██╔╝  ██║   ██║██║   ██║██╔══██╗
-   ██║   ╚██████╔╝╚██████╔╝██║  ██║
-   ╚═╝    ╚═════╝  ╚═════╝ ╚═╝  ╚═╝`}
-						
- -
-							{`
- ██████╗ ██╗    ██╗███╗   ██╗
-██╔═══██╗██║    ██║████╗  ██║
-██║   ██║██║ █╗ ██║██╔██╗ ██║
-██║   ██║██║███╗██║██║╚██╗██║
-╚██████╔╝╚███╔███╔╝██║ ╚████║
- ╚═════╝  ╚══╝╚══╝ ╚═╝  ╚═══╝`}
-						
- -
-							{`
-███████╗████████╗ █████╗  ██████╗██╗  ██╗
-██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
-███████╗   ██║   ███████║██║     █████╔╝
-╚════██║   ██║   ██╔══██║██║     ██╔═██╗
-███████║   ██║   ██║  ██║╚██████╗██║  ██╗
-╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝`}
-						
-
-
- -
-

- Modern CLI for scaffolding end-to-end type-safe TypeScript projects -

-

- Production-ready • Customizable • Best practices included -

- -
- -
-
-
-
- - CLI_COMMAND -
- - - - - - {(["bun", "pnpm", "npm"] as const).map((pm) => ( - setSelectedPM(pm)} - className={cn( - "flex items-center gap-2", - selectedPM === pm && "bg-accent text-background", - )} - > - - {pm.toUpperCase()} - {selectedPM === pm && ( - - )} - - ))} - - -
- -
-
-
- $ - - {commands[selectedPM]} - -
- -
-
-
- - -
-
-
- - STACK_BUILDER -
-
- INTERACTIVE -
-
- -
-
-
- - - Interactive configuration wizard - -
-
- START -
-
-
-
- -
- -
- -
-
- - - CLI_ANALYTICS.JSON - -
- -
-
- - - Total Projects - - -
- -
- - - Avg/Day - - - {analyticsData.avgProjectsPerDay} - -
- -
-
- - Last Updated - - - {analyticsData.lastUpdated || - new Date().toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - })} - -
-
-
-
- - - -
-
- - - GITHUB_REPO.GIT - -
- -
-
- - - Stars - - {githubRepo?.starCount !== undefined ? ( - - ) : ( -
- )} -
- -
- - - Contributors - - - {githubRepo?.contributorCount || "—"} - -
- -
-
- - Repository - - - AmanVarshney01/create-better-t-stack - -
-
-
-
- - - -
-
- - - NPM_PACKAGE.JS - -
- -
-
- - - Downloads - - {liveNpmDownloadCount?.count !== undefined ? ( - - ) : ( -
- )} -
- -
- - - Avg/Day - - - {npmPackages?.dayOfWeekAverages - ? Math.round( - npmPackages.dayOfWeekAverages.reduce( - (a, b) => a + b, - 0, - ) / 7, - ) - : "—"} - -
- -
-
- - Package - - - create-better-t-stack - -
-
-
-
- -
- + + +