├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── README.md
├── app
├── Container.tsx
├── Error.tsx
├── favicon.ico
├── globals.css
├── home
│ ├── Builder.tsx
│ ├── Card.tsx
│ ├── Config.tsx
│ ├── Header.tsx
│ ├── Hero.tsx
│ └── Themes.tsx
├── layout.tsx
├── page.tsx
├── recent
│ ├── Recents.tsx
│ └── route.tsx
├── repo
│ ├── Repo.tsx
│ └── route.tsx
├── top
│ ├── Bar.tsx
│ ├── Compact.tsx
│ ├── Normal.tsx
│ └── route.tsx
├── user
│ ├── User.tsx
│ └── route.tsx
└── wakatime
│ ├── Bar.tsx
│ ├── Compact.tsx
│ ├── Normal.tsx
│ └── route.tsx
├── assets
├── Urbanist-Bold.ttf
├── Urbanist-SemiBold.ttf
└── gitmystat.png
├── bun.lockb
├── example.env
├── helpers
├── animate.ts
├── calculateLanguage.ts
├── calculateRank.ts
├── emoji.ts
├── formatNum.ts
├── generateSvg.ts
├── getColor.ts
├── getData.ts
└── send.ts
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── providers
└── DeviceProvider.tsx
├── public
├── banner.png
├── fine.jpg
└── gitmystat.png
├── tailwind.config.ts
├── themes
└── index.ts
├── tsconfig.json
├── types
├── AnimateOptions.ts
├── Error.ts
├── Languages.ts
├── Recents.ts
├── Repo.ts
├── Theme.ts
├── UserStats.ts
└── Waka.ts
└── utils
├── colors.ts
├── languages.ts
├── repo.ts
├── repositories.ts
├── users.ts
└── wakatime.ts
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": false,
7 | "quoteProps": "as-needed",
8 | "jsxSingleQuote": false,
9 | "trailingComma": "es5",
10 | "bracketSpacing": true,
11 | "jsxBracketSameLine": false,
12 | "arrowParens": "always",
13 | "rangeStart": 0,
14 | "requirePragma": false,
15 | "insertPragma": false,
16 | "proseWrap": "preserve",
17 | "htmlWhitespaceSensitivity": "css",
18 | "vueIndentScriptAndStyle": false,
19 | "endOfLine": "auto",
20 | "embeddedLanguageFormatting": "auto",
21 | "plugins": ["prettier-plugin-tailwindcss"]
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # gitMyStat!
4 |
5 | ~~Get~~ Git my stat! 🎨 Turn your GitHub activity into sleek stats and cool visuals
6 |
7 | > Inspired by a great project [github-readme-stats](https://github.com/anuraghazra/github-readme-stats/)
8 |
9 | > [!TIP]
10 | > Lazy to read alot just for readme? Me too!
11 | > Thats why I made a [Builder](https://gitmystat.vercel.app#builder) which generates the cards for your parameters. Give it a try!
12 |
13 | ## Table of Contents
14 |
15 | - [Parameters](#parameters)
16 |
17 | - [Cards](https://gitmystat.vercel.app#config)
18 | - [User](#user)
19 | - [Recent repos](#recent-repo)
20 | - [Repo card](#repo-card)
21 | - [Top languages](#top-languages)
22 | - [Wakatime](#wakatime)
23 |
24 | - [Themes](https://gitmystat.vercel.app#themes)
25 |
26 | ---
27 |
28 | ## Parameters
29 |
30 | All cards are built equally. Which means these apply to every card possible
31 |
32 | | Parameters | Description | Default |
33 | | ---------- | --------------------------------- | ------- |
34 | | `username` | The username to get the data from | |
35 | | `theme` | The theme to apply on the cards | dark |
36 |
37 | ### Customizations
38 |
39 | > [!IMPORTANT]
40 | > As url encoding messes up the # tag character, we wanted you to provide a hexadecimal code instead.
41 | >
42 | > ### `What's that?!`
43 | >
44 | > Nothing actually. instead of `#` we use `0x`.
45 | >
46 | > For example
47 | >
48 | > `#000000` is the same as `0x000000` > `#075fff` is the same as `0x075fff`
49 |
50 | | Parameters | Description |
51 | | ------------ | -------------------------------------------------- |
52 | | `color` | The hexadecimal color of the text color |
53 | | `accent` | The hexadecimal color of the accent color |
54 | | `background` | The hexadecimal color of the background |
55 | | `border` | The hexadecimal color of the border |
56 | | `tip` | The hexadecimal color of the tertiary (tips) color |
57 | | `radius` | The radius of the border |
58 | | `padding` | The padding of the border |
59 |
60 | - Totally decked out example
61 |
62 | ```
63 | 
64 | ```
65 |
66 | 
67 |
68 | > [!TIP]
69 | >
70 | > Adding `theme={theme}` in the url would change the theme of any card
71 | >
72 | > For example
73 | >
74 | > - `https://gitmystat.vercel.app/recent?username=rahuletto&theme=gold`
75 | >
76 | > 
77 |
78 | ---
79 |
80 | ## Cards
81 | - [User](https://gitmystat.vercel.app#config)
82 | - [Recent repos](https://gitmystat.vercel.app#config)
83 | - [Repo card](https://gitmystat.vercel.app#config)
84 | - [Top languages](https://gitmystat.vercel.app#config)
85 | - [Wakatime](https://gitmystat.vercel.app#config)
86 |
87 | > [!WARNING]
88 | > For wakatime, We are using WakaTime api, which only displays for profiles that are public. Make sure the `Display code time publicly` and `Display languages, editors, os, categories publicly` are enabled.
89 |
90 | ---
91 |
92 | ## Themes
93 | Visit the [themes page](https://gitmystat.vercel.app#themes)!
94 |
95 | > #### You like a theme, but you just don't like that particular color?
96 | >
97 | > It's alright! the parameters can override particular colors of the theme while keeping the other colors the same from the theme.
98 |
99 | ### Extend it.
100 |
101 | These themes are not hard coded. You can add your own!
102 | You have a great color palette? **PLEASE** do a pull request on our repo and make it a reality.
103 |
104 | > [!NOTE]
105 | > If you are making a theme, please make a light mode version of it too. This is not mandatory to do, but it's a good thing to have more flavors.
106 |
107 | #### `How?`
108 |
109 | - Go to `/themes/index.ts`
110 |
111 | ```ts
112 | themename: {
113 | color: '#hex',
114 | accent: '#hex',
115 | background: '#hex',
116 | border: '#hex',
117 | tip: '#hex',
118 | radius: 24,
119 | padding: 24,
120 | },
121 | ```
122 |
123 | - Change the `themename` to the name you prefer
124 | - Change the color hex codes of the things, localhost would be great to test it
125 | - Push it, Do a pull request.
126 |
127 | It's that simple.
128 |
--------------------------------------------------------------------------------
/app/Container.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 | import { ReactNode } from "react";
3 |
4 | export default function Container({
5 | children,
6 | theme,
7 | }: {
8 | children: ReactNode;
9 | theme: ThemeData;
10 | }) {
11 | return (
12 |
19 | {children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/app/Error.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 |
3 | export default function Error(theme: ThemeData, error: { message: string, code: string }) {
4 | return (
5 |
12 |
13 |
17 | Error
18 |
19 |
20 | {error.message || "There is no error? Wait what"}
21 |
22 |
23 |
{error.code || "Uhm?"}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background: #fefefe;
7 | --color: #000000;
8 | --accent: #343941;
9 | --border: #C2C9D1;
10 | --tip:#D0D7DE;
11 | }
12 |
13 | @media (prefers-color-scheme: dark) {
14 | :root {
15 | --background: #0D1116;
16 | --color: #E6EDF3;
17 | --accent: #8D96A0;
18 | --border: #30363D;
19 | --tip: var(--border);
20 | }
21 | }
22 |
23 | body, html {
24 | background: radial-gradient(circle, var(--border), 0%, var(--background) 100%);
25 | color: var(--color);
26 | scroll-behavior: smooth;
27 | }
28 |
29 | * {
30 | transition: all 0.1s ease-out;
31 | -webkit-transition: all 0.1s ease-out;
32 | -moz-transition: all 0.1s ease-out;
33 | -ms-transition: all 0.1s ease-out;
34 | -o-transition: all 0.1s ease-out;
35 | }
36 |
37 | #header {
38 | -webkit-backdrop-filter: blur(10px) !important;
39 | backdrop-filter: blur(10px);
40 | }
41 |
42 | @layer utilities {
43 | .text-balance {
44 | text-wrap: balance;
45 | }
46 | }
47 |
48 | @keyframes fadeIn {
49 | 0% {
50 | opacity: 0;
51 | }
52 | 100% {
53 | opacity: 1;
54 | }
55 | }
--------------------------------------------------------------------------------
/app/home/Builder.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import { Themes } from "@/themes";
3 | import { ThemeType } from "@/types/Theme";
4 | import { useEffect, useRef, useState } from "react";
5 | import { HexColorInput, HexAlphaColorPicker } from "react-colorful";
6 |
7 | export default function Builder() {
8 | const userInput = useRef(null);
9 | const [url, setUrl] = useState("");
10 | const [theme, setTheme] = useState("");
11 | const [layout, setLayout] = useState("default");
12 | const [username, setUsername] = useState("");
13 | const [repo, setRepo] = useState("");
14 | const [card, setCard] = useState("");
15 | const [themeColors, setThemeColors] = useState(Themes["dark"]);
16 | const [error, setError] = useState(0);
17 |
18 | const handleColorChange = (key: keyof typeof themeColors, value: string) => {
19 | setThemeColors((prevThemeColors) => ({
20 | ...prevThemeColors,
21 | [key]: value,
22 | }));
23 | };
24 |
25 | const renderColorPickers = () => {
26 | if (themeColors)
27 | return Object.keys(themeColors).map((key) =>
28 | key == "radius" || key == "padding" ? (
29 |
33 | {key}
34 |
38 | handleColorChange(
39 | key as keyof typeof themeColors,
40 | e.target.value
41 | )
42 | }
43 | className="max-w-[120px] appearance-none rounded-xl border-paper-border px-4 py-2 text-lg text-paper-color dark:bg-moonlight-background dark:text-moonlight-accent"
44 | />
45 |
46 | ) : (
47 |
51 |
{key}
52 |
53 | {
56 | handleColorChange(key as keyof typeof themeColors, e);
57 | }}
58 | />
59 | {
63 | handleColorChange(key as keyof typeof themeColors, e);
64 | }}
65 | />
66 |
67 |
68 | )
69 | );
70 | };
71 |
72 | useEffect(() => {
73 | if (theme) {
74 | setThemeColors(Themes[theme]);
75 | }
76 | }, [theme]);
77 |
78 | useEffect(() => {
79 | setUrl("");
80 | }, [card]);
81 |
82 | function generate() {
83 | setError(0);
84 | if (username) {
85 | setUrl(
86 | generateURL({
87 | card,
88 | theme,
89 | username,
90 | repo,
91 | layout: card == "top" || card == "wakatime" ? layout : "",
92 | custom: themeColors as any,
93 | })
94 | );
95 | } else {
96 | userInput.current?.focus();
97 | setError(1);
98 | }
99 | }
100 |
101 | const btnStyle =
102 | "active:scale-90 hover:scale-105 rounded-full lg:py-4 lg:px-12 py-2 px-2 lg:text-xl text-lg lg:font-semibold font-medium hover:dark:bg-moonlight-background hover:dark:text-moonlight-color hover:bg-paper-accent hover:text-paper-color";
103 | return (
104 |
109 |
110 |
Builder
111 |
Build your card without messing with the url
112 |
113 |
114 |
115 | setCard("user")}
118 | >
119 | User
120 |
121 | setCard("recent")}
124 | >
125 | Recent
126 |
127 | setCard("repo")}
130 | >
131 | Repo
132 |
133 | setCard("top")}
136 | >
137 | Top Languages
138 |
139 | setCard("wakatime")}
142 | >
143 | Wakatime
144 |
145 |
146 |
147 | {card ? (
148 |
151 |
152 |
153 | {card.charAt(0).toUpperCase() + card.slice(1)}
154 |
155 |
156 | Parameters
157 |
158 |
159 | theme
160 | setTheme(e.target.value)}
164 | className="appearance-none rounded-xl border-paper-border px-4 py-2 text-lg text-paper-color dark:bg-moonlight-background dark:text-moonlight-accent"
165 | >
166 |
167 | Select Theme
168 |
169 | {Object.keys(Themes).map((key) => (
170 |
171 | {key}
172 |
173 | ))}
174 |
175 |
176 |
177 | username
178 | setUsername(e.target.value)}
183 | className={`${error == 1 ? "border-4 border-red-400" : ""} appearance-none rounded-xl border-paper-border px-4 py-2 text-lg text-paper-color dark:bg-moonlight-background dark:text-moonlight-accent`}
184 | />
185 |
186 | {card == "top" ||
187 | (card == "wakatime" && (
188 |
189 | layout
190 | setLayout(e.target.value)}
194 | className="appearance-none rounded-xl border-paper-border px-4 py-2 text-lg text-paper-color dark:bg-moonlight-background dark:text-moonlight-accent"
195 | >
196 |
197 | Select Layout
198 |
199 | bar
200 | compact
201 |
202 | default
203 |
204 |
205 |
206 | ))}
207 |
208 | {card === "repo" && (
209 |
210 | repo
211 | setRepo(e.target.value)}
215 | className="appearance-none rounded-xl border-paper-border px-4 py-2 text-lg text-paper-color dark:bg-moonlight-background dark:text-moonlight-accent"
216 | />
217 |
218 | )}
219 |
220 |
221 |
222 |
223 | Customization
224 |
225 | {renderColorPickers()}
226 |
227 |
228 |
229 |
generate()}
232 | >
233 | Generate
234 |
235 |
236 |
237 | {url && (
238 |
239 |
240 |
241 | https://gitmystat.vercel.app{url}
242 |
243 |
244 | )}
245 |
246 | ) : (
247 |
248 | Select a type
249 |
250 | )}
251 |
252 | );
253 | }
254 |
255 | function generateURL({
256 | card,
257 | theme,
258 | username,
259 | repo,
260 | layout,
261 | custom,
262 | }: {
263 | card: string;
264 | theme: string;
265 | username: string;
266 | repo?: string;
267 | layout?: string;
268 | custom: ThemeType;
269 | }) {
270 | const params = new URLSearchParams();
271 |
272 | params.append("theme", theme);
273 | params.append("username", username);
274 |
275 | if (card === "repo" && repo) {
276 | params.append("repo", repo);
277 | }
278 |
279 | if (layout) {
280 | params.append("layout", layout);
281 | }
282 |
283 | const defaultTheme = Themes[theme || "dark"];
284 |
285 | Object.keys(defaultTheme).forEach((key) => {
286 | if (custom[key] !== (defaultTheme as any)[key]) {
287 | params.append(key, (custom as any)[key]);
288 | }
289 | });
290 |
291 | return `/${card}?${params.toString().replaceAll("%23", "0x").replaceAll("#", "0x")}`;
292 | }
293 |
--------------------------------------------------------------------------------
/app/home/Card.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Themes } from "@/themes";
3 |
4 | export default function Card({
5 | theme = "dark",
6 | url = "/repo?username=rahuletto&repo=gitMyStat",
7 | text,
8 | width,
9 | }: {
10 | theme?: string;
11 | url?: string;
12 | text?: string;
13 | width?: number;
14 | }) {
15 | return (
16 |
17 |
18 |
22 |
30 |
31 |
39 | {text
40 | ? text.split(new RegExp(`(rahuletto|gitmystat)`, "gi")).map((part, index) =>
41 | ['rahuletto', 'gitmystat'].includes(part.toLowerCase()) ? (
42 |
43 | {part}
44 |
45 | ) : (
46 | part
47 | )
48 | )
49 | : theme}
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/app/home/Config.tsx:
--------------------------------------------------------------------------------
1 | import { useDevice } from "@/providers/DeviceProvider";
2 | import dynamic from "next/dynamic";
3 |
4 | const Card = dynamic(() => import("./Card"), {
5 | ssr: false,
6 | });
7 |
8 | export default function Config() {
9 | const device = useDevice()
10 | return (
11 | <>
12 |
17 | Config
18 |
19 |
20 | Base URL:{" "}
21 |
22 | https://gitmystat.vercel.app/
23 |
24 |
25 |
26 |
27 |
Cards
28 |
29 | These are the list of card component to display your GitHub profile,
30 | repository, and more. The card component is a dynamic component that
31 | can be configured.
32 |
33 |
34 |
35 |
36 |
User
37 |
42 |
47 |
48 |
49 |
50 |
51 |
Recent
52 |
57 |
62 |
63 |
64 |
65 |
66 |
Repo
67 |
72 |
77 |
78 |
79 |
80 |
81 |
Top Languages
82 |
89 |
94 |
99 |
104 |
105 |
106 |
107 |
108 |
Wakatime
109 |
116 |
121 |
126 |
131 |
132 |
133 |
134 | >
135 | );
136 | }
137 |
--------------------------------------------------------------------------------
/app/home/Header.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | export default function Header() {
4 | return (
5 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/app/home/Hero.tsx:
--------------------------------------------------------------------------------
1 |
2 | import Image from 'next/image'
3 | import Link from 'next/link'
4 |
5 | export default function Hero() {
6 | return (
7 | <>
8 |
13 |
14 | gitMyStat!
15 |
16 | Turn your GitHub activity into sleek stats and cool visuals
17 |
18 |
19 |
24 |
25 |
26 |
27 | Turn your stats into eyecandy
28 |
29 |
30 | gitMyStat! is a helpful project that generates an image svg with
31 | your GitHub stats and activity in a sleek and modern way.
32 |
33 |
34 |
35 |
39 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Modernized
53 |
54 |
55 | My mission is to modernize the README visuals of github with
56 | stunning statistics of other{"'"}s work and progress.
57 |
58 |
59 |
60 |
64 |
70 |
71 |
72 |
73 |
77 |
78 |
79 |
80 | Awesome flavors
81 |
82 |
83 | Give a little touch of your personal taste to the stats with the
84 | themes available. Heavily inspired from{" "}
85 |
89 | MonkeyType
90 | {" "}
91 | themes. But it doesn{"'"}t stop there, you can customize
92 | everything for your liking.
93 |
94 |
95 |
96 |
100 |
107 |
108 |
109 |
110 |
111 |
115 |
116 |
117 |
118 | Soothing Animations
119 |
120 |
121 | What{"'"}s better than a static sticker? An animated one! With
122 | lazy-loading, you can acheive an animated sticker with ease.
123 |
124 |
125 |
126 |
130 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | Well optimized
144 |
145 |
146 | Using edge networks, the stats are cached for a longer period of
147 | 1 hour, with benefits of lower latency and improved performance.
148 |
149 |
150 |
151 |
155 |
162 |
163 |
164 |
165 |
169 |
170 |
171 |
172 | AAAAAAAAA *crash*
173 |
174 |
175 | Imagine, what if these stats are crutial for your README? I got
176 | you covered with amazing error handling, which tells you what
177 | {"'"}s wrong instead of{" "}
178 |
179 | silence
180 |
181 | .
182 |
183 |
184 |
185 |
189 |
196 |
197 |
198 |
199 |
200 |
201 |
205 |
206 |
207 |
208 | Open Source
209 |
210 |
211 | gitMyStat! is an open-source project that is free to use and
212 | contribute to. Built with Next.js and TailwindCSS, with safety
213 | of Typescript.
214 |
215 |
216 |
217 |
221 |
228 |
229 |
230 |
231 |
232 | >
233 | )
234 | }
235 |
--------------------------------------------------------------------------------
/app/home/Themes.tsx:
--------------------------------------------------------------------------------
1 | import { useDevice } from '@/providers/DeviceProvider';
2 | import { Themes } from '@/themes';
3 | import dynamic from 'next/dynamic';
4 |
5 | const Card = dynamic(() => import('./Card'), {
6 | ssr: false,
7 | });
8 |
9 |
10 | export default function Theme() {
11 | const device = useDevice();
12 | return (
13 |
18 | Themes
19 |
26 | {Object.keys(Themes).map((theme, i) => {
27 | return (
28 |
29 | );
30 | })}
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata, Viewport } from "next";
2 | import { Urbanist } from "next/font/google";
3 | import "./globals.css";
4 | import { DeviceProvider } from "@/providers/DeviceProvider";
5 |
6 | const urbanist = Urbanist({
7 | subsets: ["latin"],
8 | weight: ["500", "700"],
9 | display: "swap",
10 | variable: "--urbanist",
11 | });
12 |
13 | export const viewport: Viewport = {
14 | themeColor: "#0D1116",
15 | colorScheme: "dark",
16 | };
17 |
18 | export const metadata: Metadata = {
19 | title: "gitMyStat!",
20 | description: "Turn your GitHub activity into sleek stats and cool visuals",
21 | icons: {
22 | icon: "/favicon.ico",
23 | },
24 | category: "tool",
25 | openGraph: {
26 | siteName: "gitMyStat!",
27 | type: "website",
28 | locale: "en_US",
29 | images: [
30 | {
31 | url: "/banner.png",
32 | alt: "gitMyStat!",
33 | },
34 | ],
35 | },
36 | twitter: {
37 | card: "summary_large_image",
38 | images: [
39 | {
40 | url: "/banner.png",
41 | alt: "gitMyStat!",
42 | },
43 | ],
44 | },
45 | };
46 |
47 | export default function RootLayout({
48 | children,
49 | }: Readonly<{
50 | children: React.ReactNode;
51 | }>) {
52 | return (
53 |
54 |
55 | {children}
56 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | "use client";
3 |
4 | import Hero from "./home/Hero";
5 | import Header from "./home/Header";
6 | import dynamic from "next/dynamic";
7 | import Builder from "./home/Builder";
8 |
9 | const Config = dynamic(() => import("./home/Config"), {
10 | ssr: false,
11 | });
12 | const Theme = dynamic(() => import("./home/Themes"), {
13 | ssr: false,
14 | });
15 |
16 | export default function Home() {
17 | return (
18 | <>
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | >
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/app/recent/Recents.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 | import { RecentData } from "@/types/Recents";
3 | import { GoRepo } from "react-icons/go";
4 |
5 | export default function Recents(data: RecentData, theme: ThemeData) {
6 | return (
7 |
14 |
15 | @{theme.user} has recently worked on
16 |
17 |
18 |
19 |
{data.name}
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/app/recent/route.tsx:
--------------------------------------------------------------------------------
1 | import RepoList from "@/utils/repositories";
2 | import Recents from "./Recents";
3 | import generateSvg from "@/helpers/generateSvg";
4 | import Send from "@/helpers/send";
5 | import { getData } from "@/helpers/getData";
6 | import { ThemeData } from "@/types/Theme";
7 | import Error from "../Error";
8 |
9 | // /recent?username=rahuletto
10 | export async function GET(request: Request) {
11 | const { searchParams } = new URL(request.url);
12 | const { user, color, accent, background, border, radius, padding } =
13 | getData(searchParams);
14 |
15 | const theme: ThemeData = {
16 | user: user ?? "rahuletto",
17 | color: color ?? "#E6EDF3",
18 | accent: accent ?? "#8D96A0",
19 | background: background ?? "#0D1116",
20 | border: border ?? "#30363D",
21 | radius: radius ?? 24,
22 | padding: padding ?? 24,
23 | };
24 | try {
25 | const rawdata = await RepoList(user || "rahuletto");
26 |
27 | if(!rawdata.data.user?.repositories) {
28 | const image = await generateSvg(
29 | Error(theme, {
30 | message: rawdata.errors
31 | ? rawdata.errors[0]?.message
32 | : `There are user with username "${user}"`,
33 | code: rawdata.errors ? rawdata.errors[0]?.type : "NO_USER",
34 | }),
35 | {
36 | width: 500,
37 | height: 170,
38 | }
39 | );
40 |
41 | return Send(image, {error: true});
42 | }
43 | if (
44 | rawdata.data.user.repositories.edges.length == 0 ||
45 | (rawdata.errors && rawdata.errors[0])
46 | ) {
47 | const image = await generateSvg(
48 | Error(theme, {
49 | message: rawdata.errors
50 | ? rawdata.errors[0]?.message
51 | : `There are no repositories for "${user}"`,
52 | code: rawdata.errors ? rawdata.errors[0]?.type : "NO_REPO",
53 | }),
54 | {
55 | width: 500,
56 | height: 170,
57 | }
58 | );
59 |
60 | return Send(image, {error: true});
61 | }
62 |
63 | const recent = {
64 | name: rawdata.data.user.repositories.edges[0].node.name as string,
65 | url: rawdata.data.user.repositories.edges[0].node.url as string,
66 | };
67 |
68 | const image = await generateSvg(Recents(recent, theme), {
69 | width: 410,
70 | height: 110,
71 | });
72 |
73 | return Send(image);
74 | } catch (err: any) {
75 | console.warn(err);
76 | const image = await generateSvg(
77 | Error(theme, {
78 | message: (err as Error).message,
79 | code: (err as Error).name,
80 | }),
81 | {
82 | width: 500,
83 | height: 170,
84 | }
85 | );
86 |
87 | return Send(image, {error: true});
88 | }
89 | }
90 |
91 | export const runtime = "edge";
92 |
--------------------------------------------------------------------------------
/app/repo/Repo.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 | import { Repo } from "@/types/Repo";
3 | import { GoRepo, GoRepoForked, GoStar } from "react-icons/go";
4 | import Container from "../Container";
5 |
6 | export default function RepoComp(data: Repo, theme: ThemeData) {
7 | const sliced = data.description.slice(0, 110);
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | {data.name}
15 |
16 |
17 |
21 | {sliced.length === data.description.length
22 | ? data.description
23 | : sliced + "..."}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 | {data.primaryLanguage.name}
35 |
36 |
37 |
38 |
39 |
{data.stargazerCount}
40 |
41 |
42 |
43 |
44 |
{data.forkCount}
45 |
46 |
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/app/repo/route.tsx:
--------------------------------------------------------------------------------
1 | import generateSvg from "@/helpers/generateSvg";
2 | import Send from "@/helpers/send";
3 | import { getData } from "@/helpers/getData";
4 | import RepoComp from "./Repo";
5 | import { ThemeData } from "@/types/Theme";
6 | import Repository from "@/utils/repo";
7 | import { Repo } from "@/types/Repo";
8 | import Error from "../Error";
9 |
10 | // /repo?user=rahuletto&repo=AcademiaPro
11 | export async function GET(request: Request) {
12 | const { searchParams } = new URL(request.url);
13 |
14 | const { user, color, accent, background, border, radius, padding, tip } =
15 | getData(searchParams);
16 |
17 | const theme: ThemeData = {
18 | user: user ?? "rahuletto",
19 | color: color ?? "#E6EDF3",
20 | accent: accent ?? "#8D96A0",
21 | background: background ?? "#0D1116",
22 | border: border ?? "#30363D",
23 | radius: radius ?? 24,
24 | padding: padding ?? 24,
25 | tip: tip ?? "#F6C655",
26 | };
27 |
28 | let repo = searchParams.get("repo");
29 | if (!repo) {
30 | const image = await generateSvg(
31 | Error(theme, {
32 | message: "You didn't provide a repository name",
33 | code: "MISSING_FIELD",
34 | }),
35 | {
36 | width: 500,
37 | height: 170,
38 | }
39 | );
40 |
41 | return Send(image, {error: true});
42 | }
43 |
44 | try {
45 | const rawdata = await Repository(
46 | user || "rahuletto",
47 | repo || "AcademiaPro"
48 | );
49 |
50 | if (rawdata.errors && rawdata.errors[0]) {
51 | const image = await generateSvg(
52 | Error(theme, {
53 | message: rawdata.errors[0].message,
54 | code: rawdata.errors[0].type,
55 | }),
56 | {
57 | width: 500,
58 | height: 170,
59 | }
60 | );
61 |
62 | return Send(image, {error: true});
63 | }
64 |
65 | const data: Repo = {
66 | name: rawdata.data.repositoryOwner.repository.name,
67 | description: rawdata.data.repositoryOwner.repository.description,
68 | primaryLanguage: rawdata.data.repositoryOwner.repository.primaryLanguage,
69 | stargazerCount: rawdata.data.repositoryOwner.repository.stargazerCount,
70 | forkCount: rawdata.data.repositoryOwner.repository.forkCount,
71 | };
72 |
73 | const image = await generateSvg(RepoComp(data, theme), {
74 | width: 500,
75 | height: 170,
76 | });
77 |
78 | return Send(image);
79 | } catch (err: any) {
80 | console.warn(err);
81 |
82 | const image = await generateSvg(
83 | Error(theme, {
84 | message: (err as Error).message,
85 | code: (err as Error).name,
86 | }),
87 | {
88 | width: 500,
89 | height: 170,
90 | }
91 | );
92 |
93 | return Send(image, {error: true});
94 | }
95 | }
96 |
97 | export const runtime = "edge";
98 |
--------------------------------------------------------------------------------
/app/top/Bar.tsx:
--------------------------------------------------------------------------------
1 | import { LanguageStat } from "@/types/Languages";
2 | import { ThemeData } from "@/types/Theme";
3 | import Container from "../Container";
4 |
5 | export default function BarTop(data: LanguageStat[], theme: ThemeData) {
6 | return (
7 |
8 |
9 | Most used languages
10 |
11 |
12 |
13 | {data.map((language, index) => (
14 |
23 | ))}
24 |
25 |
26 | {data.slice(0, 6).map((language) => (
27 |
34 | ))}
35 |
36 |
37 | );
38 | }
39 |
40 | function Percent({
41 | language,
42 | percent,
43 | color,
44 | theme,
45 | }: {
46 | language: string;
47 | percent: number;
48 | color: string;
49 | theme: ThemeData;
50 | }) {
51 | return (
52 |
53 |
54 |
55 |
{language}
56 |
57 |
{percent.toFixed(2)}%
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/app/top/Compact.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 | import { LanguageStat } from "@/types/Languages";
3 | import Container from "../Container";
4 |
5 | export default function CompactTop(data: LanguageStat[], theme: ThemeData) {
6 | return (
7 |
8 |
9 | @{theme.user} has worked alot with
10 |
11 |
12 |
15 | {data[0].name}
16 |
17 |
18 | {`(${data[0].percent}%)`}
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/app/top/Normal.tsx:
--------------------------------------------------------------------------------
1 | import { LanguageStat } from "@/types/Languages";
2 | import { ThemeData } from "@/types/Theme";
3 | import Container from "../Container";
4 |
5 | export default function NormalTop(data: LanguageStat[], theme: ThemeData) {
6 | return (
7 |
8 |
9 | {data.slice(0, 6).map((language) => (
10 |
17 | ))}
18 |
19 |
20 | );
21 | }
22 |
23 | function Percent({
24 | language,
25 | percent,
26 | color,
27 | theme,
28 | }: {
29 | language: string;
30 | percent: number;
31 | color: string;
32 | theme: ThemeData;
33 | }) {
34 | return (
35 |
36 |
37 |
38 |
39 | {language}
40 |
41 |
42 |
{percent.toFixed(2)}%
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/app/top/route.tsx:
--------------------------------------------------------------------------------
1 | import Error from "../Error";
2 | import generateSvg from "@/helpers/generateSvg";
3 | import Send from "@/helpers/send";
4 | import { getData } from "@/helpers/getData";
5 | import NormalTop from "./Normal";
6 | import LangData from "@/utils/languages";
7 | import { ThemeData } from "@/types/Theme";
8 | import BarTop from "./Bar";
9 | import calculateLanguageStats from "@/helpers/calculateLanguage";
10 | import CompactTop from "./Compact";
11 |
12 | // /top?username=rahuletto&layout=bar
13 | // /top?username=rahuletto&layout=normal
14 | // /top?username=rahuletto&layout=compact
15 |
16 | export async function GET(request: Request) {
17 | const { searchParams } = new URL(request.url);
18 | const { user, color, accent, background, border, radius, padding } =
19 | getData(searchParams);
20 |
21 | const theme: ThemeData = {
22 | user: user ?? "rahuletto",
23 | color: color ?? "#E6EDF3",
24 | accent: accent ?? "#8D96A0",
25 | background: background ?? "#0D1116",
26 | border: border ?? "#30363D",
27 | radius: radius ?? 24,
28 | padding: padding ?? 24,
29 | };
30 |
31 | const hasLayout = searchParams.has("layout");
32 | const layout = hasLayout ? searchParams.get("layout") : "normal";
33 |
34 | try {
35 | const rawdata = await LangData(user || "rahuletto");
36 |
37 | if (
38 | rawdata.data.user.repositories.edges.length == 0 ||
39 | (rawdata.errors && rawdata.errors[0])
40 | ) {
41 | const image = await generateSvg(
42 | Error(theme, {
43 | message: rawdata.errors
44 | ? rawdata.errors[0]?.message
45 | : `There is no user with username "${user}"`,
46 | code: rawdata.errors ? rawdata.errors[0]?.type : "NOT_FOUND",
47 | }),
48 | {
49 | width: 500,
50 | height: 170,
51 | }
52 | );
53 |
54 | return Send(image, {error: true});
55 | }
56 |
57 | const result = calculateLanguageStats(rawdata);
58 |
59 | switch (layout) {
60 | case "bar": {
61 | const image = await generateSvg(BarTop(result, theme), {
62 | width: 300,
63 | height: 327,
64 | });
65 |
66 | return Send(image, {delay:0.1, bar: true});
67 | }
68 | case "compact":
69 | const image = await generateSvg(CompactTop(result, theme), {
70 | width: 480,
71 | height: 130,
72 | });
73 |
74 | return Send(image);
75 | case "normal":
76 | default: {
77 | const image = await generateSvg(NormalTop(result, theme), {
78 | width: 300,
79 | height: 260,
80 | });
81 |
82 | return Send(image, {delay: 0.1});
83 | }
84 | }
85 | } catch (err: any) {
86 | console.warn(err);
87 | const image = await generateSvg(
88 | Error(theme, {
89 | message: (err as Error).message,
90 | code: (err as Error).name,
91 | }),
92 | {
93 | width: 500,
94 | height: 170,
95 | }
96 | );
97 |
98 | return Send(image, {error: true});
99 | }
100 | }
101 |
102 | export const runtime = "edge";
103 |
--------------------------------------------------------------------------------
/app/user/User.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 |
3 | import { UserStats } from "@/types/UserStats";
4 | import Container from "../Container";
5 | import { calculateRank } from "@/helpers/calculateRank";
6 | import { GoCommit, GoGitPullRequest, GoIssueOpened, GoStar } from "react-icons/go";
7 | import { formatNumber } from "@/helpers/formatNum";
8 |
9 | export default function UserComp(data: UserStats, theme: ThemeData) {
10 | return (
11 |
12 |
13 |
14 |
15 | {calculateRank(data)}
16 |
17 |
18 | @{theme.user}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Stars
27 |
28 |
29 |
30 | {formatNumber(data.stars)}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Issues
39 |
40 |
41 |
42 | {formatNumber(data.issues)}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Commits
51 |
52 |
53 |
54 | {formatNumber(data.commits)}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Pull Requests
63 |
64 |
65 |
66 | {formatNumber(data.pullRequests)}
67 |
68 |
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/app/user/route.tsx:
--------------------------------------------------------------------------------
1 | import generateSvg from "@/helpers/generateSvg";
2 | import Send from "@/helpers/send";
3 | import { getData } from "@/helpers/getData";
4 | import { ThemeData } from "@/types/Theme";
5 | import Error from "../Error";
6 | import UserData from "@/utils/users";
7 | import { parseGitHubData } from "@/helpers/calculateRank";
8 | import UserComp from "./User";
9 |
10 | // /user?user=rahuletto
11 | export async function GET(request: Request) {
12 | const { searchParams } = new URL(request.url);
13 |
14 | const { user, color, accent, background, border, radius, padding, tip } =
15 | getData(searchParams);
16 |
17 | const theme: ThemeData = {
18 | user: user ?? "rahuletto",
19 | color: color ?? "#E6EDF3",
20 | accent: accent ?? "#8D96A0",
21 | background: background ?? "#0D1116",
22 | border: border ?? "#30363D",
23 | radius: radius ?? 24,
24 | padding: padding ?? 24,
25 | tip: tip ?? "#F6C655",
26 | };
27 |
28 | try {
29 | const rawdata = await UserData(
30 | user || "rahuletto"
31 | );
32 |
33 | if (rawdata.errors && rawdata.errors[0]) {
34 | const image = await generateSvg(
35 | Error(theme, {
36 | message: rawdata.errors[0].message,
37 | code: rawdata.errors[0].type,
38 | }),
39 | {
40 | width: 500,
41 | height: 170,
42 | }
43 | );
44 |
45 | return Send(image, {error: true});
46 | }
47 |
48 | const data = parseGitHubData(rawdata)
49 |
50 | const image = await generateSvg(UserComp(data, theme), {
51 | width: 285,
52 | height: 340,
53 | });
54 |
55 | return Send(image, {delay: 0.2});
56 | } catch (err: any) {
57 | console.warn(err);
58 |
59 | const image = await generateSvg(
60 | Error(theme, {
61 | message: (err as Error).message,
62 | code: (err as Error).name,
63 | }),
64 | {
65 | width: 500,
66 | height: 170,
67 | }
68 | );
69 |
70 | return Send(image, {error: true});
71 | }
72 | }
73 |
74 | export const runtime = "edge";
75 |
--------------------------------------------------------------------------------
/app/wakatime/Bar.tsx:
--------------------------------------------------------------------------------
1 | import { generateColor } from "@/helpers/getColor";
2 | import { ThemeData } from "@/types/Theme";
3 | import { WakaData } from "@/types/Waka";
4 | import { colors } from "@/utils/colors";
5 | import Container from "../Container";
6 |
7 | export default function BarWaka(data: WakaData, theme: ThemeData) {
8 | return (
9 |
10 |
11 | Most used languages
12 |
13 |
14 |
15 | {data.languages.map((language, index) => (
16 |
25 | ))}
26 |
27 |
28 | {data.languages.slice(0, 6).map((language) => (
29 |
36 | ))}
37 |
38 |
39 | wakatime
40 |
41 |
42 | );
43 | }
44 |
45 | function Percent({
46 | language,
47 | percent,
48 | color,
49 | theme,
50 | }: {
51 | language: string;
52 | percent: number;
53 | color: string;
54 | theme: ThemeData;
55 | }) {
56 | return (
57 |
58 |
59 |
60 |
{language}
61 |
62 |
63 | {percent.toFixed(2)}%
64 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/app/wakatime/Compact.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 | import { colors } from "@/utils/colors";
3 | import { WakaData } from "@/types/Waka";
4 | import Container from "../Container";
5 |
6 | export default function CompactWaka(data: WakaData, theme: ThemeData) {
7 | return (
8 |
9 |
10 | @{theme.user} has worked with
11 |
12 |
13 |
16 | {data.languages[0].name}
17 |
18 |
19 | for {data.languages[0].hours} hours
20 |
21 |
22 |
23 |
24 | wakatime
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/app/wakatime/Normal.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeData } from "@/types/Theme";
2 | import { WakaData } from "@/types/Waka";
3 | import Container from "../Container";
4 |
5 | // /wakatime?username=rahuletto&layout=compact
6 | // /wakatime?username=rahuletto&layout=normal
7 | export default function NormalWaka(data: WakaData, theme: ThemeData) {
8 | return (
9 |
10 |
11 | @{data.user}
12 |
13 |
14 | {data.languages.slice(0, 6).map((language) => (
15 |
21 | ))}
22 |
23 |
24 | wakatime
25 |
26 |
27 | );
28 | }
29 |
30 | function Time({
31 | language,
32 | seconds,
33 | theme,
34 | }: {
35 | language: string;
36 | seconds: number;
37 | theme: ThemeData;
38 | }) {
39 | const hours = Math.floor(seconds / 3600);
40 | const minutes = Math.floor((seconds % 3600) / 60);
41 |
42 | return (
43 |
44 |
45 | {language}
46 |
47 |
48 | {hours}h {minutes}m
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/app/wakatime/route.tsx:
--------------------------------------------------------------------------------
1 | import generateSvg from "@/helpers/generateSvg";
2 | import Send from "@/helpers/send";
3 | import { getData } from "@/helpers/getData";
4 | import CompactWaka from "./Compact";
5 | import NormalWaka from "./Normal";
6 | import { ThemeData } from "@/types/Theme";
7 | import Error from "../Error";
8 | import Wakatime from "@/utils/wakatime";
9 | import { WakaData } from "@/types/Waka";
10 | import BarWaka from "./Bar";
11 |
12 | export async function GET(request: Request) {
13 | const { searchParams } = new URL(request.url);
14 |
15 | const hasLayout = searchParams.has("layout");
16 | const layout = hasLayout ? searchParams.get("layout") : "normal";
17 |
18 | const { user, color, accent, background, border, radius, padding, tip } =
19 | getData(searchParams);
20 |
21 | const theme: ThemeData = {
22 | user: user ?? "rahuletto",
23 | color: color ?? "#E6EDF3",
24 | accent: accent ?? "#8D96A0",
25 | background: background ?? "#0D1116",
26 | border: border ?? "#30363D",
27 | radius: radius ?? 24,
28 | padding: padding ?? 24,
29 | tip: tip ?? "#F6C655",
30 | };
31 |
32 | try {
33 | const rawdata = await Wakatime(user || "rahuletto");
34 |
35 | if (!rawdata || rawdata.error) {
36 | const image = await generateSvg(
37 | Error(theme, {
38 | message: rawdata?.error ?? "",
39 | code: "",
40 | }),
41 | {
42 | width: 500,
43 | height: 170,
44 | }
45 | );
46 |
47 | return Send(image, {error: true});
48 | }
49 |
50 | const data: WakaData = {
51 | user: rawdata.data.username,
52 | languages: rawdata.data.languages.sort(
53 | (a, b) => b.total_seconds - a.total_seconds
54 | ),
55 | };
56 |
57 | switch (layout) {
58 | case "bar": {
59 | const image = await generateSvg(BarWaka(data, theme), {
60 | width: 300,
61 | height: 337,
62 | });
63 |
64 | return Send(image, {delay: 0.1, bar: true});
65 | }
66 | case "compact":
67 | const image = await generateSvg(CompactWaka(data, theme), {
68 | width: 480,
69 | height: 130,
70 | });
71 |
72 | return Send(image);
73 | case "normal":
74 | default: {
75 | const image = await generateSvg(NormalWaka(data, theme), {
76 | width: 320,
77 | height: 337,
78 | });
79 |
80 | return Send(image, { delay: 0.1 });
81 | }
82 | }
83 |
84 | } catch (err: any) {
85 | console.warn(err);
86 |
87 | const image = await generateSvg(
88 | Error(theme, {
89 | message: (err as Error).message,
90 | code: (err as Error).name,
91 | }),
92 | {
93 | width: 500,
94 | height: 170,
95 | }
96 | );
97 |
98 | return Send(image, {error: true});
99 | }
100 | }
101 |
102 | export const runtime = "edge";
103 |
--------------------------------------------------------------------------------
/assets/Urbanist-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/assets/Urbanist-Bold.ttf
--------------------------------------------------------------------------------
/assets/Urbanist-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/assets/Urbanist-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/gitmystat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/assets/gitmystat.png
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/bun.lockb
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | GITHUB_TOKEN=
--------------------------------------------------------------------------------
/helpers/animate.ts:
--------------------------------------------------------------------------------
1 | import { Options } from "@/types/AnimateOptions";
2 |
3 | export function animate(svgString: string, options?: Options): string {
4 | if (options?.error) return svgString;
5 |
6 | const pathRegex = /]*\bd="([^"]*)"/gi;
7 | let match;
8 | let index = 0;
9 | while ((match = pathRegex.exec(svgString)) !== null) {
10 | if (match[1].length > 150) {
11 | svgString = svgString.replace(
12 | match[0],
13 | `${match[0]} class="long-path" style="animation: fadein 0.5s ${index * (options?.delay || 0.4) + 0.2}s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards; opacity: 0;"`
14 | );
15 | index++;
16 | }
17 | }
18 |
19 | if (options?.bar) {
20 | const clip = /]*\bclip-path="([^"]*)"/gi;
21 | match = "";
22 | index = 0;
23 | while ((match = clip.exec(svgString)) !== null) {
24 | if (match[1].includes("cp-id-1")) {
25 | svgString = svgString.replace(
26 | match[0],
27 | `${match[0]} class="barchart" style="animation: fadein 0.7s ${index * (options?.delay || 0.4) + 0.1}s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards; opacity: 0;"`
28 | );
29 | index++;
30 | }
31 | }
32 | }
33 | const style = `
34 |
57 |
58 |
82 | `;
83 | const splitter = svgString.split('/svg">');
84 | return splitter[0] + '/svg">' + style + splitter[1];
85 | }
86 |
--------------------------------------------------------------------------------
/helpers/calculateLanguage.ts:
--------------------------------------------------------------------------------
1 | import { RawLanguageData, LanguageStat } from "@/types/Languages";
2 |
3 | export default function calculateLanguageStats(languageData: RawLanguageData): LanguageStat[] {
4 | const repositories = languageData.data.user.repositories.edges;
5 | const languageMap = new Map();
6 | let totalSize = 0;
7 |
8 | repositories.forEach(repo => {
9 | const languages = repo.node.languages.edges;
10 | if (languages) {
11 | languages.forEach(lang => {
12 | const name = lang.node.name;
13 | const size = lang.size;
14 | const color = lang.node.color;
15 |
16 | if (languageMap.has(name)) {
17 | languageMap.get(name)!.size += size;
18 | } else {
19 | languageMap.set(name, { size, color });
20 | }
21 |
22 | totalSize += size;
23 | });
24 | }
25 | });
26 |
27 | const languageStats: LanguageStat[] = [];
28 | languageMap.forEach((value, key) => {
29 | const { size, color } = value;
30 | const percent = (size / totalSize) * 100;
31 | const stat: LanguageStat = {
32 | name: key,
33 | color: color,
34 | count: size,
35 | percent: parseFloat(percent.toFixed(2))
36 | };
37 | languageStats.push(stat);
38 | });
39 |
40 | languageStats.sort((a, b) => b.percent - a.percent);
41 |
42 | return languageStats;
43 | }
--------------------------------------------------------------------------------
/helpers/calculateRank.ts:
--------------------------------------------------------------------------------
1 | import { RawUserData, UserStats } from "@/types/UserStats";
2 |
3 | function calculatePercentiles(stats: UserStats): number {
4 | const { commits, pullRequests, reviews, issues, stars, followers } = stats;
5 |
6 | const commitPercentile = 1 - Math.exp(-commits / 100);
7 | const prPercentile = 1 - Math.exp(-pullRequests / 100);
8 | const reviewPercentile = 1 - Math.exp(-reviews / 100);
9 | const issuePercentile = 1 - Math.exp(-issues / 100);
10 | const starPercentile = (1 + erf(Math.log(stars) / Math.sqrt(2))) / 2;
11 | const followerPercentile = (1 + erf(Math.log(followers) / Math.sqrt(2))) / 2;
12 |
13 | const globalPercentile =
14 | (commitPercentile +
15 | prPercentile +
16 | reviewPercentile +
17 | issuePercentile +
18 | starPercentile +
19 | followerPercentile) /
20 | 6;
21 |
22 | return globalPercentile * 100;
23 | }
24 |
25 | function mapPercentileToRank(percent: number): string {
26 | const percentile = 100 - percent;
27 | if (percentile <= 1) return "S";
28 | if (percentile <= 12.5) return "A+";
29 | if (percentile <= 25) return "A";
30 | if (percentile <= 37.5) return "A-";
31 | if (percentile <= 50) return "B+";
32 | if (percentile <= 62.5) return "B";
33 | if (percentile <= 75) return "B-";
34 | if (percentile <= 87.5) return "C+";
35 | if (percentile <= 91) return "C";
36 | return "D";
37 | }
38 |
39 | export function calculateRank(stats: UserStats): string {
40 | const percentile = calculatePercentiles(stats);
41 | console.log(percentile);
42 | const rank = mapPercentileToRank(percentile);
43 | return rank;
44 | }
45 |
46 | function erf(x: number): number {
47 | const a1 = 0.254829592;
48 | const a2 = -0.284496736;
49 | const a3 = 1.421413741;
50 | const a4 = -1.453152027;
51 | const a5 = 1.061405429;
52 | const p = 0.3275911;
53 |
54 | const sign = x >= 0 ? 1 : -1;
55 | x = Math.abs(x);
56 |
57 | const t = 1.0 / (1.0 + p * x);
58 | const y = ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t;
59 | return sign * (1.0 - y * Math.exp(-x * x));
60 | }
61 |
62 | export function parseGitHubData(data: RawUserData): UserStats {
63 | const userData = data.data.user;
64 | const contributions = userData.contributionsCollection;
65 | const repositories = userData.repositories.nodes;
66 | const followers = userData.followers.totalCount;
67 |
68 | const stars = repositories.reduce(
69 | (acc, repo) => acc + repo.stargazers.totalCount,
70 | 0
71 | );
72 |
73 | const stats: UserStats = {
74 | commits: contributions.contributionCalendar.totalContributions,
75 | pullRequests: userData.pullRequests.totalCount,
76 | reviews: contributions.totalCommitContributions,
77 | issues: userData.issues.totalCount,
78 | stars,
79 | followers,
80 | };
81 |
82 | return stats;
83 | }
84 |
--------------------------------------------------------------------------------
/helpers/emoji.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js.
3 | */
4 |
5 | /* ! Copyright Twitter Inc. and other contributors. Licensed under MIT */
6 |
7 | const U200D = String.fromCharCode(8205);
8 | const UFE0Fg = /\uFE0F/g;
9 |
10 | export function getIconCode(char: string) {
11 | return toCodePoint(!char.includes(U200D) ? char.replace(UFE0Fg, "") : char);
12 | }
13 |
14 | function toCodePoint(unicodeSurrogates: string) {
15 | const r = [];
16 | let c = 0;
17 | let i = 0;
18 | let p = 0;
19 |
20 | while (i < unicodeSurrogates.length) {
21 | c = unicodeSurrogates.charCodeAt(i++);
22 | if (p) {
23 | r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16));
24 | p = 0;
25 | } else if (55296 <= c && c <= 56319) {
26 | p = c;
27 | } else {
28 | r.push(c.toString(16));
29 | }
30 | }
31 | return r.join("-");
32 | }
33 |
34 | const emojiCache: Record> = {};
35 |
36 | export async function loadEmoji(code: string) {
37 | const key = `${code}`;
38 |
39 | if (key in emojiCache) {
40 | return emojiCache[key];
41 | }
42 |
43 | const api = "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/";
44 |
45 | return (emojiCache[key] = fetch(`${api}${code.toUpperCase()}.svg`).then(
46 | async (r) => r.text()
47 | ));
48 | }
49 |
--------------------------------------------------------------------------------
/helpers/formatNum.ts:
--------------------------------------------------------------------------------
1 | export function formatNumber(num: number): string {
2 | if (num >= 1000 && num < 1000000) {
3 | return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
4 | } else if (num >= 1000000) {
5 | return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
6 | } else {
7 | return num.toString();
8 | }
9 | }
--------------------------------------------------------------------------------
/helpers/generateSvg.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 | import satori from "satori";
3 | import { getIconCode, loadEmoji } from "./emoji";
4 |
5 | const urbanistFetch = fetch(
6 | new URL("../assets/Urbanist-SemiBold.ttf", import.meta.url),
7 | {
8 | cache: "force-cache",
9 | }
10 | ).then((res) => res.arrayBuffer());
11 |
12 | const boldFetch = fetch(
13 | new URL("../assets/Urbanist-Bold.ttf", import.meta.url),
14 | {
15 | cache: "force-cache",
16 | }
17 | ).then((res) => res.arrayBuffer());
18 |
19 | export default async function generateSvg(
20 | fn: ReactNode,
21 | options: { width: number; height: number }
22 | ) {
23 | const urbanist = await urbanistFetch;
24 | const urbanistBold = await boldFetch;
25 |
26 | const data = await satori(fn, {
27 | width: options.width,
28 | height: options.height,
29 | fonts: [
30 | {
31 | weight: 500,
32 | style: "normal",
33 | data: urbanist,
34 | name: "Urbanist",
35 | },
36 | {
37 | weight: 600,
38 | style: "normal",
39 | data: urbanistBold,
40 | name: "Urbanist",
41 | },
42 | ],
43 | loadAdditionalAsset: async (code: string, segment: string) => {
44 | if (code === "emoji") {
45 |
46 | return `data:image/svg+xml;base64,${btoa(await loadEmoji(getIconCode(segment)))}`;
47 | }
48 |
49 |
50 | // if segment is normal text
51 | return code;
52 | }
53 |
54 | });
55 |
56 | return data;
57 | }
58 |
--------------------------------------------------------------------------------
/helpers/getColor.ts:
--------------------------------------------------------------------------------
1 | export function generateColor(str: string): string {
2 | let hash = 0;
3 | for (let i = 0; i < str.length; i++) {
4 | const char = str.charCodeAt(i);
5 | hash = (hash << 5) - hash + char;
6 | hash |= 0;
7 | }
8 |
9 | const color = (hash & 0x00FFFFFF).toString(16).toUpperCase().padStart(6, '0');
10 |
11 | return `#${color}`;
12 | }
--------------------------------------------------------------------------------
/helpers/getData.ts:
--------------------------------------------------------------------------------
1 | import { Themes } from "@/themes";
2 | import { ThemeData } from "@/types/Theme";
3 |
4 | export function getData(searchParams: URLSearchParams): ThemeData {
5 | const username = searchParams.has("username");
6 | const user = (username ? searchParams.get("username") : "rahuletto") as string;
7 |
8 | const hasTheme = searchParams.has("theme");
9 | let theme = hasTheme ? searchParams.get("theme") : "dark";
10 | if (!theme || !Themes[theme as string]?.accent) theme = "dark";
11 |
12 | const hasColor = searchParams.has("color");
13 | const color = hasColor
14 | ? decodeURIComponent(searchParams.get("color") as string).replaceAll(
15 | "0x",
16 | "#"
17 | )
18 | : hasTheme
19 | ? Themes[theme as string].color
20 | : "#E6EDF3";
21 |
22 | const hasAccent = searchParams.has("accent");
23 | const accent = hasAccent
24 | ? decodeURIComponent(searchParams.get("accent") as string).replaceAll(
25 | "0x",
26 | "#"
27 | )
28 | : hasTheme
29 | ? Themes[theme as string].accent
30 | : "#8D96A0";
31 |
32 | const hasBg = searchParams.has("background");
33 | const background = hasBg
34 | ? decodeURIComponent(searchParams.get("background") as string).replaceAll(
35 | "0x",
36 | "#"
37 | )
38 | : hasTheme
39 | ? Themes[theme as string].background
40 | : "#0D1116";
41 |
42 | const hasBorder = searchParams.has("border");
43 | const border = hasBorder
44 | ? decodeURIComponent(searchParams.get("border") as string).replaceAll(
45 | "0x",
46 | "#"
47 | )
48 | : hasTheme
49 | ? Themes[theme as string].border
50 | : "#30363D";
51 |
52 | const hasTip = searchParams.has("tip");
53 | const tip = hasTip
54 | ? decodeURIComponent(searchParams.get("tip") as string).replaceAll(
55 | "0x",
56 | "#"
57 | )
58 | : hasTheme
59 | ? Themes[theme as string].tip
60 | : "#30363D";
61 |
62 | const hasRad = searchParams.has("radius");
63 | const radius = hasRad
64 | ? Number(searchParams.get("radius"))
65 | : hasTheme
66 | ? Themes[theme as string].radius
67 | : 24;
68 |
69 | const hasPad = searchParams.has("padding");
70 | const padding = hasPad
71 | ? Number(searchParams.get("padding"))
72 | : hasTheme
73 | ? Themes[theme as string].padding
74 | : 24;
75 |
76 | return {
77 | theme,
78 | user,
79 | color,
80 | accent,
81 | background,
82 | border,
83 | tip,
84 | radius,
85 | padding,
86 | };
87 | }
88 |
--------------------------------------------------------------------------------
/helpers/send.ts:
--------------------------------------------------------------------------------
1 | import { Options } from "@/types/AnimateOptions";
2 | import { animate } from "./animate";
3 |
4 |
5 | export default function Send(image: string, options?: Options) {
6 | return new Response(animate(image, options), {
7 | headers: {
8 | "Accept-Encoding": "gzip, deflate, br, zstd",
9 | "cache-control": "private, max-age=43200, stale-if-error=3600, stale-while-revalidate=21600",
10 | "Content-Type": "image/svg+xml",
11 | },
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | compress: true,
4 | reactStrictMode: true,
5 | swcMinify: true,
6 | };
7 |
8 | export default nextConfig;
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mystats",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@vercel/og": "^0.6.2",
13 | "next": "14.2.4",
14 | "react": "18.3.1",
15 | "react-colorful": "^5.6.1",
16 | "react-dom": "18.3.1",
17 | "react-icons": "^5.2.1",
18 | "satori": "^0.10.13"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^20",
22 | "@types/react": "^18",
23 | "@types/react-dom": "^18",
24 | "eslint": "^8",
25 | "eslint-config-next": "14.2.4",
26 | "postcss": "^8",
27 | "prettier": "^3.3.2",
28 | "prettier-plugin-tailwindcss": "^0.6.5",
29 | "tailwindcss": "^3.4.1",
30 | "typescript": "^5"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/providers/DeviceProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | createContext,
5 | ReactNode,
6 | useContext,
7 | useEffect,
8 | useState,
9 | } from "react";
10 |
11 |
12 | export const DeviceContext = createContext<{
13 | mode: "desktop" | "mobile" | "tablet"
14 | }>({ mode: "desktop" });
15 |
16 |
17 | export function useDevice() {
18 | const { mode } = useContext(DeviceContext);
19 | return mode;
20 | }
21 | export const DeviceProvider = ({ children }: { children: ReactNode }) => {
22 | const [mode, setMode] = useState<"desktop" | "mobile" | "tablet">("desktop");
23 |
24 | useEffect(() => {
25 | setMode(device());
26 |
27 | setInterval(() => {
28 | setMode(device());
29 | }, 2000);
30 |
31 | function device() {
32 | const screenWidth = document.body.clientWidth;
33 |
34 | const userAgent = navigator.userAgent.toLowerCase();
35 | const isMobile =
36 | /iphone|ipod|android|windows phone/g.test(userAgent);
37 | const isTablet =
38 | /(ipad|tablet|playbook|silk)|(android(?!.*mobile))/g.test(userAgent);
39 |
40 | if (screenWidth < 768 || isMobile) {
41 | return "mobile";
42 | } else if ((screenWidth >= 768 && screenWidth <= 1024) || isTablet) {
43 | return "tablet";
44 | } else {
45 | return "desktop";
46 | }
47 | }
48 | const handleResize = () => {
49 | const newDeviceType = device();
50 | if (mode != newDeviceType) setMode(newDeviceType);
51 | };
52 |
53 | window.addEventListener("resize", handleResize);
54 | return () => {
55 | window.removeEventListener("resize", handleResize);
56 | };
57 | }, [mode]);
58 |
59 | return (
60 |
61 | {children}
62 |
63 | );
64 | };
--------------------------------------------------------------------------------
/public/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/public/banner.png
--------------------------------------------------------------------------------
/public/fine.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/public/fine.jpg
--------------------------------------------------------------------------------
/public/gitmystat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rahuletto/gitMyStat/095895765bea3409e36c721d790d491b8cfbdc39/public/gitmystat.png
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | import { Themes } from "./themes";
3 |
4 | const config: Config = {
5 | content: [
6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | darkMode: "media", // or 'class'
11 | theme: {
12 | extend: {
13 | colors: {
14 | background: "var(--background)",
15 | color: "var(--color)",
16 | accent: "var(--accent)",
17 | tip: "var(--tip)",
18 | ...Themes
19 | },
20 | transitionTimingFunction: {
21 | 'bouncy': 'cubic-bezier(0.68, -0.55, 0.27, 1.55)',
22 | },
23 | },
24 | },
25 | plugins: [],
26 | };
27 | export default config;
28 |
--------------------------------------------------------------------------------
/themes/index.ts:
--------------------------------------------------------------------------------
1 | import { ThemeType } from "@/types/Theme";
2 |
3 | export const Themes: ThemeType = {
4 | // Github Themes
5 | dark: {
6 | color: "#E6EDF3",
7 | accent: "#8D96A0",
8 | background: "#0D1116",
9 | border: "#30363D",
10 | tip: "#30363D",
11 | radius: 24,
12 | padding: 24,
13 | },
14 | light: {
15 | color: "#000000",
16 | accent: "#343941",
17 | background: "#fefefe",
18 | border: "#C2C9D1",
19 | tip: "#D0D7DE",
20 | radius: 24,
21 | padding: 24,
22 | },
23 | // My Portfolio themes
24 | gold: {
25 | color: "#D6BFB2",
26 | accent: "#A6A6A6",
27 | background: "#141414",
28 | border: "#1B1A1A",
29 | tip: "#5c5c5c",
30 | radius: 24,
31 | padding: 24,
32 | },
33 | "gold-light": {
34 | color: "#967C6E",
35 | accent: "#898888",
36 | background: "#EDEDED",
37 | border: "#D6CDCD",
38 | tip: "#967C6E",
39 | radius: 24,
40 | padding: 24,
41 | },
42 |
43 | // Transparent
44 | transparent: {
45 | color: "#fefefe",
46 | accent: "#fefefe",
47 | background: "#00000000",
48 | border: "#00000000",
49 | tip: "#e4e4e4",
50 | radius: 24,
51 | padding: 24,
52 | },
53 | "transparent-light": {
54 | color: "#000000",
55 | accent: "#000000",
56 | background: "#00000000",
57 | border: "#00000000",
58 | tip: "#1d1d1d",
59 | radius: 24,
60 | padding: 24,
61 | },
62 | // Tokyonight
63 | tokyonight: {
64 | color: "#B4BDE6",
65 | accent: "#6CCBBE",
66 | background: "#24283A",
67 | border: "#17B99A",
68 | tip: "#9274C9",
69 | radius: 24,
70 | padding: 24,
71 | },
72 | tokyoday: {
73 | color: "#5476C6",
74 | accent: "#3F756D",
75 | background: "#E1E2E6",
76 | border: "#50A3B5",
77 | tip: "#8C64C5",
78 | radius: 24,
79 | padding: 24,
80 | },
81 | // gruvbox
82 | gruvbox: {
83 | color: "#EBD9B4",
84 | accent: "#FABD2E",
85 | background: "#282828",
86 | border: "#3C3836",
87 | tip: "#928275",
88 | radius: 24,
89 | padding: 24,
90 | },
91 | "gruvbox-light": {
92 | color: "#3C3836",
93 | accent: "#B47524",
94 | background: "#FBF0C9",
95 | border: "#EBD9B4",
96 | tip: "#958375",
97 | radius: 24,
98 | padding: 24,
99 | },
100 | // our theme
101 | 'our-theme': {
102 | color: "#FCD116",
103 | accent: "#FFF",
104 | background: "#CE1226",
105 | border: "#FCD116",
106 | tip: "#9F1120",
107 | radius: 24,
108 | padding: 24,
109 | },
110 | // Catppuccin
111 | catppuccin: {
112 | color: "#CBA6F7",
113 | accent: "#CDD6F4",
114 | background: "#1E1E2E",
115 | border: "#181825",
116 | tip: "#CDD6F4",
117 | radius: 24,
118 | padding: 24,
119 | },
120 | "catppuccin-light": {
121 | color: "#130F1A",
122 | accent: "#130F1A",
123 | background: "#cacffa",
124 | border: "#706C91",
125 | tip: "#706C91",
126 | radius: 24,
127 | padding: 24,
128 | },
129 |
130 | // blueberry
131 | blueberry: {
132 | color: "#91B4D5",
133 | accent: "#ACD7FF",
134 | background: "#212B42",
135 | border: "#1B2334",
136 | tip: "#5C7DA5",
137 | radius: 24,
138 | padding: 24,
139 | },
140 | "blueberry-light": {
141 | color: "#678198",
142 | accent: "#506477",
143 | background: "#DAE0F5",
144 | border: "#C1C7DF",
145 | tip: "#92A4BE",
146 | radius: 24,
147 | padding: 24,
148 | },
149 |
150 | // dino
151 | dino: {
152 | color: "#FFF",
153 | accent: "#CAFAD9",
154 | background: "#1D221F",
155 | border: "#40D672",
156 | tip: "#D5D5D5",
157 | radius: 24,
158 | padding: 24,
159 | },
160 | "dino-light": {
161 | color: "#1D221F",
162 | accent: "#40D672",
163 | background: "#FFF",
164 | border: "#CAFAD9",
165 | tip: "#D5D5D5",
166 | radius: 24,
167 | padding: 24,
168 | },
169 |
170 | // bw
171 | stealth: {
172 | color: "#5E676E",
173 | accent: "#5E676E",
174 | background: "#010203",
175 | border: "#383E42",
176 | tip: "#121212",
177 | radius: 24,
178 | padding: 24,
179 | },
180 | paper: {
181 | color: "#444",
182 | accent: "#444",
183 | background: "#EEE",
184 | border: "#DDD",
185 | tip: "#B2B2B2",
186 | radius: 24,
187 | padding: 24,
188 | },
189 | // drowning
190 | drowning: {
191 | color: "#4A6FB5",
192 | accent: "#9393A7",
193 | background: "#191826",
194 | border: "#1E1F2F",
195 | tip: "#50688C",
196 | radius: 24,
197 | padding: 24,
198 | },
199 |
200 | //moonlight
201 | moonlight: {
202 | color: "#C69E69",
203 | accent: "#CCCCB5",
204 | background: "#191F28",
205 | border: "#141A22",
206 | tip: "#4B5975",
207 | radius: 24,
208 | padding: 24,
209 | },
210 |
211 | // vscode
212 | vsdark: {
213 | color: "#B5CEA8",
214 | accent: "#D4D4D4",
215 | background: "#1E1E1E",
216 | border: "#252526",
217 | tip: "#858585",
218 | radius: 24,
219 | padding: 24,
220 | },
221 | vslight: {
222 | color: "#001080",
223 | accent: "#6D7681",
224 | background: "#FFF",
225 | border: "#F8F8F8",
226 | tip: "#6A9956",
227 | radius: 24,
228 | padding: 24,
229 | },
230 |
231 | // cyred
232 | cyred: {
233 | color: "#E55150",
234 | accent: "#FAA",
235 | background: "#3F1516",
236 | border: "#6E2627",
237 | tip: "#FAA",
238 | radius: 24,
239 | padding: 24,
240 | },
241 | "cyred-light": {
242 | color: "#E55150",
243 | accent: "#6E2627",
244 | background: "#fee",
245 | border: "#E55150",
246 | tip: "#3F1516",
247 | radius: 24,
248 | padding: 24,
249 | },
250 |
251 | // One dark
252 | onedark: {
253 | color: "#ABB2BF",
254 | accent: "#98C379",
255 | background: "#23272E",
256 | border: "#3B4048",
257 | tip: "#E06C75",
258 | radius: 24,
259 | padding: 24,
260 | },
261 | onelight: {
262 | color: "#393A43",
263 | accent: "#A81AA5",
264 | background: "#FAFAFA",
265 | border: "#565B7D",
266 | tip: "#A0A1A6",
267 | radius: 24,
268 | padding: 24,
269 | },
270 |
271 | // discord
272 | discord: {
273 | color: "#5A65EA",
274 | accent: "#DCDEE2",
275 | background: "#111214",
276 | border: "#5A65EA",
277 | tip: "#2B2D31",
278 | radius: 24,
279 | padding: 24,
280 | },
281 | "discord-light": {
282 | color: "#5664F0",
283 | accent: "#313338",
284 | background: "#FFF",
285 | border: "#E1E2E4",
286 | tip: "#313338",
287 | radius: 24,
288 | padding: 24,
289 | },
290 | };
291 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strictNullChecks": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
--------------------------------------------------------------------------------
/types/AnimateOptions.ts:
--------------------------------------------------------------------------------
1 | export interface Options {
2 | delay?: number;
3 | bar?: boolean;
4 | error?: boolean;
5 | }
--------------------------------------------------------------------------------
/types/Error.ts:
--------------------------------------------------------------------------------
1 | export interface QLError {
2 | type: string;
3 | path: string[];
4 | locations: {
5 | line: number;
6 | column: number;
7 | }[];
8 | message: string;
9 | }
10 |
--------------------------------------------------------------------------------
/types/Languages.ts:
--------------------------------------------------------------------------------
1 | import { QLError } from "./Error";
2 |
3 | export interface Repository {
4 | name: string;
5 | languages: {
6 | totalSize: number;
7 | edges: {size: number; node: { name: string; color: string }}[] | null;
8 | };
9 | }
10 |
11 | export interface RawLanguageData {
12 | data: {
13 | user: {
14 | repositories: {
15 | edges: Array<{ node: Repository }>;
16 | };
17 | };
18 | };
19 | errors?: QLError[];
20 | }
21 |
22 | export interface LanguageStat {
23 | name: string;
24 | color: string;
25 | count: number;
26 | percent: number;
27 | }
28 |
--------------------------------------------------------------------------------
/types/Recents.ts:
--------------------------------------------------------------------------------
1 | export interface RecentData {
2 | name: string;
3 | url: string;
4 | }
5 |
--------------------------------------------------------------------------------
/types/Repo.ts:
--------------------------------------------------------------------------------
1 | import { QLError } from "./Error";
2 |
3 | export interface RawRepoData {
4 | data: {
5 | repositoryOwner: {
6 | repository: Repo;
7 | };
8 | };
9 | errors?: QLError[];
10 | }
11 |
12 | export interface Repo {
13 | name: string;
14 | description: string;
15 | stargazerCount: number;
16 | forkCount: number;
17 | primaryLanguage: {
18 | name: string;
19 | color: string;
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/types/Theme.ts:
--------------------------------------------------------------------------------
1 | export interface ThemeType {
2 | [key: string]: {
3 | color: string;
4 | accent: string;
5 | background: string;
6 | border: string;
7 | radius: number;
8 | padding: number;
9 | tip: string;
10 | };
11 | }
12 |
13 | export interface ThemeData {
14 | user: string;
15 | color: string;
16 | accent: string;
17 | background: string;
18 | border: string;
19 | radius: number;
20 | padding: number;
21 | tip?: string;
22 | theme?: string
23 | }
24 |
--------------------------------------------------------------------------------
/types/UserStats.ts:
--------------------------------------------------------------------------------
1 | import { QLError } from "./Error";
2 |
3 | export interface UserStats {
4 | commits: number;
5 | pullRequests: number;
6 | reviews: number;
7 | issues: number;
8 | stars: number;
9 | followers: number;
10 | }
11 |
12 | export interface RawUserData {
13 | data: {
14 | user: User;
15 | };
16 | errors?: QLError[]
17 | }
18 |
19 | interface ContributeCollection {
20 | totalCommitContributions: number;
21 | contributionCalendar: {
22 | totalContributions: number;
23 | };
24 | totalRepositoriesWithContributedCommits: number;
25 | totalPullRequestContributions: number;
26 | totalIssueContributions: number;
27 | }
28 |
29 | interface Repositories {
30 | nodes: {
31 | stargazers: {
32 | totalCount: number;
33 | };
34 | }[];
35 | }
36 |
37 | interface User {
38 | contributionsCollection: ContributeCollection;
39 | repositories: Repositories;
40 | issues: {
41 | totalCount: number;
42 | };
43 | pullRequests: {
44 | totalCount: number;
45 | };
46 | followers: {
47 | totalCount: number;
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/types/Waka.ts:
--------------------------------------------------------------------------------
1 | export interface RawWakaData {
2 | error?: string;
3 | data: {
4 | categories: WakatimeStat[];
5 | languages: WakatimeStat[];
6 | editors: WakatimeStat[];
7 | operating_systems: WakatimeStat[];
8 | daily_average: number;
9 | daily_average_including_other_language: number;
10 | days_including_holidays: number;
11 | days_minus_holidays: number;
12 | holidays: number;
13 | human_readable_daily_average: string;
14 | human_readable_daily_average_including_other_language: string;
15 | human_readable_range: string;
16 | human_readable_total: string;
17 | human_readable_total_including_other_language: string;
18 | id: string;
19 | is_already_updating: boolean;
20 | is_cached: boolean;
21 | is_coding_activity_visible: boolean;
22 | is_including_today: boolean;
23 | is_other_usage_visible: boolean;
24 | is_stuck: boolean;
25 | is_up_to_date: boolean;
26 | is_up_to_date_pending_future: boolean;
27 | percent_calculated: number;
28 | range: string;
29 | status: string;
30 | timeout: number;
31 | total_seconds: number;
32 | total_seconds_including_other_language: number;
33 | user_id: string;
34 | username: string;
35 | writes_only: boolean;
36 | };
37 | message: string;
38 | }
39 |
40 |
41 | interface WakatimeStat {
42 | decimal: string;
43 | digital: string;
44 | hours: number;
45 | minutes: number;
46 | name: string;
47 | percent: number;
48 | text: string;
49 | total_seconds: number;
50 | }
51 |
52 | export interface WakaData {
53 | user: string;
54 | languages: WakatimeStat[];
55 |
56 | }
--------------------------------------------------------------------------------
/utils/colors.ts:
--------------------------------------------------------------------------------
1 | export interface ColorType {
2 | [key: string]: string | null;
3 | }
4 |
5 | export const colors: ColorType = {
6 | "1C Enterprise": "#814CCC",
7 | "2-Dimensional Array": "#38761D",
8 | "4D": "#004289",
9 | ABAP: "#E8274B",
10 | "ABAP CDS": "#555e25",
11 | ActionScript: "#882B0F",
12 | Ada: "#02f88c",
13 | "Adblock Filter List": "#800000",
14 | "Adobe Font Metrics": "#fa0f00",
15 | Agda: "#315665",
16 | "AGS Script": "#B9D9FF",
17 | AIDL: "#34EB6B",
18 | AL: "#3AA2B5",
19 | Alloy: "#64C800",
20 | "Alpine Abuild": "#0D597F",
21 | "Altium Designer": "#A89663",
22 | AMPL: "#E6EFBB",
23 | AngelScript: "#C7D7DC",
24 | "Ant Build System": "#A9157E",
25 | Antlers: "#ff269e",
26 | ANTLR: "#9DC3FF",
27 | ApacheConf: "#d12127",
28 | Apex: "#1797c0",
29 | "API Blueprint": "#2ACCA8",
30 | APL: "#5A8164",
31 | "Apollo Guidance Computer": "#0B3D91",
32 | AppleScript: "#101F1F",
33 | Arc: "#aa2afe",
34 | AsciiDoc: "#73a0c5",
35 | ASL: null,
36 | "ASP.NET": "#9400ff",
37 | AspectJ: "#a957b0",
38 | Assembly: "#6E4C13",
39 | Astro: "#ff5a03",
40 | Asymptote: "#ff0000",
41 | ATS: "#1ac620",
42 | Augeas: "#9CC134",
43 | AutoHotkey: "#6594b9",
44 | AutoIt: "#1C3552",
45 | "Avro IDL": "#0040FF",
46 | Awk: "#c30e9b",
47 | Ballerina: "#FF5000",
48 | BASIC: "#ff0000",
49 | Batchfile: "#C1F12E",
50 | Beef: "#a52f4e",
51 | Befunge: null,
52 | Berry: "#15A13C",
53 | BibTeX: "#778899",
54 | Bicep: "#519aba",
55 | Bikeshed: "#5562ac",
56 | Bison: "#6A463F",
57 | BitBake: "#00bce4",
58 | Blade: "#f7523f",
59 | BlitzBasic: "#00FFAE",
60 | BlitzMax: "#cd6400",
61 | Bluespec: "#12223c",
62 | "Bluespec BH": "#12223c",
63 | Boo: "#d4bec1",
64 | Boogie: "#c80fa0",
65 | BQN: "#2b7067",
66 | Brainfuck: "#2F2530",
67 | BrighterScript: "#66AABB",
68 | Brightscript: "#662D91",
69 | Browserslist: "#ffd539",
70 | C: "#555555",
71 | "C#": "#178600",
72 | "C++": "#f34b7d",
73 | "C2hs Haskell": null,
74 | "Cabal Config": "#483465",
75 | Caddyfile: "#22b638",
76 | Cadence: "#00ef8b",
77 | Cairo: "#ff4a48",
78 | CameLIGO: "#3be133",
79 | "CAP CDS": "#0092d1",
80 | "Cap'n Proto": "#c42727",
81 | CartoCSS: null,
82 | Ceylon: "#dfa535",
83 | Chapel: "#8dc63f",
84 | Charity: null,
85 | ChucK: "#3f8000",
86 | Circom: "#707575",
87 | Cirru: "#ccccff",
88 | Clarion: "#db901e",
89 | Clarity: "#5546ff",
90 | "Classic ASP": "#6a40fd",
91 | Clean: "#3F85AF",
92 | Click: "#E4E6F3",
93 | CLIPS: "#00A300",
94 | Clojure: "#db5855",
95 | "Closure Templates": "#0d948f",
96 | "Cloud Firestore Security Rules": "#FFA000",
97 | CMake: "#DA3434",
98 | COBOL: null,
99 | CodeQL: "#140f46",
100 | CoffeeScript: "#244776",
101 | ColdFusion: "#ed2cd6",
102 | "ColdFusion CFC": "#ed2cd6",
103 | COLLADA: "#F1A42B",
104 | "Common Lisp": "#3fb68b",
105 | "Common Workflow Language": "#B5314C",
106 | "Component Pascal": "#B0CE4E",
107 | Cool: null,
108 | Coq: "#d0b68c",
109 | crontab: "#ead7ac",
110 | Crystal: "#000100",
111 | CSON: "#244776",
112 | Csound: "#1a1a1a",
113 | "Csound Document": "#1a1a1a",
114 | "Csound Score": "#1a1a1a",
115 | CSS: "#563d7c",
116 | CSV: "#237346",
117 | Cuda: "#3A4E3A",
118 | CUE: "#5886E1",
119 | Curry: "#531242",
120 | CWeb: "#00007a",
121 | Cycript: null,
122 | Cypher: "#34c0eb",
123 | Cython: "#fedf5b",
124 | D: "#ba595e",
125 | D2: "#526ee8",
126 | Dafny: "#FFEC25",
127 | "Darcs Patch": "#8eff23",
128 | Dart: "#00B4AB",
129 | DataWeave: "#003a52",
130 | "Debian Package Control File": "#D70751",
131 | DenizenScript: "#FBEE96",
132 | Dhall: "#dfafff",
133 | "DIGITAL Command Language": null,
134 | "DirectX 3D File": "#aace60",
135 | DM: "#447265",
136 | Dockerfile: "#384d54",
137 | Dogescript: "#cca760",
138 | Dotenv: "#e5d559",
139 | DTrace: null,
140 | Dylan: "#6c616e",
141 | E: "#ccce35",
142 | Earthly: "#2af0ff",
143 | Easybuild: "#069406",
144 | eC: "#913960",
145 | "Ecere Projects": "#913960",
146 | ECL: "#8a1267",
147 | ECLiPSe: "#001d9d",
148 | Ecmarkup: "#eb8131",
149 | Edge: "#0dffe0",
150 | EdgeQL: "#31A7FF",
151 | EditorConfig: "#fff1f2",
152 | Eiffel: "#4d6977",
153 | EJS: "#a91e50",
154 | Elixir: "#6e4a7e",
155 | Elm: "#60B5CC",
156 | Elvish: "#55BB55",
157 | "Elvish Transcript": "#55BB55",
158 | "Emacs Lisp": "#c065db",
159 | EmberScript: "#FFF4F3",
160 | EQ: "#a78649",
161 | Erlang: "#B83998",
162 | Euphoria: "#FF790B",
163 | "F#": "#b845fc",
164 | "F*": "#572e30",
165 | Factor: "#636746",
166 | Fancy: "#7b9db4",
167 | Fantom: "#14253c",
168 | Faust: "#c37240",
169 | Fennel: "#fff3d7",
170 | "FIGlet Font": "#FFDDBB",
171 | "Filebench WML": "#F6B900",
172 | Filterscript: null,
173 | FIRRTL: "#2f632f",
174 | fish: "#4aae47",
175 | Fluent: "#ffcc33",
176 | FLUX: "#88ccff",
177 | Forth: "#341708",
178 | Fortran: "#4d41b1",
179 | "Fortran Free Form": "#4d41b1",
180 | FreeBasic: "#141AC9",
181 | FreeMarker: "#0050b2",
182 | Frege: "#00cafe",
183 | Futhark: "#5f021f",
184 | "G-code": "#D08CF2",
185 | "Game Maker Language": "#71b417",
186 | GAML: "#FFC766",
187 | GAMS: "#f49a22",
188 | GAP: "#0000cc",
189 | "GCC Machine Description": "#FFCFAB",
190 | GDB: null,
191 | GDScript: "#355570",
192 | GEDCOM: "#003058",
193 | "Gemfile.lock": "#701516",
194 | Gemini: "#ff6900",
195 | "Genero 4gl": "#63408e",
196 | "Genero per": "#d8df39",
197 | Genie: "#fb855d",
198 | Genshi: "#951531",
199 | "Gentoo Ebuild": "#9400ff",
200 | "Gentoo Eclass": "#9400ff",
201 | "Gerber Image": "#d20b00",
202 | Gherkin: "#5B2063",
203 | "Git Attributes": "#F44D27",
204 | "Git Config": "#F44D27",
205 | "Git Revision List": "#F44D27",
206 | Gleam: "#ffaff3",
207 | "Glimmer JS": "#F5835F",
208 | "Glimmer TS": "#3178c6",
209 | GLSL: "#5686a5",
210 | Glyph: "#c1ac7f",
211 | Gnuplot: "#f0a9f0",
212 | Go: "#00ADD8",
213 | "Go Checksums": "#00ADD8",
214 | "Go Module": "#00ADD8",
215 | "Go Workspace": "#00ADD8",
216 | "Godot Resource": "#355570",
217 | Golo: "#88562A",
218 | Gosu: "#82937f",
219 | Grace: "#615f8b",
220 | Gradle: "#02303a",
221 | "Gradle Kotlin DSL": "#02303a",
222 | "Grammatical Framework": "#ff0000",
223 | GraphQL: "#e10098",
224 | "Graphviz (DOT)": "#2596be",
225 | Groovy: "#4298b8",
226 | "Groovy Server Pages": "#4298b8",
227 | GSC: "#FF6800",
228 | Hack: "#878787",
229 | Haml: "#ece2a9",
230 | Handlebars: "#f7931e",
231 | HAProxy: "#106da9",
232 | Harbour: "#0e60e3",
233 | Haskell: "#5e5086",
234 | Haxe: "#df7900",
235 | HCL: "#844FBA",
236 | HiveQL: "#dce200",
237 | HLSL: "#aace60",
238 | HOCON: "#9ff8ee",
239 | HolyC: "#ffefaf",
240 | hoon: "#00b171",
241 | "Hosts File": "#308888",
242 | HTML: "#e34c26",
243 | "HTML+ECR": "#2e1052",
244 | "HTML+EEX": "#6e4a7e",
245 | "HTML+ERB": "#701516",
246 | "HTML+PHP": "#4f5d95",
247 | "HTML+Razor": "#512be4",
248 | HTTP: "#005C9C",
249 | HXML: "#f68712",
250 | Hy: "#7790B2",
251 | HyPhy: null,
252 | IDL: "#a3522f",
253 | Idris: "#b30000",
254 | "Ignore List": "#000000",
255 | "IGOR Pro": "#0000cc",
256 | "ImageJ Macro": "#99AAFF",
257 | Imba: "#16cec6",
258 | "Inform 7": null,
259 | INI: "#d1dbe0",
260 | Ink: null,
261 | "Inno Setup": "#264b99",
262 | Io: "#a9188d",
263 | Ioke: "#078193",
264 | Isabelle: "#FEFE00",
265 | "Isabelle ROOT": "#FEFE00",
266 | J: "#9EEDFF",
267 | Janet: "#0886a5",
268 | "JAR Manifest": "#b07219",
269 | Jasmin: "#d03600",
270 | Java: "#b07219",
271 | "Java Properties": "#2A6277",
272 | "Java Server Pages": "#2A6277",
273 | JavaScript: "#f1e05a",
274 | "JavaScript+ERB": "#f1e05a",
275 | JCL: "#d90e09",
276 | "Jest Snapshot": "#15c213",
277 | "JetBrains MPS": "#21D789",
278 | JFlex: "#DBCA00",
279 | Jinja: "#a52a22",
280 | Jison: "#56b3cb",
281 | "Jison Lex": "#56b3cb",
282 | Jolie: "#843179",
283 | jq: "#c7254e",
284 | JSON: "#292929",
285 | "JSON with Comments": "#292929",
286 | JSON5: "#267CB9",
287 | JSONiq: "#40d47e",
288 | JSONLD: "#0c479c",
289 | Jsonnet: "#0064bd",
290 | Julia: "#a270ba",
291 | "Julia REPL": "#a270ba",
292 | "Jupyter Notebook": "#DA5B0B",
293 | Just: "#384d54",
294 | "Kaitai Struct": "#773b37",
295 | KakouneScript: "#6f8042",
296 | KerboScript: "#41adf0",
297 | "KiCad Layout": "#2f4aab",
298 | "KiCad Legacy Layout": "#2f4aab",
299 | "KiCad Schematic": "#2f4aab",
300 | Kotlin: "#A97BFF",
301 | KRL: "#28430A",
302 | kvlang: "#1da6e0",
303 | LabVIEW: "#fede06",
304 | Lark: "#2980B9",
305 | Lasso: "#999999",
306 | Latte: "#f2a542",
307 | Lean: null,
308 | "Lean 4": null,
309 | Less: "#1d365d",
310 | Lex: "#DBCA00",
311 | LFE: "#4C3023",
312 | LigoLANG: "#0e74ff",
313 | LilyPond: "#9ccc7c",
314 | Limbo: null,
315 | Liquid: "#67b8de",
316 | "Literate Agda": "#315665",
317 | "Literate CoffeeScript": "#244776",
318 | "Literate Haskell": "#5e5086",
319 | LiveScript: "#499886",
320 | LLVM: "#185619",
321 | Logos: null,
322 | Logtalk: "#295b9a",
323 | LOLCODE: "#cc9900",
324 | LookML: "#652B81",
325 | LoomScript: null,
326 | LSL: "#3d9970",
327 | Lua: "#000080",
328 | Luau: "#00A2FF",
329 | M: null,
330 | M4: null,
331 | M4Sugar: null,
332 | Macaulay2: "#d8ffff",
333 | Makefile: "#427819",
334 | Mako: "#7e858d",
335 | Markdown: "#083fa1",
336 | Marko: "#42bff2",
337 | Mask: "#f97732",
338 | Mathematica: "#dd1100",
339 | MATLAB: "#e16737",
340 | Max: "#c4a79c",
341 | MAXScript: "#00a6a6",
342 | mcfunction: "#E22837",
343 | MDX: "#fcb32c",
344 | Mercury: "#ff2b2b",
345 | Mermaid: "#ff3670",
346 | Meson: "#007800",
347 | Metal: "#8f14e9",
348 | MiniD: null,
349 | MiniYAML: "#ff1111",
350 | Mint: "#02b046",
351 | Mirah: "#c7a938",
352 | "mIRC Script": "#3d57c3",
353 | MLIR: "#5EC8DB",
354 | Modelica: "#de1d31",
355 | "Modula-2": "#10253f",
356 | "Modula-3": "#223388",
357 | "Module Management System": null,
358 | Mojo: "#ff4c1f",
359 | Monkey: null,
360 | "Monkey C": "#8D6747",
361 | Moocode: null,
362 | MoonScript: "#ff4585",
363 | Motoko: "#fbb03b",
364 | "Motorola 68K Assembly": "#005daa",
365 | Move: "#4a137a",
366 | MQL4: "#62A8D6",
367 | MQL5: "#4A76B8",
368 | MTML: "#b7e1f4",
369 | MUF: null,
370 | mupad: "#244963",
371 | Mustache: "#724b3b",
372 | Myghty: null,
373 | nanorc: "#2d004d",
374 | Nasal: "#1d2c4e",
375 | NASL: null,
376 | NCL: "#28431f",
377 | Nearley: "#990000",
378 | Nemerle: "#3d3c6e",
379 | nesC: "#94B0C7",
380 | NetLinx: "#0aa0ff",
381 | "NetLinx+ERB": "#747faa",
382 | NetLogo: "#ff6375",
383 | NewLisp: "#87AED7",
384 | Nextflow: "#3ac486",
385 | Nginx: "#009639",
386 | Nim: "#ffc200",
387 | Nit: "#009917",
388 | Nix: "#7e7eff",
389 | NMODL: "#00356B",
390 | "NPM Config": "#cb3837",
391 | NSIS: null,
392 | Nu: "#c9df40",
393 | NumPy: "#9C8AF9",
394 | Nunjucks: "#3d8137",
395 | Nushell: "#4E9906",
396 | NWScript: "#111522",
397 | "OASv2-json": "#85ea2d",
398 | "OASv2-yaml": "#85ea2d",
399 | "OASv3-json": "#85ea2d",
400 | "OASv3-yaml": "#85ea2d",
401 | Oberon: null,
402 | "Objective-C": "#438eff",
403 | "Objective-C++": "#6866fb",
404 | "Objective-J": "#ff0c5a",
405 | ObjectScript: "#424893",
406 | OCaml: "#ef7a08",
407 | Odin: "#60AFFE",
408 | Omgrofl: "#cabbff",
409 | ooc: "#b0b77e",
410 | Opa: null,
411 | Opal: "#f7ede0",
412 | "Open Policy Agent": "#7d9199",
413 | "OpenAPI Specification v2": "#85ea2d",
414 | "OpenAPI Specification v3": "#85ea2d",
415 | OpenCL: "#ed2e2d",
416 | "OpenEdge ABL": "#5ce600",
417 | OpenQASM: "#AA70FF",
418 | "OpenRC runscript": null,
419 | OpenSCAD: "#e5cd45",
420 | "Option List": "#476732",
421 | Org: "#77aa99",
422 | Ox: null,
423 | Oxygene: "#cdd0e3",
424 | Oz: "#fab738",
425 | P4: "#7055b5",
426 | Pact: "#F7A8B8",
427 | Pan: "#cc0000",
428 | Papyrus: "#6600cc",
429 | Parrot: "#f3ca0a",
430 | "Parrot Assembly": null,
431 | "Parrot Internal Representation": null,
432 | Pascal: "#E3F171",
433 | Pawn: "#dbb284",
434 | PDDL: "#0d00ff",
435 | "PEG.js": "#234d6b",
436 | Pep8: "#C76F5B",
437 | Perl: "#0298c3",
438 | PHP: "#4F5D95",
439 | PicoLisp: "#6067af",
440 | PigLatin: "#fcd7de",
441 | Pike: "#005390",
442 | "Pip Requirements": "#FFD343",
443 | Pkl: "#6b9543",
444 | PlantUML: "#fbbd16",
445 | PLpgSQL: "#336790",
446 | PLSQL: "#dad8d8",
447 | PogoScript: "#d80074",
448 | Polar: "#ae81ff",
449 | Pony: null,
450 | Portugol: "#f8bd00",
451 | PostCSS: "#dc3a0c",
452 | PostScript: "#da291c",
453 | "POV-Ray SDL": "#6bac65",
454 | PowerBuilder: "#8f0f8d",
455 | PowerShell: "#012456",
456 | Praat: "#c8506d",
457 | Prisma: "#0c344b",
458 | Processing: "#0096D8",
459 | Procfile: "#3B2F63",
460 | Prolog: "#74283c",
461 | Promela: "#de0000",
462 | "Propeller Spin": "#7fa2a7",
463 | Pug: "#a86454",
464 | Puppet: "#302B6D",
465 | PureBasic: "#5a6986",
466 | PureScript: "#1D222D",
467 | Pyret: "#ee1e10",
468 | Python: "#3572A5",
469 | "Python console": "#3572A5",
470 | "Python traceback": "#3572A5",
471 | q: "#0040cd",
472 | "Q#": "#fed659",
473 | QMake: null,
474 | QML: "#44a51c",
475 | "Qt Script": "#00b841",
476 | Quake: "#882233",
477 | R: "#198CE7",
478 | Racket: "#3c5caa",
479 | Ragel: "#9d5200",
480 | Raku: "#0000fb",
481 | RAML: "#77d9fb",
482 | Rascal: "#fffaa0",
483 | RBS: "#701516",
484 | RDoc: "#701516",
485 | REALbasic: null,
486 | Reason: "#ff5847",
487 | ReasonLIGO: "#ff5847",
488 | Rebol: "#358a5b",
489 | "Record Jar": "#0673ba",
490 | Red: "#f50000",
491 | Redcode: null,
492 | "Regular Expression": "#009a00",
493 | "Ren'Py": "#ff7f7f",
494 | RenderScript: null,
495 | ReScript: "#ed5051",
496 | reStructuredText: "#141414",
497 | REXX: "#d90e09",
498 | Rez: "#FFDAB3",
499 | Ring: "#2D54CB",
500 | Riot: "#A71E49",
501 | RMarkdown: "#198ce7",
502 | RobotFramework: "#00c0b5",
503 | Roc: "#7c38f5",
504 | Roff: "#ecdebe",
505 | "Roff Manpage": "#ecdebe",
506 | RON: "#a62c00",
507 | Rouge: "#cc0088",
508 | "RouterOS Script": "#DE3941",
509 | RPC: null,
510 | RPGLE: "#2BDE21",
511 | Ruby: "#701516",
512 | RUNOFF: "#665a4e",
513 | Rust: "#dea584",
514 | Sage: null,
515 | SaltStack: "#646464",
516 | SAS: "#B34936",
517 | Sass: "#a53b70",
518 | Scala: "#c22d40",
519 | Scaml: "#bd181a",
520 | Scenic: "#fdc700",
521 | Scheme: "#1e4aec",
522 | Scilab: "#ca0f21",
523 | SCSS: "#c6538c",
524 | sed: "#64b970",
525 | Self: "#0579aa",
526 | ShaderLab: "#222c37",
527 | Shell: "#89e051",
528 | "ShellCheck Config": "#cecfcb",
529 | ShellSession: null,
530 | Shen: "#120F14",
531 | Sieve: null,
532 | "Simple File Verification": "#C9BFED",
533 | Singularity: "#64E6AD",
534 | Slash: "#007eff",
535 | Slice: "#003fa2",
536 | Slim: "#2b2b2b",
537 | Slint: "#2379F4",
538 | Smali: null,
539 | Smalltalk: "#596706",
540 | Smarty: "#f0c040",
541 | Smithy: "#c44536",
542 | SmPL: "#c94949",
543 | SMT: null,
544 | Snakemake: "#419179",
545 | Solidity: "#AA6746",
546 | SourcePawn: "#f69e1d",
547 | SPARQL: "#0C4597",
548 | SQF: "#3F3F3F",
549 | SQL: "#e38c00",
550 | SQLPL: "#e38c00",
551 | Squirrel: "#800000",
552 | "SRecode Template": "#348a34",
553 | Stan: "#b2011d",
554 | "Standard ML": "#dc566d",
555 | Starlark: "#76d275",
556 | Stata: "#1a5f91",
557 | STL: "#373b5e",
558 | StringTemplate: "#3fb34f",
559 | Stylus: "#ff6347",
560 | "SubRip Text": "#9e0101",
561 | SugarSS: "#2fcc9f",
562 | SuperCollider: "#46390b",
563 | Svelte: "#ff3e00",
564 | SVG: "#ff9900",
565 | Sway: "#00F58C",
566 | Sweave: "#198ce7",
567 | Swift: "#F05138",
568 | SWIG: null,
569 | SystemVerilog: "#DAE1C2",
570 | Talon: "#333333",
571 | Tcl: "#e4cc98",
572 | Tcsh: null,
573 | templ: "#66D0DD",
574 | Terra: "#00004c",
575 | "Terraform Template": "#7b42bb",
576 | TeX: "#3D6117",
577 | TextGrid: "#c8506d",
578 | Textile: "#ffe7ac",
579 | "TextMate Properties": "#df66e4",
580 | Thrift: "#D12127",
581 | "TI Program": "#A0AA87",
582 | "TL-Verilog": "#C40023",
583 | TLA: "#4b0079",
584 | Toit: "#c2c9fb",
585 | TOML: "#9c4221",
586 | TSQL: "#e38c00",
587 | TSV: "#237346",
588 | TSX: "#3178c6",
589 | Turing: "#cf142b",
590 | Twig: "#c1d026",
591 | TXL: "#0178b8",
592 | TypeScript: "#3178c6",
593 | Typst: "#239dad",
594 | "Unified Parallel C": "#4e3617",
595 | "Unity3D Asset": "#222c37",
596 | "Unix Assembly": null,
597 | Uno: "#9933cc",
598 | UnrealScript: "#a54c4d",
599 | UrWeb: "#ccccee",
600 | V: "#4f87c4",
601 | Vala: "#a56de2",
602 | "Valve Data Format": "#f26025",
603 | VBA: "#867db1",
604 | VBScript: "#15dcdc",
605 | VCL: "#148AA8",
606 | "Velocity Template Language": "#507cff",
607 | Verilog: "#b2b7f8",
608 | VHDL: "#adb2cb",
609 | "Vim Help File": "#199f4b",
610 | "Vim Script": "#199f4b",
611 | "Vim Snippet": "#199f4b",
612 | "Visual Basic .NET": "#945db7",
613 | "Visual Basic 6.0": "#2c6353",
614 | Volt: "#1F1F1F",
615 | Vue: "#41b883",
616 | Vyper: "#2980b9",
617 | WDL: "#42f1f4",
618 | "Web Ontology Language": "#5b70bd",
619 | WebAssembly: "#04133b",
620 | "WebAssembly Interface Type": "#6250e7",
621 | WebIDL: null,
622 | WGSL: "#1a5e9a",
623 | Whiley: "#d5c397",
624 | Wikitext: "#fc5757",
625 | "Windows Registry Entries": "#52d5ff",
626 | wisp: "#7582D1",
627 | "Witcher Script": "#ff0000",
628 | Wollok: "#a23738",
629 | "World of Warcraft Addon Data": "#f7e43f",
630 | Wren: "#383838",
631 | X10: "#4B6BEF",
632 | xBase: "#403a40",
633 | XC: "#99DA07",
634 | XML: "#0060ac",
635 | "XML Property List": "#0060ac",
636 | Xojo: "#81bd41",
637 | Xonsh: "#285EEF",
638 | XProc: null,
639 | XQuery: "#5232e7",
640 | XS: null,
641 | XSLT: "#EB8CEB",
642 | Xtend: "#24255d",
643 | Yacc: "#4B6C4B",
644 | YAML: "#cb171e",
645 | YARA: "#220000",
646 | YASnippet: "#32AB90",
647 | Yul: "#794932",
648 | ZAP: "#0d665e",
649 | Zeek: null,
650 | ZenScript: "#00BCD1",
651 | Zephir: "#118f9e",
652 | Zig: "#ec915c",
653 | ZIL: "#dc75e5",
654 | Zimpl: "#d67711",
655 | };
656 |
--------------------------------------------------------------------------------
/utils/languages.ts:
--------------------------------------------------------------------------------
1 |
2 | import { RawLanguageData } from "@/types/Languages";
3 |
4 | export default async function LangData(user: string) {
5 | const graph = await fetch("https://api.github.com/graphql", {
6 | method: "POST",
7 | headers: {
8 | "Cache-Control": "private; stale-while-revalidate=3600",
9 | "Content-Type": "application/json",
10 | Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
11 | },
12 | body: JSON.stringify({
13 | query: `
14 | query {
15 | user(login: "${user}") {
16 | repositories(
17 | first: 100
18 | ) {
19 | edges {
20 | node {
21 | languages(first: 10) {
22 | totalSize
23 | edges {
24 | size
25 | node {
26 | name
27 | color
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | `,
37 | variables: {},
38 | }),
39 | });
40 |
41 | const data: RawLanguageData = await graph.json();
42 |
43 | return data
44 | }
--------------------------------------------------------------------------------
/utils/repo.ts:
--------------------------------------------------------------------------------
1 | import { RawRepoData } from "@/types/Repo";
2 |
3 | export default async function RepoData(user: string, repo: string) {
4 | const graph = await fetch("https://api.github.com/graphql", {
5 | method: "POST",
6 | headers: {
7 | "Content-Type": "application/json",
8 | "Cache-Control": "private; stale-while-revalidate=3600",
9 | Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
10 | },
11 | body: JSON.stringify({
12 | query: `
13 | query {
14 | repositoryOwner(login:"${user}") {
15 | repository(name:"${repo}") {
16 | name
17 | description
18 | stargazerCount
19 | forkCount
20 | primaryLanguage {
21 | name
22 | color
23 | }
24 | }
25 | }
26 | }
27 | `,
28 | variables: {},
29 | }),
30 | });
31 |
32 | const data: RawRepoData = await graph.json();
33 | return data;
34 | }
35 |
--------------------------------------------------------------------------------
/utils/repositories.ts:
--------------------------------------------------------------------------------
1 | export default async function RepoList(user: string) {
2 | const graph = await fetch("https://api.github.com/graphql", {
3 | method: "POST",
4 | headers: {
5 | "Content-Type": "application/json",
6 | "Cache-Control": "private; stale-while-revalidate=3600",
7 | Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
8 | },
9 | body: JSON.stringify({
10 | query: `
11 | query {
12 | user(login: "${user}") {
13 | repositories(first: 1, orderBy: {direction: DESC, field: PUSHED_AT}) {
14 | edges {
15 | node {
16 | id
17 | url
18 | name
19 | description
20 | primaryLanguage {
21 | name
22 | }
23 | }
24 | }
25 | }
26 | }
27 | }
28 | `,
29 | variables: {},
30 | }),
31 | });
32 |
33 | const data = await graph.json();
34 | return data;
35 | }
36 |
--------------------------------------------------------------------------------
/utils/users.ts:
--------------------------------------------------------------------------------
1 | import { RawUserData } from "@/types/UserStats";
2 |
3 | export default async function UserData(user: string) {
4 | const graph = await fetch("https://api.github.com/graphql", {
5 | method: "POST",
6 | headers: {
7 | "Content-Type": "application/json",
8 | "Cache-Control": "private; stale-while-revalidate=3600",
9 | Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
10 | },
11 | body: JSON.stringify({
12 | query: `
13 | query {
14 | user(login: "${user}") {
15 | contributionsCollection {
16 | totalCommitContributions
17 | totalPullRequestContributions
18 | totalIssueContributions
19 | contributionCalendar {
20 | totalContributions
21 | }
22 | totalRepositoriesWithContributedCommits
23 | }
24 | repositories(first: 100) {
25 | nodes {
26 | stargazers {
27 | totalCount
28 | }
29 | }
30 | }
31 | issues {
32 | totalCount
33 | }
34 | pullRequests {
35 | totalCount
36 | }
37 | followers {
38 | totalCount
39 | }
40 | }
41 | }
42 | `,
43 | variables: {},
44 | }),
45 | });
46 |
47 | const data: RawUserData = await graph.json();
48 | return data;
49 | }
50 |
--------------------------------------------------------------------------------
/utils/wakatime.ts:
--------------------------------------------------------------------------------
1 | import { RawWakaData } from "@/types/Waka";
2 |
3 | export default async function Wakatime(user: string) {
4 | const url = `https://api.wakatime.com/api/v1/users/${encodeURIComponent(user)}/stats?is_including_today=true&range=all_time`;
5 |
6 | const response = await fetch(url, {
7 | method: "GET"
8 | });
9 |
10 | const data: RawWakaData = await response.json();
11 | return data;
12 | }
13 |
--------------------------------------------------------------------------------