├── .eslintrc.json ├── .vscode └── settings.json ├── assets ├── Blanka.otf └── jwoc_logo.svg ├── public ├── favicon.ico └── vercel.svg ├── postcss.config.js ├── atoms ├── modalAtom.ts └── selectedUserAtom.ts ├── next-env.d.ts ├── pages ├── _app.tsx ├── api │ └── hello.ts └── index.tsx ├── backend ├── utils │ ├── writeJson.js │ └── allRepos.js └── generate.js ├── types └── index.ts ├── utils ├── parseName.ts └── generateConfetti.ts ├── next.config.js ├── .gitignore ├── tsconfig.json ├── components ├── Table.tsx ├── Footer.tsx ├── MetaTags.tsx ├── Header.tsx ├── TableHeader.tsx ├── EachPR.tsx ├── PRModal.tsx ├── TopCard.tsx └── UserCard.tsx ├── .github └── workflows │ └── main.yml ├── package.json ├── tailwind.config.js ├── README.md └── styles └── globals.css /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib" 3 | } -------------------------------------------------------------------------------- /assets/Blanka.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysikdar/jwoc-leaderboard/HEAD/assets/Blanka.otf -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niloysikdar/jwoc-leaderboard/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /atoms/modalAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | 3 | export const modalState = atom({ 4 | key: "modal", 5 | default: false, 6 | }); 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | import { RecoilRoot } from "recoil"; 4 | 5 | function MyApp({ Component, pageProps }: AppProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default MyApp; 14 | -------------------------------------------------------------------------------- /backend/utils/writeJson.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const writeJson = (data) => { 4 | fs.writeFile("data.json", JSON.stringify(data), (error) => { 5 | if (error) { 6 | console.log(error); 7 | } else { 8 | console.log("File has been written successfully"); 9 | } 10 | }); 11 | }; 12 | 13 | module.exports = { writeJson }; 14 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /types/index.ts: -------------------------------------------------------------------------------- 1 | export interface TableDataType { 2 | user_name: string; 3 | avatar_url: string; 4 | user_url: string; 5 | total_points: number; 6 | pr_urls: PRDataType[]; 7 | full_name: string; 8 | college: string; 9 | rank: number; 10 | } 11 | 12 | interface PRDataType { 13 | url: string; 14 | difficulty: string; 15 | phase: number; 16 | } 17 | -------------------------------------------------------------------------------- /atoms/selectedUserAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | 3 | export const selectedUserState = atom({ 4 | key: "pr", 5 | default: { 6 | user_name: "", 7 | avatar_url: "", 8 | user_url: "", 9 | total_points: 0, 10 | pr_urls: [ 11 | { 12 | url: "", 13 | difficulty: "", 14 | phase: 0, 15 | }, 16 | ], 17 | full_name: "", 18 | college: "", 19 | rank: 0, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /utils/parseName.ts: -------------------------------------------------------------------------------- 1 | export const parseName = (name: string): string => { 2 | if (name.length === 0) { 3 | return ""; 4 | } 5 | if (name.split(" ").length !== 2) { 6 | return name; 7 | } else { 8 | const [firstName, lastName] = name.split(" "); 9 | const newFirst = 10 | firstName.charAt(0).toUpperCase() + firstName.toLowerCase().substring(1); 11 | const newLast = 12 | lastName.charAt(0).toUpperCase() + lastName.toLowerCase().substring(1); 13 | 14 | return `${newFirst} ${newLast}`; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const intercept = require("intercept-stdout"); 3 | 4 | // safely ignore recoil stdout warning messages 5 | function interceptStdout(text) { 6 | if (text.includes("Duplicate atom key")) { 7 | return ""; 8 | } 9 | return text; 10 | } 11 | 12 | // Intercept in dev and prod 13 | intercept(interceptStdout); 14 | 15 | const nextConfig = { 16 | reactStrictMode: true, 17 | images: { 18 | domains: ["avatars.githubusercontent.com"], 19 | }, 20 | }; 21 | 22 | module.exports = nextConfig; 23 | -------------------------------------------------------------------------------- /.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 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /components/Table.tsx: -------------------------------------------------------------------------------- 1 | import { TableDataType } from "../types"; 2 | import TableHeader from "./TableHeader"; 3 | import UserCard from "./UserCard"; 4 | 5 | const Table = ({ data }: { data: TableDataType[] }) => { 6 | return ( 7 |
8 |
9 | 10 | 11 | 12 | 13 | {data.map((item, i) => ( 14 | 15 | ))} 16 | 17 |
18 |
19 |
20 | ); 21 | }; 22 | 23 | export default Table; 24 | -------------------------------------------------------------------------------- /utils/generateConfetti.ts: -------------------------------------------------------------------------------- 1 | import confetti from "canvas-confetti"; 2 | 3 | var end = Date.now() + 5 * 1000; 4 | 5 | var colors = ["#ff1700", "#5800ff"]; 6 | 7 | export const generateConfetti = () => { 8 | (function frame() { 9 | confetti({ 10 | particleCount: 2, 11 | angle: 60, 12 | spread: 100, 13 | origin: { x: 0 }, 14 | colors: colors, 15 | shapes: ["circle", "square"], 16 | }); 17 | confetti({ 18 | particleCount: 2, 19 | angle: 120, 20 | spread: 100, 21 | origin: { x: 1 }, 22 | colors: colors, 23 | shapes: ["circle", "square"], 24 | }); 25 | 26 | if (Date.now() < end) { 27 | requestAnimationFrame(frame); 28 | } 29 | })(); 30 | }; 31 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Logo from "../assets/jwoc_logo.svg"; 3 | 4 | const Footer = () => { 5 | return ( 6 |
7 | Logo 8 |

9 | JWOC 10 |

11 |

12 | JGEC Winter of Code 13 |

14 |

15 | Copyright © JWOC 2022. All rights reserved. 16 |

17 |
18 | ); 19 | }; 20 | 21 | export default Footer; 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Cron Job 2 | 3 | on: 4 | schedule: 5 | # At every 3 hours 6 | - cron: "0 */3 * * *" 7 | 8 | jobs: 9 | fetch: 10 | name: FetchData 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: npm install, check, build, and test using Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: npm install 20 | - run: npm run generate 21 | env: 22 | GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} 23 | MONGODB_URL: ${{ secrets.MONGODB_URL }} 24 | 25 | - uses: stefanzweifel/git-auto-commit-action@v4 26 | with: 27 | commit_message: "feat: updated data using GitHub workflow" 28 | commit_user_name: niloysikdar 29 | commit_user_email: niloysikdar30@gmail.com 30 | commit_author: Niloy Sikdar 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwoc-leaderboard", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint", 9 | "generate": "node backend/generate.js" 10 | }, 11 | "dependencies": { 12 | "@heroicons/react": "^1.0.5", 13 | "axios": "^0.25.0", 14 | "canvas-confetti": "^1.5.1", 15 | "dotenv": "^16.0.0", 16 | "fuse.js": "^6.5.3", 17 | "intercept-stdout": "^0.1.2", 18 | "mongodb": "^4.3.1", 19 | "next": "12.0.10", 20 | "react": "17.0.2", 21 | "react-dom": "17.0.2", 22 | "recoil": "^0.6.1", 23 | "sharp": "^0.30.1" 24 | }, 25 | "devDependencies": { 26 | "@types/canvas-confetti": "^1.4.2", 27 | "@types/node": "17.0.17", 28 | "@types/react": "17.0.39", 29 | "autoprefixer": "^10.4.2", 30 | "eslint": "8.8.0", 31 | "eslint-config-next": "12.0.10", 32 | "postcss": "^8.4.6", 33 | "tailwindcss": "^3.0.19", 34 | "typescript": "4.5.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx}", 4 | "./components/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | screens: { 8 | "2xl": { max: "1535px" }, 9 | xl: { max: "1279px" }, 10 | lg: { max: "1023px" }, 11 | md: { max: "767px" }, 12 | sm: { max: "639px" }, 13 | xs: { max: "325px" }, 14 | }, 15 | extend: { 16 | colors: { 17 | darkblack: "#222831", 18 | lightblack: "#3c434e", 19 | darkwhite: "#ffffff", 20 | lightwhite: "#f7fafc", 21 | darkgrey: "#787a91", 22 | lightgrey: "#9ba4b4", 23 | primaryoff: "#99feff", 24 | primarylight: "#385cf0", 25 | primarydark: "#1d4cb0", 26 | highlight: "#f7f0c2", 27 | warningoff: "#ff3838", 28 | discordBg: "#404eed", 29 | }, 30 | 31 | fontFamily: { 32 | mainfont: ["Inter", "sans-serif"], 33 | codefont: ["Outfit", "sans-serif"], 34 | curlfont: ["Quicksand", "sans-serif"], 35 | }, 36 | }, 37 | }, 38 | extend: {}, 39 | plugins: [], 40 | }; 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo 3 | 4 |

