├── .env
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── features.txt
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── App.jsx
├── assets
│ └── images
│ │ ├── avatar.png
│ │ ├── chart.png
│ │ ├── contributionGraph.svg
│ │ ├── friday.png
│ │ ├── logo.png
│ │ ├── monday.png
│ │ ├── octocat.png
│ │ ├── saturday.png
│ │ ├── sunday.png
│ │ ├── thursday.png
│ │ ├── tuesday.png
│ │ ├── webLogo.png
│ │ └── wednesday.png
├── components
│ ├── ActivityStats.jsx
│ ├── Canvas.jsx
│ ├── ChartAndProductiveDay.jsx
│ ├── ContributionGraph.jsx
│ ├── ContributionSummary.jsx
│ ├── Cursor.jsx
│ ├── Footer.jsx
│ ├── GithubContributionCalendar.jsx
│ ├── LanguageChart.jsx
│ ├── Loader.jsx
│ ├── Modal.jsx
│ ├── PopularPR.jsx
│ ├── Quote.jsx
│ ├── TopRepos.jsx
│ └── UserDetails.jsx
├── constants
│ └── scrollClass.js
├── index.css
├── main.jsx
├── pages
│ ├── Homepage.jsx
│ └── StatsPage.jsx
└── utils
│ ├── getActiveDays.js
│ ├── getAllRepos.js
│ ├── getBadge.js
│ ├── getCanvas.js
│ ├── getContributionData.js
│ ├── getDayIndex.js
│ ├── getMostUsedLanguages.js
│ ├── getOctokit.js
│ ├── getQuote.js
│ ├── getScrollAnimation.js
│ ├── moveCursor.js
│ └── validateGithubUsername.js
├── tailwind.config.js
├── vercel.json
└── vite.config.js
/.env:
--------------------------------------------------------------------------------
1 | VITE_GITHUB_TOKEN = "GITHUB_TOKEN"
2 | VITE_API_NINJAS_KEY = "API_NINJAS_KEY"
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | yarn.lock
26 | .env
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
GitWrap - GitHub Contributions Analyzer
2 |
3 | GitWrap is a web application that analyzes your GitHub contributions, showcasing a personalized summary of your coding journey throughout the year. Reflect, celebrate, and share your open-source achievements!
4 |
5 |
6 | ## Tech Stack
7 |
8 | - **React:** Frontend framework for building user interfaces.
9 | - **GraphQL:** Query language for APIs, used to fetch GitHub data.
10 | - **GitHub API:** Interface for accessing various GitHub features.
11 | - **Octokit:** GitHub REST API client for programmatic access to GitHub resources.
12 | - **React Icons:** Library providing a set of popular icons for React applications.
13 |
14 | ## Getting Started
15 | Follow these steps to get started with GitWrap:
16 |
17 | ### Prerequisite
18 |
19 | - Node.js (https://nodejs.org/) installed on your machine.
20 |
21 | ### Installation
22 |
23 | 1. Clone the repository:
24 | ```bash
25 | git clone https://github.com/singodiyashubham87/GitWrap.git
26 | cd GitWrap
27 | ```
28 |
29 | 2. Install dependencies:
30 | ```bash
31 | npm install
32 | ```
33 | 3. Edit the .env file & add your Github Personal Access Token:
34 | ```bash
35 | VITE_GITHUB_TOKEN = "GITHUB_PERSONAL_ACCESS_TOKEN"
36 | ```
37 | 4. Start the app:
38 | ```bash
39 | npm run dev
40 | ```
41 |
42 | ## Features
43 |
44 | - **Contribution Summary:** Get an overview of your GitHub contributions for a specified time range.
45 | - **Total Active Days:** Track the total number of days you actively contributed to repositories.
46 | - **Max Streak:** Identify your longest streak of consecutive days with contributions.
47 | - **Most Productive Date:** Find out the date when you made the maximum number of contributions.
48 | - **Languages Used:** Explore the languages you've used across all your repositories.
49 |
50 | ## Credits
51 | Special thanks to [GitHub](https://github.com/) for providing the [GitHub GraphQL API](https://docs.github.com/en/graphql) that makes this project possible.
52 |
53 | ## Author
54 | * Shubham Singodiya
55 |
56 | ## Contributors
57 |
58 |
63 |
64 | ## License
65 | This project is licensed under the MIT License.
66 |
67 | ## Support
68 | Support the project by starring the repository.
69 |
70 | ## Using Daytona to Develop GitWrap
71 | To make it easier for others to contribute to this project, we've integrated Daytona for a fast and consistent development environment. Follow the steps below to spin up a Daytona workspace for GitWrap:
72 |
73 | ### Prerequisites
74 | **Daytona** must be installed on your system (follow the instructions here).
75 |
76 | ## Steps to Get Started with Daytona
77 |
78 | 1. Clone the repository (if not done already):
79 | ```bash
80 | git clone https://github.com/singodiyashubham87/GitWrap.git
81 | cd GitWrap
82 | ```
83 |
84 | 2. Create a Daytona workspace for the project: To quickly spin up a development environment,
85 | use Daytona:
86 | ```bash
87 | daytona create https://github.com/singodiyashubham87/GitWrap
88 | ```
89 |
90 | 3. Access the workspace: Daytona will create and start a workspace for you. Once it's running, you can access it in your browser or through VS Code using the Remote SSH feature.
91 |
92 | 4. Start working: Once you're inside the Daytona workspace, you can start contributing to the project immediately by editing code, testing, and running the app within a pre-configured environment.
93 |
--------------------------------------------------------------------------------
/features.txt:
--------------------------------------------------------------------------------
1 | Canvas on login page
2 | Lazy Loading
3 | Search Functionality
4 | Dependabot
5 | Dockerise
6 | CI/CD
7 |
8 |
9 | Data fetch Done:
10 |
11 | Separate Card:
12 | badgeDetailsIcon
13 |
14 |
15 | Implemented:
16 |
17 |
18 | >Username
19 | >Followers
20 | >Location
21 | >Badge
22 |
23 | >Total Contributions
24 | >Issues Opened
25 | >Issues Closed
26 | >Total Commits
27 | >Total Pull Requests
28 |
29 | Separate Card:
30 | [getActiveDays()]
31 | >Active Days(activeDays)
32 | >Most Productive Date(mostProductiveDate) + maxContributionCount on that day(maxContributionCount)
33 | >Max. Streak(maxStreak)
34 |
35 | Separate Card:
36 | >Top Repo 1
37 | >Top Repo 2
38 | >Top Repo 3
39 |
40 | Separate Card:
41 | >Popular Pr name
42 | >Popular Pr state
43 | >Popular Pr creation date
44 | >Popular Pr URL
45 |
46 | Separate Card:
47 | [getMostUsedLanguage()]
48 | >Most used languages[Array of 5]
49 | {Show using piechart by utilising chart.js}
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | GitWrap - GitHub Contributions Analyzer!
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wrapgit",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@cubejs-client/core": "^0.35.23",
14 | "chart.js": "^4.4.1",
15 | "octokit": "^3.1.2",
16 | "react": "^18.3.1",
17 | "react-dom": "^18.3.1",
18 | "react-github-calendar": "^4.1.3",
19 | "react-icons": "^5.2.1",
20 | "react-router-dom": "^6.22.1"
21 | },
22 | "devDependencies": {
23 | "@types/react": "^18.3.1",
24 | "@types/react-dom": "^18.3.0",
25 | "@vitejs/plugin-react": "^4.2.1",
26 | "autoprefixer": "^10.4.19",
27 | "eslint": "^8.55.0",
28 | "eslint-plugin-react": "^7.34.1",
29 | "eslint-plugin-react-hooks": "^4.6.2",
30 | "eslint-plugin-react-refresh": "^0.4.5",
31 | "postcss": "^8.4.33",
32 | "tailwindcss": "^3.4.1",
33 | "vite": "^5.1.7"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | }
6 | }
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import Homepage from "./pages/Homepage";
2 | import StatsPage from "./pages/StatsPage";
3 | import {BrowserRouter as Router,Routes, Route} from "react-router-dom";
4 |
5 | function App() {
6 | return (
7 |
8 |
9 | }/>
10 | }/>
11 |
12 |
13 | )
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/src/assets/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/avatar.png
--------------------------------------------------------------------------------
/src/assets/images/chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/chart.png
--------------------------------------------------------------------------------
/src/assets/images/friday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/friday.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/monday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/monday.png
--------------------------------------------------------------------------------
/src/assets/images/octocat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/octocat.png
--------------------------------------------------------------------------------
/src/assets/images/saturday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/saturday.png
--------------------------------------------------------------------------------
/src/assets/images/sunday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/sunday.png
--------------------------------------------------------------------------------
/src/assets/images/thursday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/thursday.png
--------------------------------------------------------------------------------
/src/assets/images/tuesday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/tuesday.png
--------------------------------------------------------------------------------
/src/assets/images/webLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/webLogo.png
--------------------------------------------------------------------------------
/src/assets/images/wednesday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/singodiyashubham87/gitwrap/f2605c284e6e7139803f09e9aa0caf06827f03fd/src/assets/images/wednesday.png
--------------------------------------------------------------------------------
/src/components/ActivityStats.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { FaGithubAlt } from "react-icons/fa";
3 | import { scrollClass } from "../constants/scrollClass";
4 |
5 |
6 | const ActivityStats = (props) => {
7 | const { activeDays, maxStreak, mostProductiveDate, maxContributionCount } = props;
8 | return (
9 |
10 |
15 |
16 |
17 | Activity
18 |
19 |
20 |
21 | Active Days:
22 | {activeDays}
23 |
24 |
25 | Highest Streak:
26 | {maxStreak}
27 |
28 |
29 | Most Productive Date:
30 | {mostProductiveDate}
31 |
32 |
33 | Most Contribution Count:
34 | {maxContributionCount}
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default ActivityStats;
43 |
--------------------------------------------------------------------------------
/src/components/Canvas.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import getCanvas from "../utils/getCanvas";
3 |
4 | const Canvas = () => {
5 | useEffect(() => {
6 | getCanvas();
7 | }, []);
8 |
9 | return (
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | export default Canvas;
17 |
--------------------------------------------------------------------------------
/src/components/ChartAndProductiveDay.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { useState, useEffect } from "react";
3 | import getDayIndex from "../utils/getDayIndex";
4 |
5 | import LanguageChart from "../components/LanguageChart";
6 |
7 | // import Days Name images
8 | import sunday from "../assets/images/sunday.png";
9 | import monday from "../assets/images/monday.png";
10 | import tuesday from "../assets/images/tuesday.png";
11 | import wednesday from "../assets/images/wednesday.png";
12 | import thursday from "../assets/images/thursday.png";
13 | import friday from "../assets/images/friday.png";
14 | import saturday from "../assets/images/saturday.png";
15 | import { scrollClass } from "../constants/scrollClass";
16 |
17 |
18 | const ChartAndProductiveDay = (props) => {
19 | const {
20 | langArray,
21 | langButton,
22 | handleLangButton,
23 | mostProductiveDate,
24 | } = props;
25 |
26 | // Day-related states
27 | const [dayIndex, setDayIndex] = useState(null);
28 | const [dayImage, setDayImage] = useState(null);
29 |
30 | // Effect to update day index based on most productive date
31 | useEffect(() => {
32 | if (mostProductiveDate) {
33 | setDayIndex(getDayIndex(mostProductiveDate));
34 | }
35 | }, [mostProductiveDate]);
36 |
37 | // Effect to set day image based on day index
38 | useEffect(() => {
39 | if (dayIndex !== null) {
40 | switch (dayIndex) {
41 | case 0:
42 | setDayImage(sunday);
43 | break;
44 | case 1:
45 | setDayImage(monday);
46 | break;
47 | case 2:
48 | setDayImage(tuesday);
49 | break;
50 | case 3:
51 | setDayImage(wednesday);
52 | break;
53 | case 4:
54 | setDayImage(thursday);
55 | break;
56 | case 5:
57 | setDayImage(friday);
58 | break;
59 | case 6:
60 | setDayImage(saturday);
61 | break;
62 | default:
63 | setDayImage(null);
64 | }
65 | }
66 | }, [dayIndex]);
67 |
68 | return (
69 |
70 | {langButton ? (
71 |
75 | Get Most Used Languages
76 |
77 | ) : (
78 |
79 | {langArray && }
80 |
81 | Most Used Languages
82 |
83 |
84 | )}
85 |
86 |
87 |
92 |
93 |
94 | Most Productive Day
95 |
96 |
97 |
98 | );
99 | };
100 |
101 | export default ChartAndProductiveDay;
102 |
--------------------------------------------------------------------------------
/src/components/ContributionGraph.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { GiWinterGloves } from "react-icons/gi";
3 | import GithubContributionCalendar from "./GithubContributionCalendar";
4 |
5 | const ContributionGraph = ({userDetails}) => {
6 | const { username } = userDetails;
7 |
8 | return (
9 |
10 |
11 |
12 | Contribution Graph
13 |
14 |
15 | {username && (
16 |
21 | )}
22 |
23 |
24 | );
25 | };
26 |
27 | export default ContributionGraph;
--------------------------------------------------------------------------------
/src/components/ContributionSummary.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | // import icons from react-icons
3 | import { VscRepo } from "react-icons/vsc";
4 | import { IoGitCommitOutline } from "react-icons/io5";
5 | import { IoIosGitMerge, IoIosGitPullRequest } from "react-icons/io";
6 | import { GoIssueOpened, GoIssueClosed, GoIssueDraft } from "react-icons/go";
7 | import { GiWinterHat } from "react-icons/gi";
8 | import { HiOutlineExternalLink } from "react-icons/hi";
9 | import { scrollClass } from "../constants/scrollClass";
10 |
11 | const ContributionSummary = (props) => {
12 | const {
13 | totalContributions,
14 | totalRepositoryContributions,
15 | openIssues,
16 | closedIssues,
17 | totalIssueContributions,
18 | totalCommitContributions,
19 | totalPullRequestContributions,
20 | userDetails,
21 | } = props;
22 |
23 | return (
24 | <>
25 | {totalContributions && (
26 |
27 |
28 |
29 |
30 | Summary
31 |
32 |
33 |
34 |
35 |
36 | Total Contributions:{" "}
37 |
38 |
39 |
40 | {totalContributions}
41 |
42 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Total Repos Contributed:{" "}
56 |
57 |
58 |
59 | {totalRepositoryContributions}
60 |
61 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Issues Open:{" "}
75 |
76 |
77 |
78 | {openIssues}
79 |
80 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | Issues Closed:{" "}
94 |
95 |
107 |
108 |
109 |
110 |
111 |
112 | Total Issue Contributions:{" "}
113 |
114 |
115 |
116 | {totalIssueContributions}
117 |
118 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | Total Commit Contributions:{" "}
132 |
133 |
134 |
135 | {totalCommitContributions}
136 |
137 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 | Pull Requests Contributions:{" "}
151 |
152 |
153 |
154 | {totalPullRequestContributions}
155 |
156 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | )}
168 | >
169 | );
170 | };
171 |
172 | export default ContributionSummary;
173 |
--------------------------------------------------------------------------------
/src/components/Cursor.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import moveCursor from "../utils/moveCursor";
3 |
4 | const Cursor = () => {
5 | useEffect(() => {
6 | moveCursor();
7 | });
8 |
9 | return (
10 | <>
11 |
15 |
19 | >
20 | );
21 | };
22 |
23 | export default Cursor;
24 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 |
18 | );
19 | };
20 |
21 | export default Footer;
22 |
--------------------------------------------------------------------------------
/src/components/GithubContributionCalendar.jsx:
--------------------------------------------------------------------------------
1 | import GitHubCalendar from "react-github-calendar";
2 |
3 | const GithubContributionCalendar = (props) => {
4 | // eslint-disable-next-line react/prop-types
5 | const { ghUsername, sizeOfBlock, marginOfBlock } = props;
6 |
7 |
8 | return (
9 |
20 | );
21 | };
22 |
23 | export default GithubContributionCalendar;
24 |
--------------------------------------------------------------------------------
/src/components/LanguageChart.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import Chart from "chart.js/auto";
3 |
4 | // eslint-disable-next-line react/prop-types
5 | function LanguageChart({data}) {
6 | const chartContainer = useRef(null);
7 | const chartInstance = useRef(null);
8 |
9 | useEffect(() => {
10 | const chartCanvas = chartContainer.current;
11 | if (chartCanvas) {
12 | // Destroy previous chart instance
13 | if (chartInstance.current) {
14 | chartInstance.current.destroy();
15 | }
16 |
17 | // Create a new chart instance
18 | chartInstance.current = new Chart(chartCanvas, {
19 | type: "pie",
20 | data: {
21 | // eslint-disable-next-line react/prop-types
22 | labels: data.map((row) => row.name),
23 | datasets: [
24 | {
25 | label: "Lines of Code",
26 | // eslint-disable-next-line react/prop-types
27 | data: data.map((row) => row.count),
28 | },
29 | ],
30 | },
31 | });
32 | }
33 | }, [data]);
34 |
35 | return (
36 | <>
37 |
42 | >
43 | );
44 | }
45 |
46 | export default LanguageChart;
47 |
--------------------------------------------------------------------------------
/src/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import logo from "../assets/images/webLogo.png"
2 |
3 | const Loader = () => {
4 | return (
5 |
6 |
12 |
13 | )
14 | }
15 |
16 | export default Loader;
--------------------------------------------------------------------------------
/src/components/Modal.jsx:
--------------------------------------------------------------------------------
1 | const Modal = (props) => {
2 | // eslint-disable-next-line react/prop-types
3 | const { alert, alertError, closeModal } = props;
4 |
5 | return (
6 | <>
7 |
11 |
12 |
13 | {alert || "Alert!"}
14 |
15 |
16 | {alertError}
17 |
18 |
22 | Close
23 |
24 |
25 | >
26 | );
27 | };
28 |
29 | export default Modal;
--------------------------------------------------------------------------------
/src/components/PopularPR.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { FaGitAlt } from "react-icons/fa";
3 | import { HiOutlineExternalLink } from "react-icons/hi";
4 | import { scrollClass } from "../constants/scrollClass";
5 |
6 | const PopularPR = (props) => {
7 | const { popularPrName, popularPrState, popularPrCreationDate, popularPrURL } = props;
8 | return (
9 | <>
10 | {popularPrName === "undefined" ? null : (
11 |
12 |
17 |
18 |
19 | Top Pull Request
20 |
21 |
22 |
23 | Title:
24 |
25 | {popularPrName}
26 |
27 |
28 |
29 | State:
30 |
31 | {popularPrState}
32 |
33 |
34 |
35 | Created On:
36 |
37 | {popularPrCreationDate}
38 |
39 |
40 |
41 | PR Link:
42 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | )}
55 | >
56 | );
57 | };
58 |
59 | export default PopularPR;
60 |
--------------------------------------------------------------------------------
/src/components/Quote.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { LuGitPullRequestClosed, LuGitFork } from "react-icons/lu";
3 | import { scrollClass } from "../constants/scrollClass";
4 |
5 | const Quote = ({ quote, author }) => {
6 | return (
7 |
8 |
9 |
10 |
11 | Quote of the Day
12 |
13 |
14 | {`"${quote ?? 'Hope is a good thing my friend.'}"`}
15 |
16 | {`-${author ?? 'Master Mickey'}`}
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default Quote;
24 |
--------------------------------------------------------------------------------
/src/components/TopRepos.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { FaGithub, FaGitlab } from "react-icons/fa";
3 | import { scrollClass } from "../constants/scrollClass";
4 |
5 | const TopRepos = ({ topReposArray }) => {
6 | return (
7 | <>
8 | {topReposArray.length > 2 ? (
9 |
10 |
11 |
12 | Repositories
13 |
14 |
15 | {topReposArray?.slice(0,4)?.map((repo, index) => (
16 |
20 |
21 | {index + 1}. {`${repo.name}`}
22 |
23 |
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 |
40 |
41 | ) : null}
42 | >
43 | );
44 | };
45 |
46 | export default TopRepos;
47 |
--------------------------------------------------------------------------------
/src/components/UserDetails.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import avatar from "../assets/images/avatar.png";
3 | import { FaGithub, FaQuestionCircle } from "react-icons/fa";
4 | import { TbChristmasTree } from "react-icons/tb";
5 | import getBadge from "../utils/getBadge";
6 |
7 | const UserDetails = (props) => {
8 | const { userInfo, totalContributions } = props;
9 | const { userAvatar, githubURL, username, followers, location } = userInfo;
10 |
11 | return (
12 |
13 |
25 |
26 |
27 |
28 | User Details
29 |
30 |
31 |
32 | Username:
33 | {username}
34 |
35 |
36 | Followers:
37 | {followers}
38 |
39 |
40 | Location:
41 | {location}
42 |
43 |
44 | Badge:
45 |
46 | {getBadge(totalContributions)}
47 | {/* */}
51 |
52 |
53 |
54 | {"Contributions > 1500 => Master"}
55 |
56 |
57 | {"Contributions > 1000 && <= 1500 => Expert"}
58 |
59 |
60 |
61 | {
62 | "Contributions > 500 && <= 1000 => Intermediate"
63 | }
64 |
65 |
66 |
67 | {"Contributions > 250 && <= 500 => Skilled"}
68 |
69 |
70 | {"Contributions <= 250 => Novice"}
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export default UserDetails;
82 |
--------------------------------------------------------------------------------
/src/constants/scrollClass.js:
--------------------------------------------------------------------------------
1 | export const scrollClass = "reveal opacity-0 translate-y-[150px] transition ease duration-500";
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Borel&family=Inter:wght@400;500;600;700&display=swap');
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 |
8 | :root {
9 | background-color: #212325;
10 | }
11 |
12 | body {
13 | margin: 0;
14 | padding: 0;
15 | box-sizing: border-box;
16 | }
17 |
18 | /* for scroll animation */
19 | .active{
20 | transform: translateY(0px);
21 | opacity: 1;
22 | }
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.jsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/src/pages/Homepage.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import Modal from "../components/Modal";
4 | import Loader from "../components/Loader";
5 | import Canvas from "../components/Canvas";
6 | import Cursor from "../components/Cursor";
7 | import logo from "../assets/images/logo.png";
8 | import octocatImage from "../assets/images/octocat.png";
9 | import { FaRegArrowAltCircleRight } from "react-icons/fa";
10 | import validateGithubUsername from "../utils/validateGithubUsername";
11 |
12 | const Homepage = () => {
13 | const navigateTo = useNavigate();
14 | const [showModal, setShowModal] = useState(false);
15 | const [ghUsername, setGhUsername] = useState("");
16 | const [loader, setLoader] = useState(false);
17 |
18 | useEffect(() => localStorage.clear(), []);
19 |
20 | const handleInputChange = (e) => setGhUsername(e.target.value);
21 |
22 | const handleSubmit = async (e) => {
23 | if (e.type === "click" || e.key === "Enter") {
24 | e.preventDefault();
25 | if (!ghUsername) return setShowModal(true);
26 | setLoader(true);
27 | const isValidUsername = await validateGithubUsername(ghUsername);
28 | setLoader(false);
29 | isValidUsername ? navigateTo("/stats") : setShowModal(true);
30 | }
31 | };
32 |
33 | return (
34 | <>
35 | {showModal && (
36 | setShowModal(false)}
40 | />
41 | )}
42 | {loader && }
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
57 |
58 |
62 |
63 |
64 |
65 |
66 | >
67 | );
68 | };
69 |
70 | export default Homepage;
71 |
--------------------------------------------------------------------------------
/src/pages/StatsPage.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | // import Components
4 | import Loader from "../components/Loader";
5 | import Canvas from "../components/Canvas";
6 | import Cursor from "../components/Cursor";
7 | import UserDetails from "../components/UserDetails";
8 | import ContributionGraph from "../components/ContributionGraph";
9 | import ContributionSummary from "../components/ContributionSummary";
10 | import ActivityStats from "../components/ActivityStats";
11 | import TopRepos from "../components/TopRepos";
12 | import PopularPR from "../components/PopularPR";
13 | import ChartAndProductiveDay from "../components/ChartAndProductiveDay";
14 | import Quote from "../components/Quote";
15 | import Footer from "../components/Footer";
16 |
17 | // import icons from react-icons
18 | import { FaStar } from "react-icons/fa";
19 |
20 | // import utility functions
21 | import getQuote from "../utils/getQuote";
22 | import getScrollAnimation from "../utils/getScrollAnimation";
23 | import getContributionData from "../utils/getContributionData";
24 | import getMostUsedLanguages from "../utils/getMostUsedLanguages";
25 |
26 | const StatsPage = () => {
27 | getScrollAnimation();
28 | // Loading state
29 | const [loader, setLoader] = useState(true);
30 |
31 | // User info states
32 | const [userDetails, setUserDetails] = useState({
33 | userAvatar: "",
34 | username: "",
35 | followers: "",
36 | location: "unknown",
37 | githubURL: "",
38 | });
39 |
40 | // User contribution data states
41 | const [totalContributions, setTotalContributions] = useState("");
42 | const [openIssues, setOpenIssues] = useState("");
43 | const [closedIssues, setClosedIssues] = useState("");
44 | const [totalIssueContributions, setTotalIssueContributions] = useState("");
45 | const [totalCommitContributions, setTotalCommitContributions] = useState("");
46 | const [totalPullRequestContributions, setTotalPullRequestContributions] =
47 | useState("");
48 |
49 | // User's Popular PR data
50 | const [popularPrName, setPopularPrName] = useState("");
51 | const [popularPrState, setPopularPrState] = useState("");
52 | const [popularPrCreationDate, setPopularPrCreationDate] = useState("");
53 | const [popularPrURL, setPopularPrURL] = useState("");
54 |
55 | // User Top Repos data
56 | const [totalRepositoryContributions, setTotalRepositoryContributions] =
57 | useState("");
58 | const [topReposCount, setTopReposCount] = useState("");
59 | const [topReposArray, setTopReposArray] = useState([]);
60 |
61 | // User activity data states
62 | const [mostProductiveDate, setMostProductiveDate] = useState("");
63 | const [maxContributionCount, setMaxContributionCount] = useState("");
64 | const [maxStreak, setMaxStreak] = useState("");
65 | const [activeDays, setActiveDays] = useState("");
66 |
67 | // Quotes states
68 | const [quote, setQuote] = useState(
69 | "You may say I'm a dreamer, but I'm not the only one. I hope someday you'll join us. And the world will live as one."
70 | );
71 | const [author, setAuthor] = useState("John Lennon");
72 |
73 | // Language-related states
74 | const [langButton, setLangButton] = useState(true);
75 | const [langArray, setLangArray] = useState([]);
76 |
77 | // Handle language button click
78 | const handleLangButton = async () => {
79 | setLoader(true);
80 | const resArray = await getMostUsedLanguages(userDetails.username);
81 | setLangArray(resArray);
82 | setLangButton(false);
83 | setLoader(false);
84 | };
85 |
86 | // Effect to fetch user data when the username changes
87 | useEffect(() => {
88 | const fetchData = async () => {
89 | if (userDetails.username) {
90 | try {
91 | await fetchQuote()
92 | await getContributionData(userDetails.username)
93 | updateState();
94 | } catch (error) {
95 | console.error("Error fetching data:", error);
96 | } finally {
97 | setLoader(false);
98 | }
99 | }
100 | };
101 | fetchData();
102 | }, [userDetails.username]);
103 |
104 | // Function to fetch a random quote
105 | async function fetchQuote() {
106 | const res = await getQuote();
107 | setQuote(res[0].quote);
108 | setAuthor(res[0].author);
109 | }
110 |
111 | // Effect to update user-related states from local storage
112 | useEffect(() => {
113 | setUserDetails({
114 | userAvatar: localStorage.getItem("avatar") || "",
115 | username: localStorage.getItem("username") || "errorGettingUsername",
116 | followers: localStorage.getItem("followers") || "-1",
117 | location:
118 | localStorage.getItem("location") !== "null"
119 | ? localStorage.getItem("location") || "errorGettingLocation"
120 | : "unknown",
121 | githubURL: localStorage.getItem("githubUrl") || "errorGettingGithubURL",
122 | });
123 | }, []);
124 |
125 | // Function to update contribution-related states
126 | function updateState() {
127 | // Contribution data
128 | setTotalContributions(localStorage.getItem("totalContributions") || "");
129 | setOpenIssues(localStorage.getItem("openIssues") || "");
130 | setClosedIssues(localStorage.getItem("closedIssues") || "");
131 | setTotalIssueContributions(
132 | localStorage.getItem("totalIssueContributions") || ""
133 | );
134 | setTotalCommitContributions(
135 | localStorage.getItem("totalCommitContributions") || ""
136 | );
137 | setTotalPullRequestContributions(
138 | localStorage.getItem("totalPullRequestContributions") || ""
139 | );
140 | setTotalRepositoryContributions(
141 | localStorage.getItem("totalRepositoryContributions") || ""
142 | );
143 |
144 | // User's Popular PR's data
145 | setPopularPrName(localStorage.getItem("popularPrName") || "");
146 | setPopularPrState(localStorage.getItem("popularPrState") || "");
147 | setPopularPrCreationDate(
148 | localStorage.getItem("popularPrCreationDate") || ""
149 | );
150 | setPopularPrURL(localStorage.getItem("popularPrURL") || "");
151 |
152 | // User Top Repos
153 | setTopReposCount(localStorage.getItem("topReposCount") || "0");
154 |
155 | // User Activity Details
156 | setMostProductiveDate(localStorage.getItem("mostProductiveDate") || "");
157 | setMaxContributionCount(localStorage.getItem("maxContributionCount") || "");
158 | setMaxStreak(localStorage.getItem("maxStreak") || "");
159 | setActiveDays(localStorage.getItem("activeDays") || "");
160 | }
161 |
162 | // Effect to update top repos array
163 | useEffect(() => {
164 | if (topReposCount > 2) {
165 | setTopReposArray((prevTopReposArray) => {
166 | let updatedArray = [...prevTopReposArray];
167 |
168 | for (let i = 0; i < topReposCount; i++) {
169 | let repoObj = {
170 | name: localStorage.getItem(`topRepoName${i}`),
171 | url: localStorage.getItem(`topRepoURL${i}`),
172 | };
173 | updatedArray.push(repoObj);
174 | }
175 |
176 | return updatedArray;
177 | });
178 | }
179 | }, [topReposCount]);
180 |
181 | return (
182 | <>
183 | {loader && }
184 |
185 |
186 |
187 |
188 | {/* User Details Section */}
189 |
193 |
194 | {/* Contribution Graph Section */}
195 |
196 |
197 | {/* GitHub Contribution Summary Section */}
198 | {totalContributions && (
199 |
209 | )}
210 |
211 | {/* Activity Stats Section */}
212 | {activeDays && (
213 |
219 | )}
220 |
221 | {/* Top Repos Section */}
222 |
223 |
224 | {/* Popular PR Section */}
225 |
231 |
232 | {/* Chart & Most Productive Day Section */}
233 |
239 |
240 | {/* Quote of the Day Section */}
241 |
242 |
243 | {/* Star on Github */}
244 |
249 |
250 | {" "}
251 | {" "}
252 | on GitHub
253 |
254 |
255 | {/* Footer Section */}
256 |
257 |
258 |
259 | >
260 | );
261 | };
262 |
263 | export default StatsPage;
264 |
--------------------------------------------------------------------------------
/src/utils/getActiveDays.js:
--------------------------------------------------------------------------------
1 | export default async function getActiveDays(weeksArray) {
2 | // Initialize variables to store results
3 | let maxContributionCount = 0;
4 | let mostProductiveDate = null;
5 | let activeDays = 0;
6 | let maxStreak = 0;
7 |
8 | // Initialize variables for tracking streak
9 | let hasContributedLastWeek = false;
10 | let streakDays = 0;
11 |
12 | try {
13 | // Loop through each week in the provided array
14 | weeksArray.forEach((week) => {
15 | // Initialize streak variables
16 | let currStreak = 0;
17 |
18 | // Check if there was a contribution streak from the last week
19 | hasContributedLastWeek ? (currStreak += streakDays) : (currStreak = 0);
20 |
21 | // Check if there was a contribution yesterday in the current week
22 | let hasContributedYesterday =
23 | week.contributionDays[0].contributionCount > 0;
24 |
25 | // Loop through each day in the contribution days of the week
26 | week.contributionDays.map((day) => {
27 | // Get the number of contributions for the day
28 | let count = day.contributionCount;
29 |
30 | if (count > 0) {
31 | activeDays += 1; // Increment total active days
32 |
33 | // Update max contribution count and most productive date if needed
34 | if (count > maxContributionCount) {
35 | maxContributionCount = count;
36 | mostProductiveDate = day.date;
37 | }
38 |
39 | // Update streak if there was a contribution yesterday
40 | if (hasContributedYesterday) {
41 | currStreak++;
42 | } else {
43 | currStreak = 1;
44 | hasContributedYesterday = true;
45 | }
46 | } else {
47 | hasContributedYesterday = false;
48 | }
49 | });
50 |
51 | // Update max streak if the current streak is greater
52 | if (currStreak > maxStreak) {
53 | maxStreak = currStreak;
54 | }
55 |
56 | // Check if there was a contribution on the last day of the week
57 | week.contributionDays[6].contributionCount
58 | ? ((streakDays = currStreak), (hasContributedLastWeek = true))
59 | : (streakDays = 0);
60 | });
61 | } catch (err) {
62 | storeData(mostProductiveDate, maxContributionCount, maxStreak, activeDays);
63 | // Use a try-catch block to handle potential errors, The error is related to using forEach loop on an object which is behaving like an array returning the total active days in case of an error
64 | return activeDays;
65 | }
66 | // Store data to localStorage and return;
67 | storeData(mostProductiveDate, maxContributionCount, maxStreak, activeDays);
68 | return activeDays;
69 | }
70 |
71 | function storeData(mostProductiveDate, maxContributionCount, maxStreak, activeDays) {
72 | // Store the most productive date and max contribution count in local storage
73 | localStorage.setItem("mostProductiveDate", mostProductiveDate);
74 | localStorage.setItem("maxContributionCount", maxContributionCount);
75 | localStorage.setItem("maxStreak", maxStreak);
76 | localStorage.setItem("activeDays", activeDays);
77 | }
78 |
--------------------------------------------------------------------------------
/src/utils/getAllRepos.js:
--------------------------------------------------------------------------------
1 | import { octokit } from "./getOctokit";
2 |
3 | export default async function getAllRepos(username){
4 | try {
5 | let page = 1;
6 | let repos = [];
7 |
8 | // eslint-disable-next-line no-constant-condition
9 | while (true) {
10 | const response = await octokit.request(`GET https://api.github.com/users/${username}/repos?page=${page}`);
11 | const data = await response.data;
12 |
13 | if (data.length === 0) {
14 | // No more repositories, break the loop
15 | break;
16 | }
17 |
18 | // Add the current page's repositories to the overall list
19 | repos = [...repos, ...data];
20 |
21 | // Move to the next page
22 | page++;
23 | }
24 | return repos;
25 | } catch (error) {
26 | console.error('Error fetching user repositories:', error);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/utils/getBadge.js:
--------------------------------------------------------------------------------
1 | export default function getBadge(contributionCount) {
2 | if (contributionCount > 1500) {
3 | return "Master";
4 | } else if (contributionCount > 1000) {
5 | return "Expert";
6 | } else if (contributionCount > 500) {
7 | return "Intermediate";
8 | } else if (contributionCount > 250) {
9 | return "Skilled";
10 | } else if (contributionCount < 250) {
11 | return "Novice";
12 | } else {
13 | return "No Badge";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/getCanvas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Function to set up and animate circles on a canvas.
3 | */
4 |
5 | let TOTAL_CIRCLES = 2000;
6 | let width = window.innerWidth;
7 | if(width < 400 ){
8 | TOTAL_CIRCLES = 300;
9 | }else if(width < 500){
10 | TOTAL_CIRCLES = 500;
11 | }else if(width < 700){
12 | TOTAL_CIRCLES = 700;
13 | }else if(width < 1000){
14 | TOTAL_CIRCLES = 1000;
15 | }else{
16 | TOTAL_CIRCLES = 1500;
17 | }
18 |
19 | export default function getCanvas() {
20 | // Get canvas element by ID and set its dimensions
21 | const canvas = document.getElementById("myCanvas");
22 | canvas.width = window.innerWidth;
23 | canvas.height = window.innerHeight;
24 |
25 | // Get 2D rendering context
26 | const ctx = canvas.getContext("2d");
27 |
28 | // Event listener to adjust canvas dimensions on window resize
29 | window.addEventListener("resize", () => {
30 | let width = window.innerWidth;
31 | let height = window.innerHeight;
32 | if(width < 400 ){
33 | TOTAL_CIRCLES = 300;
34 | }else if(width < 500){
35 | TOTAL_CIRCLES = 500;
36 | }else if(width < 700){
37 | TOTAL_CIRCLES = 700;
38 | }else if(width < 1000){
39 | TOTAL_CIRCLES = 1000;
40 | }else{
41 | TOTAL_CIRCLES = 1500;
42 | }
43 |
44 | canvas.width = width;
45 | canvas.height = height;
46 | init();
47 | });
48 |
49 | // Array of colors for circles
50 | const colorsArray = [
51 | "#191717",
52 | "#CCC8AA",
53 | "#7D7C7C",
54 | "#191717",
55 | "#2C3333",
56 | "#1B2430",
57 | ];
58 |
59 | // Interaction Part
60 | const mousePosition = {
61 | x: undefined,
62 | y: undefined,
63 | };
64 |
65 | // Event listener to track mouse position
66 | window.addEventListener("mousemove", (e) => {
67 | mousePosition.x = e.x;
68 | mousePosition.y = e.y;
69 | });
70 |
71 | // Circle class to create and update circles
72 | class Circle {
73 | constructor(x, y, radius, dx, dy) {
74 | this.x = x;
75 | this.y = y;
76 | this.radius = radius;
77 | this.dx = dx;
78 | this.dy = dy;
79 | this.minRadius = radius;
80 | this.maxRadius = 50;
81 | this.color = colorsArray[Math.floor(Math.random() * colorsArray.length)];
82 | }
83 |
84 | /**
85 | * Method to draw the circle on the canvas.
86 | */
87 | draw = () => {
88 | ctx.beginPath();
89 | ctx.arc(this.x, this.y, this.radius, Math.PI * 2, false);
90 | ctx.strokeStyle = "black";
91 | ctx.stroke();
92 | ctx.fillStyle = this.color;
93 | ctx.fill();
94 | };
95 |
96 | /**
97 | * Method to update the circle's position and size.
98 | */
99 | update = () => {
100 | // Bounce off walls
101 | if (this.x > innerWidth - this.radius || this.x < this.radius) {
102 | this.dx = -this.dx;
103 | } else if (this.y > innerHeight - this.radius || this.y < this.radius) {
104 | this.dy = -this.dy;
105 | }
106 |
107 | // Interaction: Expand on mouse proximity, shrink otherwise
108 | if (
109 | mousePosition.x - this.x < 50 &&
110 | mousePosition.x - this.x > -50 &&
111 | mousePosition.y - this.y < 50 &&
112 | mousePosition.y - this.y > -50
113 | ) {
114 | if (this.radius < this.maxRadius) this.radius += 1;
115 | } else {
116 | if (this.radius > this.minRadius) {
117 | this.radius -= 1;
118 | }
119 | }
120 |
121 | // Move circle
122 | this.x += this.dx;
123 | this.y += this.dy;
124 |
125 | // Draw updated circle
126 | this.draw();
127 | };
128 | }
129 |
130 | // Array to store circle objects
131 | let circleArray = [];
132 |
133 | /**
134 | * Initialize the canvas with a set of circles.
135 | */
136 | function init() {
137 | circleArray = [];
138 | for (let i = 0; i < TOTAL_CIRCLES; i++) {
139 | // Generate random parameters for each circle
140 | let radius = Math.floor(Math.random() * 3) + 2;
141 | let x = Math.random() * (window.innerWidth - 2 * radius) + radius;
142 | let y = Math.random() * (window.innerHeight - 2 * radius) + radius;
143 | let dx = Math.random() - 0.5;
144 | let dy = Math.random() - 0.5;
145 |
146 | // Create and add a new circle to the array
147 | circleArray.push(new Circle(x, y, radius, dx, dy));
148 | }
149 | }
150 |
151 | /**
152 | * Animate the circles on the canvas.
153 | */
154 | function animate() {
155 | // Request animation frame for continuous rendering
156 | requestAnimationFrame(animate);
157 |
158 | // Clear canvas
159 | ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
160 |
161 | // Update and draw each circle in the array
162 | for (let i = 0; i < circleArray.length; i++) circleArray[i].update();
163 | }
164 |
165 | // Initialize and start animation loop
166 | init();
167 | animate();
168 | }
169 |
--------------------------------------------------------------------------------
/src/utils/getContributionData.js:
--------------------------------------------------------------------------------
1 | import getActiveDays from "./getActiveDays";
2 | import { octokit } from "./getOctokit";
3 |
4 |
5 | export default async function getContributionData(username) {
6 | const YEAR = 2023;
7 |
8 | const query = `
9 | query {
10 | user(login: "${username}") {
11 |
12 | openIssues: issues(filterBy: {since: "${YEAR}-01-01T00:00:00.000Z", states: OPEN}) {
13 | totalCount
14 | }
15 | closedIssues: issues(filterBy: {since: "${YEAR}-01-01T00:00:00.000Z", states: CLOSED}) {
16 | totalCount
17 | }
18 | avatarUrl
19 | login
20 | contributionsCollection(from: "${YEAR}-01-01T00:00:00.000Z", to: "${YEAR + 1}-01-01T00:00:00.000Z") {
21 | restrictedContributionsCount
22 | totalIssueContributions
23 | totalCommitContributions
24 | totalRepositoryContributions
25 | totalPullRequestContributions
26 | totalPullRequestReviewContributions
27 | popularPullRequestContribution {
28 | pullRequest {
29 | title
30 | repository {
31 | name
32 | }
33 | createdAt
34 | updatedAt
35 | totalCommentsCount
36 | state
37 | url
38 | }
39 | }
40 | contributionCalendar {
41 | totalContributions
42 | weeks {
43 | contributionDays {
44 | contributionCount
45 | date
46 | }
47 | }
48 | }
49 | commitContributionsByRepository {
50 | contributions {
51 | totalCount
52 | }
53 | repository {
54 | name
55 | url
56 | languages(first: 3, orderBy: {field: SIZE, direction: DESC}) {
57 | edges {
58 | size
59 | node {
60 | color
61 | name
62 | id
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 | }
70 | }
71 | `;
72 |
73 | const res = await octokit.graphql(query);
74 | const weeksArray = await res.user.contributionsCollection.contributionCalendar.weeks;
75 | await getActiveDays(weeksArray);
76 | storeData(res.user);
77 | }
78 |
79 |
80 | function storeData(userData){
81 | const contributionsCollection = userData.contributionsCollection;
82 | const contributionCalendar = userData.contributionsCollection.contributionCalendar;
83 |
84 | localStorage.setItem("totalContributions", contributionCalendar?.totalContributions);
85 | localStorage.setItem("openIssues", userData?.openIssues?.totalCount);
86 | localStorage.setItem("closedIssues", userData?.closedIssues?.totalCount);
87 | localStorage.setItem("totalIssueContributions", contributionsCollection?.totalIssueContributions);
88 | localStorage.setItem("totalCommitContributions", contributionsCollection?.totalCommitContributions);
89 | localStorage.setItem("totalPullRequestContributions", contributionsCollection?.totalPullRequestContributions);
90 | localStorage.setItem("totalPullRequestReviewContributions", contributionsCollection?.totalPullRequestReviewContributions);
91 | localStorage.setItem("totalRepositoryContributions", contributionsCollection?.totalRepositoryContributions);
92 | localStorage.setItem("popularPrName", contributionsCollection?.popularPullRequestContribution?.pullRequest?.title);
93 | localStorage.setItem("popularPrState", contributionsCollection?.popularPullRequestContribution?.pullRequest?.state);
94 | localStorage.setItem("popularPrCreationDate", contributionsCollection?.popularPullRequestContribution?.pullRequest?.createdAt?.slice(0, 10));
95 | localStorage.setItem("popularPrURL", contributionsCollection?.popularPullRequestContribution?.pullRequest?.url);
96 |
97 |
98 | storeTopRepos(contributionsCollection.commitContributionsByRepository);
99 | }
100 |
101 | function storeTopRepos(topReposObj){
102 | const topReposCount = Math.min(topReposObj.length, 4);
103 | localStorage.setItem("topReposCount", topReposCount);
104 | for(let i = 0; i < topReposCount;i++){
105 | localStorage.setItem(`topRepoName${i}`, topReposObj[i].repository.name);
106 | localStorage.setItem(`topRepoURL${i}`, topReposObj[i].repository.url);
107 | }
108 | }
--------------------------------------------------------------------------------
/src/utils/getDayIndex.js:
--------------------------------------------------------------------------------
1 | export default function getDayIndex(dateString) {
2 | const date = new Date(dateString);
3 | const dayIndex = date.getDay();
4 | return dayIndex;
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/getMostUsedLanguages.js:
--------------------------------------------------------------------------------
1 | import getAllRepos from "./getAllRepos";
2 | import { octokit } from "./getOctokit";
3 |
4 | // Main function to fetch and process repository languages
5 | export default async function (username) {
6 | // Get the list of repositories for the given username
7 | const reposArray = await getAllRepos(username);
8 | // Object to store the aggregated languages across all repositories
9 | const langObj = {};
10 |
11 | // Iterate over each repository
12 | for (const repo of reposArray) {
13 | const repoName = repo.full_name;
14 |
15 | try {
16 | // GitHub API token for authentication
17 | const token = import.meta.env.VITE_GITHUB_TOKEN;
18 | // Fetch the languages used in the repository
19 | const response = await octokit.request(
20 | `GET https://api.github.com/repos/${repoName}/languages`,
21 | {
22 | owner: username,
23 | repo: repoName,
24 | headers: {
25 | Authorization: `Bearer ${token}`,
26 | },
27 | }
28 | );
29 | const languages = response.data;
30 |
31 | // Process languages and update langObj
32 | if (Object.keys(languages).length !== 0) {
33 | Object.keys(languages).forEach((lang) => {
34 | // Check if langObj already has the language key
35 | if (Object.prototype.hasOwnProperty.call(langObj, lang)) {
36 | // If it exists, add the value
37 | langObj[lang] += languages[lang];
38 | } else {
39 | // If it doesn't exist, create the key with the value
40 | langObj[lang] = languages[lang];
41 | }
42 | });
43 | }
44 | } catch (error) {
45 | console.error("Error fetching repository languages:", error);
46 | }
47 | }
48 |
49 | // Get the top 5 languages and display the result
50 | const topLanguagesArray = sortAndTrim(langObj);
51 | const transformedArray = transformArrayToObject(topLanguagesArray);
52 | return transformedArray;
53 | }
54 |
55 | // Function to sort the languages object and return a languages array and then trim the languages array
56 | function sortAndTrim(obj) {
57 | const langArray = Object.entries(obj).map(([key, value]) => [
58 | key.trim(),
59 | value,
60 | ]);
61 | const sortedLangArray = langArray.sort((a, b) => b[1] - a[1]);
62 | const trimmedLangArray = sortedLangArray.slice(
63 | 0,
64 | Math.min(sortedLangArray.length, 3)
65 | );
66 | localStorage.setItem("languagesFetched", "true");
67 | return trimmedLangArray;
68 | }
69 |
70 | function transformArrayToObject(array){
71 | const transformedArray = [
72 | {
73 | name: array[0][0],
74 | count: array[0][1],
75 | },{
76 | name: array[1][0],
77 | count: array[1][1],
78 | },{
79 | name: array[2][0],
80 | count: array[2][1],
81 | }
82 | ]
83 | return transformedArray;
84 | }
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/utils/getOctokit.js:
--------------------------------------------------------------------------------
1 | import { Octokit } from "octokit";
2 |
3 | const githubToken = import.meta.env.VITE_GITHUB_TOKEN;
4 | const octokit = new Octokit({auth: githubToken});
5 |
6 | export {octokit};
--------------------------------------------------------------------------------
/src/utils/getQuote.js:
--------------------------------------------------------------------------------
1 | const apiKey = import.meta.env.VITE_API_NINJAS_KEY;
2 |
3 | export default async function getQuote(){
4 | const requestURL = "https://api.api-ninjas.com/v1/quotes"
5 | const header = {
6 | "X-Api-Key": apiKey,
7 | };
8 | try {
9 | const response = await fetch(requestURL,{headers:header});
10 | if (!response.ok) {
11 | throw new Error("Network response was not ok");
12 | }
13 | const res = await response.json();
14 | return res;
15 | } catch (error) {
16 | console.error("Error = ", error);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/utils/getScrollAnimation.js:
--------------------------------------------------------------------------------
1 | export default function getScrollAnimation() {
2 | window.addEventListener("scroll", reveal);
3 |
4 | function reveal() {
5 | const revealPoint = window.innerHeight - 150;
6 |
7 | const revealDivs = document.querySelectorAll(".reveal");
8 |
9 | revealDivs.forEach((revealDiv) => {
10 | const revealDivTop = revealDiv.getBoundingClientRect().top;
11 | if (revealDivTop < revealPoint) {
12 | revealDiv.classList.add("active");
13 | } else {
14 | revealDiv.classList.remove("active");
15 | }
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/moveCursor.js:
--------------------------------------------------------------------------------
1 | export default function moveCursor(){
2 | const cursorDot = document.getElementById('cursorDot');
3 | const cursorOutline = document.getElementById('cursorOutline');
4 | window.addEventListener('mousemove', function(e){
5 | const posX = e.clientX;
6 | const posY = e.clientY;
7 |
8 | cursorDot.style.left = `${posX}px`;
9 | cursorDot.style.top = `${posY}px`;
10 |
11 | cursorOutline.style.left = `${posX}px`;
12 | cursorOutline.style.top = `${posY}px`;
13 |
14 | cursorOutline.animate({
15 | left: `${posX}px`,
16 | top: `${posY}px`
17 | }, { duration: 500 , fill : "forwards"});
18 | })
19 | }
--------------------------------------------------------------------------------
/src/utils/validateGithubUsername.js:
--------------------------------------------------------------------------------
1 | import { octokit } from "./getOctokit";
2 |
3 | export default async function validateGithubUsername(ghUsername) {
4 | try {
5 | const res = await octokit.rest.users.getByUsername({
6 | username: ghUsername,
7 | });
8 | //Storing data to local storage before returning
9 | storeDataToLocalStorage(res);
10 | return true;
11 | } catch (error) {
12 | console.clear();
13 | return false;
14 | }
15 | }
16 |
17 | //Store user data to local storage
18 | function storeDataToLocalStorage(res) {
19 | localStorage.setItem("username", res.data.login);
20 | localStorage.setItem("followers", res.data.followers);
21 | localStorage.setItem("location", res.data.location);
22 | localStorage.setItem("avatar", res.data.avatar_url);
23 | localStorage.setItem("githubUrl", res.data.html_url);
24 | }
25 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./src/**/*.{js,jsx}", "./*.html"],
4 | theme: {
5 | extend: {
6 | colors: {
7 | bgBlack: "#212325",
8 | lightBlue: "#9CDAF1",
9 | darkBlue: "#368186",
10 | lightRed: "#F4CBB2",
11 | lightGrey: "#ABABAB",
12 | darkGrey: "#2E3136",
13 | },
14 | fontFamily: {
15 | primary: "Inter",
16 | secondary: "Borel"
17 | },
18 | screens: {
19 | xxl: "1751px",
20 | mmd: "851px",
21 | gsm: "571px",
22 | msm: "491px",
23 | vsm: "441px",
24 | vvsm: "351px",
25 | },
26 | },
27 | },
28 | plugins: [],
29 | };
30 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
3 | }
4 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------