├── .eslintrc.json
├── .github
└── workflows
│ └── integrate.yaml
├── .gitignore
├── .npmrc
├── .prettierrc
├── LICENSE
├── README.md
├── __mocks__
├── fileMock.js
└── gh-polyglot.js
├── babel.config.js
├── cypress.config.ts
├── cypress
├── .eslintrc.json
├── e2e
│ └── app.cy.js
├── fixtures
│ └── example.json
└── support
│ ├── commands.ts
│ └── e2e.ts
├── jest.config.ts
├── netlify.toml
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── setupTest.ts
├── src
├── api
│ ├── base.js
│ └── githubAPI.js
├── assets
│ ├── demo.gif
│ ├── loader.gif
│ └── logo.png
├── components
│ ├── Activities.js
│ ├── App.js
│ ├── Button.js
│ ├── Chart.js
│ ├── Error.js
│ ├── Footer.js
│ ├── Form.js
│ ├── Header.js
│ ├── Loader.js
│ ├── Logo.js
│ ├── MaterialTabs.js
│ ├── Profile.js
│ ├── Stats
│ │ ├── Stats.js
│ │ ├── __tests__
│ │ │ └── utils.test.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ ├── Timeline.js
│ ├── TimelineItem.js
│ ├── Toggle.js
│ ├── __tests__
│ │ └── Form.test.tsx
│ └── index.js
├── contexts
│ ├── LanguageContext.js
│ └── ThemeProvider.js
├── index.tsx
├── mock.data.ts
├── mocks
│ ├── handlers.ts
│ └── server.ts
├── pages
│ ├── Home.js
│ ├── UserProfile.js
│ └── __tests__
│ │ └── UserProfile.test.tsx
├── style
│ ├── GlobalStyle.js
│ ├── dark.js
│ ├── index.js
│ └── light.js
├── types.ts
└── useDarkMode.js
├── tsconfig.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "jest": true
6 | },
7 |
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:react/recommended",
12 | "plugin:prettier/recommended"
13 | ],
14 | "parser": "@typescript-eslint/parser",
15 | "parserOptions": {
16 | "ecmaVersion": "latest",
17 | "sourceType": "module"
18 | },
19 | "plugins": ["@typescript-eslint", "react"],
20 | "rules": {
21 | "strict": ["error", "never"],
22 | /* TODO: once prop types are added remove this rule */
23 | "react/prop-types": "off",
24 | "@typescript-eslint/no-explicit-any": "warn"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/integrate.yaml:
--------------------------------------------------------------------------------
1 | # workflow name
2 | name: Gitpedia Continuous Integration
3 |
4 | # when should workflow run
5 | on:
6 | pull_request:
7 | branches: [master]
8 |
9 | # each workflow has one or more jobs
10 | jobs:
11 | test-pull-request:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3
16 | - name: Setup node
17 | uses: actions/setup-node@v3
18 | with:
19 | node-version: 18
20 | - run: npm ci
21 | - run: npm test
22 | - run: npm run build
23 | - name: Run cypress tests
24 | uses: cypress-io/github-action@v5
25 | with:
26 | start: npm start
27 |
--------------------------------------------------------------------------------
/.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 |
8 | # testing
9 | /coverage
10 | cypress/videos
11 | cypress/screenshots
12 |
13 | # production
14 | /build
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # Local Netlify folder
28 | .netlify
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # TODO: remove once peer deps error are solved
2 | legacy-peer-deps=true
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "printWidth": 100,
5 | "singleQuote": true,
6 | "trailingComma": "none",
7 | "jsxBracketSameLine": true
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Khusharth A Patani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | A web application to :mag: view a github's user profile in a more simple and beautiful way.
10 |
11 | ## :film_projector: DEMO
12 |
13 |
14 |
15 |
16 |
17 | ## :man_technologist: Technology Stack
18 |
19 | 
20 | 
21 | 
22 | 
23 |
24 | - [React](https://reactjs.org/)
25 | - [styled-components](https://styled-components.com/)
26 | - [React Chart Js 2](https://www.npmjs.com/package/react-chartjs-2)
27 | - [React Icons](https://react-icons.github.io/react-icons/)
28 |
29 | ### API used
30 |
31 | - For fetching github's user data : [Github API](https://developer.github.com/v3/)
32 |
33 | ## :hatching_chick: Prerequisites
34 |
35 | - [node](https://nodejs.org/en/) >= 12.18.0
36 | - npm >= 6.14.4
37 |
38 | ## :zap: Installation
39 |
40 | 1. Clone / Download [this](https://github.com/khusharth/gitpedia) repo.
41 | 2. Inside the project open a terminal and run:
42 | ```
43 | npm install
44 | ```
45 | This will install all the project dependencies.
46 | 3. Create a **.env** file in the project root folder and add the following:
47 |
48 | ```
49 | REACT_APP_GITHUB_CLIENT_ID = yourClientId
50 | REACT_APP_GITHUB_CLIENT_SECRET = yourSecretKey
51 | ```
52 |
53 | Replace yourClientId and yourSecretKey with your own **Client and Secret Key** .
54 |
55 | > Get your Client Id and Secret by signing in to your github account and then go to your setting -> developer setting -> OAuth Apps -> New OAuth App
56 |
57 | 4. To start the development server run:
58 | ```
59 | npm start
60 | ```
61 |
62 | ## :man_in_tuxedo: Author
63 |
64 | [](https://twitter.com/khusharth19)
65 |
66 | [](https://www.linkedin.com/in/khusharth/)
67 |
68 | ## :page_with_curl: Licence
69 |
70 | [MIT License](https://github.com/khusharth/gitpedia/blob/master/LICENSE) Copyright (c) 2020 Khusharth A Patani
71 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-undef
2 | module.exports = 'test-file-stub';
3 |
--------------------------------------------------------------------------------
/__mocks__/gh-polyglot.js:
--------------------------------------------------------------------------------
1 | export default class GhPolyglot {
2 | constructor() {
3 | // console.log('GhPolyglot: constructor was called');
4 | }
5 |
6 | userStats(func) {
7 | func('', defaultStats);
8 | }
9 | }
10 |
11 | const defaultStats = [
12 | {
13 | label: 'JavaScript',
14 | value: 19,
15 | color: '#f1e05a'
16 | },
17 | {
18 | label: 'TypeScript',
19 | value: 9,
20 | color: '#3178c6'
21 | },
22 | {
23 | label: 'Python',
24 | value: 3,
25 | color: '#3572A5'
26 | },
27 | {
28 | label: 'Others',
29 | value: 2,
30 | color: '#ccc'
31 | },
32 | {
33 | label: 'Dart',
34 | value: 2,
35 | color: '#00B4AB'
36 | },
37 | {
38 | label: 'Objective-C',
39 | value: 1,
40 | color: '#438eff'
41 | },
42 | {
43 | label: 'Svelte',
44 | value: 1,
45 | color: '#ff3e00'
46 | },
47 | {
48 | label: 'MDX',
49 | value: 1,
50 | color: '#fcb32c'
51 | }
52 | ];
53 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-undef
2 | module.exports = {
3 | presets: [
4 | ['@babel/preset-env', { targets: { node: 'current' } }],
5 | '@babel/preset-react',
6 | '@babel/preset-typescript'
7 | ]
8 | };
9 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress';
2 |
3 | export default defineConfig({
4 | e2e: {
5 | setupNodeEvents(on, config) {
6 | // implement node event listeners here
7 | config.baseUrl = 'http://localhost:3000';
8 | }
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/cypress/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "plugins": ["eslint-plugin-cypress"],
4 | "extends": ["plugin:cypress/recommended"],
5 | "env": {"cypress/global": true}
6 | }
--------------------------------------------------------------------------------
/cypress/e2e/app.cy.js:
--------------------------------------------------------------------------------
1 | describe('app test', () => {
2 | it('first test', () => {
3 | cy.visit('http://localhost:3000');
4 |
5 | cy.get('.sc-fzpans').click();
6 | });
7 | });
8 |
9 | describe('app test 2', () => {
10 | it('2nd test', () => {
11 | cy.visit('http://localhost:3000');
12 |
13 | cy.findByPlaceholderText(/^Enter Github Username$/)
14 | .click()
15 | .type('khusharth');
16 |
17 | cy.findByLabelText('search').click();
18 |
19 | cy.findByRole('tab', { name: 'Timeline' }).click();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
38 |
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/e2e.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands';
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 | import '@testing-library/cypress/add-commands';
22 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'jest';
2 |
3 | const config: Config = {
4 | // enable dom apis for tests
5 | testEnvironment: 'jsdom',
6 |
7 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
8 | setupFilesAfterEnv: ['./setupTest.ts'],
9 |
10 | moduleDirectories: ['node_modules', 'src'],
11 |
12 | moduleNameMapper: {
13 | // Handle image imports
14 | // https://jestjs.io/docs/webpack#handling-static-assets
15 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
16 | '/__mocks__/fileMock.js',
17 |
18 | // Handle module aliases
19 | '^src/(.*)$': '/src/$1'
20 | },
21 |
22 | // coverage
23 | collectCoverageFrom: ['/src/**/*.{js,ts,tsx}'],
24 | coveragePathIgnorePatterns: ['/node_modules/', '/src/style']
25 | };
26 |
27 | export default config;
28 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish="build"
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitpedia",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.6.1",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/user-event": "^7.1.2",
9 | "@types/jest": "^29.5.3",
10 | "@types/node": "^20.4.4",
11 | "@types/react": "^18.2.15",
12 | "@types/react-dom": "^18.2.7",
13 | "@types/react-router-dom": "^5.3.3",
14 | "axios": "^1.4.0",
15 | "chart.js": "^2.9.3",
16 | "gh-polyglot": "^2.3.2",
17 | "react": "^17.0.2",
18 | "react-chartjs-2": "^2.9.0",
19 | "react-dom": "^17.0.2",
20 | "react-icons": "^3.10.0",
21 | "react-router-dom": "^5.2.0",
22 | "react-scripts": "^5.0.1",
23 | "styled-components": "^5.1.1",
24 | "typescript": "^5.1.6"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "eject": "react-scripts eject",
30 | "lint": "eslint --ignore-path .gitignore .",
31 | "test": "jest",
32 | "test:watch": "jest --watch --detectOpenHandles",
33 | "test:coverage": "jest --coverage"
34 | },
35 | "eslintConfig": {
36 | "extends": "react-app"
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "@babel/core": "^7.22.9",
52 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
53 | "@babel/preset-env": "^7.22.9",
54 | "@babel/preset-react": "^7.22.5",
55 | "@babel/preset-typescript": "^7.22.5",
56 | "@testing-library/cypress": "^9.0.0",
57 | "@testing-library/dom": "^9.3.1",
58 | "@testing-library/react": "^12.1.5",
59 | "@types/styled-components": "^5.1.26",
60 | "@types/testing-library__react": "^10.2.0",
61 | "@typescript-eslint/eslint-plugin": "^6.1.0",
62 | "@typescript-eslint/parser": "^6.1.0",
63 | "babel-jest": "^29.6.1",
64 | "cypress": "^12.17.2",
65 | "eslint": "^8.45.0",
66 | "eslint-config-prettier": "^8.8.0",
67 | "eslint-plugin-cypress": "^2.13.3",
68 | "eslint-plugin-prettier": "^5.0.0",
69 | "eslint-plugin-react": "^7.33.0",
70 | "jest": "^29.6.1",
71 | "jest-canvas-mock": "^2.5.2",
72 | "jest-environment-jsdom": "^29.6.1",
73 | "msw": "^1.2.3",
74 | "prettier": "^3.0.0",
75 | "ts-node": "^10.9.1"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khusharth/gitpedia/53f6ca8cf9296f7927f76be73ab1b1ea6437b190/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
28 |
29 |
30 |
39 |
40 | GitPedia
41 |
42 |
43 |
44 |
45 |
46 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khusharth/gitpedia/53f6ca8cf9296f7927f76be73ab1b1ea6437b190/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khusharth/gitpedia/53f6ca8cf9296f7927f76be73ab1b1ea6437b190/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/setupTest.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect';
2 | import 'jest-canvas-mock';
3 |
--------------------------------------------------------------------------------
/src/api/base.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import axios from 'axios';
3 |
4 | export const BASE_URL = 'https://api.github.com';
5 |
6 | let githubClientId;
7 | let githubClientSecret;
8 |
9 | if (process.env.NODE_ENV !== 'production') {
10 | // Local Environment Variables from .env.local
11 | githubClientId = process.env.REACT_APP_GITHUB_CLIENT_ID;
12 | githubClientSecret = process.env.REACT_APP_GITHUB_CLIENT_SECRET;
13 | } else {
14 | // Netlify Environment Variables
15 | githubClientId = process.env.GITHUB_CLIENT_ID;
16 | githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
17 | }
18 |
19 | // A pre configured instace of axios for github API
20 | export default axios.create({
21 | baseURL: BASE_URL,
22 | auth: {
23 | username: githubClientId,
24 | password: githubClientSecret
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/src/api/githubAPI.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import GhPolyglot from 'gh-polyglot';
3 | import github from './base';
4 |
5 | export const useGithubUserData = (username) => {
6 | const [userData, setUserData] = useState({});
7 | const [loading, setLoading] = useState(false);
8 | const [error, setError] = useState({});
9 |
10 | useEffect(() => {
11 | if (!username) return;
12 |
13 | const getUserData = async () => {
14 | try {
15 | setLoading(true);
16 | setError({ active: false, type: 200 });
17 |
18 | const response = await github.get(`/users/${username}`);
19 | setUserData(response.data);
20 | setLoading(false);
21 | } catch (error) {
22 | console.log('Error', error);
23 | if (error.response) {
24 | if (error.response.status === 404) {
25 | setError({ active: true, type: 404 });
26 | } else {
27 | setError({ active: true, type: error.response.status });
28 | }
29 | } else {
30 | setError({ active: true, type: error });
31 | console.log(error);
32 | }
33 | setLoading(false);
34 | }
35 | };
36 | getUserData();
37 | }, [username]);
38 |
39 | return [userData, loading, error];
40 | };
41 |
42 | // Using GhPolyglot library to get all the languages used
43 | export const useLangData = (username) => {
44 | const [langData, setLangData] = useState([]);
45 | const [loading, setLoading] = useState(false);
46 | const [error, setError] = useState({});
47 |
48 | useEffect(() => {
49 | const getLangData = () => {
50 | setLoading(true);
51 | setError({ active: false, type: 200 });
52 |
53 | const currentUser = new GhPolyglot(`${username}`);
54 | currentUser.userStats((err, stats) => {
55 | if (err === 'Not Found') {
56 | setError({ active: true, type: 404 });
57 | } else if (err) {
58 | setError({ active: true, type: err });
59 | console.log('err', err);
60 | }
61 |
62 | if (stats) {
63 | setLangData(stats);
64 | }
65 | });
66 | setLoading(false);
67 | };
68 | getLangData();
69 | }, [username]);
70 |
71 | return [langData, loading, error];
72 | };
73 |
74 | export const useUserRepos = (username) => {
75 | const [repoData, setRepoData] = useState([]);
76 | const [loading, setLoading] = useState(false);
77 | const [error, setError] = useState({});
78 |
79 | useEffect(() => {
80 | const getUserRepos = async () => {
81 | setLoading(true);
82 | setError({ active: false, type: 200 });
83 | try {
84 | const findTotalRepo = await github.get(`/users/${username}`);
85 | const totalRepo = findTotalRepo.data.public_repos;
86 | let totalRequest = 1;
87 | // Reset Repo data to [] after rerendering
88 | setRepoData([]);
89 |
90 | // To get more than 100 repo find number of requests needed to make
91 | if (totalRepo > 0) {
92 | totalRequest = Math.ceil(totalRepo / 100);
93 | }
94 |
95 | // Get 100 repo in each request and add them to the old array
96 | for (let i = 1; i < totalRequest + 1; i++) {
97 | let response = await github.get(
98 | `/users/${username}/repos?per_page=100&page=${i}&sort=created:dsc`
99 | );
100 | setRepoData((oldArray) => [...oldArray, ...response.data]);
101 | }
102 |
103 | setLoading(false);
104 | } catch (error) {
105 | if (error.response) {
106 | if (error.response.status === 404) {
107 | setError({ active: true, type: 404 });
108 | } else {
109 | setError({ active: true, type: error.response.status });
110 | }
111 | } else {
112 | setError({ active: true, type: error });
113 | console.log(error);
114 | }
115 |
116 | setLoading(false);
117 | }
118 | };
119 | getUserRepos();
120 | }, [username]);
121 |
122 | return [repoData, loading, error];
123 | };
124 |
125 | export const useActivityData = (username) => {
126 | const [activityData, setActivityData] = useState([]);
127 | const [loading, setLoading] = useState(false);
128 | const [error, setError] = useState({});
129 |
130 | useEffect(() => {
131 | const getActivityData = async () => {
132 | setLoading(true);
133 | setError({ active: false, type: 200 });
134 | try {
135 | const response = await github.get(`/users/${username}/events?per_page=30`);
136 |
137 | setActivityData(response.data);
138 | setLoading(false);
139 | } catch (error) {
140 | if (error.response) {
141 | if (error.response.status === 404) {
142 | setError({ active: true, type: 404 });
143 | } else {
144 | setError({ active: true, type: error.response.status });
145 | }
146 | } else {
147 | setError({ active: true, type: error });
148 | console.log(error);
149 | }
150 |
151 | setLoading(false);
152 | }
153 | };
154 | getActivityData();
155 | }, [username]);
156 |
157 | return [activityData, loading, error];
158 | };
159 |
160 | // export const useRateLimit = (username) => {
161 | // const [rateLimit, setRateLimit] = useState(null);
162 | // useEffect(() => {
163 | // const getRateLimit = async () => {
164 | // const response = await github.get(
165 | // "https://api.github.com/rate_limit"
166 | // );
167 | // setRateLimit(response.data.rate);
168 | // console.log(response.data.rate);
169 | // };
170 | // getRateLimit();
171 | // }, [username]);
172 | // };
173 |
--------------------------------------------------------------------------------
/src/assets/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khusharth/gitpedia/53f6ca8cf9296f7927f76be73ab1b1ea6437b190/src/assets/demo.gif
--------------------------------------------------------------------------------
/src/assets/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khusharth/gitpedia/53f6ca8cf9296f7927f76be73ab1b1ea6437b190/src/assets/loader.gif
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khusharth/gitpedia/53f6ca8cf9296f7927f76be73ab1b1ea6437b190/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/Activities.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import {
4 | GoTrashcan,
5 | GoRepoForked,
6 | GoRepoPull,
7 | GoComment,
8 | GoGitBranch,
9 | GoPlus,
10 | GoRepoPush,
11 | GoStar,
12 | GoBook,
13 | GoIssueClosed,
14 | GoIssueOpened
15 | } from 'react-icons/go';
16 |
17 | const ActivitiesContainer = styled.div`
18 | margin: 3rem auto;
19 | width: 100rem;
20 |
21 | & ul li a {
22 | text-decoration: none;
23 | &:link,
24 | &:visited {
25 | color: #0098f0;
26 | }
27 |
28 | &:hover,
29 | &:active {
30 | text-decoration: underline;
31 | }
32 | }
33 |
34 | @media only screen and (max-width: 1000px) {
35 | width: 100%;
36 | }
37 | `;
38 |
39 | const ActivitiesItem = styled.div`
40 | padding: 2rem 4rem;
41 | margin-bottom: 2rem;
42 | width: 100%;
43 | display: flex;
44 | flex-wrap: wrap;
45 | border-radius: 5px;
46 | box-shadow: 0 1rem 2rem -0.6rem rgba(0, 0, 0, 0.2);
47 | justify-content: space-between;
48 | background-color: ${(p) => p.theme.cardColor};
49 | `;
50 |
51 | const ActivityDiv = styled.div`
52 | width: 75%;
53 |
54 | & svg {
55 | vertical-align: middle;
56 | margin-bottom: 4px;
57 | margin-right: 5px;
58 | }
59 |
60 | @media only screen and (max-width: 600px) {
61 | width: 100%;
62 | }
63 | `;
64 |
65 | const TimeDiv = styled.div`
66 | width: 25%;
67 | display: flex;
68 | justify-content: flex-end;
69 | @media only screen and (max-width: 600px) {
70 | justify-content: flex-start;
71 | margin-top: 1rem;
72 | width: 100%;
73 | }
74 | `;
75 |
76 | const FlexContainer = styled.div`
77 | width: 100%;
78 | display: flex;
79 | justify-content: center;
80 | `;
81 |
82 | const Activities = ({ activityData }) => {
83 | const extractActivity = () => {
84 | const message = activityData.map((activity) => {
85 | let icon = '';
86 | let action = '';
87 | let actionPerformed; // For Pull req
88 | let repoName = activity.repo.name;
89 | let time = new Date(activity.created_at).toDateString().split(' ').slice(1).join(' ');
90 |
91 | switch (activity.type) {
92 | case 'CommitCommentEvent':
93 | break;
94 |
95 | case 'CreateEvent':
96 | if (activity.payload.ref_type === 'branch') {
97 | icon = ;
98 | action = `Created a branch ${activity.payload.ref} in `;
99 | } else {
100 | icon = ;
101 | action = `Created a ${activity.payload.ref_type} in `;
102 | }
103 | break;
104 |
105 | case 'DeleteEvent':
106 | icon = ;
107 | action = `Deleted a ${activity.payload.ref_type} ${activity.payload.ref} from `;
108 | break;
109 |
110 | case 'ForkEvent':
111 | icon = ;
112 | action = `Forked a repository ${repoName} to `;
113 | repoName = activity.payload.forkee.full_name;
114 | break;
115 |
116 | case 'IssueCommentEvent':
117 | icon = ;
118 | actionPerformed =
119 | activity.payload.action.charAt(0).toUpperCase() + activity.payload.action.slice(1);
120 |
121 | action = `${actionPerformed} a comment on an issue in `;
122 | break;
123 |
124 | case 'IssuesEvent':
125 | if (activity.payload.action === 'closed') {
126 | icon = ;
127 | } else {
128 | icon = ;
129 | }
130 | actionPerformed =
131 | activity.payload.action.charAt(0).toUpperCase() + activity.payload.action.slice(1);
132 |
133 | action = `${actionPerformed} an issue in `;
134 | break;
135 |
136 | case 'PullRequestEvent':
137 | if (activity.payload.action === 'closed') {
138 | icon = ;
139 | } else {
140 | icon = ;
141 | }
142 |
143 | actionPerformed =
144 | activity.payload.action.charAt(0).toUpperCase() + activity.payload.action.slice(1);
145 |
146 | action = `${actionPerformed} a pull request in `;
147 | break;
148 |
149 | case 'PullRequestReviewCommentEvent':
150 | icon = ;
151 | actionPerformed =
152 | activity.payload.action.charAt(0).toUpperCase() + activity.payload.action.slice(1);
153 |
154 | action = `${actionPerformed} a comment on their pull request in `;
155 | break;
156 |
157 | case 'PushEvent': {
158 | icon = ;
159 | let commit = 'commit';
160 | let branch = activity.payload.ref.slice(11);
161 |
162 | if (activity.payload.size > 1) {
163 | commit = 'commits';
164 | }
165 |
166 | action = `Pushed ${activity.payload.size} ${commit} to ${branch} in `;
167 | break;
168 | }
169 | case 'WatchEvent':
170 | icon = ;
171 | action = 'Starred the repository ';
172 | break;
173 |
174 | case 'ReleaseEvent':
175 | icon = ;
176 | actionPerformed =
177 | activity.payload.action.charAt(0).toUpperCase() + activity.payload.action.slice(1);
178 |
179 | action = `${actionPerformed} a release in `;
180 | break;
181 |
182 | default:
183 | action = '';
184 | }
185 | return { icon, action, repoName, time };
186 | });
187 |
188 | return message;
189 | };
190 |
191 | const buildActivityList = () => {
192 | const messages = extractActivity();
193 |
194 | if (messages.length !== 0) {
195 | return messages.map((message) => (
196 |
197 |
198 |
199 |
200 | {message.icon} {message.action}
201 |
202 | {message.repoName}
203 |
204 | {message.time}
205 |
206 |
207 | ));
208 | } else {
209 | return (
210 |
211 |
212 | No recent activities found :(
213 |
214 |
215 | );
216 | }
217 | };
218 |
219 | return (
220 | <>
221 |
222 |
223 |
224 | >
225 | );
226 | };
227 |
228 | export default Activities;
229 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
4 |
5 | import Home from '../pages/Home';
6 | import UserProfile from '../pages/UserProfile';
7 | import { GlobalStyle } from '../style';
8 | import ThemeProviderWrapper from 'src/contexts/ThemeProvider';
9 |
10 | const App = () => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Button = styled.button`
4 | color: rgb(255, 255, 255);
5 | border: none;
6 | background-image: linear-gradient(to right, #0098f0, #00e1b5);
7 | padding: 1rem 1.5rem;
8 | border-radius: 5px;
9 | border-bottom: 2px solid transparent;
10 | cursor: pointer;
11 | align-items: center;
12 | transition: all 0.3s;
13 |
14 | &:hover {
15 | transform: scale(1.1);
16 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
17 | }
18 |
19 | &:focus {
20 | outline: 0;
21 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
22 | }
23 |
24 | &:active {
25 | transform: scale(1);
26 | }
27 | `;
28 |
29 | export default Button;
30 |
--------------------------------------------------------------------------------
/src/components/Chart.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Bar, Doughnut, Pie } from 'react-chartjs-2';
3 | import { ThemeContext } from 'styled-components';
4 | import LanguageContext from '../contexts/LanguageContext';
5 |
6 | // Different Colors used in Charts
7 | const bgColor = [
8 | 'rgba(123, 13, 255, 0.7)',
9 | 'rgba(171, 36, 247, 0.7)',
10 | 'rgba(155, 110, 243, 0.4)',
11 | 'rgba(198, 128, 250, 0.6)',
12 | 'rgba(117, 221, 221, 0.8)',
13 | 'rgba(36, 196, 207, 0.8)',
14 | 'rgba(0, 146, 203, 0.8)',
15 | 'rgba(104, 106, 253, 1)',
16 | 'rgba(155, 110, 243, 1)',
17 | 'rgba(209,188,249,1)'
18 | ];
19 |
20 | export const DoughnutChart = () => {
21 | const themeContext = useContext(ThemeContext);
22 |
23 | const data = {
24 | labels: [],
25 | datasets: [
26 | {
27 | label: '',
28 | data: [],
29 | backgroundColor: bgColor,
30 | borderWidth: 0
31 | }
32 | ]
33 | };
34 |
35 | return (
36 |
37 | {(context) => {
38 | const LIMIT = 10;
39 | let labels = context.map((obj) => obj.label);
40 |
41 | // If more than LIMIT languages then reduce it to the limit
42 | if (labels.length >= LIMIT) {
43 | labels = labels.slice(0, LIMIT);
44 | }
45 | const value = context.map((obj) => obj.value).slice(0, LIMIT);
46 | data.labels = labels;
47 | data.datasets[0].data = value;
48 |
49 | return (
50 |
67 | );
68 | }}
69 |
70 | );
71 | };
72 |
73 | export const PieChart = ({ starData }) => {
74 | const themeContext = useContext(ThemeContext);
75 |
76 | let data = {};
77 |
78 | if (starData.data) {
79 | // Only display chart if there is at least 1 star available
80 | let sum = starData.data.reduce((a, b) => a + b, 0);
81 | if (sum > 0) {
82 | data = {
83 | labels: starData.label,
84 | datasets: [
85 | {
86 | label: '',
87 | data: starData.data,
88 | backgroundColor: bgColor,
89 | borderWidth: 0
90 | }
91 | ]
92 | };
93 | }
94 | }
95 |
96 | return (
97 | <>
98 |
117 | >
118 | );
119 | };
120 |
121 | export const BarChart = ({ sizeData }) => {
122 | const themeContext = useContext(ThemeContext);
123 |
124 | const data = {
125 | labels: [],
126 | datasets: [
127 | {
128 | label: '',
129 | data: [],
130 | backgroundColor: bgColor
131 | }
132 | ]
133 | };
134 | data.labels = sizeData.label;
135 | data.datasets[0].data = sizeData.data;
136 |
137 | const scales = {
138 | xAxes: [
139 | {
140 | ticks: {
141 | fontColor: themeContext.textColor,
142 | fontFamily: "'Roboto', sans-serif",
143 | fontSize: 12
144 | }
145 | }
146 | ],
147 | yAxes: [
148 | {
149 | ticks: {
150 | fontColor: themeContext.textColor,
151 | beginAtZero: true,
152 | fontFamily: "'Roboto', sans-serif",
153 | fontSize: 12
154 | }
155 | }
156 | ]
157 | };
158 |
159 | return (
160 | <>
161 |
178 | >
179 | );
180 | };
181 |
--------------------------------------------------------------------------------
/src/components/Error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { GoAlert } from 'react-icons/go';
4 |
5 | const ErrorContainer = styled.div`
6 | height: calc(100vh - 130px);
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | `;
11 |
12 | const ErrorDiv = styled.div`
13 | padding: 2rem 4rem;
14 | border-radius: 5px;
15 | background-color: ${(p) => p.theme.cardColor};
16 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
17 |
18 | & svg {
19 | vertical-align: middle;
20 | margin-bottom: 4px;
21 | margin-right: 1rem;
22 | }
23 | `;
24 |
25 | const Error = ({ error }) => {
26 | return (
27 | <>
28 |
29 |
30 |
31 |
32 |
33 | {error.type === 404
34 | ? 'No user found! Please try again :)'
35 | : 'Oops! Some error occured. Please try again :)'}
36 |
37 |
38 | >
39 | );
40 | };
41 |
42 | export default Error;
43 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { GoStar } from 'react-icons/go';
4 |
5 | const FooterContainer = styled.footer`
6 | display: flex;
7 | text-align: center;
8 | flex-direction: column;
9 | align-items: center;
10 | height: 6rem;
11 | padding: 0 2rem 1rem 2rem;
12 | font-size: 1.4rem;
13 |
14 | & svg {
15 | vertical-align: middle;
16 | }
17 |
18 | @media only screen and (max-width: 600px) {
19 | height: 8rem;
20 | }
21 | `;
22 |
23 | const ProjectLink = styled.a`
24 | text-decoration: none;
25 |
26 | &:link,
27 | &:visited {
28 | color: #0098f0;
29 | }
30 |
31 | &:hover,
32 | &:active {
33 | text-decoration: underline;
34 | }
35 | `;
36 |
37 | const Footer = () => {
38 | return (
39 |
40 |
41 | If you like this project then you can show some love by giving it a :)
42 |
43 |
44 |
48 | khusharth/gitpedia
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default Footer;
56 |
--------------------------------------------------------------------------------
/src/components/Form.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import styled from 'styled-components';
4 | import Button from './Button';
5 | import { FaSearch } from 'react-icons/fa';
6 |
7 | const Form = styled.form`
8 | display: flex;
9 | flex-wrap: wrap;
10 | justify-content: center;
11 | align-items: center;
12 |
13 | & button {
14 | padding: 1.2rem 1.5rem;
15 | }
16 |
17 | @media only screen and (max-width: 600px) {
18 | & button {
19 | align-self: flex-start;
20 | }
21 | }
22 |
23 | @media only screen and (max-width: 400px) {
24 | & button {
25 | height: auto;
26 | }
27 | }
28 |
29 | & span {
30 | }
31 | `;
32 |
33 | const Input = styled.input`
34 | font-size: 2.2rem;
35 | font-family: inherit;
36 | color: inherit;
37 | padding: 1.2rem 1.6rem;
38 | border-radius: 5px;
39 | border: none;
40 | background-color: ${(p) => p.theme.inputColor};
41 | border-bottom: 3px solid transparent;
42 | margin-left: 10px;
43 | margin-right: 15px;
44 | transition: all 0.3s;
45 |
46 | &:focus {
47 | outline: none;
48 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
49 | border-bottom: 3px solid #0098f0;
50 | }
51 |
52 | &::placeholder {
53 | color: #aaa;
54 | }
55 |
56 | @media only screen and (max-width: 600px) {
57 | text-align: center;
58 | margin: 0;
59 | margin-right: 15px;
60 | margin-bottom: 10px;
61 | }
62 | `;
63 |
64 | const Span = styled.span`
65 | font-size: 2.4rem;
66 | display: ${(p) => (p.displaySpan ? 'inline-block' : 'none')};
67 |
68 | @media only screen and (max-width: 600px) {
69 | display: none;
70 | }
71 | `;
72 |
73 | const SearchForm = ({ displaySpan }) => {
74 | const [user, updateUser] = useState('');
75 | const history = useHistory();
76 |
77 | const onFormSubmit = async (event, user) => {
78 | event.preventDefault();
79 |
80 | // If value of user is not blank then only go to next page
81 | if (user) {
82 | history.push(`/user/${user}`);
83 | }
84 | };
85 |
86 | return (
87 |
100 | );
101 | };
102 |
103 | export default SearchForm;
104 |
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import styled, { ThemeContext } from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | import Logo from './Logo';
5 | import Toggle from './Toggle';
6 | import Form from './Form';
7 |
8 | const StyledHeader = styled.header`
9 | background-color: ${(p) => p.theme.cardColor};
10 | min-height: 7rem;
11 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.1);
12 | display: flex;
13 | flex-wrap: wrap;
14 | align-items: center;
15 | padding: 0.5rem 6rem;
16 | justify-content: space-between;
17 |
18 | @media only screen and (max-width: 767px) {
19 | padding: 0.5rem 2rem;
20 | }
21 |
22 | & input {
23 | font-size: 2rem;
24 | }
25 |
26 | & svg {
27 | vertical-align: middle;
28 | font-size: 2rem;
29 | }
30 |
31 | @media only screen and (max-width: 600px) {
32 | & input {
33 | margin-bottom: 5px;
34 | padding: 1rem;
35 | width: 75%;
36 | }
37 |
38 | & svg {
39 | vertical-align: unset;
40 | }
41 | }
42 |
43 | & a {
44 | margin-top: 0.5rem;
45 | }
46 |
47 | & a:focus {
48 | outline: none;
49 | }
50 |
51 | @media only screen and (max-width: 633px) {
52 | & form {
53 | order: 1;
54 | margin-top: 0.7rem;
55 | margin-left: auto;
56 | margin-right: auto;
57 | }
58 | }
59 | `;
60 |
61 | const Header = () => {
62 | const { id, setTheme } = useContext(ThemeContext);
63 |
64 | return (
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | );
73 | };
74 |
75 | export default Header;
76 |
--------------------------------------------------------------------------------
/src/components/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import loader from '../assets/loader.gif';
4 |
5 | const LoaderContainer = styled.div`
6 | height: calc(100vh - 150px);
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | `;
11 |
12 | const Loader = () => {
13 | return (
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default Loader;
21 |
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from '../assets/logo.png';
3 |
4 | const Logo = (props) => {
5 | return (
6 | <>
7 |
8 | >
9 | );
10 | };
11 |
12 | export default Logo;
13 |
--------------------------------------------------------------------------------
/src/components/MaterialTabs.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import styled from 'styled-components';
3 | import { withStyles } from '@material-ui/styles';
4 | import Tab from '@material-ui/core/Tab';
5 | import Tabs from '@material-ui/core/Tabs';
6 |
7 | const TabsContainer = styled.div`
8 | background-color: ${(p) => p.theme.cardColor};
9 | box-shadow:
10 | 0px 2px 1px -1px rgba(0, 0, 0, 0.2),
11 | 0px 1px 1px 0px rgba(0, 0, 0, 0.14),
12 | 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
13 | border-radius: 4px;
14 | `;
15 | // Object for configuring default material UI styles
16 | const styles = {
17 | indicator: {
18 | backgroundColor: '#1890ff'
19 | },
20 | centered: {
21 | justifyContent: 'space-around'
22 | },
23 | tab: {
24 | fontFamily: "'Roboto', sans-serif",
25 | fontSize: '1.5rem'
26 | },
27 | tabRoot: {
28 | color: '#999',
29 | '&:hover': {
30 | // color: "#ffffff",
31 | // opacity: 1,
32 | },
33 | '&$tabSelected': {
34 | color: '#1890ff'
35 | },
36 | textTransform: 'initial'
37 | },
38 | tabSelected: {
39 | color: '#1890ff'
40 | }
41 | };
42 |
43 | const MaterialTabs = (props) => {
44 | const [selectedTab, setSelectedTab] = useState(0);
45 |
46 | const handleChange = (event, newValue) => {
47 | setSelectedTab(newValue);
48 | };
49 |
50 | const tabStyle = {
51 | root: props.classes.tabRoot,
52 | selected: props.classes.tabSelected
53 | };
54 |
55 | const { indicator, centered, tab } = props.classes;
56 | return (
57 | <>
58 |
59 |
68 |
69 |
70 |
71 |
72 |
73 | {selectedTab === 0 && props.tab1}
74 | {selectedTab === 1 && props.tab2}
75 | {selectedTab === 2 && props.tab3}
76 | >
77 | );
78 | };
79 |
80 | export default withStyles(styles)(MaterialTabs);
81 |
--------------------------------------------------------------------------------
/src/components/Profile.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import {
4 | GoLocation,
5 | GoGlobe,
6 | GoMarkGithub,
7 | GoBriefcase,
8 | GoMail,
9 | GoCalendar,
10 | GoPackage,
11 | GoCode
12 | } from 'react-icons/go';
13 | import LanguageContext from '../contexts/LanguageContext';
14 | import Button from './Button';
15 |
16 | const ProfileSection = styled.section`
17 | padding: 2rem 6rem;
18 | padding-bottom: 0rem;
19 | display: flex;
20 | flex-direction: column;
21 |
22 | @media only screen and (max-width: 900px) {
23 | padding: 1.5rem 2rem;
24 | }
25 | `;
26 |
27 | const UserContainer = styled.div`
28 | height: 100%;
29 | padding: 2rem 0;
30 | display: flex;
31 | flex-direction: column;
32 | align-items: center;
33 |
34 | @media only screen and (max-width: 600px) {
35 | padding-top: 10rem;
36 | }
37 | `;
38 |
39 | const UserInfoDiv = styled.div`
40 | display: flex;
41 | justify-content: center;
42 | background-color: ${(p) => p.theme.cardColor};
43 | padding: 4rem;
44 | border-radius: 5px;
45 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
46 |
47 | & ul li {
48 | font-size: 1.8rem;
49 | padding: 0.3rem 0;
50 | }
51 |
52 | & ul li h1 {
53 | font-size: 3rem;
54 | font-weight: 400;
55 | }
56 |
57 | & ul li:last-of-type {
58 | padding-top: 0.8rem;
59 | }
60 |
61 | & ul li a {
62 | text-decoration: none;
63 | &:link,
64 | &:visited {
65 | color: #0098f0;
66 | }
67 |
68 | &:hover,
69 | &:active {
70 | text-decoration: underline;
71 | }
72 | }
73 |
74 | @media only screen and (max-width: 600px) {
75 | flex-direction: column;
76 | align-items: center;
77 | padding: 3rem 4rem;
78 |
79 | & ul li {
80 | text-align: center;
81 | }
82 | }
83 |
84 | @media only screen and (max-width: 500px) {
85 | width: 100%;
86 | padding: 3rem 2rem;
87 | }
88 | `;
89 |
90 | const ProfileImgDiv = styled.div`
91 | margin-right: 2.5rem;
92 | position: relative;
93 | height: 200px;
94 | width: 100px;
95 | @media only screen and (max-width: 600px) {
96 | margin-right: 0;
97 | margin-bottom: 2.5rem;
98 | width: 100%;
99 | height: 100px;
100 | }
101 |
102 | & img {
103 | position: absolute;
104 | top: 0;
105 | left: -10rem;
106 | vertical-align: middle;
107 | text-align: center;
108 | border-radius: 2px;
109 |
110 | @media only screen and (max-width: 600px) {
111 | top: -10rem;
112 | right: 0;
113 | left: 0;
114 | margin: 0 auto;
115 | }
116 | }
117 | `;
118 |
119 | const DetailList = styled.ul`
120 | margin-top: 3rem;
121 | border-radius: 5px;
122 | background-color: ${(p) => p.theme.cardColor};
123 | padding: 2rem 4rem;
124 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
125 |
126 | @media only screen and (max-width: 600px) {
127 | padding: 3rem 4rem;
128 | }
129 |
130 | @media only screen and (max-width: 500px) {
131 | width: 100%;
132 | }
133 |
134 | & ul li {
135 | text-align: center;
136 | }
137 |
138 | & ul li span a button {
139 | font-weight: 500;
140 | }
141 | `;
142 | const FlexContainer = styled.div`
143 | display: flex;
144 |
145 | @media only screen and (max-width: 600px) {
146 | flex-direction: column;
147 | justify-content: center;
148 | }
149 | `;
150 |
151 | const LocationDiv = styled.div`
152 | @media only screen and (max-width: 600px) {
153 | padding-top: 0.6rem;
154 | }
155 | `;
156 |
157 | const IconSpan = styled.span`
158 | display: ${(p) => (p.available ? 'inline' : 'none')};
159 | margin-right: ${(p) => (p.company ? '1.5rem' : '0')};
160 |
161 | & svg {
162 | vertical-align: middle;
163 | margin-bottom: 4px;
164 | }
165 |
166 | & a button svg {
167 | margin-bottom: 0;
168 | }
169 |
170 | @media only screen and (max-width: 600px) {
171 | margin-right: 0;
172 | }
173 | `;
174 |
175 | const Span = styled.span`
176 | & svg {
177 | vertical-align: middle;
178 | margin-bottom: 3px;
179 | }
180 |
181 | margin-right: 0.5rem;
182 | `;
183 |
184 | const Profile = (props) => {
185 | const {
186 | avatar_url,
187 | email,
188 | html_url,
189 | login,
190 | company,
191 | location,
192 | blog,
193 | name,
194 | public_repos,
195 | created_at
196 | } = props.userData;
197 |
198 | let website = blog;
199 |
200 | if (blog && blog.slice(0, 4) !== 'http') {
201 | website = `http://${blog}`;
202 | }
203 |
204 | const date = new Date(created_at);
205 | const joinedDate = date.toDateString().split(' ').slice(1).join(' ');
206 |
207 | return (
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | -
216 |
{name}
217 |
218 | -
219 |
220 |
221 | {/* If available = true then only show the component */}
222 |
223 | {company}
224 |
225 |
226 |
227 |
228 | {location}
229 |
230 |
231 |
232 |
233 | -
234 |
235 | {email}
236 |
237 |
238 | -
239 |
240 | {' '}
241 |
242 | {blog}
243 |
244 |
245 |
246 | -
247 |
248 |
253 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 | -
264 | {' '}
265 |
266 |
267 | {' '}
268 | Joined github on {joinedDate}
269 |
270 | -
271 | {' '}
272 |
273 |
274 | {' '}
275 | Since have created {public_repos} projects
276 |
277 |
278 | {(context) => {
279 | const langCount = context.length;
280 | return (
281 | -
282 |
283 |
284 | {' '}
285 | Using {langCount} different languages
286 |
287 | );
288 | }}
289 |
290 |
291 |
292 |
293 |
294 | );
295 | };
296 |
297 | export default Profile;
298 |
--------------------------------------------------------------------------------
/src/components/Stats/Stats.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import styled from 'styled-components';
3 | import { GoRepo, GoOrganization, GoPerson, GoStar } from 'react-icons/go';
4 | import { DoughnutChart, PieChart, BarChart } from '../Chart';
5 |
6 | import { calculateTotalStars, calculateMostStarredRepos, calculateMaxSizeRepos } from './utils';
7 |
8 | const StatsContainer = styled.div`
9 | margin-top: 3rem;
10 | margin-bottom: 2rem;
11 | display: flex;
12 | justify-content: space-between;
13 | flex-wrap: wrap;
14 |
15 | @media only screen and (max-width: 600px) {
16 | justify-content: space-around;
17 | }
18 | `;
19 |
20 | const StatsDiv = styled.div`
21 | display: inline-block;
22 | background-color: ${(p) => {
23 | if (p.secondary) return '#00b7e1';
24 | else if (p.tertiary) return '#00cbbe';
25 | else if (p.quad) return '#00d0a7';
26 | else return '#0098f0';
27 | }};
28 | color: rgb(255, 255, 255);
29 | margin-top: 0;
30 | margin-right: 1rem;
31 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
32 | padding: 1.5rem 3rem;
33 | min-width: 25rem;
34 | border-radius: 5px;
35 | font-size: 2.2rem;
36 |
37 | & svg {
38 | vertical-align: middle;
39 | }
40 |
41 | @media only screen and (max-width: 1174px) {
42 | margin-top: ${(p) => (p.quad ? '2rem' : '0rem')};
43 | }
44 |
45 | @media only screen and (max-width: 749px) {
46 | margin-top: ${(p) => (p.tertiary || p.quad ? '2rem' : '0rem')};
47 | }
48 |
49 | @media only screen and (max-width: 600px) {
50 | text-align: center;
51 | }
52 |
53 | @media only screen and (max-width: 516px) {
54 | margin-top: ${(p) => (p.primary ? '0rem' : '2rem')};
55 | }
56 |
57 | @media only screen and (max-width: 498px) {
58 | margin-right: 0;
59 | }
60 | `;
61 |
62 | const RoundChartContainer = styled.div`
63 | display: flex;
64 | justify-content: space-between;
65 | flex-wrap: wrap;
66 | padding: 3rem 0;
67 |
68 | @media only screen and (max-width: 1290px) {
69 | justify-content: space-around;
70 | }
71 |
72 | @media only screen and (max-width: 770px) {
73 | padding-bottom: 0;
74 | }
75 | `;
76 |
77 | const ChartDiv = styled.div`
78 | display: inline-block;
79 | margin-right: 1rem;
80 | padding: 1.5rem 6rem;
81 | padding: ${(p) => (p.chart1 ? '1.5rem 3rem' : '1.5rem 5rem')};
82 | max-height: 55rem;
83 | border-radius: 5px;
84 | min-width: 38rem;
85 | box-shadow: 0 1rem 2rem 0 rgba(0, 0, 0, 0.2);
86 | background-color: ${(p) => p.theme.cardColor};
87 |
88 | & canvas {
89 | max-height: 40rem;
90 | }
91 |
92 | @media only screen and (max-width: 1290px) {
93 | margin-top: ${(p) => (p.chart3 ? '5rem' : '0rem')};
94 | }
95 |
96 | @media only screen and (max-width: 1140px) {
97 | width: ${(p) => (p.tertiary ? '100%' : '0rem')};
98 | }
99 |
100 | @media only screen and (max-width: 770px) {
101 | min-width: 80%;
102 | margin-top: ${(p) => (p.chart3 ? '0rem' : '0rem')};
103 | margin-bottom: ${(p) => (p.chart5 ? '0rem' : '5rem')};
104 | }
105 |
106 | @media only screen and (max-width: 600px) {
107 | min-width: 100%;
108 | }
109 | `;
110 |
111 | const ChartHeading = styled.h1`
112 | font-weight: 400;
113 | font-size: 2.6rem;
114 | margin-bottom: 2rem;
115 | text-align: center;
116 | `;
117 |
118 | const IconSpan = styled.span`
119 | margin-left: 0.8rem;
120 | font-size: 1.7rem;
121 | font-weight: 500;
122 | `;
123 |
124 | const Stats = ({ userData, repoData }) => {
125 | const [starData, setStarData] = useState({});
126 | const [sizeData, setSizeData] = useState({});
127 | const [totalStars, setTotalStars] = useState(null);
128 |
129 | useEffect(() => {
130 | const getMostStarredRepos = () => {
131 | setStarData(calculateMostStarredRepos(repoData));
132 | };
133 |
134 | const getTotalStars = () => {
135 | setTotalStars(calculateTotalStars(repoData));
136 | };
137 |
138 | const getMaxSizeRepos = () => {
139 | setSizeData(calculateMaxSizeRepos(repoData));
140 | };
141 |
142 | if (repoData.length) {
143 | getMostStarredRepos();
144 | getMaxSizeRepos();
145 | getTotalStars();
146 | }
147 | }, [repoData]);
148 |
149 | return (
150 | <>
151 |
152 |
153 |
154 |
155 | Repositories
156 |
157 | {userData.public_repos}
158 |
159 |
160 |
161 |
162 | Total Stars
163 |
164 | {totalStars}
165 |
166 |
167 |
168 |
169 | Followers
170 |
171 | {userData.followers}
172 |
173 |
174 |
175 |
176 | Following
177 |
178 | {userData.following}
179 |
180 |
181 |
182 |
183 |
184 | Largest in Size(kb)
185 |
186 |
187 |
188 | Top Languages
189 |
190 |
191 |
192 | Most Starred
193 |
194 |
195 |
196 | >
197 | );
198 | };
199 |
200 | export default Stats;
201 |
--------------------------------------------------------------------------------
/src/components/Stats/__tests__/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { calculateTotalStars } from '../utils';
2 | import { RepoData } from '../../../types';
3 | import { mockRepoData } from '../../../mock.data';
4 |
5 | describe('calculateTotalStars', () => {
6 | test('should return the correct total stars of non-forked repositories', () => {
7 | // Sample data for the repoData array with both forked and non-forked repositories
8 | const repoData = [
9 | { ...mockRepoData, name: 'repo1', fork: false, stargazers_count: 10 },
10 | { ...mockRepoData, name: 'repo2', fork: true, stargazers_count: 5 },
11 | { ...mockRepoData, name: 'repo3', fork: false, stargazers_count: 15 }
12 | ];
13 |
14 | // Call getTotalStars with the sample repoData
15 | const totalStars = calculateTotalStars(repoData);
16 |
17 | // Verify that the totalStars is calculated correctly
18 | expect(totalStars).toBe(25);
19 | });
20 |
21 | test('should return 0 when all repos are forked repositories', () => {
22 | const repoData = [
23 | { ...mockRepoData, name: 'repo1', fork: true, stargazers_count: 10 },
24 | { ...mockRepoData, name: 'repo2', fork: true, stargazers_count: 5 },
25 | { ...mockRepoData, name: 'repo3', fork: true, stargazers_count: 15 }
26 | ];
27 |
28 | // Call getTotalStars with the empty repoData
29 | const totalStars = calculateTotalStars(repoData);
30 |
31 | // Verify that the totalStars is 0
32 | expect(totalStars).toBe(0);
33 | });
34 |
35 | test('should return 0 when there are no repos', () => {
36 | // Sample data for an empty repoData array
37 | const repoData: RepoData = [];
38 |
39 | // Call getTotalStars with the empty repoData
40 | const totalStars = calculateTotalStars(repoData);
41 |
42 | // Verify that the totalStars is 0
43 | expect(totalStars).toBe(0);
44 | });
45 |
46 | it('should handle missing stargazers_count property', () => {
47 | // Sample data with missing stargazers_count property in one of the repos
48 | const repoData = [
49 | { ...mockRepoData, name: 'repo1', fork: false, stargazers_count: 10 },
50 | { ...mockRepoData, name: 'repo2', fork: true, stargazers_count: 5 },
51 | { ...mockRepoData, name: 'repo3', fork: false, stargazers_count: undefined }
52 | ];
53 |
54 | // Call getTotalStars with the sample repoData
55 | const totalStars = calculateTotalStars(repoData);
56 |
57 | // Verify that the missing stargazers_count property is handled gracefully (ignored in the calculation)
58 | expect(totalStars).toBe(10);
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/components/Stats/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Stats';
2 |
--------------------------------------------------------------------------------
/src/components/Stats/utils.ts:
--------------------------------------------------------------------------------
1 | import { RepoData } from '../../types';
2 |
3 | const MAX_REPO_TO_SHOW_ON_GRAPH = 5;
4 | const sortProperties = {
5 | STARGAZERS_COUNT: 'stargazers_count',
6 | SIZE: 'size'
7 | } as const;
8 |
9 | /**
10 | * returns total stars a user has from all their repos (excluding forks)
11 | */
12 | const calculateTotalStars = (repoData: RepoData) => {
13 | const myRepos = repoData.filter((repo) => !repo.fork).map((repo) => repo.stargazers_count ?? 0);
14 | const totalStars = myRepos.reduce((a, b) => a + b, 0);
15 |
16 | return totalStars;
17 | };
18 |
19 | /**
20 | * gets stats (no. of stars) for the top 5 most starred repos of the user
21 | */
22 | const calculateMostStarredRepos = (repoData: RepoData) => {
23 | const sortProperty = sortProperties.STARGAZERS_COUNT;
24 |
25 | const mostStarredRepos = repoData
26 | .filter((repo) => !repo.fork)
27 | .sort((a, b) => (b[sortProperty] ?? 0) - (a[sortProperty] ?? 0))
28 | .slice(0, MAX_REPO_TO_SHOW_ON_GRAPH);
29 |
30 | // Label and data needed for displaying Charts
31 | const label = mostStarredRepos.map((repo) => repo.name);
32 | const data = mostStarredRepos.map((repo) => repo[sortProperty]);
33 |
34 | return { label, data };
35 | };
36 |
37 | /**
38 | * gets size for the top 5 largest repos by size of the user
39 | */
40 | const calculateMaxSizeRepos = (repoData: RepoData) => {
41 | const sortProperty = sortProperties.SIZE;
42 |
43 | const mostStarredRepos = repoData
44 | .filter((repo) => !repo.fork)
45 | .sort((a, b) => b[sortProperty] - a[sortProperty])
46 | .slice(0, MAX_REPO_TO_SHOW_ON_GRAPH);
47 |
48 | const label = mostStarredRepos.map((repo) => repo.name);
49 | const data = mostStarredRepos.map((repo) => repo[sortProperty]);
50 |
51 | return { label, data };
52 | };
53 |
54 | export { calculateTotalStars, calculateMostStarredRepos, calculateMaxSizeRepos };
55 |
--------------------------------------------------------------------------------
/src/components/Timeline.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import TimelineItem from './TimelineItem';
4 |
5 | const TimelineContainer = styled.div`
6 | position: relative;
7 | margin: 3rem auto;
8 | width: 100rem;
9 |
10 | &:before {
11 | content: '';
12 | position: absolute;
13 | left: 50%;
14 | width: 2px;
15 | height: 100%;
16 | background: #c5c5c5;
17 |
18 | @media only screen and (max-width: 767px) {
19 | left: 20px;
20 | }
21 | }
22 |
23 | & ul {
24 | padding: 0;
25 | margin: 0;
26 | }
27 |
28 | & ul li {
29 | position: relative;
30 | line-height: normal;
31 | width: 50%;
32 | padding: 2rem 4rem 4rem 4rem;
33 | box-sizing: border-box;
34 |
35 | @media only screen and (max-width: 767px) {
36 | margin-bottom: 3rem;
37 | }
38 |
39 | @media only screen and (max-width: 600px) {
40 | padding: 2rem 0rem 4rem 4rem;
41 | }
42 | }
43 |
44 | & ul li a {
45 | text-decoration: none;
46 | color: inherit;
47 | }
48 |
49 | & ul li h1 svg {
50 | vertical-align: middle;
51 | }
52 |
53 | & ul li:nth-child(odd) {
54 | float: left;
55 | text-align: right;
56 | clear: both;
57 |
58 | @media only screen and (max-width: 767px) {
59 | width: 100%;
60 | text-align: left;
61 | padding-left: 5rem;
62 | padding-bottom: 5rem;
63 | }
64 | }
65 |
66 | & ul li:nth-child(even) {
67 | float: right;
68 | text-align: left;
69 | clear: both;
70 |
71 | @media only screen and (max-width: 767px) {
72 | width: 100%;
73 | text-align: left;
74 | padding-left: 5rem;
75 | padding-bottom: 5rem;
76 | }
77 | }
78 |
79 | & ul li:nth-child(odd):before {
80 | content: '';
81 | position: absolute;
82 | right: -7px;
83 | top: 25px;
84 | width: 13px;
85 | height: 13px;
86 | background-image: linear-gradient(to right, #0098f0, #00f2c3);
87 | border-radius: 50%;
88 | box-shadow: 0 0 0 4px rgba(0, 242, 195, 0.2);
89 |
90 | @media only screen and (max-width: 767px) {
91 | top: -18px;
92 | left: 14px;
93 | }
94 | }
95 |
96 | & ul li:nth-child(even):before {
97 | content: '';
98 | position: absolute;
99 | left: -5px;
100 | top: 25px;
101 | width: 13px;
102 | height: 13px;
103 | background-image: linear-gradient(to right, #0098f0, #00f2c3);
104 | border-radius: 50%;
105 | box-shadow: 0 0 0 4px rgba(0, 242, 195, 0.2);
106 |
107 | @media only screen and (max-width: 767px) {
108 | top: -18px;
109 | left: 14px;
110 | }
111 | }
112 |
113 | & ul li:nth-child(odd) .time {
114 | position: absolute;
115 | top: 12px;
116 | right: -165px;
117 |
118 | @media only screen and (max-width: 767px) {
119 | top: -30px;
120 | left: 50px;
121 | right: inherit;
122 | }
123 | }
124 |
125 | & ul li:nth-child(even) .time {
126 | position: absolute;
127 | top: 12px;
128 | left: -165px;
129 |
130 | @media only screen and (max-width: 767px) {
131 | top: -30px;
132 | left: 50px;
133 | right: inherit;
134 | }
135 | }
136 |
137 | & ul li .time h4 {
138 | font-size: 14px;
139 | font-weight: 500;
140 | }
141 |
142 | @media only screen and (max-width: 1000px) {
143 | width: 100%;
144 | }
145 |
146 | @media only screen and (max-width: 767px) {
147 | width: 100%;
148 | margin-top: 7rem;
149 | padding-bottom: 0;
150 | }
151 | `;
152 |
153 | const TimeDiv = styled.div`
154 | background-image: linear-gradient(to right, #0098f0, #00f2c3);
155 | margin: 0;
156 | padding: 8px 16px;
157 | color: #fff;
158 | border-radius: 18px;
159 | box-shadow: 0 0 0 3px rgba(0, 242, 195, 0.2);
160 | `;
161 |
162 | const ClearFloat = styled.div`
163 | clear: both;
164 | `;
165 |
166 | const Timeline = ({ repoData }) => {
167 | const buildRepoTimeline = () => {
168 | return repoData.map((repo) => (
169 |
170 |
179 |
180 | {new Date(repo.updated_at).toDateString().split(' ').slice(1).join(' ')}
181 |
182 |
183 | ));
184 | };
185 |
186 | return (
187 | <>
188 |
189 |
190 | {buildRepoTimeline()}
191 |
192 |
193 |
194 | >
195 | );
196 | };
197 |
198 | export default Timeline;
199 |
--------------------------------------------------------------------------------
/src/components/TimelineItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { GoRepo, GoRepoForked, GoStar, GoPrimitiveDot } from 'react-icons/go';
4 |
5 | const ItemContainer = styled.div`
6 | display: inline-block;
7 | background-color: ${(p) => p.theme.cardColor};
8 | padding: 2rem;
9 | border-radius: 5px;
10 | box-shadow: 0 1rem 2rem 0 rgb(0, 0, 0, 0.2);
11 | min-width: 30rem;
12 |
13 | & h1 {
14 | padding-bottom: 1.5rem;
15 | font-weight: 500;
16 | }
17 |
18 | @media only screen and (max-width: 600px) {
19 | width: 100%;
20 | }
21 | `;
22 |
23 | const FooterSpan = styled.span`
24 | display: ${(p) => (p.available ? 'inline' : 'none')};
25 | font-size: 1.5rem;
26 | margin-right: 1rem;
27 | `;
28 |
29 | const ItemFooter = styled.div`
30 | margin-top: 3rem;
31 | display: flex;
32 | justify-content: space-between;
33 | `;
34 |
35 | const TimelineItem = ({ title, description, language, forks, size, stars, url }) => {
36 | return (
37 | <>
38 |
39 |
40 |
41 |
42 |
43 | {' '}
44 | {title}
45 |
46 | {description}
47 |
48 |
49 |
50 | {language}
51 |
52 |
53 | {stars}
54 |
55 |
56 | {forks}
57 |
58 |
59 | {Number(size).toLocaleString()} Kb
60 |
61 |
62 |
63 | >
64 | );
65 | };
66 |
67 | export default TimelineItem;
68 |
--------------------------------------------------------------------------------
/src/components/Toggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from './Button';
3 | import styled from 'styled-components';
4 | import { FaMoon, FaSun } from 'react-icons/fa';
5 |
6 | const ToggleSpan = styled.span`
7 | padding-left: 12rem;
8 |
9 | @media only screen and (max-width: 1100px) {
10 | padding-left: 10rem;
11 | }
12 |
13 | @media only screen and (max-width: 950px) {
14 | padding-left: 9rem;
15 | }
16 |
17 | @media only screen and (max-width: 950px) {
18 | padding-left: 7rem;
19 | }
20 |
21 | @media only screen and (max-width: 780px) {
22 | padding-left: 3rem;
23 | }
24 |
25 | @media only screen and (max-width: 650px) {
26 | padding-left: 1rem;
27 | }
28 |
29 | & svg {
30 | vertical-align: middle;
31 | font-size: 2rem;
32 | }
33 | `;
34 |
35 | const Toggle = ({ isDark, onToggle }) => {
36 | return (
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default Toggle;
44 |
--------------------------------------------------------------------------------
/src/components/__tests__/Form.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router } from 'react-router-dom';
3 | import { render, screen } from '@testing-library/react';
4 | import user from '@testing-library/user-event';
5 | import { Form } from '../';
6 |
7 | test('renders a input with a search cta to enter github username', () => {
8 | const userName = 'khusharth';
9 |
10 | render(
11 |
12 |
13 |
14 | );
15 |
16 | const mockHistoryPushState = jest.spyOn(window.history, 'pushState');
17 |
18 | const input = screen.getByLabelText(/enter github username/i);
19 | const searchBtn = screen.getByLabelText(/search/i);
20 |
21 | user.type(input, userName);
22 | user.click(searchBtn);
23 |
24 | // async?
25 | expect(mockHistoryPushState).toBeCalledTimes(1);
26 | expect(window.location.href).toContain(`/user/${userName}`);
27 | });
28 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Activities from './Activities';
2 | import Button from './Button';
3 | import Error from './Error';
4 | import Footer from './Footer';
5 | import Form from './Form';
6 | import Header from './Header';
7 | import Loader from './Loader';
8 | import Logo from './Logo';
9 | import MaterialTabs from './MaterialTabs';
10 | import Profile from './Profile';
11 | import Stats from './Stats';
12 | import Timeline from './Timeline';
13 | import TimelineItem from './TimelineItem';
14 | import Toggle from './Toggle';
15 |
16 | export {
17 | Activities,
18 | Button,
19 | Error,
20 | Footer,
21 | Form,
22 | Header,
23 | Loader,
24 | Logo,
25 | MaterialTabs,
26 | Profile,
27 | Stats,
28 | Timeline,
29 | TimelineItem,
30 | Toggle
31 | };
32 |
--------------------------------------------------------------------------------
/src/contexts/LanguageContext.js:
--------------------------------------------------------------------------------
1 | // Context for sharing total Languages among components
2 | import React from 'react';
3 |
4 | export default React.createContext([]);
5 |
--------------------------------------------------------------------------------
/src/contexts/ThemeProvider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from 'styled-components';
3 |
4 | import { useDarkMode } from '../useDarkMode';
5 |
6 | import { light as LightTheme, dark as DarkTheme } from '../style';
7 |
8 | const ThemeProviderWrapper = ({ children }) => {
9 | // Custom hook for persistent darkmode
10 | const [theme, setTheme] = useDarkMode();
11 |
12 | return (
13 | {
17 | setTheme((state) => (state.id === 'light' ? DarkTheme : LightTheme));
18 | }
19 | }}>
20 | {children}
21 |
22 | );
23 | };
24 |
25 | export default ThemeProviderWrapper;
26 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components/App';
4 |
5 | // eslint-disable-next-line react/no-deprecated
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/src/mocks/handlers.ts:
--------------------------------------------------------------------------------
1 | import { rest } from 'msw';
2 | import { BASE_URL } from 'src/api/base';
3 | import { mockUserData, mockRepoList, mockActivityData } from 'src/mock.data';
4 |
5 | export const handlers = [
6 | rest.get(`${BASE_URL}/users/:userId/events`, (req, res, ctx) => {
7 | return res(
8 | // Respond with a 200 status code
9 | ctx.status(200),
10 | ctx.json(mockActivityData)
11 | );
12 | }),
13 | rest.get(`${BASE_URL}/users/:userId/repos`, (req, res, ctx) => {
14 | return res(
15 | // Respond with a 200 status code
16 | ctx.status(200),
17 | ctx.json(mockRepoList)
18 | );
19 | }),
20 | rest.get(`${BASE_URL}/users/:userId`, (req, res, ctx) => {
21 | return res(
22 | // Respond with a 200 status code
23 | ctx.status(200),
24 | ctx.json({ ...mockUserData, login: req.params.userId })
25 | );
26 | })
27 | ];
28 |
--------------------------------------------------------------------------------
/src/mocks/server.ts:
--------------------------------------------------------------------------------
1 | import { setupServer } from 'msw/node';
2 | import { handlers } from './handlers';
3 |
4 | // This configures a request mocking server with the given request handlers.
5 | export const server = setupServer(...handlers);
6 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import styled, { ThemeContext } from 'styled-components';
3 | import { Logo, Form, Toggle } from '../components';
4 |
5 | const StyledHeader = styled.header`
6 | height: 7rem;
7 | display: flex;
8 | align-items: center;
9 | padding: 1rem 6rem;
10 | justify-content: flex-end;
11 |
12 | & button svg {
13 | font-size: 2rem;
14 | vertical-align: middle;
15 | }
16 |
17 | @media only screen and (max-width: 600px) {
18 | padding: 1rem 2rem;
19 | }
20 | `;
21 |
22 | const Container = styled.div`
23 | display: flex;
24 | flex-direction: column;
25 | width: 100%;
26 | height: calc(100vh - 21rem);
27 | justify-content: center;
28 | align-items: center;
29 | margin-bottom: 7rem;
30 |
31 | @media only screen and (max-width: 600px) {
32 | margin-bottom: 1rem;
33 | }
34 |
35 | & form {
36 | margin-top: 4rem;
37 |
38 | & svg {
39 | font-size: 2rem;
40 | }
41 | }
42 | `;
43 |
44 | const Home = () => {
45 | const { id, setTheme } = useContext(ThemeContext);
46 |
47 | return (
48 | <>
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | >
57 | );
58 | };
59 |
60 | export default Home;
61 |
--------------------------------------------------------------------------------
/src/pages/UserProfile.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { useParams } from 'react-router-dom';
4 | import {
5 | Header,
6 | Profile,
7 | MaterialTabs,
8 | Stats,
9 | Timeline,
10 | Activities,
11 | Footer,
12 | Loader,
13 | Error
14 | } from '../components';
15 | import { useGithubUserData, useLangData, useUserRepos, useActivityData } from '../api/githubAPI';
16 | import LanguageContext from '../contexts/LanguageContext';
17 |
18 | const TabSection = styled.section`
19 | padding: 2rem 6rem;
20 | @media only screen and (max-width: 900px) {
21 | padding: 1.5rem 2rem;
22 | }
23 | `;
24 |
25 | const UserProfile = () => {
26 | const params = useParams();
27 | const username = params.id;
28 |
29 | const [langData, langLoading, langError] = useLangData(username);
30 | const [userData, userLoading, userError] = useGithubUserData(username);
31 | const [repoData, repoLoading, repoError] = useUserRepos(username);
32 | const [activityData, activityLoading, activityError] = useActivityData(username);
33 |
34 | const loading = userLoading || langLoading || repoLoading || activityLoading;
35 |
36 | const error =
37 | userError &&
38 | userError.active &&
39 | langError &&
40 | langError.active &&
41 | repoError &&
42 | repoError.active &&
43 | activityError &&
44 | activityError.active;
45 |
46 | if (loading) {
47 | return (
48 | <>
49 |
50 |
51 | >
52 | );
53 | } else {
54 | return (
55 | <>
56 |
57 |
58 | {error ? (
59 |
60 | ) : (
61 |
62 |
63 |
64 | }
66 | tab2={}
67 | tab3={}
68 | />
69 |
70 |
71 | )}
72 |
73 |
74 | >
75 | );
76 | }
77 | };
78 |
79 | export default UserProfile;
80 |
--------------------------------------------------------------------------------
/src/pages/__tests__/UserProfile.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Route } from 'react-router-dom';
3 | import { render, screen } from '@testing-library/react';
4 | import user from '@testing-library/user-event';
5 | import UserProfile from '../UserProfile';
6 | import ThemeProviderWrapper from '../../contexts/ThemeProvider';
7 |
8 | import { mockUserData } from 'src/mock.data';
9 |
10 | test('render the expected user profile when user searches a user from the user profile page', async () => {
11 | const userName = 'khusharth';
12 |
13 | window.history.pushState({ id: 'hello' }, '', `/user/${userName}`);
14 |
15 | render(
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | // verify intro card data
24 | await screen.findByLabelText(/github profile/i);
25 | await screen.findByText(mockUserData.name);
26 |
27 | // verify stats card is shown
28 | await screen.findByText(/Joined github on/);
29 | // had to create a regex as there is spacing in between
30 | const regexPattern = `created ${mockUserData.public_repos} projects`;
31 | await screen.findByText(new RegExp(regexPattern));
32 |
33 | // stats tab is visible and active
34 | const activeTab = await screen.findByRole('tab', { selected: true });
35 | expect(activeTab).toHaveTextContent('Stats');
36 |
37 | await screen.findByText(/Repositories/);
38 | await screen.findByText(/Total Stars/);
39 |
40 | // click on Timeline tab
41 | const tab2 = await screen.getByRole('tab', { name: 'Timeline' });
42 | user.click(tab2);
43 | expect(screen.getByRole('tab', { selected: true })).toHaveTextContent('Timeline');
44 | });
45 |
--------------------------------------------------------------------------------
/src/style/GlobalStyle.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyle = createGlobalStyle`
4 |
5 | * {
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | *,
11 | *::before,
12 | *::after {
13 | box-sizing: inherit;
14 | }
15 |
16 | html {
17 | box-sizing: border-box;
18 | font-size: 62.5%;
19 |
20 | @media only screen and (max-width: 900px) {
21 | font-size: 56%;
22 | }
23 | }
24 |
25 |
26 | body {
27 | font-family: 'Roboto', sans-serif;
28 | font-weight: 400;
29 | font-size: 1.6rem;
30 | line-height: 1.6;
31 | color: ${(p) => p.theme.textColor};
32 | /* background-color: #F0F1F6; */
33 | background-color: #F5F6FA;
34 | background-color: ${(p) => p.theme.bgColor};
35 | }
36 |
37 | ul {
38 | list-style: none;
39 | }
40 | `;
41 |
42 | export default GlobalStyle;
43 |
--------------------------------------------------------------------------------
/src/style/dark.js:
--------------------------------------------------------------------------------
1 | const theme = {
2 | id: 'dark',
3 | bgColor: '#1E1E2C',
4 | cardColor: '#27293d',
5 | inputColor: '#2e3047',
6 | textColor: '#F5F6FA'
7 | };
8 |
9 | export default theme;
10 |
--------------------------------------------------------------------------------
/src/style/index.js:
--------------------------------------------------------------------------------
1 | import GlobalStyle from './GlobalStyle';
2 | import light from './light';
3 | import dark from './dark';
4 |
5 | export { GlobalStyle, light, dark };
6 |
--------------------------------------------------------------------------------
/src/style/light.js:
--------------------------------------------------------------------------------
1 | const theme = {
2 | id: 'light',
3 | bgColor: '#F5F6FA',
4 | cardColor: 'rgb(255,255,255)',
5 | inputColor: 'rgba(238,238,238, 0.8)',
6 | textColor: 'rgb(85, 85, 85)'
7 | };
8 |
9 | export default theme;
10 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | type Root2 = {
2 | id: number;
3 | node_id: string;
4 | name: string;
5 | full_name: string;
6 | private: boolean;
7 | owner: Owner;
8 | html_url: string;
9 | description?: string;
10 | fork: boolean;
11 | url: string;
12 | forks_url: string;
13 | keys_url: string;
14 | collaborators_url: string;
15 | teams_url: string;
16 | hooks_url: string;
17 | issue_events_url: string;
18 | events_url: string;
19 | assignees_url: string;
20 | branches_url: string;
21 | tags_url: string;
22 | blobs_url: string;
23 | git_tags_url: string;
24 | git_refs_url: string;
25 | trees_url: string;
26 | statuses_url: string;
27 | languages_url: string;
28 | stargazers_url: string;
29 | contributors_url: string;
30 | subscribers_url: string;
31 | subscription_url: string;
32 | commits_url: string;
33 | git_commits_url: string;
34 | comments_url: string;
35 | issue_comment_url: string;
36 | contents_url: string;
37 | compare_url: string;
38 | merges_url: string;
39 | archive_url: string;
40 | downloads_url: string;
41 | issues_url: string;
42 | pulls_url: string;
43 | milestones_url: string;
44 | notifications_url: string;
45 | labels_url: string;
46 | releases_url: string;
47 | deployments_url: string;
48 | created_at: string;
49 | updated_at: string;
50 | pushed_at: string;
51 | git_url: string;
52 | ssh_url: string;
53 | clone_url: string;
54 | svn_url: string;
55 | homepage?: string;
56 | size: number;
57 | /** in case of some error on BE? */
58 | stargazers_count?: number;
59 | watchers_count: number;
60 | language?: string;
61 | has_issues: boolean;
62 | has_projects: boolean;
63 | has_downloads: boolean;
64 | has_wiki: boolean;
65 | has_pages: boolean;
66 | has_discussions: boolean;
67 | forks_count: number;
68 | mirror_url: any;
69 | archived: boolean;
70 | disabled: boolean;
71 | open_issues_count: number;
72 | license?: License | null;
73 | allow_forking: boolean;
74 | is_template: boolean;
75 | web_commit_signoff_required: boolean;
76 | topics: string[];
77 | visibility: string;
78 | forks: number;
79 | open_issues: number;
80 | watchers: number;
81 | default_branch: string;
82 | };
83 |
84 | type Owner = {
85 | login: string;
86 | id: number;
87 | node_id: string;
88 | avatar_url: string;
89 | gravatar_id: string;
90 | url: string;
91 | html_url: string;
92 | followers_url: string;
93 | following_url: string;
94 | gists_url: string;
95 | starred_url: string;
96 | subscriptions_url: string;
97 | organizations_url: string;
98 | repos_url: string;
99 | events_url: string;
100 | received_events_url: string;
101 | type: string;
102 | site_admin: boolean;
103 | };
104 |
105 | type License = {
106 | key: string;
107 | name: string;
108 | spdx_id: string;
109 | url?: string;
110 | node_id: string;
111 | };
112 |
113 | export type RepoData = Root2[];
114 |
--------------------------------------------------------------------------------
/src/useDarkMode.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { light as LightTheme, dark as DarkTheme } from './style';
3 |
4 | export const useDarkMode = () => {
5 | const [theme, setTheme] = useState(LightTheme);
6 |
7 | const toggleTheme = () => {
8 | if (theme.id === 'light') {
9 | window.localStorage.setItem('theme', 'dark');
10 | setTheme(DarkTheme);
11 | } else {
12 | window.localStorage.setItem('theme', 'light');
13 | setTheme(LightTheme);
14 | }
15 | };
16 |
17 | useEffect(() => {
18 | const localTheme = window.localStorage.getItem('theme');
19 | localTheme === 'light' ? localTheme && setTheme(LightTheme) : localTheme && setTheme(DarkTheme);
20 | }, []);
21 |
22 | return [theme, toggleTheme];
23 | };
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "types": ["cypress", "@testing-library/cypress"],
4 |
5 | "jsx": "react",
6 | /* Visit https://aka.ms/tsconfig to read more about this file */
7 |
8 | /* Projects */
9 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
10 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
11 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
12 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
13 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
14 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
15 |
16 | /* Language and Environment */
17 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
18 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
19 | // "jsx": "preserve", /* Specify what JSX code is generated. */
20 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
25 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
28 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
29 |
30 | /* Modules */
31 | "module": "commonjs", /* Specify what module code is generated. */
32 | // "rootDir": "./", /* Specify the root folder within your source files. */
33 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
34 | "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
35 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
36 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
37 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
38 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
39 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
40 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
41 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
42 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
43 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
44 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
45 | // "resolveJsonModule": true, /* Enable importing .json files. */
46 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
47 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
48 |
49 | /* JavaScript Support */
50 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
51 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
52 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
53 |
54 | /* Emit */
55 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
56 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
57 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
58 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
60 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
61 | "outDir": "./", /* Specify an output folder for all emitted files. */
62 | // "removeComments": true, /* Disable emitting comments. */
63 | // "noEmit": true, /* Disable emitting files from a compilation. */
64 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
65 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
66 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
67 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
68 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
69 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
70 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
71 | // "newLine": "crlf", /* Set the newline character for emitting files. */
72 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
73 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
74 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
75 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
76 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
77 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
78 |
79 | /* Interop Constraints */
80 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
81 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
82 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
83 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
84 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
85 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
86 |
87 | /* Type Checking */
88 | "strict": true, /* Enable all strict type-checking options. */
89 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
90 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
91 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
92 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
93 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
94 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
95 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
96 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
97 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
98 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
99 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
100 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
101 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
102 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
103 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
104 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
105 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
106 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
107 |
108 | /* Completeness */
109 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
110 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */
111 |
112 | },
113 |
114 | "exclude": ["node_modules"],
115 |
116 | }
117 |
--------------------------------------------------------------------------------