Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@better-t-stack/types": "workspace:*",
"@erquhart/convex-oss-stats": "^0.8.2",
"@number-flow/react": "^0.5.11",
"@observablehq/plot": "^0.6.17",
"@orama/orama": "^3.1.18",
"@shikijs/transformers": "^3.22.0",
"babel-plugin-react-compiler": "^1.0.0",
Expand All @@ -43,7 +44,6 @@
"react-dom": "^19.2.4",
"react-icons": "^5.5.0",
"react-tweet": "^3.3.0",
"recharts": "2.15.4",
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
"remark-mdx": "^3.1.1",
Expand Down
217 changes: 114 additions & 103 deletions apps/web/src/app/(home)/analytics/_components/analytics-header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
import { format } from "date-fns";
import { Terminal } from "lucide-react";
import Link from "next/link";
import { Activity, Radio } from "lucide-react";

import { cn } from "@/lib/utils";

import { formatCompactNumber } from "./analytics-helpers";

const utcDateTimeFormatter = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "UTC",
});

const utcDateFormatter = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
year: "numeric",
timeZone: "UTC",
});

function formatUtcDateTime(value: string) {
return `${utcDateTimeFormatter.format(new Date(value))} UTC`;
}

function formatUtcDate(value: string) {
return utcDateFormatter.format(new Date(value));
}

