diff --git a/src/app/api/sentry-example-api/route.ts b/src/app/(routes)/api/sentry-example-api/route.ts similarity index 100% rename from src/app/api/sentry-example-api/route.ts rename to src/app/(routes)/api/sentry-example-api/route.ts diff --git a/src/app/auth/callback/route.ts b/src/app/(routes)/auth/callback/route.ts similarity index 100% rename from src/app/auth/callback/route.ts rename to src/app/(routes)/auth/callback/route.ts diff --git a/src/app/fetch/route.ts b/src/app/(routes)/fetch/route.ts similarity index 100% rename from src/app/fetch/route.ts rename to src/app/(routes)/fetch/route.ts diff --git a/src/app/public-stats/page.tsx b/src/app/public-stats/page.tsx new file mode 100644 index 00000000..ca736ae7 --- /dev/null +++ b/src/app/public-stats/page.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import PublicStatsDashboard from "@/components/stats-chart/StatsChart"; +const page = () => { + return ( +
+ +
+ ); +}; + +export default page; diff --git a/src/components/stats-chart/StatsChart.tsx b/src/components/stats-chart/StatsChart.tsx new file mode 100644 index 00000000..93b01724 --- /dev/null +++ b/src/components/stats-chart/StatsChart.tsx @@ -0,0 +1,261 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Bar, + BarChart, + Cell, + Pie, + PieChart, + ResponsiveContainer, + XAxis, + YAxis, +} from "recharts"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { Skeleton } from "@/components/ui/skeleton"; + +interface PublicStats { + timestamp: string; + total_jobs: number; + main_chat_jobs: number; + individual_crew_jobs: number; + top_profile_stacks_addresses: string[]; + top_crew_names: string[]; +} + +export default function PublicStatsDashboard() { + const [data, setData] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/public_stats/` + ); + if (!response.ok) throw new Error("Failed to fetch data"); + const result: PublicStats = await response.json(); + setData(result); + } catch (err) { + console.error(err); + } + }; + void fetchData(); + }, []); + + if (!data) { + return ( +
+ +
+ + + +
+
+ + +
+ +
+ ); + } + + const executionData = [ + // RENAMING JOBS TO CHAT + { name: "Main Chat", value: data.main_chat_jobs }, + { name: "Crew Chat", value: data.individual_crew_jobs }, + ]; + + const addressData = data.top_profile_stacks_addresses.map( + (address, index) => ({ + name: `A${index + 1}`, + value: 1, + fullAddress: address, + }) + ); + + const crewData = data.top_crew_names.map((name, index) => ({ + name: name.length > 12 ? name.slice(0, 12) + "..." : name, + value: Math.max(5 - index, 1), + })); + + return ( +
+
+

Public Stats Dashboard

+ + {new Date(data.timestamp).toLocaleString()} + +
+ +
+ + + + Total Crew Executions + + + +
+ {data.total_jobs.toLocaleString()} +
+
+
+ + + + Main Chat Executions + + + +
+ {data.main_chat_jobs.toLocaleString()} +
+
+
+ + + + Individual Crew Jobs + + + +
+ {data.individual_crew_jobs.toLocaleString()} +
+
+
+
+ +
+ + + Execution Distribution + + + + + + + + } /> + + {executionData.map((entry, index) => ( + + ))} + + + + + + + + + + + Top Profile Stack Addresses + + + + + + + name} + labelLine={false} + > + {addressData.map((entry, index) => ( + + ))} + + { + if (payload && payload.length) { + const data = payload[0].payload; + return ( +
+

{data.fullAddress}

+
+ ); + } + return null; + }} + /> +
+
+
+
+
+
+ + + + Top Crews + + + + + + + + } /> + + {crewData.map((entry, index) => ( + + ))} + + + + + + +
+ ); +}