Skip to content

Commit a6b586a

Browse files
committed
feat: Add GitHub Billing Visualizer with file upload and charting capabilities
- Updated package.json to include recharts for charting. - Modified layout.tsx to reflect the new application title and description. - Redesigned the main page (page.tsx) to include a navigation bar, file upload component, and billing chart visualization. - Implemented BillingChart component for visualizing billing data using recharts. - Created FileUpload component to handle CSV file uploads and data processing. - Added file parsing logic in fileParser.ts to convert CSV data into a structured format. - Defined TypeScript types for billing data and file upload results in billing.ts.
1 parent 50742c4 commit a6b586a

File tree

9 files changed

+843
-101
lines changed

9 files changed

+843
-101
lines changed

package-lock.json

Lines changed: 387 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"next": "15.5.2",
1515
"react": "19.1.0",
1616
"react-dom": "19.1.0",
17+
"recharts": "^3.1.2",
1718
"tailwind-merge": "^3.3.1"
1819
},
1920
"devDependencies": {

src/app/layout.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ const geistMono = Geist_Mono({
1313
});
1414

1515
export const metadata: Metadata = {
16-
title: "Create Next App",
17-
description: "Generated by create next app",
16+
title: "GitHub Billing Visualizer",
17+
description:
18+
"Transform your GitHub billing reports into beautiful, interactive visualizations. Analyze costs, track usage trends, and optimize your GitHub spending with powerful analytics.",
19+
keywords:
20+
"GitHub, billing, cost analysis, usage reports, visualizations, analytics, GitHub Actions, spending optimization",
1821
};
1922

2023
export default function RootLayout({
@@ -23,9 +26,9 @@ export default function RootLayout({
2326
children: React.ReactNode;
2427
}>) {
2528
return (
26-
<html lang="en">
29+
<html lang="en" className="dark">
2730
<body
28-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
31+
className={`${geistSans.variable} ${geistMono.variable} antialiased dark`}
2932
>
3033
{children}
3134
</body>

src/app/page.tsx

Lines changed: 72 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,80 @@
1-
import Image from "next/image";
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { Navigation } from "@/components/ui/Navigation";
5+
import { FileUpload } from "@/components/ui/FileUpload";
6+
import { BillingChart } from "@/components/charts/BillingChart";
7+
import { GitHubBillingReport, BillingData } from "@/types/billing";
8+
9+
const sampleBillingData: BillingData[] = [
10+
{ month: "Jan", actions: 120, packages: 80, storage: 40 },
11+
{ month: "Feb", actions: 150, packages: 90, storage: 45 },
12+
{ month: "Mar", actions: 180, packages: 110, storage: 50 },
13+
{ month: "Apr", actions: 220, packages: 130, storage: 55 },
14+
{ month: "May", actions: 190, packages: 120, storage: 48 },
15+
{ month: "Jun", actions: 250, packages: 140, storage: 60 },
16+
];
217

318
export default function Home() {
19+
const [billingData, setBillingData] =
20+
useState<BillingData[]>(sampleBillingData);
21+
const [hasUploadedData, setHasUploadedData] = useState(false);
22+
23+
const handleDataLoaded = (report: GitHubBillingReport) => {
24+
setBillingData(report.data);
25+
setHasUploadedData(true);
26+
};
27+
428
return (
5-
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
6-
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
7-
<Image
8-
className="dark:invert"
9-
src="/next.svg"
10-
alt="Next.js logo"
11-
width={180}
12-
height={38}
13-
priority
14-
/>
15-
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
16-
<li className="mb-2 tracking-[-.01em]">
17-
Get started by editing{" "}
18-
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
19-
src/app/page.tsx
20-
</code>
21-
.
22-
</li>
23-
<li className="tracking-[-.01em]">
24-
Save and see your changes instantly.
25-
</li>
26-
</ol>
29+
<div className="min-h-screen bg-gray-950 text-white">
30+
<Navigation />
31+
32+
{/* Main Content */}
33+
<section className="relative py-32 px-4 sm:px-6 lg:px-8">
34+
<div className="max-w-4xl mx-auto text-center">
35+
<h1 className="text-5xl md:text-7xl font-bold mb-8 bg-gradient-to-r from-white to-gray-400 bg-clip-text text-transparent">
36+
Understand Your
37+
<br />
38+
<span className="bg-gradient-to-r from-green-400 to-blue-400 bg-clip-text text-transparent">
39+
GitHub Costs
40+
</span>
41+
</h1>
42+
43+
<p className="text-xl text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed">
44+
Upload your GitHub billing report to visualize spending patterns and
45+
optimize costs.
46+
<br />
47+
<span className="text-sm text-gray-500 mt-2 block">
48+
Your data is processed locally and not stored on our servers.
49+
</span>
50+
</p>
51+
52+
{/* File Upload */}
53+
<div className="mb-16">
54+
<FileUpload onDataLoaded={handleDataLoaded} />
55+
</div>
56+
57+
{/* Chart Visualization */}
58+
<div className="max-w-6xl mx-auto">
59+
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-8">
60+
<BillingChart
61+
data={billingData}
62+
title={
63+
hasUploadedData
64+
? "Your GitHub Billing Data"
65+
: "Sample GitHub Billing Visualization"
66+
}
67+
/>
68+
</div>
69+
</div>
70+
</div>
2771

28-
<div className="flex gap-4 items-center flex-col sm:flex-row">
29-
<a
30-
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
31-
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
32-
target="_blank"
33-
rel="noopener noreferrer"
34-
>
35-
<Image
36-
className="dark:invert"
37-
src="/vercel.svg"
38-
alt="Vercel logomark"
39-
width={20}
40-
height={20}
41-
/>
42-
Deploy now
43-
</a>
44-
<a
45-
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
46-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
47-
target="_blank"
48-
rel="noopener noreferrer"
49-
>
50-
Read our docs
51-
</a>
72+
{/* Background decoration */}
73+
<div className="absolute inset-0 -z-10">
74+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-green-500/10 rounded-full blur-3xl"></div>
75+
<div className="absolute top-1/4 right-1/4 w-64 h-64 bg-blue-500/10 rounded-full blur-3xl"></div>
5276
</div>
53-
</main>
54-
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
55-
<a
56-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
57-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
58-
target="_blank"
59-
rel="noopener noreferrer"
60-
>
61-
<Image
62-
aria-hidden
63-
src="/file.svg"
64-
alt="File icon"
65-
width={16}
66-
height={16}
67-
/>
68-
Learn
69-
</a>
70-
<a
71-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
72-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
73-
target="_blank"
74-
rel="noopener noreferrer"
75-
>
76-
<Image
77-
aria-hidden
78-
src="/window.svg"
79-
alt="Window icon"
80-
width={16}
81-
height={16}
82-
/>
83-
Examples
84-
</a>
85-
<a
86-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
87-
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
88-
target="_blank"
89-
rel="noopener noreferrer"
90-
>
91-
<Image
92-
aria-hidden
93-
src="/globe.svg"
94-
alt="Globe icon"
95-
width={16}
96-
height={16}
97-
/>
98-
Go to nextjs.org →
99-
</a>
100-
</footer>
77+
</section>
10178
</div>
10279
);
10380
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"use client";
2+
3+
import {
4+
AreaChart,
5+
Area,
6+
XAxis,
7+
YAxis,
8+
CartesianGrid,
9+
Tooltip,
10+
Legend,
11+
ResponsiveContainer,
12+
} from "recharts";
13+
import { BillingData } from "@/types/billing";
14+
15+
interface BillingChartProps {
16+
data: BillingData[];
17+
title?: string;
18+
}
19+
20+
export function BillingChart({
21+
data,
22+
title = "GitHub Billing Overview",
23+
}: BillingChartProps) {
24+
return (
25+
<div className="w-full">
26+
{title && (
27+
<h3 className="text-xl font-semibold mb-6 text-white text-center">
28+
{title}
29+
</h3>
30+
)}
31+
<ResponsiveContainer width="100%" height={400}>
32+
<AreaChart data={data}>
33+
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
34+
<XAxis dataKey="month" stroke="#9CA3AF" tick={{ fill: "#9CA3AF" }} />
35+
<YAxis stroke="#9CA3AF" tick={{ fill: "#9CA3AF" }} />
36+
<Tooltip
37+
contentStyle={{
38+
backgroundColor: "#1F2937",
39+
border: "1px solid #374151",
40+
borderRadius: "8px",
41+
color: "#F9FAFB",
42+
}}
43+
formatter={(value: number) => [`$${value.toFixed(2)}`, ""]}
44+
/>
45+
<Legend />
46+
<Area
47+
type="monotone"
48+
dataKey="actions"
49+
stackId="1"
50+
stroke="#10B981"
51+
fill="#10B981"
52+
fillOpacity={0.6}
53+
name="GitHub Actions ($)"
54+
/>
55+
<Area
56+
type="monotone"
57+
dataKey="packages"
58+
stackId="1"
59+
stroke="#3B82F6"
60+
fill="#3B82F6"
61+
fillOpacity={0.6}
62+
name="Packages ($)"
63+
/>
64+
<Area
65+
type="monotone"
66+
dataKey="storage"
67+
stackId="1"
68+
stroke="#8B5CF6"
69+
fill="#8B5CF6"
70+
fillOpacity={0.6}
71+
name="Storage ($)"
72+
/>
73+
</AreaChart>
74+
</ResponsiveContainer>
75+
</div>
76+
);
77+
}

0 commit comments

Comments
 (0)