JWOC Leaderboard

5 | 6 |

7 | Official leaderboard for JWOC 2k22 8 |
9 |
10 | Visit Here 11 | · 12 | YouTube Live 13 | · 14 | Report Bug 15 |

16 |

17 | 18 | ### Star the [GitHub repo](https://github.com/niloysikdar/jwoc-leaderboard) to keep the developer motivated ✨ 19 | 20 | ### Built With 21 | 22 | - [React](https://reactjs.org) 23 | - [Next.js](https://nextjs.org) 24 | - [TypeScript](https://www.typescriptlang.org) 25 | - [TailwindCSS](https://tailwindcss.com) 26 | - [Recoil](https://recoiljs.org) 27 | - [MongoDB](https://www.mongodb.com) 28 | - [GitHub API](https://docs.github.com/en/rest) 29 | - [GitHub Actions](https://docs.github.com/en/actions) 30 | -------------------------------------------------------------------------------- /components/MetaTags.tsx: -------------------------------------------------------------------------------- 1 | const MetaTags = () => { 2 | return ( 3 | <> 4 | 5 | 6 | 10 | 11 | 12 | 16 | 20 | 21 | 22 | 23 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default MetaTags; 40 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | import { HomeIcon } from "@heroicons/react/solid"; 4 | import Logo from "../assets/jwoc_logo.svg"; 5 | 6 | const Header = () => { 7 | return ( 8 |
9 | 30 |
31 | ); 32 | }; 33 | 34 | export default Header; 35 | -------------------------------------------------------------------------------- /components/TableHeader.tsx: -------------------------------------------------------------------------------- 1 | const TableHeader = () => { 2 | return ( 3 | 4 | 5 | 6 | SL No. 7 | 8 | 9 | Rank 10 | 11 | 12 | Participant's Info 13 | 14 | 15 | GitHub Handle 16 | 17 | 18 | View All PRs 19 | 20 | 21 | Points 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default TableHeader; 29 | -------------------------------------------------------------------------------- /backend/utils/allRepos.js: -------------------------------------------------------------------------------- 1 | const allRepos = [ 2 | "chiraag-kakar/createnshare", 3 | "niloysikdar/Get-Set-Go", 4 | "niloysikdar/Kahani", 5 | "niloysikdar/CFI-JGEC", 6 | "maityamit/Tracky-Track-your-goals-or-targets", 7 | "Kajal13081/Musgenix", 8 | "Kajal13081/Gallery-Application", 9 | "harshita2216/hello-jobs", 10 | "Ly0kami/QuotesApp-JWoC", 11 | "theblockchainchief/web3-hub", 12 | "BRAVO68WEB/url-minify", 13 | "HITK-TECH-Community/Community-Website", 14 | "The-Shivam-garg/BigB-E-learn-Websit-e", 15 | "Codehackerone/Medify", 16 | "aman34503/Go-Airbnb", 17 | "SauravMukherjee44/Aec-Library-Website", 18 | "riyajha2305/Instagram-Clone", 19 | "SaraswatGit/PlanZap", 20 | "abhisheks008/ML-Crate", 21 | "opticSquid/hive", 22 | "gdscjgec/Image-Editor", 23 | "prathimacode-hub/Treksy", 24 | "gdscjgec/Pictionary", 25 | "prathimacode-hub/MedFlare", 26 | "ZiaCodes/counter.js", 27 | "ZiaCodes/claim-prize", 28 | "shibam-naskar/wally", 29 | "harshita214/Chrome-Extension", 30 | "imniladri/DevHub", 31 | "skpandey885/HealhUb", 32 | "sahilsaha7773/SpotLight", 33 | "shibam-naskar/IMAGE_COMPRESOR", 34 | "mohit200008/medi-Care", 35 | "HackClubRAIT/HackClubRAIT-Website.github.io", 36 | "Saup21/Code-Sandbox", 37 | "agamjotsingh18/pollitup", 38 | "neelshah2409/Bot-Collection", 39 | "BlueBlaze6335/Illicit-Illustrations", 40 | "codejay411/Fake-News-Detection-App", 41 | "sagnik1511/Tabular-AutoML", 42 | "Tech-N-Science/FunwithScience", 43 | "smaranjitghose/awesome-portfolio-websites", 44 | "IAmTamal/Milan", 45 | "smaranjitghose/indokhaadyam", 46 | "7saikat7/supply_chain", 47 | ]; 48 | 49 | module.exports = { allRepos }; 50 | -------------------------------------------------------------------------------- /components/EachPR.tsx: -------------------------------------------------------------------------------- 1 | const EachPR = ({ 2 | link, 3 | difficulty, 4 | phase, 5 | }: { 6 | link: string; 7 | difficulty: string; 8 | phase: number; 9 | }) => { 10 | const { bgColor, textColor } = getColor(difficulty); 11 | 12 | return ( 13 | <> 14 |
  • 15 | 21 | {link} 22 | 23 |
    24 | 25 | Difficulty: 26 | 29 | {difficulty} 30 | 31 | 32 | 33 | Phase: {phase} 34 | 35 |
    36 |
  • 37 | 38 | ); 39 | }; 40 | 41 | const getColor = (difficulty: string) => { 42 | let result = { bgColor: "", textColor: "" }; 43 | 44 | const bgValues = { 45 | easy: "bg-green-200", 46 | medium: "bg-yellow-200", 47 | hard: "bg-red-200", 48 | }; 49 | const textColor = { 50 | easy: "text-green-700", 51 | medium: "text-yellow-700", 52 | hard: "text-red-700", 53 | }; 54 | 55 | if (difficulty === "Easy") { 56 | result.bgColor = bgValues.easy; 57 | result.textColor = textColor.easy; 58 | } else if (difficulty === "Medium") { 59 | result.bgColor = bgValues.medium; 60 | result.textColor = textColor.medium; 61 | } else if (difficulty === "Hard") { 62 | result.bgColor = bgValues.hard; 63 | result.textColor = textColor.hard; 64 | } 65 | 66 | return result; 67 | }; 68 | 69 | export default EachPR; 70 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | /* **************************** Fonts **************************** */ 2 | 3 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"); 4 | @import url("https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap"); 5 | @import url("https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&display=swap"); 6 | @import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700;800&display=swap"); 7 | 8 | /* **************************** Site General Settings **************************** */ 9 | 10 | * { 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | @tailwind base; 16 | @tailwind components; 17 | @tailwind utilities; 18 | 19 | /* **************************** Body Settings **************************** */ 20 | 21 | body { 22 | background: #ffffff; 23 | font-family: "Inter", sans-serif; 24 | } 25 | 26 | /* **************************** Scroll Bar **************************** */ 27 | 28 | ::-webkit-scrollbar { 29 | width: 8px; 30 | height: 8px; 31 | background: transparent; 32 | } 33 | ::-webkit-scrollbar-thumb { 34 | background: #222831; 35 | border-radius: 2px; 36 | } 37 | 38 | #div_ScrollBar::-webkit-scrollbar { 39 | width: 6px; 40 | height: 6px; 41 | background: transparent; 42 | } 43 | #div_ScrollBar::-webkit-scrollbar-track { 44 | background: #e2e8f0; 45 | border-radius: 4px; 46 | } 47 | #div_ScrollBar::-webkit-scrollbar-thumb { 48 | background: #94a3b8; 49 | border-radius: 4px; 50 | } 51 | 52 | /* **************************** Selection **************************** */ 53 | 54 | ::selection { 55 | color: #222831; 56 | background: #f7f0c2; 57 | } 58 | 59 | /* **************************** User Select None **************************** */ 60 | 61 | img, 62 | .noselect { 63 | user-select: none; 64 | -webkit-user-select: none; 65 | -moz-user-select: none; 66 | -ms-user-select: none; 67 | 68 | pointer-events: none; 69 | 70 | -webkit-user-drag: none; 71 | -moz-user-drag: none; 72 | -o-user-drag: none; 73 | } 74 | 75 | .user_image { 76 | position: relative !important; 77 | } 78 | 79 | /* **************************** Custom Fonts **************************** */ 80 | 81 | @font-face { 82 | font-family: "Blanka"; 83 | src: url(../assets/Blanka.otf); 84 | } 85 | 86 | /* **************************** Styles End **************************** */ 87 | -------------------------------------------------------------------------------- /components/PRModal.tsx: -------------------------------------------------------------------------------- 1 | import EachPR from "./EachPR"; 2 | import { XIcon } from "@heroicons/react/outline"; 3 | import { useRecoilState, useRecoilValue } from "recoil"; 4 | import { modalState } from "../atoms/modalAtom"; 5 | import { selectedUserState } from "../atoms/selectedUserAtom"; 6 | import { TableDataType } from "../types"; 7 | import { parseName } from "../utils/parseName"; 8 | 9 | const PRModal = () => { 10 | const [val, setModalOpen] = useRecoilState(modalState); 11 | const userData: TableDataType = useRecoilValue(selectedUserState); 12 | 13 | return ( 14 |
    15 |
    16 |
    17 | setModalOpen(false)} 20 | /> 21 |
    22 |
    23 |

    24 | Name: 25 | 26 | {parseName(userData.full_name)} 27 | 28 |

    29 |

    30 | Total Points: 31 | 32 | {userData.total_points} 33 | 34 |

    35 |

    36 | Total PR Count: 37 | 38 | {userData.pr_urls.length} 39 | 40 |

    41 | 42 |

    43 | PR Links: 44 |

    45 | 46 |
    50 |
      51 | {userData.pr_urls.map((prData) => ( 52 | 58 | ))} 59 |
    60 |
    61 |
    62 |
    63 |
    64 | ); 65 | }; 66 | 67 | export default PRModal; 68 | -------------------------------------------------------------------------------- /components/TopCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { useRecoilState } from "recoil"; 3 | import { modalState } from "../atoms/modalAtom"; 4 | import { selectedUserState } from "../atoms/selectedUserAtom"; 5 | import { TableDataType } from "../types"; 6 | import { parseName } from "../utils/parseName"; 7 | 8 | const TopCard = ({ userData }: { userData: TableDataType }) => { 9 | const [i, setModal] = useRecoilState(modalState); 10 | const [j, setUserData] = useRecoilState(selectedUserState); 11 | 12 | return ( 13 |
    14 |
    15 |

    16 | {userData.total_points} 17 |

    18 |

    19 | Points 20 |

    21 | 22 | Rank: {userData.rank} 23 | 24 |
    25 | 26 |
    27 |
    28 | {userData.user_name} 36 |
    37 |

    38 | {parseName(userData.full_name)} 39 |

    40 | 46 | {userData.user_name} 47 | 48 |

    49 | {userData.college} 50 |

    51 | 60 |
    61 |
    62 | ); 63 | }; 64 | 65 | export default TopCard; 66 | -------------------------------------------------------------------------------- /components/UserCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { useRecoilState } from "recoil"; 3 | import { modalState } from "../atoms/modalAtom"; 4 | import { selectedUserState } from "../atoms/selectedUserAtom"; 5 | import { TableDataType } from "../types"; 6 | import { parseName } from "../utils/parseName"; 7 | 8 | const UserCard = ({ data, index }: { data: TableDataType; index: number }) => { 9 | const [isModalOpen, setModalOpen] = useRecoilState(modalState); 10 | const [userData, setUserData] = useRecoilState(selectedUserState); 11 | 12 | return ( 13 | 14 | 15 |

    16 | {index + 4} 17 |

    18 | 19 | 20 |

    21 | # {data.rank} 22 |

    23 | 24 | 25 |
    26 |
    27 | {data.user_name} 34 |
    35 |
    36 |

    37 | {parseName(data.full_name) || ( 38 | Name not found... 39 | )} 40 |

    41 |

    42 | {data.college || ( 43 | College not found... 44 | )} 45 |

    46 |
    47 |
    48 | 49 | 50 | 56 | {data.user_name} 57 | 58 | 59 | 60 |

    { 63 | setModalOpen(true); 64 | setUserData(data); 65 | }} 66 | > 67 | View All PRs 68 |

    69 | 70 | 71 | 72 | {data.total_points} 73 | 74 | 75 | 76 | ); 77 | }; 78 | 79 | export default UserCard; 80 | -------------------------------------------------------------------------------- /assets/jwoc_logo.svg: -------------------------------------------------------------------------------- 1 | jwoc_logo -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | import Script from "next/script"; 4 | import { ChangeEvent, useState, useEffect } from "react"; 5 | import Fuse from "fuse.js"; 6 | import { useRecoilValue } from "recoil"; 7 | import { modalState } from "../atoms/modalAtom"; 8 | 9 | import Table from "../components/Table"; 10 | import Header from "../components/Header"; 11 | import Footer from "../components/Footer"; 12 | import MetaTags from "../components/MetaTags"; 13 | 14 | import data from "../data.json"; 15 | import PRModal from "../components/PRModal"; 16 | import TopCard from "../components/TopCard"; 17 | import { TableDataType } from "../types"; 18 | import { generateConfetti } from "../utils/generateConfetti"; 19 | 20 | const Home: NextPage = () => { 21 | const isModalOpen = useRecoilValue(modalState); 22 | 23 | const [tableData, setTableData] = useState( 24 | data.data.slice(3, data.data.length) 25 | ); 26 | const [searchText, setSearchText] = useState(""); 27 | const [searchedData, setSearchedData] = useState(); 28 | 29 | const handleSearch = ( 30 | e: ChangeEvent 31 | ) => { 32 | setSearchText(e.target.value); 33 | const fuse = new Fuse(tableData as TableDataType[], { 34 | keys: ["user_name", "full_name", "college"], 35 | threshold: 0.2, 36 | }); 37 | const result = fuse.search(e.target.value).map((item) => item.item); 38 | setSearchedData(result); 39 | }; 40 | 41 | useEffect(() => { 42 | generateConfetti(); 43 | }, []); 44 | 45 | return ( 46 | <> 47 | 48 | Leaderboard | JWoC 2K22 49 | 54 | 55 | 56 | 57 | 70 | 71 |
    72 | 73 |
    74 |
    75 |
    76 |

    77 | JWoC 2K22 Leaderboard 78 |

    79 |

    80 | Check your rank here! 81 |

    82 |

    83 | Last updated on: 84 | 85 | {new Date(data.lastUpdated).toLocaleString("en-US", { 86 | dateStyle: "full", 87 | timeStyle: "full", 88 | })} 89 | 90 |

    91 |
    92 | 93 |
    94 | 95 | 96 | 97 |
    98 | 99 |
    100 | 107 |
    108 | 109 | 112 | 113 | 114 | 115 | {isModalOpen && } 116 | 117 |
    118 | 119 | ); 120 | }; 121 | 122 | export default Home; 123 | -------------------------------------------------------------------------------- /backend/generate.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios"); 2 | const { MongoClient } = require("mongodb"); 3 | const { writeJson } = require("./utils/writeJson"); 4 | 5 | const dotenv = require("dotenv"); 6 | dotenv.config(); 7 | const ACCESS_TOKEN = process.env.GH_ACCESS_TOKEN; 8 | 9 | const { allRepos } = require("./utils/allRepos"); 10 | const repos = allRepos; 11 | 12 | const eventLabel = "jwoc"; 13 | const levelsData = { 14 | easy: "Easy", 15 | medium: "Medium", 16 | hard: "Hard", 17 | }; 18 | let finalData = []; 19 | 20 | const MONGODB_URL = process.env.MONGODB_URL; 21 | const client = new MongoClient(MONGODB_URL); 22 | 23 | const fetchAllData = async () => { 24 | for (let i = 0; i < repos.length; i++) { 25 | const repoName = repos[i]; 26 | const data = await fetchRepoData(repoName); 27 | console.log(`Completed for: ${repos[i]}`); 28 | console.log("!.!.!.!.!.!.!"); 29 | console.log("*************"); 30 | finalData = [...finalData, ...data]; 31 | } 32 | 33 | let leaderboardData = generateRank(finalData).sort((a, b) => 34 | a.total_points < b.total_points ? 1 : -1 35 | ); 36 | 37 | let rank = 1; 38 | 39 | for (let pos = 0; pos < leaderboardData.length; pos++) { 40 | const currentData = leaderboardData[pos]; 41 | 42 | const { full_name, college } = await getDatafromDB(currentData.user_name); 43 | 44 | currentData.full_name = full_name; 45 | currentData.college = college; 46 | 47 | if (pos === 0) { 48 | currentData.rank = rank; 49 | } else { 50 | const prevData = leaderboardData[pos - 1]; 51 | if (prevData.total_points > currentData.total_points) { 52 | rank++; 53 | currentData.rank = rank; 54 | } else { 55 | currentData.rank = rank; 56 | } 57 | } 58 | } 59 | 60 | writeJson({ 61 | lastUpdated: new Date(), 62 | data: leaderboardData, 63 | }); 64 | }; 65 | 66 | const getDatafromDB = async (userName) => { 67 | let finalData = { full_name: "", college: "" }; 68 | try { 69 | const db = client.db("jwoc"); 70 | const collection = db.collection("mentees"); 71 | const data = await collection.findOne({ $text: { $search: userName } }); 72 | if (data) { 73 | finalData.full_name = data.name; 74 | finalData.college = data.college; 75 | } 76 | } catch (error) { 77 | console.log(error); 78 | } 79 | 80 | return finalData; 81 | }; 82 | 83 | let counter = 0; 84 | 85 | const fetchRepoData = async (repoName) => { 86 | let pageCount = 1; 87 | let pageAvailabe = true; 88 | let allData = []; 89 | 90 | while (pageAvailabe) { 91 | const reqUrl = `https://api.github.com/repos/${repoName}/pulls?state=closed&per_page=100&page=${pageCount}`; 92 | try { 93 | counter++; 94 | console.log( 95 | `${counter}. Fetching data for: ${repoName} and pageCount: ${pageCount}` 96 | ); 97 | const res = await axios.get(reqUrl, { 98 | headers: { 99 | authorization: `token ${ACCESS_TOKEN}`, 100 | "User-Agent": "request", 101 | Accept: "application/vnd.github.v3+json", 102 | }, 103 | }); 104 | const resData = res.data; 105 | 106 | console.log( 107 | `Data has been fetched for: ${repoName} and pageCount: ${pageCount}` 108 | ); 109 | // await sleep(2000); 110 | console.log("_____________"); 111 | 112 | if (resData.length !== 0) { 113 | const jwocData = filterJwoc(resData); 114 | allData = [...allData, ...jwocData]; 115 | pageCount++; 116 | } else { 117 | pageAvailabe = false; 118 | } 119 | } catch (error) { 120 | console.log(error.message); 121 | pageAvailabe = false; 122 | process.exit(1); 123 | } 124 | } 125 | 126 | return allData; 127 | }; 128 | 129 | const filterJwoc = (allData) => { 130 | let finalData = []; 131 | 132 | const jwocData = allData.filter((prData) => { 133 | let isJwoc = false; 134 | if (prData.merged_at) { 135 | prData.labels.map((eachLabel) => { 136 | if (eachLabel.name.toLowerCase().includes(eventLabel.toLowerCase())) { 137 | isJwoc = true; 138 | } 139 | }); 140 | } 141 | 142 | return isJwoc; 143 | }); 144 | 145 | if (jwocData.length !== 0) { 146 | jwocData.map((prData) => { 147 | const data = { 148 | user_name: prData.user.login, 149 | avatar_url: prData.user.avatar_url, 150 | user_url: prData.user.html_url, 151 | pr_url: prData.html_url, 152 | labels: prData.labels.map((labelData) => labelData.name), 153 | phase: getPhase(prData.created_at), 154 | }; 155 | finalData = [...finalData, data]; 156 | }); 157 | } 158 | 159 | return finalData; 160 | }; 161 | 162 | const getPhase = (created_at) => { 163 | const phase1deadlineISO = "2022-02-28T18:31:00.000Z"; 164 | const deadline1 = new Date(phase1deadlineISO); 165 | const createdAtDate = new Date(created_at); 166 | 167 | if (createdAtDate > deadline1) return 2; 168 | return 1; 169 | }; 170 | 171 | const generateRank = (fullData) => { 172 | let finalData = []; 173 | 174 | fullData.map((eachPrData) => { 175 | const index = finalData.findIndex( 176 | (data) => data.user_name === eachPrData.user_name 177 | ); 178 | 179 | const { point, difficulty } = getPoints( 180 | eachPrData.labels, 181 | eachPrData.phase 182 | ); 183 | 184 | if (index === -1) { 185 | const userData = { 186 | user_name: eachPrData.user_name, 187 | avatar_url: eachPrData.avatar_url, 188 | user_url: eachPrData.user_url, 189 | total_points: point, 190 | pr_urls: [ 191 | { 192 | url: eachPrData.pr_url, 193 | difficulty: difficulty, 194 | phase: eachPrData.phase, 195 | }, 196 | ], 197 | }; 198 | 199 | finalData = [...finalData, userData]; 200 | } else { 201 | finalData[index].total_points += point; 202 | finalData[index].pr_urls.push({ 203 | url: eachPrData.pr_url, 204 | difficulty: difficulty, 205 | phase: eachPrData.phase, 206 | }); 207 | } 208 | }); 209 | 210 | return finalData; 211 | }; 212 | 213 | const getPoints = (labelsArray, phase) => { 214 | let point = 0, 215 | difficulty = ""; 216 | 217 | labelsArray.map((label) => { 218 | if (label.toLowerCase().includes(levelsData.easy.toLowerCase())) { 219 | difficulty = levelsData.easy; 220 | if (phase === 1) { 221 | point = 1; 222 | } else { 223 | point = 2; 224 | } 225 | } 226 | if (label.toLowerCase().includes(levelsData.medium.toLowerCase())) { 227 | difficulty = levelsData.medium; 228 | if (phase === 1) { 229 | point = 3; 230 | } else { 231 | point = 4; 232 | } 233 | } 234 | if (label.toLowerCase().includes(levelsData.hard.toLowerCase())) { 235 | difficulty = levelsData.hard; 236 | if (phase === 1) { 237 | point = 5; 238 | } else { 239 | point = 8; 240 | } 241 | } 242 | }); 243 | 244 | return { point, difficulty }; 245 | }; 246 | 247 | client 248 | .connect() 249 | .then(() => fetchAllData().then(() => client.close())) 250 | .catch((error) => console.log(error.message)); 251 | --------------------------------------------------------------------------------