export function AnalyticsHeader({
lastUpdated,
liveTotal,
trackingDays,
legacy,
connectionStatus,
}: {
lastUpdated: string | null;
liveTotal: number;
trackingDays: number;
legacy: {
total: number;
avgPerDay: number;
Expand All @@ -18,13 +47,11 @@ export function AnalyticsHeader({
};
connectionStatus: "online" | "connecting" | "reconnecting" | "offline";
}) {
const formattedDate = lastUpdated
? format(new Date(lastUpdated), "MMM d, yyyy 'at' HH:mm")
: null;
const legacyDate = format(new Date(legacy.lastUpdatedIso), "MMM d, yyyy 'at' HH:mm");
const formattedDate = lastUpdated ? formatUtcDateTime(lastUpdated) : null;
const legacyDate = formatUtcDate(legacy.lastUpdatedIso);
const statusMeta = {
online: {
label: "online",
label: "streaming",
textClass: "text-primary",
dotClass: "bg-primary",
},
Expand All @@ -46,120 +73,104 @@ export function AnalyticsHeader({
}[connectionStatus];

return (
<div className="mb-4 space-y-4">
<section className="space-y-4">
<div className="flex flex-wrap items-end justify-between gap-4">
<div className="flex flex-col gap-1">
<div className="space-y-2">
<div className="flex items-center gap-2 text-primary">
<Terminal className="h-5 w-5" />
<Activity className="h-5 w-5" />
<h1 className="font-bold font-mono text-xl sm:text-2xl">CLI_ANALYTICS.JSON</h1>
</div>
<p className="text-muted-foreground text-sm">
Real-time usage statistics from create-better-t-stack (powered by Convex)
<p className="max-w-2xl text-muted-foreground text-sm">
Observable Plot-powered usage analytics for Better T Stack, keeping the site’s existing
terminal-style visual language while making the charts much more readable.
</p>
</div>
</div>

<div className="rounded-xl p-4 ring-1 ring-border/40 sm:p-5">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-2">
<span className="text-primary">$</span>
<span className="text-muted-foreground">status:</span>
<span className={cn("inline-flex items-center gap-2", statusMeta.textClass)}>
<span
className={cn(
"h-2 w-2 rounded-full",
statusMeta.dotClass,
connectionStatus !== "online" && "animate-pulse",
)}
/>
{statusMeta.label}
</span>
</div>
{formattedDate && (
<div className="flex items-center gap-2 text-muted-foreground text-xs">
<span>last_event:</span>
<span className="text-foreground">{formattedDate}</span>
</div>
)}
<div className="rounded-xl bg-fd-background/85 p-5 ring-1 ring-border/35">
<div className="flex flex-wrap items-center gap-2">
<span
className={cn(
"inline-flex items-center gap-2 rounded-full px-3 py-1 font-mono text-[10px] uppercase tracking-[0.22em]",
statusMeta.textClass,
"bg-muted/30",
)}
>
<span
className={cn(
"h-2 w-2 rounded-full",
statusMeta.dotClass,
connectionStatus !== "online" && "animate-pulse",
)}
/>
{statusMeta.label}
</span>
<span className="inline-flex items-center gap-2 rounded-full bg-muted/30 px-3 py-1 font-mono text-[10px] uppercase tracking-[0.22em] text-muted-foreground">
<Radio className="h-3 w-3" />
anonymous telemetry
</span>
</div>

<div className="my-3 h-px w-full bg-border/35" />

<div className="space-y-1.5 text-muted-foreground text-xs">
<div className="flex items-start gap-2">
<span className="mt-0.5 shrink-0 text-primary">&gt;</span>
<span>No personal data collected - anonymous usage stats only</span>
</div>
<div className="flex items-start gap-2">
<span className="mt-0.5 shrink-0 text-primary">&gt;</span>
<span>
Client event source:{" "}
<Link
href="https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/analytics.ts"
target="_blank"
rel="noopener noreferrer"
className="text-accent underline underline-offset-2 hover:text-primary"
>
apps/cli/src/utils/analytics.ts
</Link>
</span>
</div>
<div className="flex items-start gap-2">
<span className="mt-0.5 shrink-0 text-primary">&gt;</span>
<span>
Backend aggregation:{" "}
<Link
href="https://github.com/AmanVarshney01/create-better-t-stack/blob/main/packages/backend/convex/analytics.ts"
target="_blank"
rel="noopener noreferrer"
className="text-accent underline underline-offset-2 hover:text-primary"
>
packages/backend/convex/analytics.ts
</Link>
</span>
</div>
<div className="flex items-start gap-2">
<span className="mt-0.5 shrink-0 text-primary">&gt;</span>
<span>
Website analytics dashboard:{" "}
<Link
href="https://umami.amanv.cloud/share/pHvqHleyOl9PBfaK"
target="_blank"
rel="noopener noreferrer"
className="text-accent underline underline-offset-2 hover:text-primary"
>
Umami
</Link>{" "}
- self-hosted on a Hostinger VPS
</span>
<div className="mt-4 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<div className="rounded-lg bg-muted/20 p-4">
<div className="font-mono text-[10px] uppercase tracking-[0.24em] text-muted-foreground">
live projects
</div>
<div className="mt-2 font-semibold text-3xl tracking-tight">
{formatCompactNumber(liveTotal)}
</div>
<p className="mt-2 text-muted-foreground text-xs leading-5">
Convex-tracked creations since the telemetry cutover.
</p>
</div>
</div>

<div className="mt-4 rounded-lg bg-muted/25 p-3 text-xs">
<div className="mb-2 flex items-center gap-2 font-mono font-semibold">
<span className="text-primary">#</span>
<span className="text-foreground">Legacy Data (pre-Convex)</span>
<div className="rounded-lg bg-muted/20 p-4">
<div className="font-mono text-[10px] uppercase tracking-[0.24em] text-muted-foreground">
tracked span
</div>
<div className="mt-2 font-semibold text-3xl tracking-tight">{trackingDays}</div>
<p className="mt-2 text-muted-foreground text-xs leading-5">
Calendar days represented in the live dataset.
</p>
</div>
<div className="grid grid-cols-1 gap-x-8 gap-y-1 sm:grid-cols-2 md:grid-cols-4">
<div>
<span className="text-muted-foreground">Total:</span>{" "}
<span className="text-foreground">{legacy.total.toLocaleString()}</span>

<div className="rounded-lg bg-muted/20 p-4">
<div className="font-mono text-[10px] uppercase tracking-[0.24em] text-muted-foreground">
archive total
</div>
<div>
<span className="text-muted-foreground">Avg/Day:</span>{" "}
<span className="text-foreground">{legacy.avgPerDay.toFixed(1)}</span>
<div className="mt-2 font-semibold text-3xl tracking-tight">
{formatCompactNumber(legacy.total)}
</div>
<div>
<span className="text-muted-foreground">As of:</span>{" "}
<span className="text-foreground">{legacyDate}</span>
<p className="mt-2 text-muted-foreground text-xs leading-5">
Historical pre-Convex dataset captured through {legacyDate}.
</p>
</div>

<div className="rounded-lg bg-muted/20 p-4">
<div className="flex items-center gap-2 font-mono text-[10px] uppercase tracking-[0.24em] text-muted-foreground">
<Activity className="h-3.5 w-3.5 text-primary" />
telemetry status
</div>
<div>
<span className="text-muted-foreground">Source:</span>{" "}
<span className="text-foreground">{legacy.source}</span>

<div className="mt-4 space-y-3 text-sm">
<div className="flex items-center justify-between gap-3">
<span className="text-muted-foreground">Convex stream</span>
<span className={cn("font-medium", statusMeta.textClass)}>{statusMeta.label}</span>
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-muted-foreground">Latest event</span>
<span className="text-right text-foreground/90">
{formattedDate ?? "Waiting for first event"}
</span>
</div>
<div className="flex items-center justify-between gap-3">
<span className="text-muted-foreground">Archive snapshot</span>
<span className="text-right text-foreground/90">{legacyDate}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
Loading
Loading